From b060b4b6d2a89a161d020d43321727f10f1202de Mon Sep 17 00:00:00 2001
From: Luc <89928798+lewcc@users.noreply.github.com>
Date: Wed, 21 Sep 2022 16:46:03 -0400
Subject: [PATCH] Surgery Hotfixes: Part 1 (#19108)
* Fixes up a few notable bugs with surgery
- Synthetic cavity implants now have a close step (oops!)
- Fixes up logic for intermediate surgery steps
* nah I'll deal with this later
* oops this was unused
---
code/__DEFINES/surgery.dm | 18 +++++
code/datums/components/surgery_initiator.dm | 2 +-
code/modules/surgery/abstract_steps.dm | 73 ++++++++++++++-----
code/modules/surgery/cavity_implant.dm | 80 +++++++++++----------
code/modules/surgery/surgery.dm | 73 ++++++++++---------
5 files changed, 155 insertions(+), 91 deletions(-)
diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm
index b08132fb219..19f7894d838 100644
--- a/code/__DEFINES/surgery.dm
+++ b/code/__DEFINES/surgery.dm
@@ -54,3 +54,21 @@
/// This surgery step will be conditionally retried, so long as the surgery step's can_repeat() proc returns TRUE.
/// Otherwise, it'll behave just like SURGERY_STEP_INCOMPLETE.
#define SURGERY_STEP_RETRY 3
+
+// Return values for surgery_step.initiate().
+// Before you ask, yes, we need another definition for surgery steps here, since these control how we will act on the attack-chain
+// side of things.
+// Unless you're changing the mechanics of the surgery attack chain, you almost surely don't want to use these, and should
+// instead be using the above SURGERY_STEP_X defines.
+
+/// The surgery initiation isn't even going to be started. If you're working with the attack chain, this is probably what you'll be using.
+#define SURGERY_INITIATE_CONTINUE_CHAIN 0
+
+/// The surgery initiaition was a success. We're advancing the current surgery.
+#define SURGERY_INITIATE_SUCCESS 1
+
+/// The surgery initiation was interrupted, or for some reason never completed. We don't want to return FALSE to the attack chain, though.
+#define SURGERY_INITIATE_FAILURE 2
+
+/// The surgery never reached (or finished) the do_after. Go back to the state we were in before this even happened.
+#define SURGERY_INITIATE_INTERRUPTED 3
diff --git a/code/datums/components/surgery_initiator.dm b/code/datums/components/surgery_initiator.dm
index eba5c0a6633..0ff4eb2fc58 100644
--- a/code/datums/components/surgery_initiator.dm
+++ b/code/datums/components/surgery_initiator.dm
@@ -97,7 +97,7 @@
if(!isnull(current_surgery) && !current_surgery.step_in_progress)
var/datum/surgery_step/current_step = current_surgery.get_surgery_step()
- if(current_step.try_op(user, target, user.zone_selected, parent, current_surgery))
+ if(current_step.try_op(user, target, user.zone_selected, parent, current_surgery) == SURGERY_INITIATE_SUCCESS)
return
if(istype(parent, /obj/item/scalpel/laser/manager/debug))
return
diff --git a/code/modules/surgery/abstract_steps.dm b/code/modules/surgery/abstract_steps.dm
index adefe2cf855..9a4460fee8f 100644
--- a/code/modules/surgery/abstract_steps.dm
+++ b/code/modules/surgery/abstract_steps.dm
@@ -45,7 +45,7 @@
/obj/item/scalpel/laser/manager // IMS
)
- /// Whether or not we should add ourselves as a step after we run a branch
+ /// Whether or not we should add ourselves as a step after we run a branch. This doesn't apply to failures, those will always add ourselves after.
var/insert_self_after = TRUE
/datum/surgery_step/proxy/New()
@@ -133,7 +133,7 @@
if(istype(next_surgery_step, /datum/surgery_step/proxy))
// It might make sense to support this, and I think the flow could work (just treating them like a single step, sorta)
// but I think for simplicity's sake it's better to just say no
- CRASH("[src] was followed by another proxy surgery step in [surgery].")
+ CRASH("[src] was followed by another proxy surgery step [next_surgery_step] in [surgery].")
if((SURGERY_TOOL_HAND in starting_tools) && next_surgery_step.accept_hand)
CRASH("[src] has a conflict with the next main step [next_surgery_step] in surgery [surgery]: both require an open hand.")
@@ -173,10 +173,8 @@
return FALSE
if(overridden_tool || next_surgery == surgery || !next_surgery)
- // Continue along with the original surgery
- surgery.step_number++
- var/datum/surgery_step/next_step = surgery.get_surgery_step()
- return next_step.try_op(user, target, target_zone, tool, surgery)
+ // Continue along with the original surgery.
+ return try_next_step(user, target, target_zone, tool, surgery, null, TRUE)
if(!target.can_run_surgery(next_surgery, user))
// Make sure the target can support the surgery.
@@ -188,20 +186,59 @@
// Let them try other tools if necessary.
return TRUE
- // Insert the steps in our intermediate surgery into the current surgery.
- // This is how we keep our surgeries still technically linear.
- var/list/steps_to_insert = next_surgery.steps
- if(insert_self_after)
- // add ourselves afterwards as well so we can repeat this step
- steps_to_insert.Add(type)
+ return try_next_step(user, target, target_zone, tool, surgery, next_surgery.steps)
- // Also, bump the status so we skip past this abstract step.
- surgery.steps.Insert(surgery.step_number + 1, next_surgery.steps)
- surgery.step_number++
+/**
+ * Test the next step, but don't fully commit to it unless it completes successfully.
+ * If the next step doesn't fully complete (such as being interrupted or failing), we'll insert ourselves again to bring us back
+ * to the "base" state.
+ * If it does, we'll add the subsequent steps to the surgery and continue down the expected branch. If you complete the surgery step, it
+ * means you've committed to what comes next.
+ * Part of the motivation behind this is that I don't want to mutate a surgery retroactively. We can insert, but we shouldn't be changing anything
+ * behind us.
+ *
+ * Arguments:
+ * * next_surgery_steps - the steps for the branching surgery to add to the current surgery. If there's no branching surgery (or this would continue the main surgery) ignore this.
+ * * override_adding_self - If true, then regardless of the value of insert_self_after, we won't add ourselves in as another step.
+ * (for other arguments, see try_op())
+ */
+/datum/surgery_step/proxy/proc/try_next_step(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/running_surgery, list/next_surgery_steps, override_adding_self)
+
+ var/list/following_steps = list()
+
+ if(length(next_surgery_steps))
+
+ // add the first step from the following surgery into the surgery list, to make it the next step.
+ running_surgery.steps.Insert(running_surgery.step_number + 1, next_surgery_steps[1])
+
+ // grab the remaining steps to possibly insert after this surgery, depending on what we're doing
+ // skip the current step though, since if our try_op works, we've completed it.
+ following_steps = next_surgery_steps.Copy()
+ following_steps.Cut(1, 2)
+
+ running_surgery.step_number++
+
+ var/datum/surgery_step/next_step = running_surgery.get_surgery_step()
+ var/step_status = next_step.try_op(user, target, target_zone, tool, running_surgery)
+
+ if(step_status != SURGERY_INITIATE_SUCCESS)
+ // always add ourselves after a failure so someone can make a different choice.
+ running_surgery.steps.Insert(running_surgery.step_number + 1, type)
+ running_surgery.step_number++
+
+ else
+ // Insert the steps in our intermediate surgery into the current surgery.
+ // This is how we keep our surgeries still technically linear.
+ if(insert_self_after && !override_adding_self)
+ // add ourselves afterwards as well so we can repeat this step
+ following_steps.Add(type)
+
+ // insert at the current step number since we're not trying to bump it up
+ running_surgery.steps.Insert(running_surgery.step_number, following_steps)
+
+
+ return step_status
- // force the next surgery step so we don't have to click again.
- var/datum/surgery_step/next_step = surgery.get_surgery_step()
- return next_step.try_op(user, target, target_zone, tool, surgery)
// Some intermediate surgeries
/datum/surgery/intermediate/bleeding
diff --git a/code/modules/surgery/cavity_implant.dm b/code/modules/surgery/cavity_implant.dm
index b4683b5f931..d9f118950d6 100644
--- a/code/modules/surgery/cavity_implant.dm
+++ b/code/modules/surgery/cavity_implant.dm
@@ -52,11 +52,52 @@
/datum/surgery_step/robotics/external/unscrew_hatch,
/datum/surgery_step/robotics/external/open_hatch,
/datum/surgery_step/proxy/cavity_manipulation/robotic,
+ /datum/surgery_step/cavity/close_space,
/datum/surgery_step/robotics/external/close_hatch
)
possible_locs = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_PRECISE_GROIN)
requires_organic_bodypart = FALSE
+
+/datum/surgery_step/proxy/cavity_manipulation
+ name = "Cavity Manipulation (proxy)"
+ branches = list(
+ /datum/surgery/intermediate/open_cavity/implant,
+ /datum/surgery/intermediate/open_cavity/extract,
+ /datum/surgery/intermediate/bleeding
+ )
+
+ insert_self_after = TRUE
+
+/datum/surgery_step/proxy/cavity_manipulation/robotic
+ name = "Robotic Cavity Manipulation (proxy)"
+ branches = list(
+ /datum/surgery/intermediate/open_cavity/implant/robotic,
+ /datum/surgery/intermediate/open_cavity/extract/robotic
+ )
+
+/datum/surgery/intermediate/open_cavity
+ possible_locs = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD)
+
+/datum/surgery/intermediate/open_cavity/implant
+ name = "implant object"
+ steps = list(
+ /datum/surgery_step/cavity/place_item
+ )
+
+/datum/surgery/intermediate/open_cavity/extract
+ name = "extract object"
+ steps = list(
+ /datum/surgery_step/cavity/remove_item
+ )
+
+/datum/surgery/intermediate/open_cavity/implant/robotic
+ requires_organic_bodypart = FALSE
+
+/datum/surgery/intermediate/open_cavity/extract/robotic
+ requires_organic_bodypart = FALSE
+
+
/datum/surgery_step/cavity
/datum/surgery_step/cavity/proc/get_max_wclass(obj/item/organ/external/affected)
@@ -206,43 +247,6 @@
return SURGERY_STEP_INCOMPLETE
-/datum/surgery_step/proxy/cavity_manipulation
- name = "Cavity Manipulation (proxy)"
- branches = list(
- /datum/surgery/intermediate/open_cavity/implant,
- /datum/surgery/intermediate/open_cavity/extract
- )
-
- insert_self_after = TRUE
-
-/datum/surgery_step/proxy/cavity_manipulation/robotic
- name = "Robotic Cavity Manipulation (proxy)"
- branches = list(
- /datum/surgery/intermediate/open_cavity/implant/robotic,
- /datum/surgery/intermediate/open_cavity/extract/robotic
- )
-
-/datum/surgery/intermediate/open_cavity
- possible_locs = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD)
-
-/datum/surgery/intermediate/open_cavity/implant
- name = "implant object"
- steps = list(
- /datum/surgery_step/cavity/place_item
- )
-
-/datum/surgery/intermediate/open_cavity/extract
- name = "extract object"
- steps = list(
- /datum/surgery_step/cavity/remove_item
- )
-
-/datum/surgery/intermediate/open_cavity/implant/robotic
- requires_organic_bodypart = FALSE
-
-/datum/surgery/intermediate/open_cavity/extract/robotic
- requires_organic_bodypart = FALSE
-
/datum/surgery_step/cavity/place_item
name = "implant object"
accept_any_item = TRUE
@@ -281,7 +285,7 @@
var/can_fit = !affected.hidden && tool.w_class <= get_max_wclass(affected)
if(!can_fit)
to_chat(user, "\The [tool] won't fit in \the [affected]!")
- return SURGERY_BEGINSTEP_ABORT
+ return SURGERY_BEGINSTEP_SKIP
user.visible_message(
"[user] starts putting \the [tool] inside [target]'s [get_cavity(affected)] cavity.",
diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm
index 9da4313d171..cf2b6bcfac9 100644
--- a/code/modules/surgery/surgery.dm
+++ b/code/modules/surgery/surgery.dm
@@ -227,10 +227,9 @@
if(is_valid_tool(user, tool))
if(target_zone == surgery.location)
if(get_location_accessible(target, target_zone) || surgery.ignore_clothes)
- initiate(user, target, target_zone, tool, surgery)
- else
- to_chat(user, "You need to expose [target]'s [parse_zone(target_zone)] before you can perform surgery on it!")
- return TRUE //returns TRUE so we don't stab the guy in the dick or wherever.
+ return initiate(user, target, target_zone, tool, surgery)
+ to_chat(user, "You need to expose [target]'s [parse_zone(target_zone)] before you can perform surgery on it!")
+ return SURGERY_INITIATE_FAILURE //returns TRUE so we don't stab the guy in the dick or wherever.
if(repeatable)
// you can continuously, manually, perform a step, so long as you continue to use the correct tool.
@@ -241,11 +240,11 @@
if(next_step)
surgery.step_number++
if(next_step.try_op(user, target, user.zone_selected, user.get_active_hand(), surgery))
- return TRUE
+ return SURGERY_INITIATE_SUCCESS
else
surgery.step_number--
- return FALSE
+ return SURGERY_INITIATE_CONTINUE_CHAIN
/**
* Determines whether or not this surgery step can repeat if its end/fail steps returned SURGERY_STEP_RETRY.
@@ -286,14 +285,15 @@
var/begin_step_result = begin_step(user, target, target_zone, tool, surgery)
if(begin_step_result == SURGERY_BEGINSTEP_ABORT)
surgery.step_in_progress = FALSE
- return
+ return SURGERY_INITIATE_FAILURE
+
if(begin_step_result == SURGERY_BEGINSTEP_SKIP)
surgery.step_number++
if(surgery.step_number > length(surgery.steps))
surgery.complete(target)
surgery.step_in_progress = FALSE
- return TRUE
+ return SURGERY_INITIATE_SUCCESS
if(tool)
speed_mod = tool.toolspeed
@@ -314,40 +314,45 @@
prob_success = allowed_tools[implement_type]
prob_success *= get_location_modifier(target)
- if(do_after(user, modded_time, target = target))
+ if(!do_after(user, modded_time, target = target))
+ surgery.step_in_progress = FALSE
+ return SURGERY_INITIATE_INTERRUPTED
- var/chem_check_result = chem_check(target)
- var/pain_mod = deal_pain(user, target, target_zone, tool, surgery)
- prob_success *= pain_mod
+ var/chem_check_result = chem_check(target)
+ var/pain_mod = deal_pain(user, target, target_zone, tool, surgery)
+ prob_success *= pain_mod
- var/step_result
+ var/step_result
- if((prob(prob_success) || isrobot(user) && !silicons_obey_prob) && chem_check_result && !try_to_fail)
- step_result = end_step(user, target, target_zone, tool, surgery)
- else
- step_result = fail_step(user, target, target_zone, tool, surgery)
- switch(step_result)
- if(SURGERY_STEP_CONTINUE)
- advance = TRUE
- if(SURGERY_STEP_RETRY_ALWAYS)
+ if((prob(prob_success) || isrobot(user) && !silicons_obey_prob) && chem_check_result && !try_to_fail)
+ step_result = end_step(user, target, target_zone, tool, surgery)
+ else
+ step_result = fail_step(user, target, target_zone, tool, surgery)
+ switch(step_result)
+ if(SURGERY_STEP_CONTINUE)
+ advance = TRUE
+ if(SURGERY_STEP_RETRY_ALWAYS)
+ retry = TRUE
+ if(SURGERY_STEP_RETRY)
+ if(can_repeat(user, target, target_zone, tool, surgery))
retry = TRUE
- if(SURGERY_STEP_RETRY)
- if(can_repeat(user, target, target_zone, tool, surgery))
- retry = TRUE
- if(retry)
- // if at first you don't succeed...
- return .(user, target, target_zone, tool, surgery, try_to_fail)
+ if(retry)
+ // if at first you don't succeed...
+ return .(user, target, target_zone, tool, surgery, try_to_fail)
- // Bump the surgery status
- // if it's repeatable, don't let it truly "complete" though
- if(advance && !repeatable)
- surgery.step_number++
- if(surgery.step_number > length(surgery.steps))
- surgery.complete(target)
+ // Bump the surgery status
+ // if it's repeatable, don't let it truly "complete" though
+ if(advance && !repeatable)
+ surgery.step_number++
+ if(surgery.step_number > length(surgery.steps))
+ surgery.complete(target)
surgery.step_in_progress = FALSE
- return advance
+ if(advance)
+ return SURGERY_INITIATE_SUCCESS
+ else
+ return SURGERY_INITIATE_FAILURE
/**
* Try to inflict pain during a surgery, a surgeon's dream come true.