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/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm index 5b7c70b1b6..2303278f5c 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -706,11 +706,11 @@ CIGARETTE PACKETS ARE IN FANCY.DM name = "\improper E-Cigarette" desc = "A classy and highly sophisticated electronic cigarette, for classy and dignified gentlemen. A warning label reads \"Warning: Do not fill with flammable materials.\""//<<< i'd vape to that. icon = 'icons/obj/clothing/masks.dmi' - icon_state = null - item_state = null + icon_state = "black_vape" + item_state = "black_vape" w_class = WEIGHT_CLASS_TINY var/chem_volume = 100 - var/vapetime = FALSE //this so it won't puff out clouds every tick + var/vapetime = FALSE //this so it won't puff out clouds every tick var/screw = FALSE // kinky var/super = FALSE //for the fattest vapes dude. @@ -723,11 +723,10 @@ CIGARETTE PACKETS ARE IN FANCY.DM . = ..() create_reagents(chem_volume, NO_REACT, NO_REAGENTS_VALUE) // so it doesn't react until you light it reagents.add_reagent(/datum/reagent/drug/nicotine, 50) - if(!icon_state) - if(!param_color) - param_color = pick("red","blue","black","white","green","purple","yellow","orange") - icon_state = "[param_color]_vape" - item_state = "[param_color]_vape" + if(!param_color) + param_color = pick("red","blue","black","white","green","purple","yellow","orange") + icon_state = "[param_color]_vape" + item_state = "[param_color]_vape" /obj/item/clothing/mask/vape/attackby(obj/item/O, mob/user, params) if(O.tool_behaviour == TOOL_SCREWDRIVER) @@ -1067,4 +1066,4 @@ CIGARETTE PACKETS ARE IN FANCY.DM name = "coconut bong" icon_off = "coconut_bong" icon_on = "coconut_bong_lit" - desc = "A water bong used for smoking dried plants. This one's made out of a coconut and some bamboo." \ No newline at end of file + desc = "A water bong used for smoking dried plants. This one's made out of a coconut and some bamboo." 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/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index 0b58ea234c..d5632a1e4c 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -187,14 +187,15 @@ /obj/item/clothing/head/wig name = "wig" desc = "A bunch of hair without a head attached." - icon_state = "" - item_state = "pwig" + icon = 'icons/mob/human_face.dmi' // default icon for all hairs + icon_state = "hair_vlong" flags_inv = HIDEHAIR + color = "#000" var/hair_style = "Very Long Hair" - var/hair_color = "#000" /obj/item/clothing/head/wig/Initialize(mapload) . = ..() + icon_state = "" //Shitty hack that i dont know if it is even neccesary to deal with the vendor stack exception update_icon() /obj/item/clothing/head/wig/update_icon_state() @@ -202,14 +203,9 @@ if(!S) icon = 'icons/obj/clothing/hats.dmi' icon_state = "pwig" - -/obj/item/clothing/head/wig/update_overlays() - . = ..() - var/datum/sprite_accessory/S = GLOB.hair_styles_list[hair_style] - if(S) - var/mutable_appearance/M = mutable_appearance(S.icon, S.icon_state, color = hair_color) - M.appearance_flags |= RESET_COLOR - . += M + else + icon = S.icon + icon_state = S.icon_state /obj/item/clothing/head/wig/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) . = list() @@ -219,12 +215,12 @@ return var/mutable_appearance/M = mutable_appearance(S.icon, S.icon_state,layer = -HAIR_LAYER) M.appearance_flags |= RESET_COLOR - M.color = hair_color + M.color = color . += M /obj/item/clothing/head/wig/random/Initialize(mapload) hair_style = pick(GLOB.hair_styles_list - "Bald") //Don't want invisible wig - hair_color = "#[random_short_color()]" + color = "#[random_short_color()]" . = ..() /obj/item/clothing/head/bronze diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 8a62c19e16..8fdc1c7c0c 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -16,12 +16,26 @@ 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 +/** + * # 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 +50,117 @@ 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! - 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 - var/obj/item/coin/coin - var/obj/item/stack/spacecash/bill - - var/static/vending_cache = list() //used for storing the icons of items being vended - + ///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 + 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 + //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 + ///Default price of items if not overridden + /** + * 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/obj/item/vending_refill/refill_canister = null //The type of refill canisters used by this machine. + ///ID's that can load this vending machine wtih refills + var/list/canload_access_list -/obj/machinery/vending/Initialize() + + var/list/vending_machine_input = list() + ///Display header on the input view + var/input_display_header = "Custom Vendor" + + //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 @@ -94,9 +182,11 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C /obj/machinery/vending/Destroy() QDEL_NULL(wires) QDEL_NULL(coin) - 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 +208,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(!(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 +257,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 +275,21 @@ 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 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 +301,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 +317,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 +329,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 +350,14 @@ 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) 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() @@ -232,9 +376,6 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C if(coin) to_chat(user, "[src] already has [coin] inserted") return - if(bill) - to_chat(user, "[src] already has [bill] inserted") - return if(!premium.len) to_chat(user, "[src] doesn't have a coin slot.") return @@ -243,41 +384,87 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C coin = I to_chat(user, "You insert [I] into [src].") return - else if(istype(I, /obj/item/stack/spacecash)) - if(coin) - to_chat(user, "[src] already has [coin] inserted") - return - if(bill) - to_chat(user, "[src] already has [bill] inserted") - return - var/obj/item/stack/S = I - if(!premium.len) - to_chat(user, "[src] doesn't have a bill slot.") - return - S.use(1) - bill = new S.type(src, 1) - to_chat(user, "You insert [I] into [src].") - 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 + 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 - return ..() + . = ..() + +/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 +484,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 @@ -319,188 +506,144 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C 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", name, 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, + 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, + 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, + 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() + .["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 + .["coin"] = coin -/obj/machinery/vending/Topic(href, href_list) - if(..()) +/obj/machinery/vending/ui_act(action, params) + . = ..() + if(.) return - - if(href_list["remove_coin"]) - if(!(coin || bill)) - to_chat(usr, "There is no money in this machine.") - 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() - 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!") - 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"]) - if(!R || !istype(R) || !R.product_path) - vend_ready = 1 - return - - if(R in hidden_records) - if(!extended_inventory) - vend_ready = 1 + switch(action) + if("vend") + . = TRUE + if(!vend_ready) 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 + if(panel_open) + to_chat(usr, "The vending machine cannot dispense products while its service panel is open!") 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 + 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 = FALSE //One thing at a time!! + var/datum/data/vending_product/R = locate(params["ref"]) + if(!R || !istype(R) || !R.product_path) + vend_ready = TRUE + return + if(R in hidden_records) + if(!extended_inventory) + vend_ready = TRUE + return + else if(R in coin_records) + if(!(coin)) + to_chat(usr, "You need to a coin to get this item!") + vend_ready = TRUE + return + if(coin && coin.string_attached) + if(!prob(50)) + to_chat(usr, "You weren't able to pull [coin] out fast enough, the machine ate it, string and all!") + QDEL_NULL(coin) + return + if(!usr.CanReach(src)) 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!") + if(!usr.put_in_hands(coin)) + to_chat(usr, "You couldn't pull [coin] out because your hands are full!") + QDEL_NULL(coin) + to_chat(usr, "You successfully pull [coin] out before [src] could swallow it.") + coin = null QDEL_NULL(coin) + else if(!(R in product_records)) + 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 = TRUE + return else - QDEL_NULL(coin) - QDEL_NULL(bill) - - else if (!(R in product_records)) - vend_ready = 1 - message_admins("Vending machine exploit attempted by [ADMIN_LOOKUPFLW(usr)]!") - return - - if (R.amount <= 0) - to_chat(usr, "Sold out.") - vend_ready = 1 - return - else - R.amount-- - - if(((last_reply + 200) <= world.time) && vend_reply) - speak(vend_reply) - last_reply = world.time - - 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)) + R.amount-- + if(((last_reply + 200) <= world.time) && vend_reply) + speak(vend_reply) + last_reply = world.time + 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) && 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!") - SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[R.product_path]")) - vend_ready = 1 - updateUsrDialog() + + SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[R.product_path]")) + vend_ready = TRUE + return + if("takeoutcoin") + usr.put_in_hands(coin) + to_chat(usr, "You remove [coin] from [src].") + coin = null return - - else if(href_list["togglevoice"] && panel_open) + + else if("togglevoice" && panel_open) shut_up = !shut_up - - updateUsrDialog() - - + /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 +654,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,47 +671,61 @@ 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 + . = ..() + if(powered()) + 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) + var/mob/living/target = locate() in view(7, src) if(!target) - return 0 - + return FALSE for(var/datum/data/vending_product/R in shuffle(product_records)) if(R.amount <= 0) //Try to use a record that actually has something to dump. continue var/dump_path = R.product_path if(!dump_path) continue - R.amount-- throw_item = new dump_path(loc) break if(!throw_item) - return 0 + return FALSE pre_throw(throw_item) throw_item.throw_at(target, 16, 3) visible_message("[src] launches [throw_item] at [target]!") - return 1 - + return TRUE +/** + * 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 +735,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 diff --git a/sound/machines/machine_vend.ogg b/sound/machines/machine_vend.ogg new file mode 100644 index 0000000000..92867a1f3d Binary files /dev/null and b/sound/machines/machine_vend.ogg differ diff --git a/tgui-next/packages/tgui/interfaces/Vending.js b/tgui-next/packages/tgui/interfaces/Vending.js index e394c2d241..b6ffde015d 100644 --- a/tgui-next/packages/tgui/interfaces/Vending.js +++ b/tgui-next/packages/tgui/interfaces/Vending.js @@ -9,54 +9,40 @@ export const Vending = props => { const { ref } = config; let inventory; let custom = false; - if (data.vending_machine_input) { - inventory = data.vending_machine_input; - custom = true; + const onstation = true; + if (data.extended_inventory && data.coin) { + inventory = [ + ...data.product_records, + ...data.hidden_records, + ...data.coin_records, + ]; } else if (data.extended_inventory) { inventory = [ ...data.product_records, - ...data.coin_records, ...data.hidden_records, ]; + } else if (data.coin) { + inventory = [ + ...data.product_records, + ...data.coin_records, + ]; } else { inventory = [ ...data.product_records, - ...data.coin_records, ]; } return ( - {data.onstation && ( -
- {data.user && ( - - Welcome, {data.user.name}, - {' '} - {data.user.job || "Unemployed"}! -
- Your balance is {data.user.cash} credits. -
- ) || ( - - No registered ID card!
- Please contact your local HoP! -
- )} + {data.coin && ( +
+
)}
{inventory.map((product => { - const free = ( - !data.onstation - || product.price === 0 - || ( - !product.premium - && data.department - && data.user - && data.department === data.user.department - ) - ); return ( @@ -91,23 +77,13 @@ export const Vending = props => { {custom && (