From e8a61cf2f179533545fe03a3c67bc828683dee6b Mon Sep 17 00:00:00 2001
From: Luc <89928798+lewcc@users.noreply.github.com>
Date: Wed, 5 Feb 2025 13:33:06 -0500
Subject: [PATCH] Adds Tend Wounds/Tend Burns surgeries (#28105)
* Add wound tending surgeries
* Reworks the surgery a bit more to work with the open organ framework
* Minor tidiness
* oop
---
code/modules/surgery/other.dm | 2 +-
code/modules/surgery/surgery.dm | 8 +
code/modules/surgery/tending.dm | 292 ++++++++++++++++++++++++++++++++
paradise.dme | 1 +
4 files changed, 302 insertions(+), 1 deletion(-)
create mode 100644 code/modules/surgery/tending.dm
diff --git a/code/modules/surgery/other.dm b/code/modules/surgery/other.dm
index 09ba772bae4..0261027e548 100644
--- a/code/modules/surgery/other.dm
+++ b/code/modules/surgery/other.dm
@@ -126,7 +126,7 @@
return SURGERY_STEP_RETRY
/datum/surgery_step/treat_burns
- name = "mend burns"
+ name = "treat severe burns"
allowed_tools = list(
/obj/item/stack/medical/ointment/advanced = 100,
/obj/item/stack/medical/ointment = 90
diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm
index 2ee8ddfb8ea..a6e80322516 100644
--- a/code/modules/surgery/surgery.dm
+++ b/code/modules/surgery/surgery.dm
@@ -49,6 +49,14 @@
/// How likely it should be for the surgery to cause infection: 0-1
var/germ_prevention_quality = 0
+/**
+ * Create a new surgery.
+ *
+ * Arguments:
+ * * surgery_target - The atom the target is being performed on.
+ * * surgery_location - The body zone that the surgery is being performed on.
+ * * surgery_bodypart - The body part that the surgery is being performed on.
+ */
/datum/surgery/New(atom/surgery_target, surgery_location, surgery_bodypart)
..()
if(!surgery_target)
diff --git a/code/modules/surgery/tending.dm b/code/modules/surgery/tending.dm
new file mode 100644
index 00000000000..2613d204f8a
--- /dev/null
+++ b/code/modules/surgery/tending.dm
@@ -0,0 +1,292 @@
+/**
+ * Surgeries for tending brute and burn damage without needing to expend items.
+ */
+
+
+// Since these both share surgery steps, we can only realistically expose one
+/datum/surgery_step/proxy/open_organ/extra
+ /// Other surgeries to fire off next.
+ var/list/other_surgeries
+
+/datum/surgery_step/proxy/open_organ/extra/New()
+ for(var/healing_step_type in other_surgeries)
+ if(!ispath(healing_step_type))
+ CRASH("open_organ was given an additional option that was not a type path: [healing_step_type]")
+ branches |= healing_step_type
+ return ..()
+
+/datum/surgery_step/proxy/open_organ/extra/brute
+ other_surgeries = list(/datum/surgery/intermediate/heal/brute)
+
+/datum/surgery_step/proxy/open_organ/extra/burn
+ other_surgeries = list(/datum/surgery/intermediate/heal/burn)
+
+/datum/surgery/heal
+ abstract = TRUE // don't need this popping up
+ requires_organic_bodypart = TRUE
+ /// A subtype of /datum/surgery_step/heal that this will invoke.
+ var/healing_step_type
+ possible_locs = list(BODY_ZONE_CHEST)
+ steps = list(
+ /datum/surgery_step/generic/cut_open,
+ /datum/surgery_step/generic/clamp_bleeders,
+ /datum/surgery_step/generic/retract_skin,
+ /datum/surgery_step/heal,
+ /datum/surgery_step/generic/cauterize
+ )
+
+/datum/surgery/heal/New(atom/surgery_target, surgery_location, surgery_bodypart)
+ ..()
+ if(ispath(healing_step_type))
+ steps = list(
+ /datum/surgery_step/generic/cut_open,
+ /datum/surgery_step/generic/clamp_bleeders,
+ /datum/surgery_step/generic/retract_skin,
+ healing_step_type,
+ /datum/surgery_step/generic/cauterize
+ )
+
+/datum/surgery_step/heal
+ name = "tend something"
+
+ allowed_tools = list(
+ TOOL_HEMOSTAT = 100,
+ TOOL_WIRECUTTER = 65,
+ /obj/item/pen = 55
+ )
+
+ time = 2.5 SECONDS
+ repeatable = TRUE
+
+ // no sounds, since it would quickly get annoying
+
+ var/shown_starting_message_already = FALSE
+
+ COOLDOWN_DECLARE(success_message_spam_cooldown)
+
+ var/damage_name_pretty = "wounds"
+ /// Amount of brute damage to treat per op.
+ var/brute_damage_healed
+ /// Amount of burn damage to treat per op.
+ var/burn_damage_healed
+
+ /// Multiplier based on the amount of brute damage that the patient has, increasing the efficiency at higher levels of damage.
+ var/brute_damage_healmod
+ /// Multiplier based on the amount of burn damage that the patient has.
+ var/burn_damage_healmod
+
+/// Get a message indicating how the surgery is going.
+/datum/surgery_step/heal/proc/get_progress(mob/user, mob/living/carbon/target, brute_healed, burn_healed)
+ return
+
+/datum/surgery_step/heal/proc/can_be_healed(mob/living/user, mob/living/carbon/target, target_zone)
+ if(brute_damage_healed == 0 && burn_damage_healed == 0)
+ stack_trace("A healing surgery was given no healing values.")
+ return FALSE
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ if(affected.is_broken())
+ return FALSE
+
+ return (brute_damage_healed && target.get_damage_amount(BRUTE) || burn_damage_healed && target.get_damage_amount(BURN))
+
+/datum/surgery_step/heal/can_repeat(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(!can_be_healed(user, target, target_zone))
+ to_chat(user, "It doesn't look like [target] has any more [damage_name_pretty].")
+ return FALSE
+ return TRUE
+
+/datum/surgery_step/heal/begin_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ if(!shown_starting_message_already)
+ shown_starting_message_already = TRUE
+ user.visible_message(
+ "[user] starts to patch some of the [damage_name_pretty] on [target]'s [affected.name] with [tool].",
+ "You start to patch some of the [damage_name_pretty] on [target]'s [affected.name] with [tool].",
+ chat_message_type = MESSAGE_TYPE_COMBAT
+ )
+ affected.custom_pain("Something in your [affected.name] is causing you a lot of pain!")
+
+ return ..()
+
+/datum/surgery_step/heal/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery)
+
+ if(!can_be_healed(user, target, target_zone))
+ to_chat(user, "It doesn't look like [target] has any more [damage_name_pretty].")
+ return SURGERY_BEGINSTEP_SKIP
+
+ var/brute_healed = brute_damage_healed
+ var/burn_healed = burn_damage_healed
+
+ var/outer_msg = "[user] succeeds in fixing some of [target]'s [damage_name_pretty]"
+ var/self_msg = "You successfully manage to patch up some of [target]'s [damage_name_pretty]"
+
+ if(target.stat == DEAD) //dead patients get way less additional heal from the damage they have.
+ brute_healed += round((target.getBruteLoss() * (brute_damage_healmod * 0.2)), 0.1)
+ burn_healed += round((target.getFireLoss() * (burn_damage_healmod * 0.2)), 0.1)
+ else
+ brute_healed += round((target.getBruteLoss() * brute_damage_healmod), 0.1)
+ burn_healed += round((target.getFireLoss() * burn_damage_healmod), 0.1)
+
+ if(!get_location_accessible(target, target_zone))
+ brute_healed *= 0.55
+ burn_healed *= 0.55
+ self_msg += " as best as you can while [target.p_they()] [target.p_have()] clothing on"
+ outer_msg += " as best as [user.p_they()] can while [target.p_they()] [target.p_have()] clothing on"
+
+ target.heal_overall_damage(brute_healed, burn_healed)
+
+ self_msg += get_progress(user, target, brute_healed, burn_healed)
+
+ if(COOLDOWN_FINISHED(src, success_message_spam_cooldown))
+ user.visible_message(
+ "[outer_msg].",
+ "[self_msg].",
+ chat_message_type = MESSAGE_TYPE_COMBAT
+ )
+
+ COOLDOWN_START(src, success_message_spam_cooldown, 10 SECONDS)
+
+ // retry ad nauseum; can_repeat should handle anything else.
+ return SURGERY_STEP_RETRY
+
+/datum/surgery_step/heal/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ user.visible_message(
+ "[user] screws up, making things worse!",
+ "You screw up, making things worse!",
+ chat_message_type = MESSAGE_TYPE_COMBAT
+ )
+ var/burn_dealt = burn_damage_healed * 0.8
+ var/brute_dealt = brute_damage_healed * 0.8
+
+ brute_dealt += round((target.getBruteLoss() * (brute_damage_healmod * 0.5)), 0.1)
+ burn_dealt += round((target.getFireLoss() * (burn_damage_healmod * 0.5)), 0.1)
+
+ target.take_overall_damage(brute_dealt, burn_dealt)
+
+ return SURGERY_STEP_RETRY
+
+/datum/surgery/heal/wounds
+ name = "Tend Wounds"
+ desc = "Tend a patient's wounds, gradually treating their brute damage."
+ abstract = FALSE
+ healing_step_type = /datum/surgery_step/proxy/open_organ/extra/brute
+
+/datum/surgery/heal/burns
+ name = "Treat Burns"
+ desc = "Tend a patient's burns, gradually treating their burn damage."
+ abstract = FALSE
+ healing_step_type = /datum/surgery_step/proxy/open_organ/extra/burn
+
+
+/********************BRUTE STEPS********************/
+
+
+/datum/surgery_step/heal/brute
+ name = "tend wounds"
+ damage_name_pretty = "wounds"
+ brute_damage_healed = 5
+ brute_damage_healmod = 0.07
+
+/datum/surgery_step/heal/brute/get_progress(mob/user, mob/living/carbon/target, brute_healed, burn_healed)
+ if(!brute_healed)
+ return
+
+ var/estimated_remaining_steps = target.getBruteLoss() / brute_healed
+ var/progress_text
+
+ if(locate(/obj/item/healthanalyzer) in list(user.l_hand, user.r_hand))
+ progress_text = ". Remaining brute: [target.getBruteLoss()]"
+ else
+ switch(estimated_remaining_steps)
+ if(-INFINITY to 1)
+ return
+ if(1 to 3)
+ progress_text = ", stitching up the last few scrapes"
+ if(3 to 6)
+ progress_text = ", counting down the last few bruises left to treat"
+ if(6 to 9)
+ progress_text = ", continuing to plug away at [target.p_their()] extensive rupturing"
+ if(9 to 12)
+ progress_text = ", steadying yourself for the long surgery ahead"
+ if(12 to 15)
+ progress_text = ", though [target.p_they()] still look[target.p_s()] more like ground beef than a person"
+ if(15 to INFINITY)
+ progress_text = ", though you feel like you're barely making a dent in treating [target.p_their()] pulped body"
+
+ return progress_text
+
+/datum/surgery_step/heal/burn
+ name = "treat burns"
+ damage_name_pretty = "burns"
+ burn_damage_healed = 5
+ burn_damage_healmod = 0.07
+
+/********************BURN STEPS********************/
+/datum/surgery_step/heal/burn/get_progress(mob/living/user, mob/living/carbon/target, brute_healed, burn_healed)
+ if(!burn_healed)
+ return
+ var/estimated_remaining_steps = target.getFireLoss() / burn_healed
+ var/progress_text
+
+ if(locate(/obj/item/healthanalyzer) in list(user.l_hand, user.r_hand))
+ progress_text = ". Remaining burn: [target.getFireLoss()]"
+ else
+ switch(estimated_remaining_steps)
+ if(-INFINITY to 1)
+ return
+ if(1 to 3)
+ progress_text = ", finishing up the last few singe marks"
+ if(3 to 6)
+ progress_text = ", counting down the last few blisters left to treat"
+ if(6 to 9)
+ progress_text = ", continuing to plug away at [target.p_their()] thorough roasting"
+ if(9 to 12)
+ progress_text = ", steadying yourself for the long surgery ahead"
+ if(12 to 15)
+ progress_text = ", though [target.p_they()] still look[target.p_s()] more like burnt steak than a [target.dna?.species.name || "person" ]"
+ if(15 to INFINITY)
+ progress_text = ", though you feel like you're barely making a dent in treating [target.p_their()] charred body"
+
+ return progress_text
+
+
+// Intermediate versions of the above surgeries
+
+
+/datum/surgery/intermediate/heal
+ possible_locs = list(BODY_ZONE_CHEST)
+
+/datum/surgery/intermediate/heal/brute
+ name = "Tend Wounds (abstract)"
+ desc = "An intermediate surgery to tend to a patient's wounds while they're undergoing another procedure."
+ steps = list(
+ /datum/surgery_step/heal/brute
+ )
+
+
+/datum/surgery/intermediate/heal/brute/can_start(mob/user, mob/living/carbon/target)
+ . = ..()
+ if(!.)
+ return
+ if(target.getBruteLoss() == 0)
+ to_chat(user, "[target] doesn't even have a bruise on [target.p_them()], there's nothing to treat.")
+ return FALSE
+ return TRUE
+ // Normally, adding to_chat to can_start is poor practice since this gets called when listing surgery steps.
+ // It's alright for intermediate surgeries, though, since they never get listed out
+
+/datum/surgery/intermediate/heal/burn
+ name = "Treat Burns (abstract)"
+ desc = "An intermediate surgery to tend to a patient's burns while they're undergoing another procedure."
+ steps = list(
+ /datum/surgery_step/heal/burn
+ )
+
+/datum/surgery/intermediate/heal/burn/can_start(mob/user, mob/living/carbon/target)
+ . = ..()
+ if(!.)
+ return
+ if(target.getFireLoss() == 0)
+ to_chat(user, "[target] doesn't even have a blister on [target.p_them()], there's nothing to treat.")
+ return FALSE
+ return TRUE
diff --git a/paradise.dme b/paradise.dme
index 760ff4f5c62..5b2179beca8 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -2936,6 +2936,7 @@
#include "code\modules\surgery\robotics.dm"
#include "code\modules\surgery\surgery.dm"
#include "code\modules\surgery\surgery_helpers.dm"
+#include "code\modules\surgery\tending.dm"
#include "code\modules\surgery\tools.dm"
#include "code\modules\surgery\organs\augments_arms.dm"
#include "code\modules\surgery\organs\augments_eyes.dm"