From 4d08255ebd4243724d9282b4b387256f0615b86f Mon Sep 17 00:00:00 2001 From: CHOMPStation2StaffMirrorBot <94713762+CHOMPStation2StaffMirrorBot@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:02:11 -0700 Subject: [PATCH] [MIRROR] Mob Hose Inflation and Draining (#11070) Co-authored-by: Will <7099514+Willburd@users.noreply.github.com> Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com> --- code/__defines/dcs/signals.dm | 3 + code/__defines/hoses.dm | 2 + .../components/reagent_hose/connector.dm | 137 +++++++++--- code/datums/components/reagent_hose/datum.dm | 4 +- .../components/reagent_hose/inflation.dm | 201 ++++++++++++++++++ code/datums/components/reagent_hose/item.dm | 27 ++- code/game/objects/structures/fence.dm | 17 ++ .../game/objects/structures/fence_electric.dm | 66 ++++++ code/modules/mob/living/carbon/human/human.dm | 1 + .../modules/mob/living/silicon/robot/robot.dm | 2 + code/modules/power/fusion/core/_core.dm | 3 +- code/modules/reagents/holder/holder.dm | 4 +- code/modules/reagents/machinery/pump.dm | 7 +- .../reagents/reagent_containers/spray.dm | 11 +- code/modules/vore/eating/living_vr.dm | 7 +- icons/obj/fence_electric.dmi | Bin 0 -> 7609 bytes vorestation.dme | 2 + 17 files changed, 433 insertions(+), 61 deletions(-) create mode 100644 code/datums/components/reagent_hose/inflation.dm create mode 100644 code/game/objects/structures/fence_electric.dm create mode 100644 icons/obj/fence_electric.dmi diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm index c4290a9456..873e6776ed 100644 --- a/code/__defines/dcs/signals.dm +++ b/code/__defines/dcs/signals.dm @@ -857,6 +857,9 @@ ///from /datum/species/xenochimera/handle_environment_special() #define COMSIG_XENOCHIMERA_COMPONENT "xenochimera_component" +// Hose Connector Component +#define COMSIG_HOSE_FORCEPUMP "hose_force_pump" + //Unittest data update #ifdef UNIT_TEST #define COMSIG_UNITTEST_DATA "unittest_send_data" diff --git a/code/__defines/hoses.dm b/code/__defines/hoses.dm index 056949fac8..d416a44766 100644 --- a/code/__defines/hoses.dm +++ b/code/__defines/hoses.dm @@ -1,3 +1,5 @@ #define HOSE_INPUT "Input"// Only pull liquid. #define HOSE_OUTPUT "Output"// Only push liquid. #define HOSE_NEUTRAL "Neutral"// Equalize liquids, not super efficient, can waste reagents due to rounding. + +#define HOSE_MAX_DISTANCE 10 diff --git a/code/datums/components/reagent_hose/connector.dm b/code/datums/components/reagent_hose/connector.dm index 2db8950d68..d909da9c0c 100644 --- a/code/datums/components/reagent_hose/connector.dm +++ b/code/datums/components/reagent_hose/connector.dm @@ -1,5 +1,6 @@ /datum/component/hose_connector var/name = "" + dupe_mode = COMPONENT_DUPE_ALLOWED VAR_PROTECTED/obj/carrier = null VAR_PROTECTED/flow_direction = HOSE_NEUTRAL VAR_PROTECTED/datum/hose/my_hose = null @@ -17,6 +18,7 @@ connector_number = CL.len + 1 RegisterSignal(carrier, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine)) RegisterSignal(carrier, COMSIG_MOVABLE_MOVED, PROC_REF(move_react)) + RegisterSignal(carrier, COMSIG_HOSE_FORCEPUMP, PROC_REF(force_pump)) carrier.verbs |= /atom/proc/disconnect_hose START_PROCESSING(SSobj, src) @@ -25,6 +27,7 @@ STOP_PROCESSING(SSobj, src) UnregisterSignal(carrier, COMSIG_PARENT_EXAMINE) UnregisterSignal(carrier, COMSIG_MOVABLE_MOVED) + UnregisterSignal(carrier, COMSIG_HOSE_FORCEPUMP) carrier.verbs -= /atom/proc/disconnect_hose carrier = null if(my_hose) @@ -43,10 +46,35 @@ /datum/component/hose_connector/proc/get_flow_direction() return flow_direction -/datum/component/hose_connector/output/process() - return +/datum/component/hose_connector/proc/get_id() + return "[name] #[connector_number]" + +/datum/component/hose_connector/proc/connected_reagents() + return carrier.reagents + +/datum/component/hose_connector/process() + // Return reagents to source if no hose, lossy to avoid exploits + if(!my_hose) + if(!reagents.total_volume) + reagents.trans_to_holder(connected_reagents(), reagents.maximum_volume) + reagents.clear_reagents() // Wipe it to avoid exploits + return + var/datum/reagents/connected_to = connected_reagents() + if(!connected_to) // Emergency. the vorebelly was deleted or something. Lets just hard lock that out from maintaining state by disconnecting the tube. + reagents.clear_reagents() + my_hose.disconnect() + return + handle_pump(connected_to) + +/datum/component/hose_connector/proc/handle_pump(var/datum/reagents/connected_to) + PROTECTED_PROC(TRUE) + ASSERT(connected_to) + // Drain our connector back into tank, and then fill it randomly. The hose handles swapping. + reagents.trans_to_holder(connected_to, reagents.maximum_volume) + connected_to.trans_to_holder(reagents, rand(1,reagents.maximum_volume)) /datum/component/hose_connector/proc/force_pump() + SIGNAL_HANDLER process() if(prob(5)) carrier.visible_message(span_infoplain(span_bold("\The [carrier]") + " gurgles as it pumps fluid.")) @@ -55,6 +83,8 @@ if(istype(C)) if(C.my_hose) return FALSE + if(C.flow_direction == HOSE_NEUTRAL || flow_direction == HOSE_NEUTRAL) // Always allowed + return TRUE if(C.flow_direction in (list(HOSE_INPUT, HOSE_OUTPUT) - flow_direction)) return TRUE return FALSE @@ -68,10 +98,47 @@ /datum/component/hose_connector/proc/connect(var/datum/hose/H = null) my_hose = H -/datum/component/hose_connector/proc/setup_hoses(var/datum/component/hose_connector/target, var/distancetonode) - if(target) - var/datum/hose/H = new() - H.set_hose(src, target, distancetonode) +/datum/component/hose_connector/proc/setup_hoses(var/datum/component/hose_connector/target, var/distancetonode, var/mob/user) + if(!target || QDELETED(target)) + to_chat(user,span_danger("What you were connecting to has stopped existing! Ohno!")) + return FALSE + + // Logic for handling two mobs at once would be a mess of option selections and prefs... + if(istype(src,/datum/component/hose_connector/inflation) && istype(target,/datum/component/hose_connector/inflation)) + to_chat(user,span_notice("Nothing would flow between \the [get_carrier()] and \the [target.get_carrier()] without anything to pump it!")) + return FALSE + + // Check for vore inflation connectors. + if(istype(src,/datum/component/hose_connector/inflation) || istype(target,/datum/component/hose_connector/inflation)) + // Handle the connection target once we setup the hose. Needs to be done like this as either ends can be the inflation connector + // Also has to be done on finalize, as players would be able to click one then the other, then potentially drop or do other stuff with the hose! + var/datum/component/hose_connector/inflation/I = src + if(istype(I)) + if(!I.inflation_setup(user,target)) + return FALSE + else + I = target + if(istype(I)) + if(!I.inflation_setup(user,src)) + return FALSE + else // Good going, you broke it + to_chat(user,span_notice("You're not sure what happened, but you couldn't connect the hose...")) + return FALSE + else + to_chat(user, span_notice("You connect the [src] to \the [target].")) + + // Handle invalid vorebellies, has to be done after inflation_setup() + if(!src.connected_reagents()) + to_chat(user,span_warning("\The [get_carrier()] doesn't seem ready to connect yet.")) + return FALSE + if(!target.connected_reagents()) + to_chat(user,span_warning("\The [target.get_carrier()] doesn't seem ready to connect yet.")) + return FALSE + + // Hose prepared! + var/datum/hose/H = new() + H.set_hose(src, target, distancetonode, user) + return TRUE /datum/component/hose_connector/proc/get_pairing() RETURN_TYPE(/datum/component/hose_connector) @@ -85,7 +152,9 @@ /datum/component/hose_connector/proc/on_examine(datum/source, mob/user, list/examine_texts) SIGNAL_HANDLER var/datum/component/hose_connector/hose_pair = my_hose?.get_pairing(src) - if(hose_pair) + if(istype(hose_pair,/datum/component/hose_connector/inflation)) + hose_pair = "\the [hose_pair.name]" // Slightly different, so it shows the belly attached + else if(hose_pair) hose_pair = "\the [hose_pair.get_carrier()]" else hose_pair = "nothing" @@ -101,30 +170,6 @@ // Handle distance check if too far my_hose.update_beam() -/* - * Subtypes - */ - -/// Pumps reagents out of carrier -/datum/component/hose_connector/input - name = "hose input" - flow_direction = HOSE_INPUT - -/datum/component/hose_connector/input/process() - if(carrier) - reagents.trans_to_obj(carrier, reagents.maximum_volume) - -/// Pumps reagents into carrier -/datum/component/hose_connector/output - name = "hose output" - flow_direction = HOSE_OUTPUT - -/datum/component/hose_connector/output/process() - if(carrier && my_hose) - carrier.reagents.trans_to_holder(reagents, reagents.maximum_volume) - else if(reagents.total_volume && carrier && !my_hose) - reagents.trans_to_obj(carrier, reagents.maximum_volume) - /* * Support procs/verbs */ @@ -138,15 +183,39 @@ var/list/available_sockets = list() for(var/datum/component/hose_connector/HC in GetComponents(/datum/component/hose_connector)) if(HC.get_hose()) - available_sockets |= HC + available_sockets[HC.get_id()] = HC if(!LAZYLEN(available_sockets)) return if(available_sockets.len == 1) - var/datum/component/hose_connector/AC = available_sockets[1] + var/key = available_sockets[1] + var/datum/component/hose_connector/AC = available_sockets[key] AC.disconnect_action(usr) else var/choice = tgui_input_list(usr, "Select a target hose connector.", "Socket Disconnect", available_sockets) if(choice) - var/datum/component/hose_connector/AC = choice + var/datum/component/hose_connector/AC = available_sockets[choice] AC.disconnect_action(usr) + + +/* + * Standard subtypes + */ + +/// Pumps reagents out of carrier +/datum/component/hose_connector/input + name = "hose input" + flow_direction = HOSE_INPUT + +/datum/component/hose_connector/input/handle_pump(var/datum/reagents/connected_to) + ASSERT(connected_to) + reagents.trans_to_holder(connected_to, reagents.maximum_volume) + +/// Pumps reagents into carrier +/datum/component/hose_connector/output + name = "hose output" + flow_direction = HOSE_OUTPUT + +/datum/component/hose_connector/output/handle_pump(var/datum/reagents/connected_to) + ASSERT(connected_to) + connected_to.trans_to_holder(reagents, reagents.maximum_volume) diff --git a/code/datums/components/reagent_hose/datum.dm b/code/datums/components/reagent_hose/datum.dm index 12741f3ea6..35b2887604 100644 --- a/code/datums/components/reagent_hose/datum.dm +++ b/code/datums/components/reagent_hose/datum.dm @@ -10,7 +10,7 @@ VAR_PRIVATE/hose_color = "#ffffff" - VAR_PRIVATE/initial_distance = 7 + VAR_PRIVATE/initial_distance = HOSE_MAX_DISTANCE VAR_PRIVATE/datum/beam/current_beam = null /datum/hose/proc/get_pairing(var/datum/component/hose_connector/target) @@ -104,7 +104,7 @@ new_col = reagent_node2.get_color() // We are in the beam! - qdel_swap(current_beam, A.Beam(B, icon_state = "hose", beam_color = new_col, maxdistance = world.view, beam_type = /obj/effect/ebeam/hose)) + qdel_swap(current_beam, A.Beam(B, icon_state = "hose", beam_color = new_col, maxdistance = (HOSE_MAX_DISTANCE + 1), beam_type = /obj/effect/ebeam/hose)) return TRUE diff --git a/code/datums/components/reagent_hose/inflation.dm b/code/datums/components/reagent_hose/inflation.dm new file mode 100644 index 0000000000..8006a6e23b --- /dev/null +++ b/code/datums/components/reagent_hose/inflation.dm @@ -0,0 +1,201 @@ +/* + * Inflation subtype, Big and round! + */ +/datum/component/hose_connector/inflation + flow_direction = HOSE_NEUTRAL + var/connection_mode = CHEM_INGEST + var/mob/living/carbon/human/human_owner + +/datum/component/hose_connector/inflation/Initialize() + if(!ishuman(parent)) + return COMPONENT_INCOMPATIBLE + . = ..() + human_owner = parent + remove_verb(human_owner,/atom/proc/disconnect_hose) + +/datum/component/hose_connector/inflation/Destroy() + human_owner = null + . = ..() + +/datum/component/hose_connector/inflation/on_examine(datum/source, mob/user, list/examine_texts) + return + +/datum/component/hose_connector/inflation/proc/get_destination_name() + switch(connection_mode) + if(CHEM_INGEST) + return "mouth" + if(CHEM_VORE) + return human_owner?.vore_selected?.name ? sanitize(human_owner.vore_selected.name) : "belly" + if(CHEM_BLOOD) + return "bloodstream" + return "something" + +/datum/component/hose_connector/inflation/get_id() + return "\The [human_owner]'s [get_destination_name()]" + +// Adding and removing the verb is more complex on humans... This code also expects only ONE hose connector +/datum/component/hose_connector/inflation/connect(datum/hose/H) + . = ..() + add_verb(human_owner,/atom/proc/disconnect_hose) + +/datum/component/hose_connector/inflation/remove_hose() + remove_verb(human_owner,/atom/proc/disconnect_hose) + . = ..() + +// Succ command center +/datum/component/hose_connector/inflation/proc/inflation_setup(var/mob/user,var/datum/component/hose_connector/other) + if(!other || QDELETED(other)) + to_chat(user,span_danger("You couldn't connect the hose, as the connection stopped existing! Ohno!")) + return FALSE + + // Check for destinations + var/list/options = list("Mouth") + if(human_owner?.vore_selected) + options.Add("Belly ([sanitize(human_owner.vore_selected.name)])") + if(!human_owner.isSynthetic()) // Results in a lot of bad behaviors... + options.Add("Bloodstream") + + // Choose destination + var/choice = tgui_alert(user, "Select where this hose connects.", "Hose Connection", options) + if(!user.Adjacent(human_owner) || !choice) + to_chat(user,span_notice("You decide not to connect \the [human_owner] to the hose.")) + return FALSE + + // Setup the connection to mouth, vore, or blood + var/feedback = "" + switch(choice) + if("Mouth") + connection_mode = CHEM_INGEST + feedback = "mouth" + if("Bloodstream") + connection_mode = CHEM_BLOOD + feedback = span_danger("bloodstream") + else + connection_mode = CHEM_VORE // Anything else is a vore belly name + if(human_owner.vore_selected) + feedback = sanitize(human_owner.vore_selected.name) + + // Display action + name = "[human_owner]'s [feedback]" + user.visible_message("\The [user] starts to connect the hose to \the [human_owner]'s [feedback]...") + if(!do_after(user,7 SECONDS,human_owner)) + to_chat(user,span_warning("You couldn't connect the hose!")) + return FALSE + if(other.get_hose() || get_hose()) // SHouldn't be connected to anything yet! + to_chat(user,span_warning("You couldn't connect the hose, another hose is already connected!")) + return FALSE + if(connection_mode == CHEM_BLOOD) //OWCH! + human_owner.adjustBruteLossByPart(10,BP_TORSO) + if(human_owner.can_pain_emote) // Doing this probably doesn't feel too good + human_owner.emote("pain") + to_chat(user, span_notice("You connect the hose to \the [human_owner]'s [feedback]...")) + return TRUE + +/datum/component/hose_connector/inflation/connected_reagents() + switch(connection_mode) + if(CHEM_INGEST) + return human_owner.ingested + if(CHEM_VORE) + return human_owner?.vore_selected?.reagents + if(CHEM_BLOOD) + // Inflating + var/datum/component/hose_connector/other = get_pairing() + if(!other || other.flow_direction == HOSE_OUTPUT) + return human_owner.bloodstr // Pump into blood reagents + // Draining + if(prob(30) && my_hose) // NEVER put normal reagents into the vessel... + return human_owner.vessel // Suck blood + return human_owner.bloodstr // Suck reagents from blood + +/datum/component/hose_connector/inflation/handle_pump(var/datum/reagents/connected_to) + ASSERT(connected_to) + var/datum/component/hose_connector/other = get_pairing() + var/rate = reagents.maximum_volume * 0.5 + if(connection_mode == CHEM_BLOOD) + rate = 10 // SLOW here + else + if(other.flow_direction == HOSE_OUTPUT || other.flow_direction == HOSE_NEUTRAL) // If filling mouth check prefs for belly fluid consumption. + if(connection_mode == CHEM_INGEST) + if(!human_owner.consume_liquid_belly) + for(var/datum/reagent/R in reagents.reagent_list) + if(R.from_belly) + to_chat(human_owner, span_warning("You can't consume that, it contains something produced from a belly!")) + my_hose.disconnect() // Pop! + return + if(connection_mode == CHEM_VORE) + if(!human_owner.receive_reagents) + to_chat(human_owner, span_warning("You can't transfer reagents into your [sanitize(human_owner.vore_selected.name)], your prefs dont allow it!")) + my_hose.disconnect() // Pop! + return + if(other.flow_direction == HOSE_INPUT || other.flow_direction == HOSE_NEUTRAL) // If filling mouth check prefs for belly fluid consumption. + if(connection_mode == CHEM_VORE) + if(!human_owner.give_reagents) + to_chat(human_owner, span_warning("You can't transfer reagents from your [sanitize(human_owner.vore_selected.name)], your prefs dont allow it!")) + my_hose.disconnect() // Pop! + return + + // Inflation station + switch(other.flow_direction) + if(HOSE_OUTPUT) + // inflating us + if(reagents.total_volume > 0) + reagents.vore_trans_to_mob(human_owner, rate, connection_mode, 1, 0, human_owner.vore_selected) + if(HOSE_INPUT) + // draining us + if(connected_to.total_volume > 0) + connected_to.trans_to_holder(reagents,rate) + if(HOSE_NEUTRAL) + // Sharing with us + reagents.trans_to_holder(connected_to, reagents.maximum_volume) // Load our current reagents back into tank, it's mixed! + connected_to.trans_to_holder(reagents, rand(1,reagents.maximum_volume) ) // Fill back up to a random amount + + if(connection_mode == CHEM_VORE && human_owner.vore_selected.count_liquid_for_sprite) + human_owner.handle_belly_update() + + if(prob(5) && (reagents.total_volume > 0 || connected_to.total_volume > 0)) + var/atom/pumper = other.get_carrier() + pumper.visible_message(span_infoplain(span_bold("\The [pumper]") + " gurgles.")) + +/* + * Inflation subtype, Borg edition. Geewiz janihound how come the AI lets you have two? + */ + +/// Pumps reagents out of carrier +/datum/component/hose_connector/input/borg + VAR_PRIVATE/mob/living/silicon/robot/borg_owner + +/datum/component/hose_connector/input/borg/Initialize() + if(!isrobot(parent)) + return COMPONENT_INCOMPATIBLE + . = ..() + borg_owner = parent + +/datum/component/hose_connector/input/borg/Destroy() + borg_owner = null + . = ..() + +/datum/component/hose_connector/input/borg/connected_reagents() + return borg_owner?.vore_selected?.reagents + +/datum/component/hose_connector/input/borg/on_examine(datum/source, mob/user, list/examine_texts) + return + +/// Pumps reagents into carrier +/datum/component/hose_connector/output/borg + VAR_PRIVATE/mob/living/silicon/robot/borg_owner + +/datum/component/hose_connector/output/borg/Initialize() + if(!isrobot(parent)) + return COMPONENT_INCOMPATIBLE + . = ..() + borg_owner = parent + +/datum/component/hose_connector/output/borg/Destroy() + borg_owner = null + . = ..() + +/datum/component/hose_connector/output/borg/connected_reagents() + return borg_owner?.vore_selected?.reagents + +/datum/component/hose_connector/output/borg/on_examine(datum/source, mob/user, list/examine_texts) + return diff --git a/code/datums/components/reagent_hose/item.dm b/code/datums/components/reagent_hose/item.dm index e50de303c7..b250a07717 100644 --- a/code/datums/components/reagent_hose/item.dm +++ b/code/datums/components/reagent_hose/item.dm @@ -13,7 +13,7 @@ icon_state = "hose" origin_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 1) amount = 1 - max_amount = 10 + max_amount = HOSE_MAX_DISTANCE w_class = ITEMSIZE_SMALL no_variants = TRUE stacktype = /obj/item/stack/hose @@ -40,28 +40,29 @@ if(!HC.get_hose()) if(REMB) if(HC.get_flow_direction() == HOSE_NEUTRAL || HC.get_flow_direction() != REMB.get_flow_direction()) - available_sockets |= HC + available_sockets[HC.get_id()] = HC else - available_sockets |= HC + available_sockets[HC.get_id()] = HC if(LAZYLEN(available_sockets)) if(available_sockets.len == 1) - var/datum/component/hose_connector/AC = available_sockets[1] + var/key = available_sockets[1] + var/datum/component/hose_connector/AC = available_sockets[key] if(REMB && REMB.get_carrier() == AC.get_carrier()) to_chat(user, span_notice("Connecting \the [REMB.get_carrier()] to itself seems like a bad idea. You wind \the [src] back up.")) remembered = null // Unintuitive if it does not reset state else if(REMB && REMB.valid_connection(AC)) var/distancetonode = get_dist(REMB.get_carrier(),AC.get_carrier()) - if(distancetonode > world.view) + if(distancetonode > HOSE_MAX_DISTANCE) to_chat(user, span_notice("\The [src] would probably burst if it were this long. You wind \the [src] back up.")) remembered = null // Unintuitive if it does not reset state else if(distancetonode <= amount) - to_chat(user, span_notice("You join \the [REMB] to \the [AC].")) - REMB.setup_hoses(AC,distancetonode) - use(distancetonode) + if(REMB.setup_hoses(AC,distancetonode,user)) + use(distancetonode) remembered = null + else to_chat(user, span_notice("You do not have enough tubing to connect the sockets. You wind \the [src] back up.")) remembered = null // Unintuitive if it does not reset state @@ -74,7 +75,7 @@ var/choice = tgui_input_list(user, "Select a target hose connector.", "Socket Selection", available_sockets) if(choice) - var/datum/component/hose_connector/CC = choice + var/datum/component/hose_connector/CC = available_sockets[choice] if(REMB) if(REMB.get_carrier() == CC.get_carrier()) to_chat(user, span_notice("Connecting \the [REMB.get_carrier()] to itself seems like a bad idea. You wind \the [src] back up.")) @@ -82,15 +83,13 @@ else if(REMB.valid_connection(CC)) var/distancetonode = get_dist(REMB.get_carrier(), CC.get_carrier()) - if(distancetonode > world.view) + if(distancetonode > HOSE_MAX_DISTANCE) to_chat(user, span_notice("\The [src] would probably burst if it were this long. You wind \the [src] back up.")) remembered = null // Unintuitive if it does not reset state else if(distancetonode <= amount) - to_chat(user, span_notice("You join \the [REMB] to \the [CC]")) - - REMB.setup_hoses(CC,distancetonode) - use(distancetonode) + if(REMB.setup_hoses(CC,distancetonode,user)) + use(distancetonode) remembered = null else diff --git a/code/game/objects/structures/fence.dm b/code/game/objects/structures/fence.dm index 5a20120040..8180e59dbd 100644 --- a/code/game/objects/structures/fence.dm +++ b/code/game/objects/structures/fence.dm @@ -22,6 +22,7 @@ var/cuttable = TRUE var/hole_size= NO_HOLE var/invulnerable = FALSE + var/electric = FALSE /obj/structure/fence/Initialize(mapload) update_cut_status() @@ -68,8 +69,17 @@ return TRUE return ..() +/obj/structure/fence/attack_hand(mob/user) + if(electric && isliving(user) && !user.is_incorporeal()) + if(electrocute(user)) + return + . = ..() + /obj/structure/fence/attackby(obj/item/W, mob/user) user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + if(electric && isliving(user) && !user.is_incorporeal() && !(W.flags & NOCONDUCT)) + if(electrocute(user)) + return if(W.has_tool_quality(TOOL_WIRECUTTER)) if(!cuttable) to_chat(user, span_warning("This section of the fence can't be cut.")) @@ -100,6 +110,13 @@ update_cut_status() return TRUE +/obj/structure/fence/Bumped(AM) + . = ..() + if(electric && isliving(AM)) + var/mob/living/L = AM + if(!L.is_incorporeal()) + electrocute(L) + /obj/structure/fence/proc/update_cut_status() if(!cuttable) return diff --git a/code/game/objects/structures/fence_electric.dm b/code/game/objects/structures/fence_electric.dm new file mode 100644 index 0000000000..3d27f1c072 --- /dev/null +++ b/code/game/objects/structures/fence_electric.dm @@ -0,0 +1,66 @@ +#define NAME_ELECFENCE "electric fence" +#define DESC_ELECFENCE "A chain link fence attached to a nearby area power controller. Zap zap!" +#define ICON_ELECFENCE 'icons/obj/fence_electric.dmi' + +// Required to be in an area with a powered APC to electrocute! Intended for POIs. +/obj/structure/fence/electric + name = NAME_ELECFENCE + desc = DESC_ELECFENCE + icon = ICON_ELECFENCE + electric = TRUE + +/obj/structure/fence/electric_sign + name = NAME_ELECFENCE + desc = DESC_ELECFENCE + icon = ICON_ELECFENCE + electric = TRUE + icon_state = "straight_sign" + +/obj/structure/fence/door/electric + name = NAME_ELECFENCE + desc = DESC_ELECFENCE + icon = ICON_ELECFENCE + electric = TRUE + +/obj/structure/fence/end/electric + name = NAME_ELECFENCE + desc = DESC_ELECFENCE + icon = ICON_ELECFENCE + electric = TRUE + +/obj/structure/fence/corner/electric + name = NAME_ELECFENCE + desc = DESC_ELECFENCE + icon = ICON_ELECFENCE + electric = TRUE + +/obj/structure/fence/post/electric + name = NAME_ELECFENCE + desc = DESC_ELECFENCE + icon = ICON_ELECFENCE + electric = TRUE + +/obj/structure/fence/cut/medium/electric + name = NAME_ELECFENCE + desc = DESC_ELECFENCE + icon = ICON_ELECFENCE + electric = TRUE + +/obj/structure/fence/cut/large/electric + name = NAME_ELECFENCE + desc = DESC_ELECFENCE + icon = ICON_ELECFENCE + electric = TRUE + +/obj/structure/fence/proc/electrocute(mob/living/M) + if(electrocute_mob(M, get_area(src), src, 0.7)) + visible_message("\The [src] zaps [M]!") + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(5, 1, src) + s.start() + return TRUE + return FALSE + +#undef NAME_ELECFENCE +#undef DESC_ELECFENCE +#undef ICON_ELECFENCE diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 1ccc48350e..1a0c0d49ce 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -69,6 +69,7 @@ regenerate_icons() AddComponent(/datum/component/personal_crafting) + AddComponent(/datum/component/hose_connector/inflation) // Comment out to disable all human mob inflation mechanics // Chicken Stuff var/animal = pick("cow","chicken_brown", "chicken_black", "chicken_white", "chick", "mouse_brown", "mouse_gray", "mouse_white", "lizard", "cat2", "goose", "penguin") diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 0ea9776094..982b6a5cec 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -205,6 +205,8 @@ riding_datum = new /datum/riding/dogborg(src) + AddComponent(/datum/component/hose_connector/input/borg) + AddComponent(/datum/component/hose_connector/output/borg) /mob/living/silicon/robot/LateInitialize() pick_module() diff --git a/code/modules/power/fusion/core/_core.dm b/code/modules/power/fusion/core/_core.dm index 5e4f046b8a..5ca45bdcb6 100644 --- a/code/modules/power/fusion/core/_core.dm +++ b/code/modules/power/fusion/core/_core.dm @@ -63,8 +63,7 @@ GLOBAL_LIST_EMPTY(fusion_cores) if((stat & BROKEN) || !powernet || !owned_field) Shutdown() - var/datum/component/hose_connector/HC = GetComponent(/datum/component/hose_connector) - HC.force_pump() + SEND_SIGNAL(src, COMSIG_HOSE_FORCEPUMP) if(owned_field) diff --git a/code/modules/reagents/holder/holder.dm b/code/modules/reagents/holder/holder.dm index 56b6ac11ef..206a50c27c 100644 --- a/code/modules/reagents/holder/holder.dm +++ b/code/modules/reagents/holder/holder.dm @@ -264,9 +264,11 @@ var/part = amount / total_volume + var/target_is_belly = isbelly(target.my_atom) // Sending reagents into bellies turns them into belly reagents + for(var/datum/reagent/current in reagent_list) var/amount_to_transfer = current.volume * part - target.add_reagent(current.id, amount_to_transfer * multiplier, current.get_data(), safety = 1, was_from_belly = current.from_belly) // We don't react until everything is in place + target.add_reagent(current.id, amount_to_transfer * multiplier, current.get_data(), safety = 1, was_from_belly = (current.from_belly || target_is_belly)) // We don't react until everything is in place if(!copy) remove_reagent(current.id, amount_to_transfer, 1) diff --git a/code/modules/reagents/machinery/pump.dm b/code/modules/reagents/machinery/pump.dm index 8fae5b7268..e9e77f5548 100644 --- a/code/modules/reagents/machinery/pump.dm +++ b/code/modules/reagents/machinery/pump.dm @@ -33,6 +33,10 @@ RefreshParts() update_icon() +/obj/machinery/pump/Destroy() + QDEL_NULL(cell) + . = ..() + /obj/machinery/pump/RefreshParts() var/obj/item/stock_parts/manipulator/SM = locate() in component_parts active_power_usage = initial(active_power_usage) / SM.rating @@ -88,8 +92,7 @@ T.pump_reagents(reagents, reagents_per_cycle) update_icon() - var/datum/component/hose_connector/HC = GetComponent(/datum/component/hose_connector) - HC.force_pump() + SEND_SIGNAL(src, COMSIG_HOSE_FORCEPUMP) // Sets the power state, if possible. // Returns TRUE/FALSE on power state changing diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index fe52cf22e0..3470f9af4e 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -240,8 +240,8 @@ /obj/item/reagent_containers/spray/chemsprayer/hosed/proc/update_hose(atom/source, atom/oldloc, direction, forced, list/old_locs, momentum_change) SIGNAL_HANDLER - var/datum/component/hose_connector/HC = GetComponent(/datum/component/hose_connector) - HC.update_hose_beam() + for(var/datum/component/hose_connector/HC in GetComponents(/datum/component/hose_connector)) + HC.update_hose_beam() /obj/item/reagent_containers/spray/chemsprayer/hosed/update_icon() ..() @@ -251,9 +251,10 @@ if(!hose_overlay) hose_overlay = new/icon(icon, "[icon_state]+hose") - var/datum/component/hose_connector/HC = GetComponent(/datum/component/hose_connector) - if(HC.get_pairing()) - add_overlay(hose_overlay) + for(var/datum/component/hose_connector/HC in GetComponents(/datum/component/hose_connector)) + if(HC.get_pairing()) + add_overlay(hose_overlay) + break /obj/item/reagent_containers/spray/chemsprayer/hosed/AltClick(mob/living/carbon/user) if(++spray_particles > 3) spray_particles = 1 diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index a04dbe404f..bed9ad6d01 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -1411,7 +1411,12 @@ if(!RTB) return FALSE - to_chat(src, span_vnotice("[RTB] has [RTB.reagents.total_volume] units of liquid.")) + var/total_report = span_vnotice("[RTB] has [RTB.reagents.total_volume] units of liquid.") + if(RTB.reagents.total_volume > 0) + for(var/datum/reagent/R in RTB.reagents.reagent_list) + total_report += "
" + total_report += span_info(" -[R.name]: [R.volume]u") + to_chat(src, total_report) /mob/living/proc/vore_transfer_reagents() set name = "Transfer Liquid (Vore)" diff --git a/icons/obj/fence_electric.dmi b/icons/obj/fence_electric.dmi new file mode 100644 index 0000000000000000000000000000000000000000..369eb209ecc90afd78c4ad4a8a18487f15c741eb GIT binary patch literal 7609 zcmZWuXH-*Nmrle0hH9aA8yFCfB1$oYYC&p{B7!0{QeLFDgrXv#BGN=kf`|kVkRly| z3W9X$JxK2*)R2(O%{$+$HM8bN?oDpa+2@?SpWU7tZEUE|0TqOTKp>7=H+4*b`xN8D z$_!iuFQfhfx8fkArH_uI_v5Fo|M<9idVoLy=}F!7&`3@0<5P$0mw4B-sh4_fPA5Lh zDi)u1uZx_$=O_1jC71R4MZ{&68&2=fbnWIW;ZQ*>j$-uoOEcACR) zEZ%y*a%x3l>g$@ug{EgOs&MH|pRW|2I}>SC#bVJU8yz=>vwoA5E-bWn zM(PB8bv;D<%QnTOAr<52=R_boV+u_3G?~lF%GS4LTK>2w$tx;G=H*?wlP`YDczZ!4 zV~Xy*&{YlE>^C!t-_e-aSXy!&`PI^LZXoBrcZO-y+E`_6zb~!{xSNH}Ov|BRH0R7f zX3%Dr+J;U;=2JLG=ryx154-6bz3gx}Z7XhZWhFL5!?zik+dp-YXo%L#Or>?r2BaU9L0jNj2-XAy5MjjR0M z0xl(ox+9HG(H?H_>x3J?XVDT0LV7kr^e>OHA5J$-$V(iTOZD(L1bIBUK26x_waRCN;i zlx@qm(}5uL=64oZ&?=E2Huu!(zHTpbzMA|t95h}Z%ok>jR<)uXQT9EjocGR?M|SnW z6~{nlwwc;yTV}SiN(|-SM#j+F=SrWVRRw&Y7)cvU8yW0KnyDwxrvFPSbPrbF2EmHlbxNMPOW6icXV`URvbMr z(RSHe8%uE*Q{c*=?a$_(hADPZ_1_4GyN|iAoK|rg`xSpq-x}*UO3XRc=78gTKuh5; zYKOv=KN)G`pDFxNR#9WDyIEdNGV2Pl9j@RQ+4BdJ;&*Q;%dZ&k5;};az4C1{?VO_r zuzw%6GlsiR%SmGu6IWXD59Afar(Ib`@)C-wMh` z)T!JT{ftqHOTQd>YN$pG9GU&Z<1Xo1GXRnuVc1h(tl~M@)Yf);ncP>z_#0R=&m6(_ z{8$$j9=E;Fo#5ay(2TtYfR2dAHQ~B?@?-5%=y{C!sMZR`GXUy0&?tT$rRP0!7zVm$4u)2w^Ke5&5AKx5TRn@R`_AUxrzGng;%ATPEEw8uG#bBWL_ejBCSLocJZ zwJMI1-N)KqXSH=YkxmQB8}AU_JgwP&Z~Z0VAr))5xLnyP@brk(r?J~_cAoD~wfBNe z*hRSLKZK)i)~{zdznp_VKstP*&$w9_YnWlhdS-JK*MEXw^Qn$eQPn?A4dIb9%v_T}byO)>bFKuUK>&%(5tvmu9#7=IZIG zBCMVk{Vzp82i+K2pWrj}ZsG*X@vq5P_wD)4s!&SBD`MiKv8#fwnLl)13}YdcHCJ;% zrqC3l^lK;Y3q>}LV&>X-Kt5|m-gHIE)YQW%j_1Uia!I$d-^^4+Mn>LyTR$N!O%8IJ zS9%uxOJyy^m4MBV^LKRARUNJ?F6L76Tm2fboEqK7UCOEO=OD;X+gLk?ezY1g5*ixX z`+aaypeIh{bgqPh$ocIDlcM_f9UhJr57NZd#Kqj!&2^93x|>{ClN_VmC}pQ!WA|zM zWl>%)yYkQOc60zBb&PVwG>pk9{KN0y)crNcX*omwq9Cyxj-u#}eKLd01-^!&MzJe< zlo9e%bMv0BLDosPFOWFlWn3H_C86T))TRNCoB&eG%24q+)I?w{ig9j4kYHrZk?RJg z_~>A3CRRg3gI`Rnl9!ivd}(RPq{WLDOH`Aysf62TyaLh;`^?PDAYjn(%*@O|nppP} zb6CYi{;hp*SbY&o)+%#UC<{F%qWHUgb+ejH~?Rhmk;B!4S3YA z0eSSdGl(Z!QBg6r*}g;>grSTPZ!|tZ8fQn$*8uwZGA`F}@I!>3-^D8+u+U-%U02~p z1RxtXQ4S}rahW3g*G3&D>H_;xx;>|?ijS@5x$G*z76lC`7upxkFWy6t1>W*G4^Md; zxu-+O-<<)$$3E=c8MR8S^;;`NeG|OuK6<=D$+0Zm|MNCF^F6m2;;szdfpS;I-Hx;do<;3)tlF!>p0Y4|NW8lI3QHx)o zZXuVm#EMN2cPZWzKoCGZGXdYFWLFTo*E@q*TMKXu95zr%?Ht|h4KTdH8b=8bjpx$VJ>rRq{tkv%x|nA>DsHv`&Cz-NhS)u(ob3xqtO*!D$yE{8m>l3vNOJf=L456wA zN7U|x+yux<if<3;%+qNosw>BXEr)5m)E+vnjQWT5{BUgDvK>*6%u76utY z-D+7sZKVE4cNb$!FH z8Ezb@^u%uPy=P}zWE&I#oJ;j`dNg$PGf#_R{7tlKJZ*@hfe!33DySaq;_AAz0f2w{ znfq9-UC35*bWTnVfn}uS*Dve)5LZ2mp2m@q$5G7@CXRbr)CiLiLWZD8yb{&JNwuwT zXr<`C>qIb_ZT?TKYaO z`iM9&!STl`u95&0Zi{Pc7uGN$i_`Ff%WNa;_;Z#jw>xb3--xKa$jXvf9joNxf>Xbu zyyCZZ`ivgCxv?9;kCZArCh~1E4T+#9Ot;Sq5{o(O3X6;7JA+osII;f6 zYIK<26g`}f>W+RC09X_Zs`QvRyYvdM>3vX-ixRrJrY0Ymd{e1`^I$?IAw*_#1vBE@ zXquOp;oKM?EIXip_U%xH~off6RsB}8O% zQpWcN@5_IJS3qAnJqUu;0X_9QZ3Ei%p4IC)7DY7F?DY4A{VogYDxa*)@Jh{-b=uyT z=l7!LN*~o^+A{KRkK&2wJ#l-FZsR;~+>JxL=T6Xgz|ro|#5w&pQ>3|e%QWy@@^`OH zdG0{TTb3fz#t5=1P%p`hR)h*-6DPTk?vE9v(>guT+rLU4w`>+;j_%}%GJ>Pckgr7P ze(M;QsQODW>J&=={b;|$weO-UO_S$L6`g%J4YnTf8plzC%hWJIU~nifsUK7k%OUhS=a-Tsy( zBow`&K7OZIMszu}-4PBPK4k3{9>)%xNot9Synw|Ng)sHxMJP07MJ(eJDmhmL-qLL? zrxO4KFY*x%o+mpuuCIHrb8@1qg7?>37Ut*EpH@?Q$H&vl7{4P|G30i;p0Tl5ebGRj zXgfMa4_Ije1&THhCGSkLCpPLOq++awj}A2XBH-TI4U}?lG9C$o7*VFer#{ae=?Az zzP`TAx7L3{RL5CklD0c!H#6NFTNSW%wt;r|#Ff?xlM>GqTAS}o8~*mhV&%JS3bZXU zh#6TAJ|Nq=GE-;w!|8>eB3>j;aJn(?6fn^>{xnI3O*(5be8+RFf}@x~Q?(6VQ_c}E zwuCrhb-lB}TwP)6y+siU;{@=c8KT&xIwCVubanLGoji|>j}e0?WuUtE|0(X^doZzq zuCy#3;tKR!M*Jgu)Ex#A#ipfEf~#$4*y`$P`ec3Z{>+M21} z5^`#k$;ag@;)eEN@2 zl;q>aP4UX0^C|lI)Iy=KW0*{H;}J{aiSDpRY4o~!8(G39gSs~!G$x^I#r1pEcXI3% zr*uAzyFB5v1q!=|uV)7QR4c$p+>;X>lzG4*+UCe)c*p+QA;B~+IDA|2HUla=5Bx}b*UWq1baFQgQ}n8mh!zUoTmAl8NTs~) zl*9(i1sOo>C_J>El?n&Nl$;ad#z&M6_3B#rF;7hR6yPSgbI)&W{^WYxo_u+@)G?MM zePtSn_(|%E-XX~P*wejBa6iI9qgZg?mx#|I^E&Dp4)zFNFG@b6RbcwMXI<0WtZQKo zah%)g;bHsWcX0W)Pjf}gZ0>eJWF^q6{`q}mB*HeTam5R6kvem&$Uh2u$CTcf@qwel z&+p=~uecF_ct~z{1bVw)Y`>kse%6eFz@hl_(D#kqdiOEwh*yC;iAgGIYAd7$=k|%O zG>eQp)p|M~FYgV|;H2Dkw%E}ka99tU8sC*cmR0)Lh_ARjT)aJwS?5mckFlSm?7UC< z!Am*cat@aIa&M9PmZsua(FJ#DCu zKJnM?_f;xscJ2g>OfCg2)4Gp929#63G&%Z@S(ddRVm#gECqbCWOZh(pISo=*ju&Q zU;LBp5FArVHKAU;e*O9=kw^zJ-@PAgf7hysEVIdw%efV5*I+2;J3K93TJ5>>Q9APF zL{qun-*Xe-F_=M?_#SWXZ`QuL)M8)e*`d}mZ~w+U4pdQBciY|Hr-*9a)q=eKAvd$& z#}{mRRGa{Bg}1l2_qq#;idN`Qi`_w=y*97tSp70DFyEj3oe;a$(48zv-S^GMd2Oy} z@=`DX0mC2C(-Un!#~#?Nu(u+`VffA1{HRwmjM0patu}V}$xu$jujXPVs}+o2S)WPe zuz8P?8hp957*Nbve?3B{wFnIFffi}Cr!#Sr%G}7-<~=~BYdu=bj%!VpD5d7-+j9?^ zD;`hM?i3raTYK9~!=|Sr(ME>v={ZQ_jUdYA-=Cm&wQS3?Ls?oC1&>W(cqqp5`dZsO zTuBcr_}O|K_rsTvW1Ar`(L9-2B4A>4_IT zzvY%OR!tux4j-%zpa$kJNLYpvrr%M_tYvw(<~}TmGobx(J6}9WEgTM`I;Ek$EdyS| zcWZ~>L6fH!R7m1O)`YfPL-|vzIA!JJ9zVG7WTapqUk$L{^0ltBVpmR9e94;U(>1QU zAjI$0^}=f_ifqNR);5$MVaj=f2R+j1`S9-ILqEUVSEh!NoBGd*kQ;!xt^|)GMrz2B z)N`l&=A(f2N3)4;ZEpO)@xt%ok6J-Yo|2N1`XhHW%Y{)iJPoB%3xIIYaqGQTS6B^M zmwGPf@j?pWBW>s^2t(V(Cdg_ylB*pNY_~)?KF)LPy}2f(%g=-6p&w{?k|NEU=+$rz ziEORqa#thzod-F0XiXog;G-7*zg1b-1K@$bHrzXTXGbiJY3DeHs?vgZyCAM=7>IM z48UXNGWf&{78HE}pebc2rTB=?JpVeRL-iJ*do>^sm8zMU`T@KS-7IpHeot3HJFkf9 zQrgpuZRxn7j-)yr+KE(SE}4m%#R`nG_|oslA?~8p6U4fA2tjmzvju%c@{$2sYgyI! zW11@5dh!t!>*-^%pr7prQpO({0fa6F<>O5`GdkHJ^Q8Nn&w+br!ai8{{wHA+dbW;U z>+sXnXHGvG6_Bg>i;jrW$v(t>u#4mCi2B#7&?wafmIU{jKJMh?gp4tckfr(2{($}7 zTyCZJQv>xZ66dspfd&l*S5_VvJ?jRe zwxBDja0vwIwx?d9*GYu4kYYmW^<aMyt=m#CNQc@cq z6vEEI0ml6{%8kYD8=cy?$6<=Nbg=y&N%>Xiqaf+)?Nn;Wbjux|t;*p2mmbe9v*jIZ z#{kvxOE~<)?dosV9NOC2g|jksQtyuYZ0V0U_njcL9D~x-&lyARo`rufNCjwFk7^se zEMpMp)vH(MKVS_d;oCodCrI)o*yshD8^dUy4pe#K?GcXL`s64sF+B_h^Mv_W*!@b6 zJY>Qv>;r1!DCvuJ;YE`0uL4{mRlWpA@?_>k2*ae9(!yo!8VpD%9}(&}4Ho8*8P8y~ zY#}+bacKp85@*;1ozY6Eq$1n;n5Wh35APlazw0&vBI+B~Gja#x(Fm9n1mh4w3chmR zrl*w{Bx%s|C;r-Ywk~AsxHIJ+;AH#)0^ql9Xw?a7M>h~rOJu-SscXyv${5>|f^337 zu1hVA6V*T^wLAXHu;0 zT3O$Y3kwQry#oerN!CNSdD%WFVNRuIyt-L}vt4YB`<#7J(+^0q=oh=va#FlXt_-_9 ze+SJ!2PiZLsKi$kPCl!=kcu_r!;BweWs^LkaT2S95l(lF*o;1?B4z6q%44#y*SwBV zHE&K!nF>=Ur&ub)B-Ks_(q%gUNSbT`g%a2JBGt_cAxYSb4iL|zx|n?LR%nS6YuVZH zY0qq8=jS5~!8?X(m=XP_w{C4O@cfXRo@tBI>s#&8Qlsze?^oJ;jVQp`b4-^shf9ri zbV5u4sDC=WMdb>CbG zV1~o5S)Vn?L#j49eS+>6aXMkp8+HF=VXA`o*V?v5f70L{=yN~#jkPQxzD9_H%}=|q z@&cUKRvOqv^TZ>beoE9}!4tTx5O?NVT|LO_1Y%;azLH0tAr*v&vl__!tAOv_tURaLP!HeANF|5X zWqbg(erjH)4$uwjBf};ke&YznYp4ICvy#lr)i9vEbsv*W6JWg4kz9Jh#3Tn8)im1% kc+?Zdeo-TpAz_QY;LY`JDv*isE*5l4*HEWO>tV!y0pto_@c;k- literal 0 HcmV?d00001 diff --git a/vorestation.dme b/vorestation.dme index 59e5935d28..2e16e50d44 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -557,6 +557,7 @@ #include "code\datums\components\disabilities\tourettes.dm" #include "code\datums\components\reagent_hose\connector.dm" #include "code\datums\components\reagent_hose\datum.dm" +#include "code\datums\components\reagent_hose\inflation.dm" #include "code\datums\components\reagent_hose\item.dm" #include "code\datums\components\species\shadekin.dm" #include "code\datums\components\species\xenochimera.dm" @@ -1798,6 +1799,7 @@ #include "code\game\objects\structures\electricchair.dm" #include "code\game\objects\structures\extinguisher.dm" #include "code\game\objects\structures\fence.dm" +#include "code\game\objects\structures\fence_electric.dm" #include "code\game\objects\structures\fireaxe.dm" #include "code\game\objects\structures\fitness.dm" #include "code\game\objects\structures\fitness_vr.dm"