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 += "
| [sanitize(R.name)] | " - if(R.amount > 0) - dat += "[R.amount] | Vend | " - else - dat += "0 | Vend | " - dat += "