From bf3cfb62d1b7ed09f67c1b6a667017c72d19770e Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 31 Mar 2020 20:52:10 +0300 Subject: [PATCH 1/6] Cherry pick --- tgui-next/packages/tgui/routes.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tgui-next/packages/tgui/routes.js b/tgui-next/packages/tgui/routes.js index 76c2834c7b..3a3ef14dc9 100644 --- a/tgui-next/packages/tgui/routes.js +++ b/tgui-next/packages/tgui/routes.js @@ -93,6 +93,7 @@ import { ThermoMachine } from './interfaces/ThermoMachine'; import { TurbineComputer } from './interfaces/TurbineComputer'; import { Uplink } from './interfaces/Uplink'; import { VaultController } from './interfaces/VaultController'; +import { Vending } from './interfaces/Vending'; import { Wires } from './interfaces/Wires'; import { AtmosRelief } from './interfaces/AtmosRelief'; @@ -510,6 +511,10 @@ const ROUTES = { component: () => VaultController, scrollable: false, }, + vending: { + component: () => Vending, + scrollable: true, + }, wires: { component: () => Wires, scrollable: false, From 4d53c3f6c01fa35436c722e403bb5e22a56c53bd Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 31 Mar 2020 21:00:50 +0300 Subject: [PATCH 2/6] updates vendors.. --- code/modules/vending/_vending.dm | 596 ++++++++++++++++++++----------- 1 file changed, 392 insertions(+), 204 deletions(-) diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 8a62c19e16..c1f6a3a4be 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -16,12 +16,30 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY CANISTER CHARGES in vending_items.dm */ +#define MAX_VENDING_INPUT_AMOUNT 30 +/** + * # vending record datum + * + * A datum that represents a product that is vendable + */ /datum/data/vending_product name = "generic" + ///Typepath of the product that is created when this record "sells" var/product_path = null + ///How many of this product we currently have var/amount = 0 + ///How many we can store at maximum var/max_amount = 0 + ///Does the item have a custom price override + var/custom_price + ///Does the item have a custom premium price override + var/custom_premium_price +/** + * # vending machines + * + * Captalism in the year 2525, everything in a vending machine, even love + */ /obj/machinery/vending name = "\improper Vendomat" desc = "A generic vending machine." @@ -36,43 +54,120 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C integrity_failure = 0.33 armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) circuit = /obj/item/circuitboard/machine/vendor - var/active = 1 //No sales pitches if off! - var/vend_ready = 1 //Are we ready to vend?? Is it time?? + light_power = 0.5 + light_range = MINIMUM_USEFUL_LIGHT_RANGE + /// Is the machine active (No sales pitches if off)! + var/active = TRUE + ///Are we ready to vend?? Is it time?? + var/vend_ready = TRUE + ///Next world time to send a purchase message + var/purchase_message_cooldown + ///Last mob to shop with us - // To be filled out at compile time - var/list/products = list() //For each, use the following pattern: - var/list/contraband = list() //list(/type/path = amount, /type/path2 = amount2) - var/list/premium = list() //No specified amount = only one in stock + /** + * List of products this machine sells + * + * form should be list(/type/path = amount, /type/path2 = amount2) + */ + var/list/products = list() + + /** + * List of products this machine sells when you hack it + * + * form should be list(/type/path = amount, /type/path2 = amount2) + */ + var/list/contraband = list() + + /** + * List of premium products this machine sells + * + * form should be list(/type/path, /type/path2) as there is only ever one in stock + */ + var/list/premium = list() + + ///String of slogans separated by semicolons, optional + var/product_slogans = "" + ///String of small ad messages in the vending screen - random chance + var/product_ads = "" - var/product_slogans = "" //String of slogans separated by semicolons, optional - var/product_ads = "" //String of small ad messages in the vending screen - random chance var/list/product_records = list() var/list/hidden_records = list() var/list/coin_records = list() var/list/slogan_list = list() - var/list/small_ads = list() //Small ad messages in the vending screen - random chance of popping up whenever you open it - var/vend_reply //Thank you for shopping! - var/last_reply = 0 - var/last_slogan = 0 //When did we last pitch? - var/slogan_delay = 6000 //How long until we can pitch again? - var/icon_vend //Icon_state when vending! - var/icon_deny //Icon_state when vending! - var/seconds_electrified = 0 //Shock customers like an airlock. - var/shoot_inventory = 0 //Fire items at customers! We're broken! + ///Small ad messages in the vending screen - random chance of popping up whenever you open it + var/list/small_ads = list() + ///Message sent post vend (Thank you for shopping!) + var/vend_reply + ///Last world tick we sent a vent reply + var/last_reply + ///Last world tick we sent a slogan message out + var/last_slogan + ///How many ticks until we can send another + var/slogan_delay = 6000 + ///Icon when vending an item to the user + var/icon_vend + ///Icon to flash when user is denied a vend + var/icon_deny + ///World ticks the machine is electified for + var/seconds_electrified = MACHINE_NOT_ELECTRIFIED + ///When this is TRUE, we fire items at customers! We're broken! + var/shoot_inventory + ///How likely this is to happen (prob 100) var/shoot_inventory_chance = 2 - var/shut_up = 0 //Stop spouting those godawful pitches! - var/extended_inventory = 0 //can we access the hidden inventory? - var/scan_id = 1 + //Stop spouting those godawful pitches! + var/shut_up + ///can we access the hidden inventory? + var/extended_inventory + ///Are we checking the users ID + var/scan_id = TRUE + ///Coins that we accept? var/obj/item/coin/coin + ///Bills we accept? var/obj/item/stack/spacecash/bill + ///Default price of items if not overridden + var/default_price = 25 + ///Default price of premium items if not overridden + var/extra_price = 50 + /** + * Is this item on station or not + * + * if it doesn't originate from off-station during mapload, everything is free + */ + var/onstation = TRUE //if it doesn't originate from off-station during mapload, everything is free + ///A variable to change on a per instance basis on the map that allows the instance to force cost and ID requirements + var/onstation_override = FALSE //change this on the object on the map to override the onstation check. DO NOT APPLY THIS GLOBALLY. - var/static/vending_cache = list() //used for storing the icons of items being vended + ///ID's that can load this vending machine wtih refills + var/list/canload_access_list - var/dish_quants = list() //used by the snack machine's custom compartment to count dishes. - var/obj/item/vending_refill/refill_canister = null //The type of refill canisters used by this machine. + var/list/vending_machine_input = list() + ///Display header on the input view + var/input_display_header = "Custom Vendor" -/obj/machinery/vending/Initialize() + //The type of refill canisters used by this machine. + var/obj/item/vending_refill/refill_canister = null + + /// how many items have been inserted in a vendor + var/loaded_items + + ///Name of lighting mask for the vending machine + var/light_mask + +/obj/item/circuitboard + ///determines if the circuit board originated from a vendor off station or not. + var/onstation = TRUE + +/** + * Initialize the vending machine + * + * Builds the vending machine inventory, sets up slogans and other such misc work + * + * This also sets the onstation var to: + * * FALSE - if the machine was maploaded on a zlevel that doesn't pass the is_station_level check + * * TRUE - all other cases + */ +/obj/machinery/vending/Initialize(mapload) var/build_inv = FALSE if(!refill_canister) circuit = null @@ -97,6 +192,9 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C QDEL_NULL(bill) return ..() +/obj/machinery/vending/can_speak() + return !shut_up + /obj/machinery/vending/RefreshParts() //Better would be to make constructable child if(!component_parts) return @@ -118,10 +216,31 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C else ..() -/obj/machinery/vending/obj_break(damage_flag) - if(!(stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1)) - stat |= BROKEN +/obj/machinery/vending/update_icon_state() + if(stat & BROKEN) icon_state = "[initial(icon_state)]-broken" + set_light(0) + else if(powered()) + icon_state = initial(icon_state) + set_light(1.4) + else + icon_state = "[initial(icon_state)]-off" + set_light(0) + + +/obj/machinery/vending/update_overlays() + . = ..() + if(!light_mask) + return + + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + if(!(machine_stat & BROKEN) && powered()) + SSvis_overlays.add_vis_overlay(src, icon, light_mask, EMISSIVE_LAYER, EMISSIVE_PLANE) + +/obj/machinery/vending/obj_break(damage_flag) + . = ..() + if(!.) + return var/dump_amount = 0 var/found_anything = TRUE @@ -146,6 +265,16 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C if (dump_amount >= 16) return +GLOBAL_LIST_EMPTY(vending_products) +/** + * Build the inventory of the vending machine from it's product and record lists + * + * This builds up a full set of /datum/data/vending_products from the product list of the vending machine type + * Arguments: + * * productlist - the list of products that need to be converted + * * recordlist - the list containing /datum/data/vending_product datums + * * startempty - should we set vending_product record amount from the product list (so it's prefilled at roundstart) + */ /obj/machinery/vending/proc/build_inventory(list/productlist, list/recordlist, start_empty = FALSE) for(var/typepath in productlist) var/amount = productlist[typepath] @@ -154,13 +283,23 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C var/atom/temp = typepath var/datum/data/vending_product/R = new /datum/data/vending_product() + GLOB.vending_products[typepath] = 1 R.name = initial(temp.name) R.product_path = typepath if(!start_empty) R.amount = amount R.max_amount = amount + R.custom_price = initial(temp.custom_price) + R.custom_premium_price = initial(temp.custom_premium_price) recordlist += R - +/** + * Refill a vending machine from a refill canister + * + * This takes the products from the refill canister and then fills the products,contraband and premium product categories + * + * Arguments: + * * canister - the vending canister we are refilling from + */ /obj/machinery/vending/proc/restock(obj/item/vending_refill/canister) if (!canister.products) canister.products = products.Copy() @@ -172,7 +311,13 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C . += refill_inventory(canister.products, product_records) . += refill_inventory(canister.contraband, hidden_records) . += refill_inventory(canister.premium, coin_records) - +/** + * Refill our inventory from the passed in product list into the record list + * + * Arguments: + * * productlist - list of types -> amount + * * recordlist - existing record datums + */ /obj/machinery/vending/proc/refill_inventory(list/productlist, list/recordlist) . = 0 for(var/R in recordlist) @@ -182,7 +327,11 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C productlist[record.product_path] -= diff record.amount += diff . += diff - +/** + * Set up a refill canister that matches this machines products + * + * This is used when the machine is deconstructed, so the items aren't "lost" + */ /obj/machinery/vending/proc/update_canister() if (!component_parts) return @@ -190,12 +339,14 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C var/obj/item/vending_refill/R = locate() in component_parts if (!R) CRASH("Constructible vending machine did not have a refill canister") - return R.products = unbuild_inventory(product_records) R.contraband = unbuild_inventory(hidden_records) R.premium = unbuild_inventory(coin_records) +/** + * Given a record list, go through and and return a list of type -> amount + */ /obj/machinery/vending/proc/unbuild_inventory(list/recordlist) . = list() for(var/R in recordlist) @@ -209,11 +360,15 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C return TRUE /obj/machinery/vending/wrench_act(mob/living/user, obj/item/I) + ..() if(panel_open) default_unfasten_wrench(user, I, time = 60) + unbuckle_all_mobs(TRUE) return TRUE /obj/machinery/vending/screwdriver_act(mob/living/user, obj/item/I) + if(..()) + return TRUE if(anchored) default_deconstruction_screwdriver(user, icon_state, icon_state, I) cut_overlays() @@ -260,24 +415,84 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C return else if(refill_canister && istype(I, refill_canister)) if (!panel_open) - to_chat(user, "You should probably unscrew the service panel first.") + to_chat(user, "You should probably unscrew the service panel first!") else if (stat & (BROKEN|NOPOWER)) to_chat(user, "[src] does not respond.") else //if the panel is open we attempt to refill the machine var/obj/item/vending_refill/canister = I if(canister.get_part_rating() == 0) - to_chat(user, "[canister] is empty!") + to_chat(user, "[canister] is empty!") else // instantiate canister if needed var/transferred = restock(canister) if(transferred) to_chat(user, "You loaded [transferred] items in [src].") else - to_chat(user, "There's nothing to restock!") + to_chat(user, "There's nothing to restock!") return + if(compartmentLoadAccessCheck(user) && user.a_intent != INTENT_HARM) + if(canLoadItem(I)) + loadingAttempt(I,user) + updateUsrDialog() //can't put this on the proc above because we spam it below + + if(istype(I, /obj/item/storage/bag)) //trays USUALLY + var/obj/item/storage/T = I + var/loaded = 0 + var/denied_items = 0 + for(var/obj/item/the_item in T.contents) + if(contents.len >= MAX_VENDING_INPUT_AMOUNT) // no more than 30 item can fit inside, legacy from snack vending although not sure why it exists + to_chat(user, "[src]'s compartment is full.") + break + if(canLoadItem(the_item) && loadingAttempt(the_item,user)) + SEND_SIGNAL(T, COMSIG_TRY_STORAGE_TAKE, the_item, src, TRUE) + loaded++ else - return ..() + denied_items++ + if(denied_items) + to_chat(user, "[src] refuses some items!") + if(loaded) + to_chat(user, "You insert [loaded] dishes into [src]'s compartment.") + updateUsrDialog() + else + . = ..() + +/obj/machinery/vending/proc/loadingAttempt(obj/item/I, mob/user) + . = TRUE + if(!user.transferItemToLoc(I, src)) + return FALSE + if(vending_machine_input[format_text(I.name)]) + vending_machine_input[format_text(I.name)]++ + else + vending_machine_input[format_text(I.name)] = 1 + to_chat(user, "You insert [I] into [src]'s input compartment.") + loaded_items++ + +/** + * Is the passed in user allowed to load this vending machines compartments + * + * Arguments: + * * user - mob that is doing the loading of the vending machine + */ +/obj/machinery/vending/proc/compartmentLoadAccessCheck(mob/user) + if(!canload_access_list) + return TRUE + else + var/do_you_have_access = FALSE + var/req_access_txt_holder = req_access_txt + for(var/i in canload_access_list) + req_access_txt = i + if(!allowed(user) && !(obj_flags & EMAGGED) && scan_id) + continue + else + do_you_have_access = TRUE + break //you passed don't bother looping anymore + req_access_txt = req_access_txt_holder // revert to normal (before the proc ran) + if(do_you_have_access) + return TRUE + else + to_chat(user, "[src]'s input compartment blinks red: Access denied.") + return FALSE /obj/machinery/vending/exchange_parts(mob/user, obj/item/storage/part_replacer/W) if(!istype(W)) @@ -297,7 +512,7 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C else display_parts(user) if(moved) - to_chat(user, "[moved] items restocked.") + to_chat(user, "[moved] items restocked.") W.play_rped_sound() return TRUE @@ -306,201 +521,142 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C . = ..() /obj/machinery/vending/emag_act(mob/user) - . = ..() if(obj_flags & EMAGGED) return obj_flags |= EMAGGED to_chat(user, "You short out the product lock on [src].") - return TRUE /obj/machinery/vending/_try_interact(mob/user) if(seconds_electrified && !(stat & NOPOWER)) if(shock(user, 100)) return + + if(tilted && !user.buckled && !isAI(user)) + to_chat(user, "You begin righting [src].") + if(do_after(user, 50, target=src)) + untilt(user) + return + return ..() -/obj/machinery/vending/ui_interact(mob/user) - var/dat = "" +/obj/machinery/vending/ui_base_html(html) + var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/vending) + . = replacetext(html, "", assets.css_tag()) - dat += "

Select an item

" - dat += "
" - if(!product_records.len) - dat += "No product loaded!" - else - var/list/display_records = product_records - if(extended_inventory) - display_records = product_records + hidden_records - if(coin || bill) - display_records = product_records + coin_records - if((coin || bill) && extended_inventory) - display_records = product_records + hidden_records + coin_records - dat += "" - for (var/datum/data/vending_product/R in display_records) - dat += "" - dat += "" - if(R.amount > 0) - dat += "" - else - dat += "" - dat += "" - dat += "
[sanitize(R.name)][R.amount] VendVend
" - dat += "
" - if(premium.len > 0) - dat += "Change Return: " - if (coin || bill) - dat += "[(coin ? coin : "")][(bill ? bill : "")]  Remove" - else - dat += "No money  Remove" - if(istype(src, /obj/machinery/vending/snack)) - dat += "

Chef's Food Selection

" - dat += "
" - for (var/O in dish_quants) - if(dish_quants[O] > 0) - var/N = dish_quants[O] - dat += "Dispense " - dat += "[capitalize(O)]: [N]
" - dat += "
" +/obj/machinery/vending/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/vending) + assets.send(user) + ui = new(user, src, ui_key, "vending", ui_key, 450, 600, master_ui, state) + ui.open() - var/datum/browser/popup = new(user, "vending", (name)) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() +/obj/machinery/vending/ui_static_data(mob/user) + . = list() + .["product_records"] = list() + for (var/datum/data/vending_product/R in product_records) + var/list/data = list( + path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"), + name = R.name, + price = R.custom_price || default_price, + max_amount = R.max_amount, + ref = REF(R) + ) + .["product_records"] += list(data) + .["coin_records"] = list() + for (var/datum/data/vending_product/R in coin_records) + var/list/data = list( + path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"), + name = R.name, + price = R.custom_premium_price || extra_price, + max_amount = R.max_amount, + ref = REF(R), + premium = TRUE + ) + .["coin_records"] += list(data) + .["hidden_records"] = list() + for (var/datum/data/vending_product/R in hidden_records) + var/list/data = list( + path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"), + name = R.name, + price = R.custom_price || default_price, + max_amount = R.max_amount, + ref = REF(R), + premium = TRUE + ) + .["hidden_records"] += list(data) -/obj/machinery/vending/proc/GetIconForProduct(datum/data/vending_product/P) - if(vending_cache[P.product_path]) - return vending_cache[P.product_path] - var/product = new P.product_path() - vending_cache[P.product_path] = icon2base64(getFlatIcon(product, no_anim = TRUE)) - qdel(product) - return vending_cache[P.product_path] +/obj/machinery/vending/ui_data(mob/user) + . = list() + var/mob/living/carbon/human/H + var/obj/item/card/id/C + .["stock"] = list() + for (var/datum/data/vending_product/R in product_records + coin_records + hidden_records) + .["stock"][R.name] = R.amount + .["extended_inventory"] = extended_inventory -/obj/machinery/vending/Topic(href, href_list) - if(..()) - return - - if(href_list["remove_coin"]) - if(!(coin || bill)) - to_chat(usr, "There is no money in this machine.") +/obj/machinery/vending/ui_act(action, params) + . = ..() + if(.) return - if(coin) - usr.put_in_hands(coin) - to_chat(usr, "You remove [coin] from [src].") - coin = null - if(bill) - usr.put_in_hands(bill) - to_chat(usr, "You remove [bill] from [src].") - bill = null - - - usr.set_machine(src) - - if((href_list["dispense"]) && (vend_ready)) - var/N = href_list["dispense"] - if(dish_quants[N] <= 0) // Sanity check, there are probably ways to press the button when it shouldn't be possible. - return - vend_ready = 0 - use_power(5) - - dish_quants[N] = max(dish_quants[N] - 1, 0) - for(var/obj/O in contents) - if(O.name == N) - O.forceMove(drop_location()) - break - vend_ready = 1 - updateUsrDialog() + switch(action) + if("vend") + . = TRUE + if(!vend_ready) return - - if((href_list["vend"]) && (vend_ready)) if(panel_open) - to_chat(usr, "The vending machine cannot dispense products while its service panel is open!") + to_chat(usr, "The vending machine cannot dispense products while its service panel is open!") return - - if((!allowed(usr)) && !(obj_flags & EMAGGED) && scan_id) //For SECURE VENDING MACHINES YEAH - to_chat(usr, "Access denied." ) - flick(icon_deny,src) - return - - vend_ready = 0 //One thing at a time!! - - var/datum/data/vending_product/R = locate(href_list["vend"]) + vend_ready = FALSE //One thing at a time!! + var/datum/data/vending_product/R = locate(params["ref"]) + var/list/record_to_check = product_records + coin_records + if(extended_inventory) + record_to_check = product_records + coin_records + hidden_records if(!R || !istype(R) || !R.product_path) - vend_ready = 1 + vend_ready = TRUE return - + var/price_to_use = default_price + if(R.custom_price) + price_to_use = R.custom_price if(R in hidden_records) if(!extended_inventory) - vend_ready = 1 + vend_ready = TRUE return - else if(R in coin_records) - if(!(coin || bill)) - to_chat(usr, "You need to insert money to get this item!") - vend_ready = 1 - return - if(coin && coin.string_attached) - if(prob(50)) - if(usr.CanReach(src)) - if(usr.put_in_hands(coin)) - to_chat(usr, "You successfully pull [coin] out before [src] could swallow it.") - coin = null - else - to_chat(usr, "You couldn't pull [coin] out because your hands are full!") - QDEL_NULL(coin) - else - to_chat(usr, "You successfully pull [coin] out of [src] to the floor.") - coin = null - else - to_chat(usr, "You weren't able to pull [coin] out fast enough, the machine ate it, string and all!") - QDEL_NULL(coin) - else - QDEL_NULL(coin) - QDEL_NULL(bill) - - else if (!(R in product_records)) - vend_ready = 1 + else if (!(R in record_to_check)) + vend_ready = TRUE message_admins("Vending machine exploit attempted by [ADMIN_LOOKUPFLW(usr)]!") return - if (R.amount <= 0) - to_chat(usr, "Sold out.") - vend_ready = 1 + say("Sold out of [R.name].") + flick(icon_deny,src) + vend_ready = TRUE return - else - R.amount-- + if(onstation && ishuman(usr)) + var/mob/living/carbon/human/H = usr + var/obj/item/card/id/C = H.get_idcard(TRUE) - if(((last_reply + 200) <= world.time) && vend_reply) - speak(vend_reply) - last_reply = world.time + + if(last_shopper != usr || purchase_message_cooldown < world.time) + say("Thank you for shopping with [src]!") + purchase_message_cooldown = world.time + 5 SECONDS + last_shopper = usr use_power(5) if(icon_vend) //Show the vending animation if needed flick(icon_vend,src) - var/vended = new R.product_path(get_turf(src)) - if(usr.CanReach(src)) - if(usr.put_in_hands(vended)) - to_chat(usr, "You take [R.name] out of the slot.") - else - to_chat(usr, "[capitalize(R.name)] falls onto the floor!") - else - to_chat(usr, "[capitalize(R.name)] falls onto the floor!") + playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3) + new R.product_path(get_turf(src)) + R.amount-- SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[R.product_path]")) - vend_ready = 1 - - updateUsrDialog() - return - - else if(href_list["togglevoice"] && panel_open) - shut_up = !shut_up - - updateUsrDialog() - + vend_ready = TRUE /obj/machinery/vending/process() if(stat & (BROKEN|NOPOWER)) - return + return PROCESS_KILL if(!active) return - if(seconds_electrified > 0) + if(seconds_electrified > MACHINE_NOT_ELECTRIFIED) seconds_electrified-- //Pitch to the people! Really sell it! @@ -511,7 +667,14 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C if(shoot_inventory && prob(shoot_inventory_chance)) throw_item() - +/** + * Speak the given message verbally + * + * Checks if the machine is powered and the message exists + * + * Arguments: + * * message - the message to speak + */ /obj/machinery/vending/proc/speak(message) if(stat & (BROKEN|NOPOWER)) return @@ -521,17 +684,17 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C say(message) /obj/machinery/vending/power_change() - if(stat & BROKEN) - icon_state = "[initial(icon_state)]-broken" - else + . = ..() if(powered()) - icon_state = initial(icon_state) - stat &= ~NOPOWER - else - icon_state = "[initial(icon_state)]-off" - stat |= NOPOWER + START_PROCESSING(SSmachines, src) //Somebody cut an important wire and now we're following a new definition of "pitch." +/** + * Throw an item from our internal inventory out in front of us + * + * This is called when we are hacked, it selects a random product from the records that has an amount > 0 + * This item is then created and tossed out in front of us with a visible message + */ /obj/machinery/vending/proc/throw_item() var/obj/throw_item = null var/mob/living/target = locate() in view(7,src) @@ -556,12 +719,28 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C throw_item.throw_at(target, 16, 3) visible_message("[src] launches [throw_item] at [target]!") return 1 - +/** + * A callback called before an item is tossed out + * + * Override this if you need to do any special case handling + * + * Arguments: + * * I - obj/item being thrown + */ /obj/machinery/vending/proc/pre_throw(obj/item/I) return - -/obj/machinery/vending/proc/shock(mob/user, prb) - if(stat & (BROKEN|NOPOWER)) // unpowered, no shock +/** + * Shock the passed in user + * + * This checks we have power and that the passed in prob is passed, then generates some sparks + * and calls electrocute_mob on the user + * + * Arguments: + * * user - the user to shock + * * prb - probability the shock happens + */ +/obj/machinery/vending/proc/shock(mob/living/user, prb) + if(!istype(user) || stat & (BROKEN|NOPOWER)) // unpowered, no shock return FALSE if(!prob(prb)) return FALSE @@ -571,6 +750,15 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C return TRUE else return FALSE +/** + * Are we able to load the item passed in + * + * Arguments: + * * I - the item being loaded + * * user - the user doing the loading + */ +/obj/machinery/vending/proc/canLoadItem(obj/item/I, mob/user) + return FALSE /obj/machinery/vending/onTransitZ() return From 881f8765f448c8c6a7827c4a325759755da10f7d Mon Sep 17 00:00:00 2001 From: Artur Date: Wed, 1 Apr 2020 15:29:40 +0300 Subject: [PATCH 3/6] Stufffs --- code/datums/browser.dm | 2 +- code/modules/client/asset_cache.dm | 36 ++++++++- code/modules/vending/_vending.dm | 76 ++++++------------ sound/machines/machine_vend.ogg | Bin 0 -> 13777 bytes tgui-next/packages/tgui/interfaces/Vending.js | 23 +----- tgui-next/packages/tgui/public/tgui.bundle.js | 4 +- 6 files changed, 64 insertions(+), 77 deletions(-) create mode 100644 sound/machines/machine_vend.ogg diff --git a/code/datums/browser.dm b/code/datums/browser.dm index 8b82c1812e..5b1df6cb68 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -39,7 +39,7 @@ //title_image = ntitle_image /datum/browser/proc/add_stylesheet(name, file) - if (istype(name, /datum/asset/spritesheet)) + if(istype(name, /datum/asset/spritesheet)) var/datum/asset/spritesheet/sheet = name stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]" else diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm index 7bafefcf53..d78152acd3 100644 --- a/code/modules/client/asset_cache.dm +++ b/code/modules/client/asset_cache.dm @@ -704,9 +704,43 @@ GLOBAL_LIST_EMPTY(asset_datums) Insert(initial(D.id), I) return ..() +/datum/asset/spritesheet/vending + name = "vending" + +/datum/asset/spritesheet/vending/register() + for(var/k in GLOB.vending_products) + var/atom/item = k + if(!ispath(item, /atom)) + continue + + var/icon_file = initial(item.icon) + var/icon_state = initial(item.icon_state) + var/icon/I + + var/icon_states_list = icon_states(icon_file) + if(icon_state in icon_states_list) + I = icon(icon_file, icon_state, SOUTH) + var/c = initial(item.color) + if(!isnull(c) && c != "#FFFFFF") + I.Blend(c, ICON_MULTIPLY) + else + var/icon_states_string + for(var/an_icon_state in icon_states_list) + if(!icon_states_string) + icon_states_string = "[json_encode(an_icon_state)](\ref[an_icon_state])" + else + icon_states_string += ", [json_encode(an_icon_state)](\ref[an_icon_state])" + stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)](\ref[icon_state]), icon_states=[icon_states_string]") + I = icon('icons/turf/floors.dmi', "", SOUTH) + + var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-") + + Insert(imgid, I) + return ..() + /datum/asset/simple/genetics assets = list( "dna_discovered.gif" = 'html/dna_discovered.gif', "dna_undiscovered.gif" = 'html/dna_undiscovered.gif', "dna_extra.gif" = 'html/dna_extra.gif' -) \ No newline at end of file +) diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index c1f6a3a4be..d2ea0ada47 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -30,10 +30,6 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C var/amount = 0 ///How many we can store at maximum var/max_amount = 0 - ///Does the item have a custom price override - var/custom_price - ///Does the item have a custom premium price override - var/custom_premium_price /** * # vending machines @@ -96,12 +92,14 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C var/list/slogan_list = list() ///Small ad messages in the vending screen - random chance of popping up whenever you open it var/list/small_ads = list() + var/dish_quants = list() //used by the snack machine's custom compartment to count dishes. ///Message sent post vend (Thank you for shopping!) var/vend_reply ///Last world tick we sent a vent reply var/last_reply ///Last world tick we sent a slogan message out var/last_slogan + var/last_shopper ///How many ticks until we can send another var/slogan_delay = 6000 ///Icon when vending an item to the user @@ -125,9 +123,6 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C ///Bills we accept? var/obj/item/stack/spacecash/bill ///Default price of items if not overridden - var/default_price = 25 - ///Default price of premium items if not overridden - var/extra_price = 50 /** * Is this item on station or not * @@ -234,7 +229,7 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C return SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - if(!(machine_stat & BROKEN) && powered()) + if(!(stat & BROKEN) && powered()) SSvis_overlays.add_vis_overlay(src, icon, light_mask, EMISSIVE_LAYER, EMISSIVE_PLANE) /obj/machinery/vending/obj_break(damage_flag) @@ -289,8 +284,6 @@ GLOBAL_LIST_EMPTY(vending_products) if(!start_empty) R.amount = amount R.max_amount = amount - R.custom_price = initial(temp.custom_price) - R.custom_premium_price = initial(temp.custom_premium_price) recordlist += R /** * Refill a vending machine from a refill canister @@ -447,7 +440,7 @@ GLOBAL_LIST_EMPTY(vending_products) if(canLoadItem(the_item) && loadingAttempt(the_item,user)) SEND_SIGNAL(T, COMSIG_TRY_STORAGE_TAKE, the_item, src, TRUE) loaded++ - else + else denied_items++ if(denied_items) to_chat(user, "[src] refuses some items!") @@ -530,13 +523,6 @@ GLOBAL_LIST_EMPTY(vending_products) if(seconds_electrified && !(stat & NOPOWER)) if(shock(user, 100)) return - - if(tilted && !user.buckled && !isAI(user)) - to_chat(user, "You begin righting [src].") - if(do_after(user, 50, target=src)) - untilt(user) - return - return ..() /obj/machinery/vending/ui_base_html(html) @@ -558,7 +544,6 @@ GLOBAL_LIST_EMPTY(vending_products) var/list/data = list( path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"), name = R.name, - price = R.custom_price || default_price, max_amount = R.max_amount, ref = REF(R) ) @@ -568,7 +553,6 @@ GLOBAL_LIST_EMPTY(vending_products) var/list/data = list( path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"), name = R.name, - price = R.custom_premium_price || extra_price, max_amount = R.max_amount, ref = REF(R), premium = TRUE @@ -579,7 +563,6 @@ GLOBAL_LIST_EMPTY(vending_products) var/list/data = list( path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"), name = R.name, - price = R.custom_price || default_price, max_amount = R.max_amount, ref = REF(R), premium = TRUE @@ -588,66 +571,55 @@ GLOBAL_LIST_EMPTY(vending_products) /obj/machinery/vending/ui_data(mob/user) . = list() - var/mob/living/carbon/human/H - var/obj/item/card/id/C .["stock"] = list() - for (var/datum/data/vending_product/R in product_records + coin_records + hidden_records) + for(var/datum/data/vending_product/R in product_records + coin_records + hidden_records) .["stock"][R.name] = R.amount .["extended_inventory"] = extended_inventory /obj/machinery/vending/ui_act(action, params) . = ..() if(.) - return + return switch(action) if("vend") . = TRUE if(!vend_ready) - return - if(panel_open) + return + if(panel_open) to_chat(usr, "The vending machine cannot dispense products while its service panel is open!") - return + return vend_ready = FALSE //One thing at a time!! var/datum/data/vending_product/R = locate(params["ref"]) var/list/record_to_check = product_records + coin_records if(extended_inventory) record_to_check = product_records + coin_records + hidden_records - if(!R || !istype(R) || !R.product_path) + if(!R || !istype(R) || !R.product_path) vend_ready = TRUE - return - var/price_to_use = default_price - if(R.custom_price) - price_to_use = R.custom_price - if(R in hidden_records) - if(!extended_inventory) - vend_ready = TRUE return - else if (!(R in record_to_check)) + if(R in hidden_records) + if(!extended_inventory) + vend_ready = TRUE + return + else if(!(R in record_to_check)) vend_ready = TRUE - message_admins("Vending machine exploit attempted by [ADMIN_LOOKUPFLW(usr)]!") - return - if (R.amount <= 0) + message_admins("Vending machine exploit attempted by [ADMIN_LOOKUPFLW(usr)]!") + return + if (R.amount <= 0) say("Sold out of [R.name].") flick(icon_deny,src) vend_ready = TRUE - return - if(onstation && ishuman(usr)) - var/mob/living/carbon/human/H = usr - var/obj/item/card/id/C = H.get_idcard(TRUE) - - - + return if(last_shopper != usr || purchase_message_cooldown < world.time) say("Thank you for shopping with [src]!") purchase_message_cooldown = world.time + 5 SECONDS last_shopper = usr - use_power(5) - if(icon_vend) //Show the vending animation if needed - flick(icon_vend,src) + use_power(5) + if(icon_vend) //Show the vending animation if needed + flick(icon_vend,src) playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3) new R.product_path(get_turf(src)) R.amount-- - SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[R.product_path]")) + SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[R.product_path]")) vend_ready = TRUE /obj/machinery/vending/process() @@ -685,7 +657,7 @@ GLOBAL_LIST_EMPTY(vending_products) /obj/machinery/vending/power_change() . = ..() - if(powered()) + if(powered()) START_PROCESSING(SSmachines, src) //Somebody cut an important wire and now we're following a new definition of "pitch." diff --git a/sound/machines/machine_vend.ogg b/sound/machines/machine_vend.ogg new file mode 100644 index 0000000000000000000000000000000000000000..92867a1f3d3bd6b5b9a22e766ebeca6590420770 GIT binary patch literal 13777 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 { Welcome, {data.user.name}, {' '} {data.user.job || "Unemployed"}! -
- Your balance is {data.user.cash} credits. ) || ( @@ -47,16 +45,6 @@ export const Vending = props => {
{inventory.map((product => { - const free = ( - !data.onstation - || product.price === 0 - || ( - !product.premium - && data.department - && data.user - && data.department === data.user.department - ) - ); return ( @@ -91,7 +79,7 @@ export const Vending = props => { {custom && (
{inventory.map((product => { @@ -79,7 +65,7 @@ export const Vending = props => { {custom && (
{inventory.map((product => { @@ -65,16 +77,13 @@ export const Vending = props => { {custom && (