mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-10 01:49:19 +00:00
750 lines
23 KiB
Plaintext
750 lines
23 KiB
Plaintext
/*
|
|
* Vending machine types - Can be found under /code/modules/vending/
|
|
*/
|
|
|
|
/*
|
|
|
|
/obj/machinery/vending/[vendors name here] // --vending machine template :)
|
|
name = ""
|
|
desc = ""
|
|
icon = ''
|
|
icon_state = ""
|
|
products = list()
|
|
contraband = list()
|
|
premium = list()
|
|
|
|
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."
|
|
icon = 'icons/obj/vending.dmi'
|
|
icon_state = "generic"
|
|
layer = BELOW_OBJ_LAYER
|
|
density = TRUE
|
|
verb_say = "beeps"
|
|
verb_ask = "beeps"
|
|
verb_exclaim = "beeps"
|
|
max_integrity = 300
|
|
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
|
|
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
|
|
|
|
/**
|
|
* 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/list/product_records = list()
|
|
var/list/hidden_records = list()
|
|
var/list/coin_records = list()
|
|
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
|
|
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.
|
|
|
|
///ID's that can load this vending machine wtih refills
|
|
var/list/canload_access_list
|
|
|
|
|
|
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
|
|
build_inv = TRUE
|
|
. = ..()
|
|
wires = new /datum/wires/vending(src)
|
|
if(build_inv) //non-constructable vending machine
|
|
build_inventory(products, product_records)
|
|
build_inventory(contraband, hidden_records)
|
|
build_inventory(premium, coin_records)
|
|
|
|
slogan_list = splittext(product_slogans, ";")
|
|
// So not all machines speak at the exact same time.
|
|
// The first time this machine says something will be at slogantime + this random value,
|
|
// so if slogantime is 10 minutes, it will say it at somewhere between 10 and 20 minutes after the machine is crated.
|
|
last_slogan = world.time + rand(0, slogan_delay)
|
|
power_change()
|
|
|
|
/obj/machinery/vending/Destroy()
|
|
QDEL_NULL(wires)
|
|
QDEL_NULL(coin)
|
|
return ..()
|
|
|
|
/obj/machinery/vending/can_speak()
|
|
return !shut_up
|
|
|
|
/obj/machinery/vending/RefreshParts() //Better would be to make constructable child
|
|
if(!component_parts)
|
|
return
|
|
|
|
product_records = list()
|
|
hidden_records = list()
|
|
coin_records = list()
|
|
build_inventory(products, product_records, start_empty = TRUE)
|
|
build_inventory(contraband, hidden_records, start_empty = TRUE)
|
|
build_inventory(premium, coin_records, start_empty = TRUE)
|
|
for(var/obj/item/vending_refill/VR in component_parts)
|
|
restock(VR)
|
|
|
|
/obj/machinery/vending/deconstruct(disassembled = TRUE)
|
|
if(!refill_canister) //the non constructable vendors drop metal instead of a machine frame.
|
|
if(!(flags_1 & NODECONSTRUCT_1))
|
|
new /obj/item/stack/sheet/metal(loc, 3)
|
|
qdel(src)
|
|
else
|
|
..()
|
|
|
|
/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
|
|
while (found_anything)
|
|
found_anything = FALSE
|
|
for(var/record in shuffle(product_records))
|
|
var/datum/data/vending_product/R = record
|
|
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--
|
|
// busting open a vendor will destroy some of the contents
|
|
if(found_anything && prob(80))
|
|
continue
|
|
|
|
var/obj/O = new dump_path(loc)
|
|
step(O, pick(GLOB.alldirs))
|
|
found_anything = TRUE
|
|
dump_amount++
|
|
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]
|
|
if(isnull(amount))
|
|
amount = 0
|
|
|
|
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()
|
|
if (!canister.contraband)
|
|
canister.contraband = contraband.Copy()
|
|
if (!canister.premium)
|
|
canister.premium = premium.Copy()
|
|
. = 0
|
|
. += 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)
|
|
var/datum/data/vending_product/record = R
|
|
var/diff = min(record.max_amount - record.amount, productlist[record.product_path])
|
|
if (diff)
|
|
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
|
|
|
|
var/obj/item/vending_refill/R = locate() in component_parts
|
|
if (!R)
|
|
CRASH("Constructible vending machine did not have a refill canister")
|
|
|
|
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)
|
|
var/datum/data/vending_product/record = R
|
|
.[record.product_path] += record.amount
|
|
|
|
/obj/machinery/vending/crowbar_act(mob/living/user, obj/item/I)
|
|
if(!component_parts)
|
|
return FALSE
|
|
default_deconstruction_crowbar(I)
|
|
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()
|
|
if(panel_open)
|
|
add_overlay("[initial(icon_state)]-panel")
|
|
updateUsrDialog()
|
|
else
|
|
to_chat(user, "<span class='warning'>You must first secure [src].</span>")
|
|
return TRUE
|
|
|
|
/obj/machinery/vending/attackby(obj/item/I, mob/user, params)
|
|
if(panel_open && is_wire_tool(I))
|
|
wires.interact(user)
|
|
return
|
|
else if(istype(I, /obj/item/coin))
|
|
if(coin)
|
|
to_chat(user, "<span class='warning'>[src] already has [coin] inserted</span>")
|
|
return
|
|
if(!premium.len)
|
|
to_chat(user, "<span class='warning'>[src] doesn't have a coin slot.</span>")
|
|
return
|
|
if(!user.transferItemToLoc(I, src))
|
|
return
|
|
coin = I
|
|
to_chat(user, "<span class='notice'>You insert [I] into [src].</span>")
|
|
return
|
|
else if(refill_canister && istype(I, refill_canister))
|
|
if (!panel_open)
|
|
to_chat(user, "<span class='warning'>You should probably unscrew the service panel first!</span>")
|
|
else if (stat & (BROKEN|NOPOWER))
|
|
to_chat(user, "<span class='notice'>[src] does not respond.</span>")
|
|
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, "<span class='warning'>[canister] is empty!</span>")
|
|
else
|
|
// instantiate canister if needed
|
|
var/transferred = restock(canister)
|
|
if(transferred)
|
|
to_chat(user, "<span class='notice'>You loaded [transferred] items in [src].</span>")
|
|
else
|
|
to_chat(user, "<span class='warning'>There's nothing to restock!</span>")
|
|
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, "<span class='warning'>[src]'s compartment is full.</span>")
|
|
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, "<span class='warning'>[src] refuses some items!</span>")
|
|
if(loaded)
|
|
to_chat(user, "<span class='notice'>You insert [loaded] dishes into [src]'s compartment.</span>")
|
|
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, "<span class='notice'>You insert [I] into [src]'s input compartment.</span>")
|
|
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, "<span class='warning'>[src]'s input compartment blinks red: Access denied.</span>")
|
|
return FALSE
|
|
|
|
/obj/machinery/vending/exchange_parts(mob/user, obj/item/storage/part_replacer/W)
|
|
if(!istype(W))
|
|
return FALSE
|
|
if((flags_1 & NODECONSTRUCT_1) && !W.works_from_distance)
|
|
return FALSE
|
|
if(!component_parts || !refill_canister)
|
|
return FALSE
|
|
|
|
var/moved = 0
|
|
if(panel_open || W.works_from_distance)
|
|
if(W.works_from_distance)
|
|
display_parts(user)
|
|
for(var/I in W)
|
|
if(istype(I, refill_canister))
|
|
moved += restock(I)
|
|
else
|
|
display_parts(user)
|
|
if(moved)
|
|
to_chat(user, "<span class='notice'>[moved] items restocked.</span>")
|
|
W.play_rped_sound()
|
|
return TRUE
|
|
|
|
/obj/machinery/vending/on_deconstruction()
|
|
update_canister()
|
|
. = ..()
|
|
|
|
/obj/machinery/vending/emag_act(mob/user)
|
|
. = ..()
|
|
if(obj_flags & EMAGGED)
|
|
return
|
|
obj_flags |= EMAGGED
|
|
to_chat(user, "<span class='notice'>You short out the product lock on [src].</span>")
|
|
return TRUE
|
|
|
|
/obj/machinery/vending/_try_interact(mob/user)
|
|
if(seconds_electrified && !(stat & NOPOWER))
|
|
if(shock(user, 100))
|
|
return
|
|
return ..()
|
|
|
|
/obj/machinery/vending/ui_base_html(html)
|
|
var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/vending)
|
|
. = replacetext(html, "<!--customheadhtml-->", assets.css_tag())
|
|
|
|
/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()
|
|
|
|
/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/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/ui_act(action, params)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
switch(action)
|
|
if("vend")
|
|
. = TRUE
|
|
if(!vend_ready)
|
|
return
|
|
if(panel_open)
|
|
to_chat(usr, "<span class='warning'>The vending machine cannot dispense products while its service panel is open!</span>")
|
|
return
|
|
if((!allowed(usr)) && !(obj_flags & EMAGGED) && scan_id) //For SECURE VENDING MACHINES YEAH
|
|
to_chat(usr, "<span class='warning'>Access denied.</span>" )
|
|
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, "<span class='warning'>You need to a coin to get this item!</span>")
|
|
vend_ready = TRUE
|
|
return
|
|
if(coin && coin.string_attached)
|
|
if(!prob(50))
|
|
to_chat(usr, "<span class='warning'>You weren't able to pull [coin] out fast enough, the machine ate it, string and all!</span>")
|
|
QDEL_NULL(coin)
|
|
return
|
|
if(!usr.CanReach(src))
|
|
to_chat(usr, "<span class='notice'>You successfully pull [coin] out of [src] to the floor.</span>")
|
|
coin = null
|
|
if(!usr.put_in_hands(coin))
|
|
to_chat(usr, "<span class='warning'>You couldn't pull [coin] out because your hands are full!</span>")
|
|
QDEL_NULL(coin)
|
|
to_chat(usr, "<span class='notice'>You successfully pull [coin] out before [src] could swallow it.</span>")
|
|
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, "<span class='warning'>Sold out.</span>")
|
|
vend_ready = TRUE
|
|
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) && usr.put_in_hands(vended))
|
|
to_chat(usr, "<span class='notice'>You take [R.name] out of the slot.</span>")
|
|
else
|
|
to_chat(usr, "<span class='warning'>[capitalize(R.name)] falls onto the floor!</span>")
|
|
|
|
|
|
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, "<span class='notice'>You remove [coin] from [src].</span>")
|
|
coin = null
|
|
return
|
|
|
|
else if("togglevoice" && panel_open)
|
|
shut_up = !shut_up
|
|
|
|
/obj/machinery/vending/process()
|
|
if(stat & (BROKEN|NOPOWER))
|
|
return PROCESS_KILL
|
|
if(!active)
|
|
return
|
|
|
|
if(seconds_electrified > MACHINE_NOT_ELECTRIFIED)
|
|
seconds_electrified--
|
|
|
|
//Pitch to the people! Really sell it!
|
|
if(last_slogan + slogan_delay <= world.time && slogan_list.len > 0 && !shut_up && prob(5))
|
|
var/slogan = pick(slogan_list)
|
|
speak(slogan)
|
|
last_slogan = world.time
|
|
|
|
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
|
|
if(!message)
|
|
return
|
|
|
|
say(message)
|
|
|
|
/obj/machinery/vending/power_change()
|
|
. = ..()
|
|
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)
|
|
if(!target)
|
|
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 FALSE
|
|
|
|
pre_throw(throw_item)
|
|
|
|
throw_item.throw_at(target, 16, 3)
|
|
visible_message("<span class='danger'>[src] launches [throw_item] at [target]!</span>")
|
|
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
|
|
/**
|
|
* 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
|
|
do_sparks(5, TRUE, src)
|
|
var/check_range = TRUE
|
|
if(electrocute_mob(user, get_area(src), src, 0.7, check_range))
|
|
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
|