# Surgery Code Refactor *From `op_stage` to `surgeryHolder`* [toc] ## Foreword This is a difficult problem, and the currently implemented solution most importanly *works very consistently*; it's already worked through most potential corner-cases and potential issues. I am coming to this code with a different objective and perspective, many years on from the original constraints that this design fulfilled. Thank you for your consideration. ## Goals & Non-Goals ### Goals 1. Make it easier to figure out current/next surgery states 2. Reduce duplication within surgery code 3. Provide greater flexibility for creating/modifying surgery steps ### Non-goals 1. Disrupt the current player actions/order for surgery ## Current Implementation Surgery status is handled via an integer variable stored on an object, usually the `organ`, and which "stage" the surgery is in is represented via integers. These integers live on different objects: * Every `/obj/item/organ` (i.e. head, chest, internal organs) * `op_stage` - keeps track of the current OPeration STAGE * `organ_holder_required_op_stage` - value to check against `op_stage` when attaching * Every `/obj/item/parts` (i.e. limbs) has the `remove_stage` variable * Every `/obj/item/organ/head` has the `scalp_op_stage` variable * Every `/mob/living` has the `butt_op_stage` variable These integers are then gently nudged by large procs custom built for each surgery type. Individual items must specifically call the related surgery proc in order to attempt surgery. ### Impact of current design on code The more surgery steps done with the tool, the greater the complexity of individual procs: | Surgery Proc Name | Lines in File | Number of `if`s | | ----------------- | ------------- | --------------- | | scalpel_surgery | 690 | 81 | | saw_surgery | 361 | 46 | | suture_surgery | 165 | 29 | | cautery_surgery | 150 | 19 | | spoon_surgery | 105 | 16 | | snip_surgery | 271 | 32 | | crowbar_surgery | 54 | 8 | | wrench_surgery | 34 | 8 | In addition, this complexity spills out into other code; `get_desc.dm` can be impenetrable without knowing op_stages. Within `surgery_procs` itself, there are duplicated sections of code that resist condensing into helper procs because they are, in essence, data about the surgery step. This style of design makes the data encoded within difficult to use in other contexts without writing bespoke interpretations for the various organs and `op_stage` values. To quote from the code at writing: ```surgery_procs.dm:6 Understanding Op Stages Step 1 - dont Step 2 - uhg, fine ``` ### The Underlying Issue This is a difficult problem. Surgery itself has state, and is also the interface between a surgeon and several **other** state systems at once: * the `/datum/human_limbs` system for limbs * the `/datum/organHolder` system for internal organs * changes to the system are called from various `/obj/item/` attacks It also interacts more directly with some other play systems: * Trait checking (`clumsy`, `training_medical`) * `bioHolder.mutantrace` (tails, skeletons) on some surgery types ## Proposed Implementation Represent the state of surgery as a set of node graphs, with a state machine-like traversal proc. This works alongside the existing limb and `organHolder` systems; it is not a replacement. ### Diagram This shows all node graphs, joined together for ease of viewing; not designed to work with the Simulate option. https://stately.ai/registry/editor/3668c891-176d-4275-9b7c-adca95c720fc?machineId=a9531a72-9fd9-4a9f-a011-c4468208e88b ### Component Parts * A node in the graph, `node` * The `transitions` map, whose keys are other `node` datums and values are a map: * `conditions` - a list of `condition` * `effects` - a list of `effect` * Static data about the surgery state (e.g. descriptions for examine) * These are not mutated. They can be reused across all mobs. * A condition that must be met, `condition` * Calls a `check` proc that returns bool `TRUE`/`FALSE` * Can contain variable data * Can be any check on the surgeon, patient, or tool; commonly: * `/datum/surgeryHolder/condition/has/*` has a part in place? * `/datum/surgeryHolder/condition/tool/*` surgeon's tool? * `/datum/surgeryHolder/condition/intent/*` surgeon's intent? * `/datum/surgeryHolder/condition/hand/*` surgeon using specific hand? * A side-effect of changing nodes, `effect` * Calls a `do_effect` proc * Can contain variable data e.g. min-max for damage rolls * Can do anything to the surgeon, patient, or tool; commonly: * `/datum/surgeryHolder/effect/damage/*` surgery damage * `/datum/surgeryHolder/effect/bleed` Bleed damage/animation * `/datum/surgeryHolder/condition/attach/*` Part (de-)attachment * A proc for which surgery to try, `get_surgery_transition` * Runs the `check` for every `condition` * If all checks run, return the next node & effects to take on success * The traversal proc, `attempt_surgery` * Rolls the actual surgery success attempt * On success * Applies side effects * Changes relevant `surgeryHolder` state ### Supplemental Parts * Surgeon `zone_sel` switch to determine which graph(s) to check * Reduces complexity of node graphs considerably * Re-use/adapt some existing surgery procs * `surgeryCheck` - adaptable * `calc_screw_up_prob` - as-is ### State Storage Example From some lightweight testing, I think this is the minimum collection of graphs to keep track of. This is an example of what we'd commonly see on a new `/mob/living/carbon/human`, having all their parts together. * `/obj/item/organ/head` * `var/list/surgeryHolders` * `"neck" = /datum/surgeryHolder/node/neck/attached` * `"l_eye" = /datum/surgeryHolder/node/l_eye/attached` * `"r_eye" = /datum/surgeryHolder/node/r_eye/attached` * `"brain" = /datum/surgeryHolder/node/brain/attached` * `/obj/item/organ/chest` * `var/surgeryHolder` * `/datum/surgeryHolder/node/chest/neutral` * `/obj/item/limb` * `var/surgeryHolder` * `/datum/surgeryHolder/node/limb/attached` ### Other Additions * New item flag: `surgery_flags` & helper `issurgerytool` defines * assigning items as surgery tools is super easy * minimize special handling for tools with multiple potential uses (welder, omnitool) ### Benefits * Much smaller size per-function * Easy to add individual condtions and effects to surgery * Data is structured so we can programatically find next steps * User interfaces can rely on game data for surgery steps * Possible to include `secret` surgeries * Potential for validating the node graph works correctly in test ### Challenges * Debugging requires understanding node traversal * Replacing all `op_stage` checks is a lot of work * Unknown size of impact on `secret` content