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.