diff --git a/code/datums/components/reagent_hose/connector.dm b/code/datums/components/reagent_hose/connector.dm index 9e425655375..749eac99b56 100644 --- a/code/datums/components/reagent_hose/connector.dm +++ b/code/datums/components/reagent_hose/connector.dm @@ -47,8 +47,29 @@ /datum/component/hose_connector/proc/get_id() return "[name] #[connector_number]" -/datum/component/hose_connector/output/process() - return +/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() process() @@ -59,6 +80,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 @@ -72,10 +95,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) @@ -89,7 +149,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" @@ -105,30 +167,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 */ @@ -155,3 +193,26 @@ if(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/inflation.dm b/code/datums/components/reagent_hose/inflation.dm new file mode 100644 index 00000000000..8006a6e23bc --- /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 0e061301f61..b250a077173 100644 --- a/code/datums/components/reagent_hose/item.dm +++ b/code/datums/components/reagent_hose/item.dm @@ -59,10 +59,10 @@ 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 @@ -88,10 +88,8 @@ 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/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index d3db393b070..fa888811b84 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 a19c8bae7b7..a09bd7c779d 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -203,6 +203,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/reagents/holder/holder.dm b/code/modules/reagents/holder/holder.dm index b2a2da51fe5..df3e24ced69 100644 --- a/code/modules/reagents/holder/holder.dm +++ b/code/modules/reagents/holder/holder.dm @@ -261,9 +261,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/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index 64e8ed93586..e3fdf572ce0 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/vorestation.dme b/vorestation.dme index 8c222c17ddb..780c69e0bfa 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -527,6 +527,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"