mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-16 10:35:41 +01:00
b79743df08
## About The Pull Request Adds a helper `SStts.random_tts_voice(gender)` which attempts to pick an appropriate voice for the passed gender ## Why It's Good For The Game Randomization into a completely wrong voice kinda ruins it ## Changelog 🆑 Melbert qol: TTS voice is more appropriately randomized to gender qol: Getting randomized through means such as mulligan toxin randomizes TTS voices /🆑 --------- Co-authored-by: Watermelon914 <37270891+Watermelon914@users.noreply.github.com>
480 lines
17 KiB
Plaintext
480 lines
17 KiB
Plaintext
///Maximum credits dump threshold
|
|
#define CREDITS_DUMP_THRESHOLD 50
|
|
/**
|
|
* # 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
|
|
///The price of the item
|
|
var/price
|
|
///Whether spessmen with an ID with an age below AGE_MINOR (20 by default) can buy this item
|
|
var/age_restricted = FALSE
|
|
///Whether the product can be recolored by the GAGS system
|
|
var/colorable
|
|
/// The category the product was in, if any.
|
|
/// Sourced directly from product_categories.
|
|
var/category
|
|
///List of items that have been returned to the vending machine.
|
|
var/list/returned_products
|
|
|
|
/datum/data/vending_product/Destroy(force)
|
|
returned_products = null
|
|
return ..()
|
|
|
|
/**
|
|
* # 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/machines/vending.dmi'
|
|
icon_state = "generic"
|
|
abstract_type = /obj/machinery/vending
|
|
layer = BELOW_OBJ_LAYER
|
|
density = TRUE
|
|
verb_say = "beeps"
|
|
verb_ask = "beeps"
|
|
verb_exclaim = "beeps"
|
|
max_integrity = 300
|
|
integrity_failure = 0.33
|
|
armor_type = /datum/armor/machinery_vending
|
|
circuit = /obj/item/circuitboard/machine/vendor
|
|
payment_department = ACCOUNT_SRV
|
|
light_power = 0.7
|
|
light_range = MINIMUM_USEFUL_LIGHT_RANGE
|
|
voice_filter = "alimiter=0.9,acompressor=threshold=0.2:ratio=20:attack=10:release=50:makeup=2,highpass=f=1000"
|
|
|
|
///Next world time to send a purchase message
|
|
var/purchase_message_cooldown
|
|
///The ref of the last mob to shop with us
|
|
var/last_shopper
|
|
///Whether the vendor is tilted or not
|
|
var/tilted = FALSE
|
|
/// If tilted, this variable should always be the rotation that was applied when we were tilted. Stored for the purposes of unapplying it.
|
|
var/tilted_rotation = 0
|
|
///Whether this vendor can be tilted over or not
|
|
var/tiltable = TRUE
|
|
///Damage this vendor does when tilting onto an atom
|
|
var/squish_damage = 75
|
|
/// The chance, in percent, of this vendor performing a critical hit on anything it crushes via [tilt].
|
|
var/crit_chance = 15
|
|
///List of mobs stuck under the vendor
|
|
var/list/pinned_mobs = list()
|
|
///Icon for the maintenance panel overlay
|
|
var/panel_type = "panel1"
|
|
///Whether this vendor can be selected when building a custom vending machine
|
|
var/allow_custom = TRUE
|
|
|
|
/**
|
|
* 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, categorized.
|
|
* Can only be used as an alternative to `products`, not alongside it.
|
|
*
|
|
* Form should be list(
|
|
* "name" = "Category Name",
|
|
* "icon" = "UI Icon (Font Awesome or tgfont)",
|
|
* "products" = list(/type/path = amount, ...),
|
|
* )
|
|
*/
|
|
var/list/product_categories = null
|
|
|
|
/**
|
|
* 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 = amount, /type/path2 = amount2)
|
|
*/
|
|
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 = ""
|
|
|
|
///List of standard product records
|
|
var/list/datum/data/vending_product/product_records = list()
|
|
///List of contraband product records
|
|
var/list/datum/data/vending_product/hidden_records = list()
|
|
///List of premium product records
|
|
var/list/datum/data/vending_product/coin_records = list()
|
|
///List of slogans to scream at potential customers; built upon Iniitialize() of the vendor from product_slogans
|
|
var/list/slogan_list = list()
|
|
///List of ads built from product_ads upon Iniitialize()
|
|
var/list/ad_list = list()
|
|
///Message sent post vend (Thank you for shopping!)
|
|
var/vend_reply
|
|
///Last world tick we sent a vent reply
|
|
var/last_reply = 0
|
|
///Last world tick we sent a slogan message out
|
|
var/last_slogan = 0
|
|
///How many ticks until we can send another
|
|
var/slogan_delay = 10 MINUTES
|
|
///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 = FALSE
|
|
///How likely this is to happen (prob 100) per second
|
|
var/shoot_inventory_chance = 1
|
|
//Stop spouting those godawful pitches!
|
|
var/shut_up = FALSE
|
|
///can we access the hidden inventory?
|
|
var/extended_inventory = FALSE
|
|
///Are we checking the users ID
|
|
var/scan_id = TRUE
|
|
///Default price of items if not overridden
|
|
var/default_price = 25
|
|
///Default price of premium items if not overridden
|
|
var/extra_price = 50
|
|
///fontawesome icon name to use in to display the user's balance in the vendor UI
|
|
var/displayed_currency_icon = "coins"
|
|
///String of the used currency to display in the vendor UI
|
|
var/displayed_currency_name = MONEY_SYMBOL
|
|
///Whether our age check is currently functional
|
|
var/age_restrictions = TRUE
|
|
/// How many credits does this vending machine have? 20% of all sales go to this pool, and are given freely when the machine is restocked, or successfully tilted. Lost on deconstruction.
|
|
var/credits_contained = 0
|
|
/**
|
|
* Is this item on station or not
|
|
*
|
|
* if it doesn't originate from off-station during mapload, all_products_free gets automatically set to TRUE if it was unset previously.
|
|
* if it's off-station during mapload, it's also safe from the brand intelligence event
|
|
*/
|
|
var/onstation = TRUE
|
|
/**
|
|
* DO NOT APPLY THIS GLOBALLY. For mapping var edits only.
|
|
* A variable to change on a per instance basis that allows the instance to avoid having onstation set for them during mapload.
|
|
* Setting this to TRUE means that the vending machine is treated as if it were still onstation if it spawns off-station during mapload.
|
|
* Useful to specify an off-station machine that will be affected by machine-brand intelligence for whatever reason.
|
|
*/
|
|
var/onstation_override = FALSE
|
|
/**
|
|
* If this is set to TRUE, all products sold by the vending machine are free (cost nothing).
|
|
* If unset, this will get automatically set to TRUE during init if the machine originates from off-station during mapload.
|
|
* Defaults to null, set it to TRUE or FALSE explicitly on a per-machine basis if you want to force it to be a certain value.
|
|
*/
|
|
var/all_products_free
|
|
|
|
//The type of refill canisters used by this machine.
|
|
var/obj/item/vending_refill/refill_canister = null
|
|
|
|
///Name of lighting mask for the vending machine
|
|
var/light_mask
|
|
|
|
//the path of the fish_source datum to use for the fishing_spot component
|
|
var/fish_source_path = /datum/fish_source/vending
|
|
|
|
/datum/armor/machinery_vending
|
|
melee = 20
|
|
fire = 50
|
|
acid = 70
|
|
|
|
/**
|
|
* 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)
|
|
//means we produce products with fixed amounts
|
|
if(!refill_canister)
|
|
circuit = null
|
|
RefreshParts()
|
|
|
|
. = ..()
|
|
|
|
set_wires(new /datum/wires/vending(src))
|
|
|
|
if(SStts.tts_enabled)
|
|
var/static/list/vendor_voice_by_type = list()
|
|
vendor_voice_by_type[type] ||= SStts.random_tts_voice()
|
|
voice = vendor_voice_by_type[type]
|
|
|
|
slogan_list = splittext(product_slogans, ";")
|
|
ad_list = splittext(product_ads, ";")
|
|
// 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()
|
|
|
|
if(mapload) //check if it was initially created off station during mapload.
|
|
if(!is_station_level(z))
|
|
if(!onstation_override)
|
|
onstation = FALSE
|
|
if(isnull(all_products_free)) // Only auto-set the free products var if we haven't explicitly assigned a value to it yet.
|
|
all_products_free = TRUE
|
|
if(circuit)
|
|
circuit.all_products_free = all_products_free //sync up the circuit so the pricing schema is carried over if it's reconstructed.
|
|
circuit.desc = "This board's card reader component has been cut out, along with its brand selection dial."
|
|
|
|
else if(circuit)
|
|
all_products_free = circuit.all_products_free //if it was constructed outside mapload, sync the vendor up with the circuit's var so you can't bypass price requirements by moving / reconstructing it off station.
|
|
if(!all_products_free)
|
|
AddComponent(/datum/component/payment, 0, SSeconomy.get_dep_account(payment_department), PAYMENT_VENDING)
|
|
register_context()
|
|
|
|
if(fish_source_path)
|
|
AddComponent(/datum/component/fishing_spot, fish_source_path)
|
|
|
|
/obj/machinery/vending/atom_break(damage_flag)
|
|
. = ..()
|
|
if(!.)
|
|
return
|
|
|
|
var/dump_amount = 0
|
|
var/found_anything = TRUE
|
|
while (found_anything)
|
|
found_anything = FALSE
|
|
for(var/datum/data/vending_product/record as anything in shuffle(product_records))
|
|
//first dump any of the items that have been returned, in case they contain the nuke disk or something
|
|
for(var/i in 1 to LAZYLEN(record.returned_products))
|
|
var/obj/item/returned_obj_to_dump = dispense(record, get_turf(src), dispense_returned = TRUE)
|
|
step(returned_obj_to_dump, pick(GLOB.alldirs))
|
|
|
|
if(record.amount <= 0) //Try to use a record that actually has something to dump.
|
|
continue
|
|
var/dump_path = record.product_path
|
|
if(!dump_path)
|
|
continue
|
|
// busting open a vendor will destroy some of the contents
|
|
if(found_anything && prob(80))
|
|
record.amount--
|
|
continue
|
|
|
|
var/obj/obj_to_dump = dispense(record, loc)
|
|
step(obj_to_dump, pick(GLOB.alldirs))
|
|
found_anything = TRUE
|
|
dump_amount++
|
|
if (dump_amount >= 16)
|
|
return
|
|
|
|
/obj/machinery/vending/on_deconstruction(disassembled)
|
|
var/obj/item/vending_refill/installed_refill = locate() in component_parts
|
|
if(!installed_refill)
|
|
return
|
|
|
|
var/list/datum/data/vending_product/record_list
|
|
var/list/canister_list
|
|
for(var/i in 1 to 3)
|
|
switch(i)
|
|
if (1)
|
|
record_list = product_records
|
|
canister_list = installed_refill.products
|
|
if (2)
|
|
record_list = hidden_records
|
|
canister_list = installed_refill.contraband
|
|
else
|
|
record_list = coin_records
|
|
canister_list = installed_refill.premium
|
|
|
|
canister_list.Cut()
|
|
for(var/datum/data/vending_product/record as anything in record_list)
|
|
var/stock = record.amount - LAZYLEN(record.returned_products)
|
|
if(stock)
|
|
canister_list[record.product_path] = stock
|
|
|
|
/obj/machinery/vending/Destroy()
|
|
QDEL_LIST(product_records)
|
|
QDEL_LIST(hidden_records)
|
|
QDEL_LIST(coin_records)
|
|
return ..()
|
|
|
|
/obj/machinery/vending/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
|
. = NONE
|
|
if(tilted && !held_item)
|
|
context[SCREENTIP_CONTEXT_LMB] = "Right machine"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
if(held_item?.tool_behaviour == TOOL_SCREWDRIVER)
|
|
context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
if(panel_open && held_item?.tool_behaviour == TOOL_WRENCH)
|
|
context[SCREENTIP_CONTEXT_LMB] = anchored ? "Unsecure" : "Secure"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
if(panel_open && held_item?.tool_behaviour == TOOL_CROWBAR)
|
|
context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
if(!isnull(held_item) && canLoadItem(held_item, user, send_message = FALSE))
|
|
context[SCREENTIP_CONTEXT_LMB] = "Load item"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
if(panel_open && istype(held_item, refill_canister))
|
|
context[SCREENTIP_CONTEXT_LMB] = "Restock vending machine[credits_contained ? " and collect [MONEY_NAME]" : null]"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
/**
|
|
* Returns the total loaded & max amount of items i.e list(total_loaded, total_maximum) in the vending machine based on the product records and premium records
|
|
*
|
|
* Arguments
|
|
* * contraband - should we count contrabrand as well
|
|
*/
|
|
/obj/machinery/vending/proc/total_stock(contrabrand = TRUE)
|
|
SHOULD_BE_PURE(TRUE)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
RETURN_TYPE(/list)
|
|
|
|
var/total_loaded = 0
|
|
var/total_max = 0
|
|
var/list/stock = product_records + coin_records
|
|
if(contrabrand)
|
|
stock += hidden_records
|
|
for(var/datum/data/vending_product/record as anything in stock)
|
|
total_loaded += record.amount
|
|
total_max += record.max_amount
|
|
return list(total_loaded, total_max)
|
|
|
|
/obj/machinery/vending/examine(mob/user)
|
|
. = ..()
|
|
if(isnull(refill_canister))
|
|
return // you can add the comment here instead
|
|
|
|
. += span_notice("Its maintenance panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].")
|
|
if(panel_open)
|
|
. += span_notice("The machine may be [EXAMINE_HINT("pried")] apart.")
|
|
|
|
var/list/total_stock = total_stock()
|
|
if(total_stock[2])
|
|
if(total_stock[1] < total_stock[2])
|
|
. += span_notice("\The [src] can be restocked with [span_boldnotice("\a [initial(refill_canister.machine_name)] [initial(refill_canister.name)]")] with the panel open.")
|
|
else
|
|
. += span_notice("\The [src] is fully stocked.")
|
|
if(credits_contained < CREDITS_DUMP_THRESHOLD && credits_contained > 0)
|
|
. += span_notice("It should have a handfull of [MONEY_NAME] stored based on the missing items.")
|
|
else if (credits_contained > PAYCHECK_CREW)
|
|
. += span_notice("It should have at least a full paycheck worth of [MONEY_NAME] inside!")
|
|
|
|
/obj/machinery/vending/update_appearance(updates = ALL)
|
|
. = ..()
|
|
if(machine_stat & BROKEN)
|
|
set_light(0)
|
|
return
|
|
set_light(powered() ? MINIMUM_USEFUL_LIGHT_RANGE : 0)
|
|
|
|
/obj/machinery/vending/update_icon_state()
|
|
if(machine_stat & BROKEN)
|
|
icon_state = "[initial(icon_state)]-broken"
|
|
return ..()
|
|
icon_state = "[initial(icon_state)][powered() ? null : "-off"]"
|
|
return ..()
|
|
|
|
/obj/machinery/vending/update_overlays()
|
|
. = ..()
|
|
if(panel_open)
|
|
. += panel_type
|
|
if(light_mask && !(machine_stat & BROKEN) && powered())
|
|
. += emissive_appearance(icon, light_mask, src)
|
|
|
|
/obj/machinery/vending/vv_edit_var(vname, vval)
|
|
. = ..()
|
|
if(vname == NAMEOF(src, all_products_free))
|
|
if (all_products_free)
|
|
qdel(GetComponent(/datum/component/payment))
|
|
else
|
|
AddComponent(/datum/component/payment, 0, SSeconomy.get_dep_account(payment_department), PAYMENT_VENDING)
|
|
update_static_data_for_all_viewers()
|
|
|
|
/obj/machinery/vending/emp_act(severity)
|
|
. = ..()
|
|
var/datum/language_holder/vending_languages = get_language_holder()
|
|
var/datum/wires/vending/vending_wires = wires
|
|
// if the language wire got pulsed during an EMP, this will make sure the language_iterator is synched correctly
|
|
vending_languages.selected_language = vending_languages.spoken_languages[vending_wires.language_iterator]
|
|
|
|
/obj/machinery/vending/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE)
|
|
if(!force)
|
|
return
|
|
. = ..()
|
|
|
|
/obj/machinery/vending/emag_act(mob/user, obj/item/card/emag/emag_card)
|
|
if(obj_flags & EMAGGED)
|
|
return FALSE
|
|
obj_flags |= EMAGGED
|
|
balloon_alert(user, "product lock disabled")
|
|
return TRUE
|
|
|
|
|
|
/obj/machinery/vending/power_change()
|
|
. = ..()
|
|
if(powered())
|
|
START_PROCESSING(SSmachines, src)
|
|
|
|
/obj/machinery/vending/process(seconds_per_tick)
|
|
if(!is_operational)
|
|
return PROCESS_KILL
|
|
|
|
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 && !shut_up && SPT_PROB(2.5, seconds_per_tick))
|
|
say(pick(slogan_list))
|
|
last_slogan = world.time
|
|
|
|
if(shoot_inventory && SPT_PROB(shoot_inventory_chance, seconds_per_tick))
|
|
throw_item()
|
|
|
|
|
|
//===============================SPEACH===================================================
|
|
/obj/machinery/vending/can_speak(allow_mimes)
|
|
return is_operational && !shut_up && ..()
|
|
|
|
|
|
/**
|
|
* 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(!is_operational)
|
|
return
|
|
if(!message)
|
|
return
|
|
|
|
say(message)
|
|
|
|
/datum/aas_config_entry/vendomat_age_control
|
|
name = "Security Alert: Underaged Substance Abuse"
|
|
announcement_lines_map = list(
|
|
"Message" = "SECURITY ALERT: Underaged crewmember %PERSON recorded attempting to purchase %PRODUCT in %LOCATION by %VENDOR. Please watch for substance abuse."
|
|
)
|
|
vars_and_tooltips_map = list(
|
|
"PERSON" = "will be replaced with the name of the crewmember",
|
|
"PRODUCT" = "with the product, he attempted to purchase",
|
|
"LOCATION" = "with place of purchase",
|
|
"VENDOR" = "with the vending machine"
|
|
)
|
|
//=============================================================================
|