From bfdb2376125007a85842938d9a070271475e1be1 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Fri, 19 Dec 2025 11:42:58 -0600 Subject: [PATCH] Spooky Scary Supreme Surgery (Rework) (#93697) --- code/__DEFINES/bodyparts.dm | 108 ++ code/__DEFINES/combat.dm | 11 +- code/__DEFINES/dcs/signals/signals_food.dm | 3 + code/__DEFINES/dcs/signals/signals_medical.dm | 24 +- .../signals/signals_mob/signals_mob_carbon.dm | 4 +- .../signals/signals_mob/signals_mob_living.dm | 11 +- code/__DEFINES/dcs/signals/signals_techweb.dm | 3 + code/__DEFINES/surgery.dm | 106 +- code/__DEFINES/traits/declarations.dm | 11 +- code/__DEFINES/wounds.dm | 5 + code/__HELPERS/global_lists.dm | 8 - code/__HELPERS/type2type.dm | 65 +- code/_globalvars/bitfields.dm | 11 +- code/_globalvars/lists/engineering.dm | 43 + code/_globalvars/lists/objects.dm | 7 - code/_globalvars/traits/_traits.dm | 6 +- code/_globalvars/traits/admin_tooling.dm | 1 - code/_onclick/hud/radial.dm | 2 +- code/_onclick/item_attack.dm | 21 +- code/datums/components/fishing_spot.dm | 2 +- code/datums/components/food/edible.dm | 6 +- code/datums/components/surgery_initiator.dm | 344 ----- code/datums/elements/examine_lore.dm | 6 +- code/datums/elements/noticable_organ.dm | 4 +- code/datums/elements/surgery_aid.dm | 151 ++ code/datums/status_effects/debuffs/choke.dm | 2 +- code/datums/wounds/_wounds.dm | 15 - code/datums/wounds/bones.dm | 13 +- code/datums/wounds/cranial_fissure.dm | 3 + code/datums/wounds/pierce.dm | 4 +- .../machinery/computer/operating_computer.dm | 272 +++- .../dna_infuser/organ_sets/fly_organs.dm | 2 +- .../experimental_cloner_fuckup.dm | 2 +- code/game/objects/effects/info.dm | 2 +- code/game/objects/items/cosmetics.dm | 14 +- code/game/objects/items/debug_items.dm | 66 +- code/game/objects/items/hand_items.dm | 2 +- code/game/objects/items/melee/energy.dm | 1 - .../game/objects/items/robot/items/storage.dm | 36 +- code/game/objects/items/stacks/medical.dm | 14 +- code/game/objects/items/stacks/stack.dm | 2 + code/game/objects/structures/bedsheet_bin.dm | 2 +- code/game/objects/structures/tables_racks.dm | 53 +- code/modules/antagonists/abductor/abductor.dm | 38 +- .../abductor/equipment/abduction_surgery.dm | 64 - .../abductor/equipment/glands/heal.dm | 2 +- .../heretic/knowledge/starting_lore.dm | 4 +- .../pirate/pirate_shuttle_equipment.dm | 18 +- code/modules/asset_cache/assets/body_zones.dm | 1 + code/modules/autowiki/pages/surgery.dm | 125 ++ .../preferences/species_features/lizard.dm | 2 +- code/modules/clothing/neck/_neck.dm | 2 +- code/modules/clothing/suits/cloaks.dm | 3 +- .../experiment/handlers/experiment_handler.dm | 11 +- .../experisci/experiment/types/experiment.dm | 11 + .../fishing/sources/subtypes/surgery.dm | 14 +- .../generic_skillchips/self_surgery.dm | 15 - .../ruins/spaceruin_code/forgottenship.dm | 7 +- .../oldstation/oldstation_rnd.dm | 13 +- .../mob/living/basic/bots/medbot/medbot.dm | 45 +- .../mob/living/basic/ruin_defender/flesh.dm | 2 +- .../carbon/alien/special/alien_embryo.dm | 10 +- code/modules/mob/living/carbon/carbon.dm | 5 +- .../mob/living/carbon/carbon_defense.dm | 2 +- code/modules/mob/living/carbon/examine.dm | 5 + .../mob/living/carbon/human/_species.dm | 2 +- code/modules/mob/living/carbon/inventory.dm | 38 +- code/modules/mob/living/death.dm | 9 +- code/modules/mob/living/living.dm | 1 - code/modules/mob/living/living_defense.dm | 23 +- code/modules/mob/living/living_defines.dm | 6 +- .../modules/mob/living/silicon/robot/robot.dm | 2 +- code/modules/mod/modules/modules_medical.dm | 88 +- .../file_system/programs/techweb.dm | 12 +- .../projectiles/ammunition/ballistic/rifle.dm | 4 +- .../projectiles/guns/ballistic/automatic.dm | 1 - .../projectiles/guns/ballistic/pistol.dm | 1 - .../projectiles/guns/energy/recharge.dm | 1 - .../reagents/drinks/alcohol_reagents.dm | 4 +- .../chemistry/reagents/food_reagents.dm | 6 +- .../impure_medicine_reagents.dm | 5 +- .../chemistry/reagents/medicine_reagents.dm | 12 +- .../chemistry/reagents/other_reagents.dm | 5 +- .../reagents/pyrotechnic_reagents.dm | 5 +- code/modules/religion/burdened/psyker.dm | 2 +- .../research/designs/medical_designs.dm | 197 ++- code/modules/research/rdconsole.dm | 12 +- code/modules/research/techweb/_techweb.dm | 8 + .../research/techweb/nodes/alien_nodes.dm | 1 + .../research/techweb/nodes/surgery_nodes.dm | 9 +- .../xenobiology/crossbreeding/_misc.dm | 2 +- .../advanced/bioware/bioware_surgery.dm | 30 - .../advanced/bioware/cortex_folding.dm | 80 - .../advanced/bioware/cortex_imprint.dm | 81 - .../surgery/advanced/bioware/ligament_hook.dm | 60 - .../bioware/ligament_reinforcement.dm | 60 - .../surgery/advanced/bioware/muscled_veins.dm | 58 - .../advanced/bioware/nerve_grounding.dm | 56 - .../advanced/bioware/nerve_splicing.dm | 59 - .../advanced/bioware/vein_threading.dm | 58 - code/modules/surgery/advanced/brainwashing.dm | 116 -- code/modules/surgery/advanced/lobotomy.dm | 129 -- .../surgery/advanced/necrotic_revival.dm | 52 - code/modules/surgery/advanced/pacification.dm | 89 -- .../modules/surgery/advanced/viral_bonding.dm | 60 - .../surgery/advanced/wingreconstruction.dm | 59 - code/modules/surgery/amputation.dm | 113 -- code/modules/surgery/asthmatic_bypass.dm | 96 -- code/modules/surgery/autopsy.dm | 66 - code/modules/surgery/blood_filter.dm | 106 -- code/modules/surgery/bodyparts/_bodyparts.dm | 217 ++- .../surgery/bodyparts/dismemberment.dm | 31 +- code/modules/surgery/bodyparts/head.dm | 1 + code/modules/surgery/bodyparts/parts.dm | 5 + code/modules/surgery/bone_mending.dm | 268 ---- code/modules/surgery/brain_surgery.dm | 92 -- code/modules/surgery/burn_dressing.dm | 181 --- code/modules/surgery/cavity_implant.dm | 87 -- code/modules/surgery/core_removal.dm | 51 - code/modules/surgery/coronary_bypass.dm | 168 --- code/modules/surgery/dental_implant.dm | 117 -- code/modules/surgery/ear_surgery.dm | 74 - .../surgery/experimental_dissection.dm | 153 -- code/modules/surgery/eye_surgery.dm | 72 - code/modules/surgery/gastrectomy.dm | 99 -- code/modules/surgery/healing.dm | 356 ----- code/modules/surgery/hepatectomy.dm | 98 -- code/modules/surgery/implant_removal.dm | 93 -- code/modules/surgery/limb_augmentation.dm | 96 -- code/modules/surgery/lipoplasty.dm | 143 -- code/modules/surgery/lobectomy.dm | 99 -- code/modules/surgery/mechanic_steps.dm | 153 -- .../operations/_basic_surgery_state.dm | 34 + code/modules/surgery/operations/_operation.dm | 1317 +++++++++++++++++ .../surgery/operations/operation_add_limb.dm | 201 +++ .../operations/operation_amputation.dm | 115 ++ .../surgery/operations/operation_asthma.dm | 72 + .../surgery/operations/operation_autopsy.dm | 56 + .../surgery/operations/operation_bioware.dm | 341 +++++ .../operations/operation_bone_repair.dm | 225 +++ .../surgery/operations/operation_brainwash.dm | 150 ++ .../operations/operation_cavity_implant.dm | 194 +++ .../surgery/operations/operation_core.dm | 43 + .../surgery/operations/operation_debride.dm | 86 ++ .../surgery/operations/operation_dental.dm | 141 ++ .../operations/operation_dissection.dm | 151 ++ .../surgery/operations/operation_filter.dm | 78 + .../surgery/operations/operation_generic.dm | 504 +++++++ .../operations/operation_generic_basic.dm | 185 +++ .../operations/operation_generic_mechanic.dm | 227 +++ .../surgery/operations/operation_healing.dm | 254 ++++ .../operations/operation_implant_removal.dm | 83 ++ .../surgery/operations/operation_lipo.dm | 104 ++ .../surgery/operations/operation_lobotomy.dm | 97 ++ .../operations/operation_organ_manip.dm | 282 ++++ .../operations/operation_organ_repair.dm | 529 +++++++ .../surgery/operations/operation_pacify.dm | 69 + .../operations/operation_plastic_surgery.dm | 139 ++ .../surgery/operations/operation_pump.dm | 58 + .../surgery/operations/operation_puncture.dm | 138 ++ .../operations/operation_replace_limb.dm | 91 ++ .../surgery/operations/operation_revival.dm | 119 ++ .../surgery/operations/operation_virus.dm | 69 + .../operations/operation_wing_repair.dm | 61 + .../surgery/operations/operation_zombie.dm | 74 + code/modules/surgery/organ_manipulation.dm | 353 ----- code/modules/surgery/organic_steps.dm | 231 --- code/modules/surgery/organs/_organ.dm | 27 +- .../organs/internal/stomach/stomach_golem.dm | 2 +- code/modules/surgery/organs/organ_movement.dm | 65 +- code/modules/surgery/plastic_surgery.dm | 135 -- .../modules/surgery/prosthetic_replacement.dm | 189 --- code/modules/surgery/repair_puncture.dm | 155 -- code/modules/surgery/revival.dm | 161 -- code/modules/surgery/sleeper_protocol.dm | 91 -- code/modules/surgery/stomachpump.dm | 75 - code/modules/surgery/surgery.dm | 212 --- code/modules/surgery/surgery_disks.dm | 59 + code/modules/surgery/surgery_helpers.dm | 76 - code/modules/surgery/surgery_step.dm | 375 ----- .../surgery/{tools.dm => surgery_tools.dm} | 25 +- code/modules/unit_tests/designs.dm | 30 +- code/modules/unit_tests/omnitools.dm | 15 + .../unit_tests/organ_bodypart_shuffle.dm | 29 +- code/modules/unit_tests/surgeries.dm | 155 +- strings/tips.txt | 19 +- tgstation.dme | 88 +- .../tgui/interfaces/ExperimentConfigure.tsx | 115 +- .../tgui/interfaces/OperatingComputer.jsx | 151 -- .../OperatingComputer/ExperimentView.tsx | 44 + .../OperatingComputer/PatientStateView.tsx | 354 +++++ .../SurgeryProceduresView.tsx | 238 +++ .../interfaces/OperatingComputer/helpers.ts | 77 + .../interfaces/OperatingComputer/index.tsx | 92 ++ .../interfaces/OperatingComputer/types.ts | 73 + tgui/packages/tgui/interfaces/SimpleBot.tsx | 22 - .../tgui/interfaces/SurgeryInitiator.tsx | 172 --- .../interfaces/Techweb/nodes/TechNode.tsx | 4 +- .../packages/tgui/interfaces/Techweb/types.ts | 14 +- .../interfaces/common/BodyZoneSelector.tsx | 102 +- .../tgui/styles/assets/bg-deforest.svg | 3 + .../interfaces/ExperimentConfigure.scss | 25 +- .../styles/interfaces/OperatingComputer.scss | 25 + 203 files changed, 9202 insertions(+), 7568 deletions(-) delete mode 100644 code/datums/components/surgery_initiator.dm create mode 100644 code/datums/elements/surgery_aid.dm delete mode 100644 code/modules/antagonists/abductor/equipment/abduction_surgery.dm create mode 100644 code/modules/autowiki/pages/surgery.dm delete mode 100644 code/modules/surgery/advanced/bioware/bioware_surgery.dm delete mode 100644 code/modules/surgery/advanced/bioware/cortex_folding.dm delete mode 100644 code/modules/surgery/advanced/bioware/cortex_imprint.dm delete mode 100644 code/modules/surgery/advanced/bioware/ligament_hook.dm delete mode 100644 code/modules/surgery/advanced/bioware/ligament_reinforcement.dm delete mode 100644 code/modules/surgery/advanced/bioware/muscled_veins.dm delete mode 100644 code/modules/surgery/advanced/bioware/nerve_grounding.dm delete mode 100644 code/modules/surgery/advanced/bioware/nerve_splicing.dm delete mode 100644 code/modules/surgery/advanced/bioware/vein_threading.dm delete mode 100644 code/modules/surgery/advanced/brainwashing.dm delete mode 100644 code/modules/surgery/advanced/lobotomy.dm delete mode 100644 code/modules/surgery/advanced/necrotic_revival.dm delete mode 100644 code/modules/surgery/advanced/pacification.dm delete mode 100644 code/modules/surgery/advanced/viral_bonding.dm delete mode 100644 code/modules/surgery/advanced/wingreconstruction.dm delete mode 100644 code/modules/surgery/amputation.dm delete mode 100644 code/modules/surgery/asthmatic_bypass.dm delete mode 100644 code/modules/surgery/autopsy.dm delete mode 100644 code/modules/surgery/blood_filter.dm delete mode 100644 code/modules/surgery/bone_mending.dm delete mode 100644 code/modules/surgery/brain_surgery.dm delete mode 100644 code/modules/surgery/burn_dressing.dm delete mode 100644 code/modules/surgery/cavity_implant.dm delete mode 100644 code/modules/surgery/core_removal.dm delete mode 100644 code/modules/surgery/coronary_bypass.dm delete mode 100644 code/modules/surgery/dental_implant.dm delete mode 100644 code/modules/surgery/ear_surgery.dm delete mode 100644 code/modules/surgery/experimental_dissection.dm delete mode 100644 code/modules/surgery/eye_surgery.dm delete mode 100644 code/modules/surgery/gastrectomy.dm delete mode 100644 code/modules/surgery/healing.dm delete mode 100644 code/modules/surgery/hepatectomy.dm delete mode 100644 code/modules/surgery/implant_removal.dm delete mode 100644 code/modules/surgery/limb_augmentation.dm delete mode 100644 code/modules/surgery/lipoplasty.dm delete mode 100644 code/modules/surgery/lobectomy.dm delete mode 100644 code/modules/surgery/mechanic_steps.dm create mode 100644 code/modules/surgery/operations/_basic_surgery_state.dm create mode 100644 code/modules/surgery/operations/_operation.dm create mode 100644 code/modules/surgery/operations/operation_add_limb.dm create mode 100644 code/modules/surgery/operations/operation_amputation.dm create mode 100644 code/modules/surgery/operations/operation_asthma.dm create mode 100644 code/modules/surgery/operations/operation_autopsy.dm create mode 100644 code/modules/surgery/operations/operation_bioware.dm create mode 100644 code/modules/surgery/operations/operation_bone_repair.dm create mode 100644 code/modules/surgery/operations/operation_brainwash.dm create mode 100644 code/modules/surgery/operations/operation_cavity_implant.dm create mode 100644 code/modules/surgery/operations/operation_core.dm create mode 100644 code/modules/surgery/operations/operation_debride.dm create mode 100644 code/modules/surgery/operations/operation_dental.dm create mode 100644 code/modules/surgery/operations/operation_dissection.dm create mode 100644 code/modules/surgery/operations/operation_filter.dm create mode 100644 code/modules/surgery/operations/operation_generic.dm create mode 100644 code/modules/surgery/operations/operation_generic_basic.dm create mode 100644 code/modules/surgery/operations/operation_generic_mechanic.dm create mode 100644 code/modules/surgery/operations/operation_healing.dm create mode 100644 code/modules/surgery/operations/operation_implant_removal.dm create mode 100644 code/modules/surgery/operations/operation_lipo.dm create mode 100644 code/modules/surgery/operations/operation_lobotomy.dm create mode 100644 code/modules/surgery/operations/operation_organ_manip.dm create mode 100644 code/modules/surgery/operations/operation_organ_repair.dm create mode 100644 code/modules/surgery/operations/operation_pacify.dm create mode 100644 code/modules/surgery/operations/operation_plastic_surgery.dm create mode 100644 code/modules/surgery/operations/operation_pump.dm create mode 100644 code/modules/surgery/operations/operation_puncture.dm create mode 100644 code/modules/surgery/operations/operation_replace_limb.dm create mode 100644 code/modules/surgery/operations/operation_revival.dm create mode 100644 code/modules/surgery/operations/operation_virus.dm create mode 100644 code/modules/surgery/operations/operation_wing_repair.dm create mode 100644 code/modules/surgery/operations/operation_zombie.dm delete mode 100644 code/modules/surgery/organ_manipulation.dm delete mode 100644 code/modules/surgery/organic_steps.dm delete mode 100644 code/modules/surgery/plastic_surgery.dm delete mode 100644 code/modules/surgery/prosthetic_replacement.dm delete mode 100644 code/modules/surgery/repair_puncture.dm delete mode 100644 code/modules/surgery/revival.dm delete mode 100644 code/modules/surgery/sleeper_protocol.dm delete mode 100644 code/modules/surgery/stomachpump.dm delete mode 100644 code/modules/surgery/surgery.dm create mode 100644 code/modules/surgery/surgery_disks.dm delete mode 100644 code/modules/surgery/surgery_helpers.dm delete mode 100644 code/modules/surgery/surgery_step.dm rename code/modules/surgery/{tools.dm => surgery_tools.dm} (97%) delete mode 100644 tgui/packages/tgui/interfaces/OperatingComputer.jsx create mode 100644 tgui/packages/tgui/interfaces/OperatingComputer/ExperimentView.tsx create mode 100644 tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx create mode 100644 tgui/packages/tgui/interfaces/OperatingComputer/SurgeryProceduresView.tsx create mode 100644 tgui/packages/tgui/interfaces/OperatingComputer/helpers.ts create mode 100644 tgui/packages/tgui/interfaces/OperatingComputer/index.tsx create mode 100644 tgui/packages/tgui/interfaces/OperatingComputer/types.ts delete mode 100644 tgui/packages/tgui/interfaces/SurgeryInitiator.tsx create mode 100644 tgui/packages/tgui/styles/assets/bg-deforest.svg create mode 100644 tgui/packages/tgui/styles/interfaces/OperatingComputer.scss diff --git a/code/__DEFINES/bodyparts.dm b/code/__DEFINES/bodyparts.dm index 5c6bb1b4960..2f396cfbabc 100644 --- a/code/__DEFINES/bodyparts.dm +++ b/code/__DEFINES/bodyparts.dm @@ -66,3 +66,111 @@ #define BODYPART_OVERLAY_CARP_INFUSION 2 #define BODYPART_OVERLAY_CSS_SUICIDE 3 #define BODYPART_OVERLAY_VOIDWALKER_CURSE 4 + +// Bodypart surgery state +/// An incision has been made into the skin +#define SURGERY_SKIN_CUT (1<<0) +/// Skin has been pulled back - 99% of surgeries require this +#define SURGERY_SKIN_OPEN (1<<1) +/// Blood vessels are accessible, cut, and bleeding +#define SURGERY_VESSELS_UNCLAMPED (1<<2) +/// Blood vessels are accessible but clamped +#define SURGERY_VESSELS_CLAMPED (1<<3) +/// Indicates either an incision has been made into the organs present in the limb or organs have been incised from the limb +#define SURGERY_ORGANS_CUT (1<<4) +/// Holes have been drilled in our bones, exclusive with sawed +#define SURGERY_BONE_DRILLED (1<<5) +/// Bones have been sawed apart +#define SURGERY_BONE_SAWED (1<<6) +/// Used in advanced plastic surgery: Has plastic been applied +#define SURGERY_PLASTIC_APPLIED (1<<7) +/// Used in prosthetic surgery: Is the prosthetic unsecured +#define SURGERY_PROSTHETIC_UNSECURED (1<<8) +/// Used for cavity implants +#define SURGERY_CAVITY_WIDENED (1<<9) + +DEFINE_BITFIELD(surgery_state, list( + "SKIN CUT" = SURGERY_SKIN_CUT, + "SKIN OPEN" = SURGERY_SKIN_OPEN, + "VESSELS UNCLAMPED" = SURGERY_VESSELS_UNCLAMPED, + "VESSELS CLAMPED" = SURGERY_VESSELS_CLAMPED, + "ORGANS CUT" = SURGERY_ORGANS_CUT, + "BONE DRILLED" = SURGERY_BONE_DRILLED, + "BONE SAWED" = SURGERY_BONE_SAWED, + "PLASTIC APPLIED" = SURGERY_PLASTIC_APPLIED, + "PROSTHETIC UNSECURED" = SURGERY_PROSTHETIC_UNSECURED, + "CAVITY OPENED" = SURGERY_CAVITY_WIDENED, +)) + +/// For use in translating bitfield to human readable strings. Keep in the correct order! +#define SURGERY_STATE_READABLE list(\ + "Skin is cut" = SURGERY_SKIN_CUT, \ + "Skin is open" = SURGERY_SKIN_OPEN, \ + "Blood vessels are unclamped" = SURGERY_VESSELS_UNCLAMPED, \ + "Blood vessels are clamped" = SURGERY_VESSELS_CLAMPED, \ + "Organs are cut" = SURGERY_ORGANS_CUT, \ + "Bone is drilled" = SURGERY_BONE_DRILLED, \ + "Bone is sawed" = SURGERY_BONE_SAWED, \ + "Plastic is applied" = SURGERY_PLASTIC_APPLIED, \ + "Prosthetic is unsecured" = SURGERY_PROSTHETIC_UNSECURED, \ + "Cavity is opened wide" = SURGERY_CAVITY_WIDENED, \ +) + +/// For use in translating bitfield to steps required for surgery. Keep in the correct order! +#define SURGERY_STATE_GUIDES(must_must_not) list(\ + "the skin [must_must_not] be cut" = SURGERY_SKIN_CUT, \ + "the skin [must_must_not] be open" = SURGERY_SKIN_OPEN, \ + "the blood vessels [must_must_not] be unclamped" = SURGERY_VESSELS_UNCLAMPED, \ + "the blood vessels [must_must_not] be clamped" = SURGERY_VESSELS_CLAMPED, \ + "the organs [must_must_not] be cut" = SURGERY_ORGANS_CUT, \ + "the bone [must_must_not] be drilled" = SURGERY_BONE_DRILLED, \ + "the bone [must_must_not] be sawed" = SURGERY_BONE_SAWED, \ + "plastic [must_must_not] be applied" = SURGERY_PLASTIC_APPLIED, \ + "the prosthetic [must_must_not] be unsecured" = SURGERY_PROSTHETIC_UNSECURED, \ + "the chest cavity [must_must_not] be opened wide" = SURGERY_CAVITY_WIDENED, \ +) + +// Yes these are glorified bitflag manipulation macros, they're meant to make reading surgical operations a bit easier +/// Checks if the input surgery state has all of the bitflags passed +#define HAS_SURGERY_STATE(input_state, check_state) ((input_state & (check_state)) == (check_state)) +/// Checks if the input surgery state has any of the bitflags passed +#define HAS_ANY_SURGERY_STATE(input_state, check_state) ((input_state & (check_state))) +/// Checks if the limb has all of the bitflags passed +#define LIMB_HAS_SURGERY_STATE(limb, check_state) HAS_SURGERY_STATE(limb?.surgery_state, check_state) +/// Checks if the limb has any of the bitflags passed +#define LIMB_HAS_ANY_SURGERY_STATE(limb, check_state) HAS_ANY_SURGERY_STATE(limb?.surgery_state, check_state) + +/// All states that concern itself with the skin +#define ALL_SURGERY_SKIN_STATES (SURGERY_SKIN_CUT|SURGERY_SKIN_OPEN) +/// All states that concern itself with the blood vessels +#define ALL_SURGERY_VESSEL_STATES (SURGERY_VESSELS_UNCLAMPED|SURGERY_VESSELS_CLAMPED) +/// All states that concern itself with the bones +#define ALL_SURGERY_BONE_STATES (SURGERY_BONE_DRILLED|SURGERY_BONE_SAWED) +/// All states that concern itself with internal organs +#define ALL_SURGERY_ORGAN_STATES (SURGERY_ORGANS_CUT) + +/// These states are automatically cleared when the surgery is closed for ease of use +#define ALL_SURGERY_STATES_UNSET_ON_CLOSE (ALL_SURGERY_SKIN_STATES|ALL_SURGERY_VESSEL_STATES|ALL_SURGERY_BONE_STATES|ALL_SURGERY_ORGAN_STATES|SURGERY_CAVITY_WIDENED) +/// Surgery state required for a limb with a certain zone to... be... fished... in... +#define ALL_SURGERY_FISH_STATES(for_zone) (SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT|(for_zone == BODY_ZONE_CHEST ? SURGERY_BONE_SAWED : NONE)) + +/// Surgery states flipped on automatically if the bodypart lacks a form of skin +#define SKINLESS_SURGERY_STATES (SURGERY_SKIN_OPEN) +// (These are normally mutually exclusive, but as a bonus for lacking bones, you can do drill and saw operations simultaneously!) +/// Surgery states flipped on automatically if the bodypart lacks bones +#define BONELESS_SURGERY_STATES (SURGERY_BONE_DRILLED|SURGERY_BONE_SAWED) +/// Surgery states flipped on automatically if the bodypart lacks vessels +#define VESSELLESS_SURGERY_STATES (SURGERY_VESSELS_CLAMPED|SURGERY_ORGANS_CUT) + +/// Biological state that has some kind of skin that can be cut. +#define BIOSTATE_HAS_SKIN (BIO_FLESH|BIO_METAL|BIO_CHITIN) +/// Checks if a bodypart lacks both flesh and metal, meaning it has no skin to cut. +#define LIMB_HAS_SKIN(limb) (limb?.biological_state & BIOSTATE_HAS_SKIN) +/// Biological state that has some kind of bones that can be sawed. +#define BIOSTATE_HAS_BONES (BIO_BONE|BIO_METAL) +/// Checks if a bodypart lacks both bone and metal, meaning it has no bones to saw. +#define LIMB_HAS_BONES(limb) (limb?.biological_state & BIOSTATE_HAS_BONES) +/// Biological state that has some kind of vessels that can be clamped. +#define BIOSTATE_HAS_VESSELS (BIO_BLOODED|BIO_WIRED) +/// Checks if a bodypart lacks both blood and wires, meaning it has no vessels to manipulate. +#define LIMB_HAS_VESSELS(limb) (limb?.biological_state & BIOSTATE_HAS_VESSELS) diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 7d9276b08c7..66bea5b01c8 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -285,11 +285,6 @@ DEFINE_BITFIELD(ammo_box_multiload, list( #define BODY_ZONE_L_LEG "l_leg" #define BODY_ZONE_R_LEG "r_leg" -GLOBAL_LIST_INIT(all_body_zones, list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) -GLOBAL_LIST_INIT(limb_zones, list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) -GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) -GLOBAL_LIST_INIT(leg_zones, list(BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) - #define BODY_ZONE_PRECISE_EYES "eyes" #define BODY_ZONE_PRECISE_MOUTH "mouth" #define BODY_ZONE_PRECISE_GROIN "groin" @@ -298,6 +293,12 @@ GLOBAL_LIST_INIT(leg_zones, list(BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) #define BODY_ZONE_PRECISE_L_FOOT "l_foot" #define BODY_ZONE_PRECISE_R_FOOT "r_foot" +GLOBAL_LIST_INIT(all_body_zones, list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) +GLOBAL_LIST_INIT(limb_zones, list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) +GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) +GLOBAL_LIST_INIT(leg_zones, list(BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) +GLOBAL_LIST_INIT(all_precise_body_zones, list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_GROIN, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_PRECISE_L_FOOT, BODY_ZONE_PRECISE_R_FOOT)) + //We will round to this value in damage calculations. #define DAMAGE_PRECISION 0.1 diff --git a/code/__DEFINES/dcs/signals/signals_food.dm b/code/__DEFINES/dcs/signals/signals_food.dm index 2122f57add8..9dca9d304d0 100644 --- a/code/__DEFINES/dcs/signals/signals_food.dm +++ b/code/__DEFINES/dcs/signals/signals_food.dm @@ -7,6 +7,9 @@ /// From datum/component/edible/proc/TakeBite: (mob/living/eater, mob/feeder, bitecount, bitesize) #define COMSIG_FOOD_EATEN "food_eaten" #define DESTROY_FOOD (1<<0) +/// From datum/component/edible/proc/AttemptEat: (mob/living/eater, mob/feeder) +#define COMSIG_FOOD_ATTEMPT_EAT "food_attempt_eat" + // #define BLOCK_EAT_ATTEMPT (1<<0) /// From base of datum/component/edible/on_entered: (mob/crosser, bitecount) #define COMSIG_FOOD_CROSSED "food_crossed" /// From base of Component/edible/On_Consume: (mob/living/eater, mob/living/feeder) diff --git a/code/__DEFINES/dcs/signals/signals_medical.dm b/code/__DEFINES/dcs/signals/signals_medical.dm index 39bcb1d7cdd..de3814adb4a 100644 --- a/code/__DEFINES/dcs/signals/signals_medical.dm +++ b/code/__DEFINES/dcs/signals/signals_medical.dm @@ -1,11 +1,11 @@ -/// From /datum/surgery/New(): (datum/surgery/surgery, surgery_location (body zone), obj/item/bodypart/targeted_limb) -#define COMSIG_MOB_SURGERY_STARTED "mob_surgery_started" - -/// From /datum/surgery/Destroy(): (surgery_type, surgery_location, obj/item/bodypart/targeted_limb) -#define COMSIG_MOB_SURGERY_FINISHED "mob_surgery_finished" - -/// From /datum/surgery_step/success(): (datum/surgery_step/step, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) -#define COMSIG_MOB_SURGERY_STEP_SUCCESS "mob_surgery_step_success" +/// From /datum/surgery_operation/try_perform(): (datum/surgery_operation/operation, atom/movable/operating_on, tool) +#define COMSIG_LIVING_SURGERY_STARTED "mob_surgery_started" +/// From /datum/surgery_operation/try_perform(): (datum/surgery_operation/operation, atom/movable/operating_on, tool) +#define COMSIG_LIVING_SURGERY_FINISHED "mob_surgery_finished" +/// From /datum/surgery_operation/success(): (datum/surgery_operation/operation, atom/movable/operating_on, tool) +#define COMSIG_LIVING_SURGERY_SUCCESS "mob_surgery_step_success" +/// From /datum/surgery_operation/failure(): (datum/surgery_operation/operation, atom/movable/operating_on, tool) +#define COMSIG_LIVING_SURGERY_FAILED "mob_surgery_step_failed" /// From /obj/item/shockpaddles/do_help, after the defib do_after is complete, but before any effects are applied: (mob/living/defibber, obj/item/shockpaddles/source) #define COMSIG_DEFIBRILLATOR_PRE_HELP_ZAP "carbon_being_defibbed" @@ -19,7 +19,7 @@ /// From /obj/item/shockpaddles/proc/do_disarm(), sent to the shock-ee in non-revival scenarios: (obj/item/shockpaddles/source) #define COMSIG_HEARTATTACK_DEFIB "heartattack_defib" -/// From /datum/surgery/can_start(): (mob/source, datum/surgery/surgery, mob/living/patient) -#define COMSIG_SURGERY_STARTING "surgery_starting" - #define COMPONENT_CANCEL_SURGERY (1<<0) - #define COMPONENT_FORCE_SURGERY (1<<1) +/// Sent from /mob/living/perform_surgery: (mob/living/patient, list/possible_operations) +#define COMSIG_LIVING_OPERATING_ON "living_operating_on" +/// Sent from /mob/living/perform_surgery: (mob/living/patient, list/possible_operations) +#define COMSIG_LIVING_BEING_OPERATED_ON "living_being_operated_on" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index c3da00070c9..9e2c68979b5 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -97,8 +97,8 @@ #define COMSIG_CARBON_MOOD_UPDATE "carbon_mood_update" ///Called when a carbon attempts to eat (eating) #define COMSIG_CARBON_ATTEMPT_EAT "carbon_attempt_eat" - // Prevents the breath - #define COMSIG_CARBON_BLOCK_EAT (1 << 0) + // Prevents eating the food + #define BLOCK_EAT_ATTEMPT (1 << 0) ///Called when a carbon vomits : (distance, force) #define COMSIG_CARBON_VOMITED "carbon_vomited" ///Called from apply_overlay(cache_index, overlay) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index 58ce66e0896..2b1f33adf3f 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -345,14 +345,6 @@ /// From /obj/item/book/bible/attack() : (mob/living/user, obj/item/book/bible/bible, bless_result) #define COMSIG_LIVING_BLESSED "living_blessed" -/// From /datum/surgery_step/initiate() : (mob/living/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, datum/surgery_step/step, list/modifiers) -#define COMSIG_LIVING_INITIATE_SURGERY_STEP "living_initiate_surgery_step" -#define COMSIG_LIVING_SURGERY_STEP_INITIATED_ON "living_surgery_step_initiated_on" - /// Index in modifiers containing the modifier to failure chance - #define FAIL_PROB_INDEX 1 - /// Index in modifiers containing the modifer to surgery speed - #define SPEED_MOD_INDEX 2 - /// From /datum/status_effect/proc/on_creation() : (datum/status_effect/effect) #define COMSIG_LIVING_STATUS_APPLIED "living_status_applied" @@ -380,3 +372,6 @@ /// From /mob/living/proc/update_blood_status(), sent when the return value of /mob/living/proc/can_have_blood() changes : (had_blood, has_blood, old_blood_volume, new_blood_volume) #define COMSIG_LIVING_UPDATE_BLOOD_STATUS "living_update_blood_status" + +/// Sent to a mob when one of their bodypart's surgery state changes, OR sent from the basic_surgery_state holder when its surgery state changes (old_state, new_state, changed_states) +#define COMSIG_LIVING_UPDATING_SURGERY_STATE "carbon_updating_surgery_state" diff --git a/code/__DEFINES/dcs/signals/signals_techweb.dm b/code/__DEFINES/dcs/signals/signals_techweb.dm index 7765bfe5a3a..ce0b5bc3f45 100644 --- a/code/__DEFINES/dcs/signals/signals_techweb.dm +++ b/code/__DEFINES/dcs/signals/signals_techweb.dm @@ -3,3 +3,6 @@ /// Called when a techweb design is removed (datum/design/removed_design, custom) #define COMSIG_TECHWEB_REMOVE_DESIGN "techweb_remove_design" + +/// Called when an experiment is completed (datum/experiment/completed_experiment,) +#define COMSIG_TECHWEB_EXPERIMENT_COMPLETED "techweb_experiment_completed" diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm index 9d73f12a980..dc54c139dba 100644 --- a/code/__DEFINES/surgery.dm +++ b/code/__DEFINES/surgery.dm @@ -36,6 +36,11 @@ #define ORGAN_GHOST (1<<14) /// This is a mutant organ, having this makes you a -derived mutant to health analyzers. #define ORGAN_MUTANT (1<<15) +/// The organ has been chomped or otherwise rendered unusable. +#define ORGAN_UNUSABLE (1<<16) + +/// Organ flags that correspond to bodytypes +#define ORGAN_TYPE_FLAGS (ORGAN_ORGANIC | ORGAN_ROBOTIC | ORGAN_MINERAL | ORGAN_GHOST) /// Scarring on the right eye #define RIGHT_EYE_SCAR (1<<0) @@ -85,28 +90,83 @@ /// Default for most heads #define HEAD_DEFAULT_FEATURES (HEAD_HAIR|HEAD_FACIAL_HAIR|HEAD_LIPS|HEAD_EYESPRITES|HEAD_EYECOLOR|HEAD_EYEHOLES|HEAD_DEBRAIN) -/// Return value when the surgery step fails :( -#define SURGERY_STEP_FAIL -1 +/// Checks if the mob is lying down if they can lie down, otherwise always passes +#define IS_LYING_OR_CANNOT_LIE(mob) ((mob.mobility_flags & MOBILITY_LIEDOWN) ? (mob.body_position == LYING_DOWN) : TRUE) -// Flags for surgery_flags on surgery datums -///Will allow the surgery to bypass clothes -#define SURGERY_IGNORE_CLOTHES (1<<0) -///Will allow the surgery to be performed by the user on themselves. -#define SURGERY_SELF_OPERABLE (1<<1) -///Will allow the surgery to work on mobs that aren't lying down. -#define SURGERY_REQUIRE_RESTING (1<<2) -///Will allow the surgery to work only if there's a limb. -#define SURGERY_REQUIRE_LIMB (1<<3) -///Will allow the surgery to work only if there's a real (eg. not pseudopart) limb. -#define SURGERY_REQUIRES_REAL_LIMB (1<<4) -///Will grant a bonus during surgery steps to users with TRAIT_MORBID while they're using tools with CRUEL_IMPLEMENT -#define SURGERY_MORBID_CURIOSITY (1<<5) -/** - * Instead of checking if the tool used is an actual surgery tool to avoid accidentally whacking patients with the wrong tool, - * it'll check if it has a defined tool behaviour instead. Useful for surgeries that use mechanical tools instead of medical ones, - * like hardware manipulation. - */ -#define SURGERY_CHECK_TOOL_BEHAVIOUR (1<<6) +/// Applies moodlets after the surgical operation is complete +#define OPERATION_AFFECTS_MOOD (1<<0) +/// Notable operations are specially logged and also leave memories +#define OPERATION_NOTABLE (1<<1) +/// Operation will automatically repeat until it can no longer be performed +#define OPERATION_LOOPING (1<<2) +/// Grants a speed bonus if the user is morbid and their tool is morbid +#define OPERATION_MORBID (1<<3) +/// Not innately available to doctors, must be added via COMSIG_MOB_ATTEMPT_SURGERY to show up +#define OPERATION_LOCKED (1<<4) +/// A surgeon can perform this operation on themselves +#define OPERATION_SELF_OPERABLE (1<<5) +/// Operation can be performed on standing patients - note: mobs that cannot lie down are *always* considered lying down for surgery +#define OPERATION_STANDING_ALLOWED (1<<6) +/// Some traits may cause operations to be infalliable - this flag disables that behavior, always allowing it to be failed +#define OPERATION_ALWAYS_FAILABLE (1<<7) +/// If set, the operation will ignore clothing when checking for access to the target body part. +#define OPERATION_IGNORE_CLOTHES (1<<8) +/// This operation should be prioritized as the next step in a surgery sequence. (In the operating computer it will flash red) +#define OPERATION_PRIORITY_NEXT_STEP (1<<9) +/// Operation is a mechanic / robotic surgery +#define OPERATION_MECHANIC (1<<10) +/// Hides the operation from autowiki generation +#define OPERATION_NO_WIKI (1<<11) -///Return true if target is not in a valid body position for the surgery -#define IS_IN_INVALID_SURGICAL_POSITION(target, surgery) ((surgery.surgery_flags & SURGERY_REQUIRE_RESTING) && (target.mobility_flags & MOBILITY_LIEDOWN && target.body_position != LYING_DOWN)) +DEFINE_BITFIELD(operation_flags, list( + "AFFECTS MOOD" = OPERATION_AFFECTS_MOOD, + "NOTABLE" = OPERATION_NOTABLE, + "LOOPING" = OPERATION_LOOPING, + "MORBID" = OPERATION_MORBID, + "LOCKED" = OPERATION_LOCKED, + "SELF OPERABLE" = OPERATION_SELF_OPERABLE, + "STANDING ALLOWED" = OPERATION_STANDING_ALLOWED, + "ALWAYS FAILABLE" = OPERATION_ALWAYS_FAILABLE, + "IGNORE CLOTHES" = OPERATION_IGNORE_CLOTHES, + "PRIORITY NEXT STEP" = OPERATION_PRIORITY_NEXT_STEP, + "MECHANIC" = OPERATION_MECHANIC, +)) + +/// All of these equipment slots are ignored when checking for clothing coverage during surgery +#define IGNORED_OPERATION_CLOTHING_SLOTS (ITEM_SLOT_NECK) + +// Surgery related mood defines +#define SURGERY_STATE_STARTED "surgery_started" +#define SURGERY_STATE_FAILURE "surgery_failed" +#define SURGERY_STATE_SUCCESS "surgery_success" +#define SURGERY_MOOD_CATEGORY "surgery" + +/// Dummy "tool" for surgeries which use hands +#define IMPLEMENT_HAND "hands" + +/// Surgery speed modifiers are soft-capped at this value +/// The actual modifier can exceed this but it gets +#define SURGERY_MODIFIER_FAILURE_THRESHOLD 2.5 +/// There is an x percent chance of failure per second beyond 2.5x the base surgery time +#define FAILURE_CHANCE_PER_SECOND 10 +/// Calculates failure chance of an operation based on the base time and the effective speed modifier +/// This may look something like: Base time 1 second and 4x effective multiplier -> 4 seconds - 2.5 seconds = 1.5 seconds * 10 = 15% failure chance +/// Or: Base time 2 seconds and 1x effective multiplier -> 2 seconds - 5 seconds = -3 seconds * 10 = -30% failure chance (clamped to 0%) +#define GET_FAILURE_CHANCE(base_time, speed_mod) (FAILURE_CHANCE_PER_SECOND * (((speed_mod * (base_time)) - (SURGERY_MODIFIER_FAILURE_THRESHOLD * (base_time))) / (1 SECONDS))) + +// Operation argument indexes +/// Total speed/failure modifier applied to the operation +#define OPERATION_SPEED "speed_modifier" +/// The action being performed, simply "default" for 95% of surgeries +#define OPERATION_ACTION "action" +/// Whether the operation should automatically fail +#define OPERATION_FORCE_FAIL "force_fail" +/// The body zone being targeted by the operation +#define OPERATION_TARGET_ZONE "target_zone" +/// The specific target of the operation, usually a bodypart or organ, generally redundant +#define OPERATION_TARGET "target" +// For tend wounds - only reason these aren't local is we use them in unit testing +#define OPERATION_BRUTE_HEAL "brute_heal" +#define OPERATION_BURN_HEAL "burn_heal" +#define OPERATION_BRUTE_MULTIPLIER "brute_multiplier" +#define OPERATION_BURN_MULTIPLIER "burn_multiplier" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 2c8799a13b0..a3f7c0738b4 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -424,7 +424,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_SPINNING_WEB_TURF "spinning_web_turf" #define TRAIT_ABDUCTOR_TRAINING "abductor-training" #define TRAIT_ABDUCTOR_SCIENTIST_TRAINING "abductor-scientist-training" -#define TRAIT_SURGEON "surgeon" #define TRAIT_STRONG_GRABBER "strong_grabber" #define TRAIT_SOOTHED_THROAT "soothed-throat" #define TRAIT_SOOTHED_HEADACHE "soothed-headache" @@ -1589,8 +1588,13 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///Attached to objects currently on tables and such, allowing them to walk on other objects without the climbing delay #define TRAIT_ON_CLIMBABLE "on_climbable" -/// Trait that allows mobs to perform surgery on themselves +/// Allows a mob to perform any operation on themselves (with a penalty), where normally only specific operations allow self-surgery. #define TRAIT_SELF_SURGERY "self_surgery" +/// This mob's surgical operations ignore ALL speed modifiers (even positive ones!) besides tool quality. +/// The mob can also no longer fail their operations, unless the operation says otherwise +#define TRAIT_IGNORE_SURGERY_MODIFIERS "ignore_surgery_modifiers" +/// Trait applied to mobs or bodyparts which allows for them to be operated on +#define TRAIT_READY_TO_OPERATE "ready_to_operate" /// Trait that makes mobs with it immune to mining gear AOE attacks #define TRAIT_MINING_AOE_IMMUNE "mining_aoe_immune" @@ -1604,6 +1608,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Trait specifying that an AI has a remote connection to an integrated circuit #define TRAIT_CONNECTED_TO_CIRCUIT "connected_to_circuit" +/// Applied to an organ that has been operated on - some organs can't be operated on multiple times +#define TRAIT_ORGAN_OPERATED_ON "organ_operated_on" + /// Mob is artificially spawned rather than being created through more natural means - applied to monkey cubes and such #define TRAIT_SPAWNED_MOB "spawned_mob" diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm index 4f2c87174b1..b345fda1995 100644 --- a/code/__DEFINES/wounds.dm +++ b/code/__DEFINES/wounds.dm @@ -88,6 +88,8 @@ GLOBAL_LIST_INIT(wound_severities_chronological, list( #define BIO_BLOODED (1<<5) /// Is connected by a joint - can suffer T1 bone blunt wounds (dislocation) #define BIO_JOINTED (1<<6) +/// Skin is covered in thick chitin and is resistant to cutting +#define BIO_CHITIN (1<<7) /// Robotic - can suffer all metal/wired wounds, such as: UNIMPLEMENTED PLEASE UPDATE ONCE SYNTH WOUNDS 9/5/2023 ~Niko #define BIO_ROBOTIC (BIO_METAL|BIO_WIRED) /// Has flesh and bone - See BIO_BONE and BIO_FLESH @@ -96,6 +98,8 @@ GLOBAL_LIST_INIT(wound_severities_chronological, list( #define BIO_STANDARD_UNJOINTED (BIO_FLESH_BONE|BIO_BLOODED) /// Standard humanoid limbs - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt. Can also bleed, and be dislocated. Think human arms and legs #define BIO_STANDARD_JOINTED (BIO_STANDARD_UNJOINTED|BIO_JOINTED) +/// Xenomorph limbs (xenos are immune to wounds anyhow) +#define BIO_STANDARD_ALIEN (BIO_CHITIN|BIO_BONE|BIO_BLOODED|BIO_JOINTED) // "Where" a specific biostate is within a given limb // Interior is hard shit, the last line, shit like bones @@ -117,6 +121,7 @@ GLOBAL_LIST_INIT(bio_state_anatomy, list( "[BIO_METAL]" = ANATOMY_INTERIOR, "[BIO_FLESH]" = ANATOMY_EXTERIOR, "[BIO_BONE]" = ANATOMY_INTERIOR, + "[BIO_CHITIN]" = ANATOMY_EXTERIOR, )) // Wound series diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index d5638e0312b..5ba6af88746 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -2,14 +2,6 @@ /////Initial Building///// ////////////////////////// -/// Inits GLOB.surgeries -/proc/init_surgeries() - var/surgeries = list() - for(var/path in subtypesof(/datum/surgery)) - surgeries += new path() - sort_list(surgeries, GLOBAL_PROC_REF(cmp_typepaths_asc)) - return surgeries - /// Legacy procs that really should be replaced with proper _INIT macros /proc/make_datum_reference_lists() // I tried to eliminate this proc but I couldn't untangle their init-order interdependencies -Dominion/Cyberboss diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm index c7b7cdd4bc1..4b8e02699f8 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -181,6 +181,8 @@ GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH, switch(def_zone) if(BODY_ZONE_CHEST) return CHEST|GROIN + if(BODY_ZONE_PRECISE_GROIN) + return GROIN if(BODY_ZONE_HEAD) return HEAD if(BODY_ZONE_L_ARM) @@ -198,50 +200,33 @@ GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH, var/list/covered_parts = list() if(!bpc) - return 0 + return covered_parts - if(bpc == FULL_BODY) - covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM,BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) + if(bpc & HEAD) + covered_parts |= list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH) - else - if(bpc & HEAD) - covered_parts |= list(BODY_ZONE_HEAD) - if(bpc & CHEST) - covered_parts |= list(BODY_ZONE_CHEST) - if(bpc & GROIN) - covered_parts |= list(BODY_ZONE_CHEST) + if(bpc & CHEST) + covered_parts |= list(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_GROIN) + else if(bpc & GROIN) + covered_parts |= BODY_ZONE_PRECISE_GROIN - if(bpc & ARMS) - covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM) - else - if(bpc & ARM_LEFT) - covered_parts |= list(BODY_ZONE_L_ARM) - if(bpc & ARM_RIGHT) - covered_parts |= list(BODY_ZONE_R_ARM) + if(bpc & HAND_LEFT) + covered_parts |= list(BODY_ZONE_L_ARM, BODY_ZONE_PRECISE_L_HAND) + else if(bpc & ARM_LEFT) + covered_parts |= BODY_ZONE_L_ARM + if(bpc & HAND_RIGHT) + covered_parts |= list(BODY_ZONE_R_ARM, BODY_ZONE_PRECISE_R_HAND) + else if(bpc & ARM_RIGHT) + covered_parts |= BODY_ZONE_R_ARM - if(bpc & HANDS) - covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM) - else - if(bpc & HAND_LEFT) - covered_parts |= list(BODY_ZONE_L_ARM) - if(bpc & HAND_RIGHT) - covered_parts |= list(BODY_ZONE_R_ARM) - - if(bpc & LEGS) - covered_parts |= list(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) - else - if(bpc & LEG_LEFT) - covered_parts |= list(BODY_ZONE_L_LEG) - if(bpc & LEG_RIGHT) - covered_parts |= list(BODY_ZONE_R_LEG) - - if(bpc & FEET) - covered_parts |= list(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) - else - if(bpc & FOOT_LEFT) - covered_parts |= list(BODY_ZONE_L_LEG) - if(bpc & FOOT_RIGHT) - covered_parts |= list(BODY_ZONE_R_LEG) + if(bpc & FOOT_LEFT) + covered_parts |= list(BODY_ZONE_L_LEG, BODY_ZONE_PRECISE_L_FOOT) + else if(bpc & LEG_LEFT) + covered_parts |= BODY_ZONE_L_LEG + if(bpc & FOOT_RIGHT) + covered_parts |= list(BODY_ZONE_R_LEG, BODY_ZONE_PRECISE_R_FOOT) + else if(bpc & LEG_RIGHT) + covered_parts |= BODY_ZONE_R_LEG return covered_parts diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 6be87df0909..88e622add8c 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -332,12 +332,14 @@ DEFINE_BITFIELD(mob_respiration_type, list( DEFINE_BITFIELD(mobility_flags, list( "MOVE" = MOBILITY_MOVE, - "PICKUP" = MOBILITY_PICKUP, - "PULL" = MOBILITY_PULL, "STAND" = MOBILITY_STAND, - "STORAGE" = MOBILITY_STORAGE, - "UI" = MOBILITY_UI, + "PICKUP" = MOBILITY_PICKUP, "USE" = MOBILITY_USE, + "UI" = MOBILITY_UI, + "STORAGE" = MOBILITY_STORAGE, + "PULL" = MOBILITY_PULL, + "REST" = MOBILITY_REST, + "LIE DOWN" = MOBILITY_LIEDOWN, )) DEFINE_BITFIELD(movement_type, list( @@ -513,6 +515,7 @@ DEFINE_BITFIELD(biological_state, list( "BIO_WIRED" = BIO_WIRED, "BIO_BLOODED" = BIO_BLOODED, "BIO_JOINTED" = BIO_JOINTED, + "BIO_CHITIN" = BIO_CHITIN, )) DEFINE_BITFIELD(change_exempt_flags, list( diff --git a/code/_globalvars/lists/engineering.dm b/code/_globalvars/lists/engineering.dm index 70114905b5f..602452d4789 100644 --- a/code/_globalvars/lists/engineering.dm +++ b/code/_globalvars/lists/engineering.dm @@ -21,3 +21,46 @@ GLOBAL_LIST_INIT(all_tool_behaviours, list( TOOL_WIRECUTTER, TOOL_WRENCH, )) + +GLOBAL_LIST_INIT(all_mechanical_tools, list( + TOOL_ANALYZER, + TOOL_CROWBAR, + TOOL_MULTITOOL, + TOOL_SCREWDRIVER, + TOOL_WELDER, + TOOL_WIRECUTTER, + TOOL_WRENCH, +)) + +GLOBAL_LIST_INIT(all_surgical_tools, list( + TOOL_BONESET, + TOOL_CAUTERY, + TOOL_HEMOSTAT, + TOOL_RETRACTOR, + TOOL_SAW, + TOOL_SCALPEL, +)) + +/// Mapping of tool types to icons that represent them +GLOBAL_LIST_INIT(tool_to_image, list( + TOOL_CROWBAR = image(/obj/item/crowbar), + TOOL_MULTITOOL = image(/obj/item/multitool), + TOOL_SCREWDRIVER = image(/obj/item/screwdriver), + TOOL_WIRECUTTER = image(/obj/item/wirecutters), + TOOL_WRENCH = image(/obj/item/wrench), + TOOL_WELDER = image(/obj/item/weldingtool/mini), + TOOL_ANALYZER = image(/obj/item/analyzer), + TOOL_MINING = image(/obj/item/pickaxe), + TOOL_SHOVEL = image(/obj/item/shovel), + TOOL_RETRACTOR = image(/obj/item/retractor), + TOOL_HEMOSTAT = image(/obj/item/hemostat), + TOOL_CAUTERY = image(/obj/item/cautery), + TOOL_DRILL = image(/obj/item/surgicaldrill), + TOOL_SCALPEL = image(/obj/item/scalpel), + TOOL_SAW = image(/obj/item/circular_saw), + TOOL_BONESET = image(/obj/item/bonesetter), + TOOL_KNIFE = image(/obj/item/knife/kitchen), + TOOL_BLOODFILTER = image(/obj/item/blood_filter), + TOOL_ROLLINGPIN = image(/obj/item/kitchen/rollingpin), + TOOL_RUSTSCRAPER = image(/obj/item/wirebrush), +)) diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index 4707ebf0a96..4eae1a55301 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -33,12 +33,6 @@ GLOBAL_LIST_EMPTY_TYPED(singularities, /datum/component/singularity) GLOBAL_LIST_EMPTY(item_to_design_list) -/// list of all surgeries by name, associated with their path. -GLOBAL_LIST_INIT(surgeries_list, init_surgeries()) - -/// list of all surgery steps, associated by their path. -GLOBAL_LIST_INIT(surgery_steps, init_subtypes_w_path_keys(/datum/surgery_step, list())) - /// Global list of all non-cooking related crafting recipes. GLOBAL_LIST_EMPTY(crafting_recipes) /// This is a global list of typepaths, these typepaths are atoms or reagents that are associated with crafting recipes. @@ -103,4 +97,3 @@ GLOBAL_LIST_INIT(prototype_organs, typecacheof(list( /obj/item/organ/tongue/dullahan, /obj/item/organ/eyes/dullahan, ), only_root_path = TRUE)) - diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 3782c2a2d35..2bf0398d272 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -345,8 +345,9 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_HUSK" = TRAIT_HUSK, "TRAIT_HYPOTHERMIC" = TRAIT_HYPOTHERMIC, "TRAIT_ID_APPRAISER" = TRAIT_ID_APPRAISER, - "TRAIT_IGNORE_ELEVATION" = TRAIT_IGNORE_ELEVATION, "TRAIT_IGNORESLOWDOWN" = TRAIT_IGNORESLOWDOWN, + "TRAIT_IGNORE_ELEVATION" = TRAIT_IGNORE_ELEVATION, + "TRAIT_IGNORE_SURGERY_MODIFIERS" = TRAIT_IGNORE_SURGERY_MODIFIERS, "TRAIT_IGNORING_GRAVITY" = TRAIT_IGNORING_GRAVITY, "TRAIT_ILLITERATE" = TRAIT_ILLITERATE, "TRAIT_IMMOBILIZED" = TRAIT_IMMOBILIZED, @@ -497,6 +498,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_QUICK_CARRY" = TRAIT_QUICK_CARRY, "TRAIT_RADIMMUNE" = TRAIT_RADIMMUNE, "TRAIT_RDS_SUPPRESSED" = TRAIT_RDS_SUPPRESSED, + "TRAIT_READY_TO_OPERATE" = TRAIT_READY_TO_OPERATE, "TRAIT_REAGENT_SCANNER" = TRAIT_REAGENT_SCANNER, "TRAIT_RECENTLY_BLOCKED_MAGIC" = TRAIT_RECENTLY_BLOCKED_MAGIC, "TRAIT_RECENTLY_TREATED" = TRAIT_RECENTLY_TREATED, @@ -572,7 +574,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_SUCCUMB_OVERRIDE" = TRAIT_SUCCUMB_OVERRIDE, "TRAIT_SUICIDED" = TRAIT_SUICIDED, "TRAIT_SUPERMATTER_SOOTHER" = TRAIT_SUPERMATTER_SOOTHER, - "TRAIT_SURGEON" = TRAIT_SURGEON, "TRAIT_SURGICALLY_ANALYZED" = TRAIT_SURGICALLY_ANALYZED, "TRAIT_SWIMMER" = TRAIT_SWIMMER, "TRAIT_TACKLING_FRAIL_ATTACKER" = TRAIT_TACKLING_FRAIL_ATTACKER, @@ -776,6 +777,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CLIENT_STARTING_ORGAN" = TRAIT_CLIENT_STARTING_ORGAN, "TRAIT_LIVING_HEART" = TRAIT_LIVING_HEART, "TRAIT_USED_ORGAN" = TRAIT_USED_ORGAN, + "TRAIT_ORGAN_OPERATED_ON" = TRAIT_ORGAN_OPERATED_ON, ), /obj/item/organ/brain = list( "TRAIT_BRAIN_DAMAGE_NODEATH" = TRAIT_BRAIN_DAMAGE_NODEATH, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 737658cb8db..5fe6c87c6fa 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -301,7 +301,6 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_STUNIMMUNE" = TRAIT_STUNIMMUNE, "TRAIT_STURDY_FRAME" = TRAIT_STURDY_FRAME, "TRAIT_SUPERMATTER_SOOTHER" = TRAIT_SUPERMATTER_SOOTHER, - "TRAIT_SURGEON" = TRAIT_SURGEON, "TRAIT_SWIMMER" = TRAIT_SWIMMER, "TRAIT_SURGICALLY_ANALYZED" = TRAIT_SURGICALLY_ANALYZED, "TRAIT_TACKLING_FRAIL_ATTACKER" = TRAIT_TACKLING_FRAIL_ATTACKER, diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index e71876ba1e5..d960724f75b 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -429,7 +429,7 @@ GLOBAL_LIST_EMPTY(radial_menus) /// Can be provided to choices in radial menus if you want to provide more information /datum/radial_menu_choice /// Required -- what to display for this button - var/image + var/image/image /// If provided, this will be the name the radial slice hud button. This has priority over everything else. var/name diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index adc4f8c9c6b..dda9b43345a 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -181,13 +181,18 @@ return attacking_item.attack_atom(src, user, modifiers, attack_modifiers) /mob/living/item_interaction(mob/living/user, obj/item/tool, list/modifiers) - for(var/datum/surgery/operation as anything in surgeries) - if(IS_IN_INVALID_SURGICAL_POSITION(src, operation)) - continue - if(!(operation.surgery_flags & SURGERY_SELF_OPERABLE) && (user == src) && !HAS_TRAIT(user, TRAIT_SELF_SURGERY)) - continue - if(operation.next_step(user, modifiers)) - return ITEM_INTERACT_SUCCESS + if(user.combat_mode) + return NONE + + if(HAS_TRAIT(src, TRAIT_READY_TO_OPERATE)) + var/surgery_ret = user.perform_surgery(src, tool, LAZYACCESS(modifiers, RIGHT_CLICK)) + if(surgery_ret) + return surgery_ret + + if(src == user) + var/manual_cauterization = try_manual_cauterize(tool) + if(manual_cauterization & ITEM_INTERACT_ANY_BLOCKER) + return manual_cauterization return NONE @@ -337,7 +342,7 @@ final_force *= attacking_item.get_demolition_modifier(src) var/wounding = attacking_item.wound_bonus - if((attacking_item.item_flags & SURGICAL_TOOL) && !user.combat_mode && body_position == LYING_DOWN && (LAZYLEN(surgeries) > 0)) + if((attacking_item.item_flags & SURGICAL_TOOL) && !user.combat_mode && HAS_TRAIT(user, TRAIT_READY_TO_OPERATE)) wounding = CANT_WOUND if(user != src) diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm index 127f542f561..be645066bb6 100644 --- a/code/datums/components/fishing_spot.dm +++ b/code/datums/components/fishing_spot.dm @@ -1,5 +1,6 @@ // A thing you can fish in /datum/component/fishing_spot + dupe_mode = COMPONENT_DUPE_UNIQUE /// Defines the probabilities and fish availibilty var/datum/fish_source/fish_source @@ -26,7 +27,6 @@ REMOVE_TRAIT(parent, TRAIT_FISHING_SPOT, REF(src)) fish_source.on_fishing_spot_del(src) fish_source = null - REMOVE_TRAIT(parent, TRAIT_FISHING_SPOT, REF(src)) return ..() /datum/component/fishing_spot/proc/handle_cast(datum/source, obj/item/fishing_rod/rod, mob/user) diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm index 9d8a9c0709a..b58ef88a3dc 100644 --- a/code/datums/components/food/edible.dm +++ b/code/datums/components/food/edible.dm @@ -568,8 +568,10 @@ Behavior that's still missing from this component that original food items had t qdel(food) return FALSE - if(SEND_SIGNAL(eater, COMSIG_CARBON_ATTEMPT_EAT, food) & COMSIG_CARBON_BLOCK_EAT) - return + if(SEND_SIGNAL(eater, COMSIG_CARBON_ATTEMPT_EAT, food) & BLOCK_EAT_ATTEMPT) + return FALSE + if(SEND_SIGNAL(food, COMSIG_FOOD_ATTEMPT_EAT, eater, feeder) & BLOCK_EAT_ATTEMPT) + return FALSE return TRUE ///Applies food buffs according to the crafting complexity diff --git a/code/datums/components/surgery_initiator.dm b/code/datums/components/surgery_initiator.dm deleted file mode 100644 index 56c49e4c0ec..00000000000 --- a/code/datums/components/surgery_initiator.dm +++ /dev/null @@ -1,344 +0,0 @@ -/// Allows an item to be used to initiate surgeries. -/datum/component/surgery_initiator - /// The currently selected target that the user is proposing a surgery on - var/datum/weakref/surgery_target_ref - - /// The last user, as a weakref - var/datum/weakref/last_user_ref - -/datum/component/surgery_initiator/Initialize() - . = ..() - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - - var/obj/item/surgery_tool = parent - surgery_tool.item_flags |= ITEM_HAS_CONTEXTUAL_SCREENTIPS - ///Make sure we can use the surgery UI while laying down - surgery_tool.interaction_flags_atom |= INTERACT_ATOM_IGNORE_MOBILITY - -/datum/component/surgery_initiator/Destroy(force) - last_user_ref = null - surgery_target_ref = null - - return ..() - -/datum/component/surgery_initiator/RegisterWithParent() - RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(initiate_surgery_moment)) - RegisterSignal(parent, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, PROC_REF(add_item_context)) - -/datum/component/surgery_initiator/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_ITEM_ATTACK, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET) - unregister_signals() - -/datum/component/surgery_initiator/proc/unregister_signals() - var/mob/living/last_user = last_user_ref?.resolve() - if (!isnull(last_user_ref)) - UnregisterSignal(last_user, COMSIG_MOB_SELECTED_ZONE_SET) - - var/mob/living/surgery_target = surgery_target_ref?.resolve() - if (!isnull(surgery_target_ref)) - UnregisterSignal(surgery_target, COMSIG_MOB_SURGERY_STARTED) - -/// Does the surgery initiation. -/datum/component/surgery_initiator/proc/initiate_surgery_moment(datum/source, atom/target, mob/user) - SIGNAL_HANDLER - if(!isliving(target)) - return - INVOKE_ASYNC(src, PROC_REF(do_initiate_surgery_moment), target, user) - return COMPONENT_CANCEL_ATTACK_CHAIN - -/datum/component/surgery_initiator/proc/do_initiate_surgery_moment(mob/living/target, mob/user) - var/datum/surgery/current_surgery - - for(var/i_one in target.surgeries) - var/datum/surgery/surgeryloop = i_one - if(surgeryloop.location == user.zone_selected) - current_surgery = surgeryloop - break - - if (!isnull(current_surgery) && !current_surgery.step_in_progress) - attempt_cancel_surgery(current_surgery, target, user) - return - - var/list/available_surgeries = get_available_surgeries(user, target) - - if(!length(available_surgeries)) - if (target.body_position == LYING_DOWN || !(target.mobility_flags & MOBILITY_LIEDOWN)) - target.balloon_alert(user, "no surgeries available!") - else - target.balloon_alert(user, "make them lie down!") - - return - - unregister_signals() - - last_user_ref = WEAKREF(user) - surgery_target_ref = WEAKREF(target) - - RegisterSignal(user, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_set_selected_zone)) - RegisterSignal(target, COMSIG_MOB_SURGERY_STARTED, PROC_REF(on_mob_surgery_started)) - - ui_interact(user) - -/datum/component/surgery_initiator/proc/get_available_surgeries(mob/user, mob/living/target) - var/list/available_surgeries = list() - - var/obj/item/bodypart/affecting = target.get_bodypart(check_zone(user.zone_selected)) - - for(var/datum/surgery/surgery as anything in GLOB.surgeries_list) - if(!surgery.possible_locs.Find(user.zone_selected)) - continue - if(!is_type_in_list(target, surgery.target_mobtypes)) - continue - if(user == target && !HAS_TRAIT(user, TRAIT_SELF_SURGERY) && !(surgery.surgery_flags & SURGERY_SELF_OPERABLE)) - continue - - if(isnull(affecting)) - if(surgery.surgery_flags & SURGERY_REQUIRE_LIMB) - continue - else - if(surgery.requires_bodypart_type && !(affecting.bodytype & surgery.requires_bodypart_type)) - continue - if(surgery.targetable_wound && !affecting.get_wound_type(surgery.targetable_wound)) - continue - if((surgery.surgery_flags & SURGERY_REQUIRES_REAL_LIMB) && (affecting.bodypart_flags & BODYPART_PSEUDOPART)) - continue - - if(IS_IN_INVALID_SURGICAL_POSITION(target, surgery)) - continue - if(!surgery.can_start(user, target)) - continue - - available_surgeries += surgery - - return available_surgeries - -/// Does the surgery de-initiation. -/datum/component/surgery_initiator/proc/attempt_cancel_surgery(datum/surgery/the_surgery, mob/living/patient, mob/user) - var/selected_zone = user.zone_selected - - if(the_surgery.status == 1) - patient.surgeries -= the_surgery - user.visible_message( - span_notice("[user] removes [parent] from [patient]'s [patient.parse_zone_with_bodypart(selected_zone)]."), - span_notice("You remove [parent] from [patient]'s [patient.parse_zone_with_bodypart(selected_zone)]."), - ) - - patient.balloon_alert(user, "stopped work on [patient.parse_zone_with_bodypart(selected_zone)]") - - qdel(the_surgery) - return - - var/required_tool_type = TOOL_CAUTERY - var/obj/item/close_tool = user.get_inactive_held_item() - var/is_robotic = the_surgery.requires_bodypart_type == BODYTYPE_ROBOTIC - if(is_robotic) - required_tool_type = TOOL_SCREWDRIVER - - if(!close_tool || close_tool.tool_behaviour != required_tool_type) - patient.balloon_alert(user, "need a [is_robotic ? "screwdriver": "cautery"] in your inactive hand to stop the surgery!") - return - - if(the_surgery.operated_bodypart) - the_surgery.operated_bodypart.adjustBleedStacks(-5) - - patient.surgeries -= the_surgery - - user.visible_message( - span_notice("[user] closes [patient]'s [patient.parse_zone_with_bodypart(selected_zone)] with [close_tool] and removes [parent]."), - span_notice("You close [patient]'s [patient.parse_zone_with_bodypart(selected_zone)] with [close_tool] and remove [parent]."), - ) - - patient.balloon_alert(user, "closed up [patient.parse_zone_with_bodypart(selected_zone)]") - - qdel(the_surgery) - -/datum/component/surgery_initiator/proc/on_mob_surgery_started(mob/source, datum/surgery/surgery, surgery_location) - SIGNAL_HANDLER - - var/mob/living/last_user = last_user_ref.resolve() - - if (surgery_location != last_user.zone_selected) - return - - if (!isnull(last_user)) - source.balloon_alert(last_user, "someone else started a surgery!") - - ui_close() - -/datum/component/surgery_initiator/proc/on_set_selected_zone(mob/source, new_zone) - ui_interact(source) - -/datum/component/surgery_initiator/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if (!ui) - ui = new(user, src, "SurgeryInitiator") - ui.open() - -/datum/component/surgery_initiator/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) - . = ..() - if (.) - return . - - var/mob/user = usr - var/mob/living/surgery_target = surgery_target_ref.resolve() - - if (isnull(surgery_target)) - return TRUE - - switch (action) - if ("change_zone") - var/zone = params["new_zone"] - if (!(zone in list( - BODY_ZONE_HEAD, - BODY_ZONE_CHEST, - BODY_ZONE_L_ARM, - BODY_ZONE_R_ARM, - BODY_ZONE_L_LEG, - BODY_ZONE_R_LEG, - BODY_ZONE_PRECISE_EYES, - BODY_ZONE_PRECISE_MOUTH, - BODY_ZONE_PRECISE_GROIN, - ))) - return TRUE - - var/atom/movable/screen/zone_sel/zone_selector = user.hud_used?.zone_select - zone_selector?.set_selected_zone(zone, user, should_log = FALSE) - - return TRUE - if ("start_surgery") - for (var/datum/surgery/surgery as anything in get_available_surgeries(user, surgery_target)) - if (surgery.name == params["surgery_name"]) - try_choose_surgery(user, surgery_target, surgery) - return TRUE - -/datum/component/surgery_initiator/ui_assets(mob/user) - return list( - get_asset_datum(/datum/asset/simple/body_zones), - ) - -/datum/component/surgery_initiator/ui_data(mob/user) - var/mob/living/surgery_target = surgery_target_ref?.resolve() - - var/list/surgeries = list() - if (!isnull(surgery_target)) - for (var/datum/surgery/surgery as anything in get_available_surgeries(user, surgery_target)) - var/list/surgery_info = list( - "name" = surgery.name, - ) - - if (surgery_needs_exposure(surgery, surgery_target)) - surgery_info["blocked"] = TRUE - - surgeries += list(surgery_info) - - return list( - "selected_zone" = user.zone_selected, - "target_name" = surgery_target?.name, - "surgeries" = surgeries, - ) - -/datum/component/surgery_initiator/ui_close(mob/user) - unregister_signals() - surgery_target_ref = null - - return ..() - -/datum/component/surgery_initiator/ui_status(mob/user, datum/ui_state/state) - var/mob/living/surgery_target = surgery_target_ref?.resolve() - if (isnull(surgery_target)) - return UI_CLOSE - - if (!can_start_surgery(user, surgery_target)) - return UI_CLOSE - - return ..() - -/datum/component/surgery_initiator/proc/can_start_surgery(mob/user, mob/living/target) - if (!user.Adjacent(target)) - return FALSE - - // The item was moved somewhere else - var/atom/movable/tool = parent - if (tool.loc != user) - return FALSE - - // While we were choosing, another surgery was started at the same location - for (var/datum/surgery/surgery in target.surgeries) - if (surgery.location == user.zone_selected) - return FALSE - - return TRUE - -/datum/component/surgery_initiator/proc/try_choose_surgery(mob/user, mob/living/target, datum/surgery/surgery) - if (!can_start_surgery(user, target)) - // This could have a more detailed message, but the UI closes when this is true anyway, so - // if it ever comes up, it'll be because of lag. - target.balloon_alert(user, "can't start the surgery!") - return - - var/selected_zone = user.zone_selected - var/obj/item/bodypart/affecting_limb = target.get_bodypart(check_zone(selected_zone)) - - if ((surgery.surgery_flags & SURGERY_REQUIRE_LIMB) && isnull(affecting_limb)) - target.balloon_alert(user, "patient has no [parse_zone(selected_zone)]!") - return - - if (!isnull(affecting_limb)) - if(surgery.requires_bodypart_type && !(affecting_limb.bodytype & surgery.requires_bodypart_type)) - target.balloon_alert(user, "not the right type of limb!") - return - if(surgery.targetable_wound && !affecting_limb.get_wound_type(surgery.targetable_wound)) - target.balloon_alert(user, "no wound to operate on!") - return - - if (IS_IN_INVALID_SURGICAL_POSITION(target, surgery)) - target.balloon_alert(user, "patient is not lying down!") - return - - if (!surgery.can_start(user, target)) - target.balloon_alert(user, "can't start the surgery!") - return - - if (surgery_needs_exposure(surgery, target)) - target.balloon_alert(user, "expose [target.p_their()] [target.parse_zone_with_bodypart(selected_zone)]!") - return - - ui_close() - - var/datum/surgery/procedure = new surgery.type(target, selected_zone, affecting_limb) - - target.balloon_alert(user, "starting \"[LOWER_TEXT(procedure.name)]\"") - - user.visible_message( - span_notice("[user] drapes [parent] over [target]'s [target.parse_zone_with_bodypart(selected_zone)] to prepare for surgery."), - span_notice("You drape [parent] over [target]'s [target.parse_zone_with_bodypart(selected_zone)] to prepare for \an [procedure.name]."), - ) - - log_combat(user, target, "operated on", null, "(OPERATION TYPE: [procedure.name]) (TARGET AREA: [selected_zone])") - -/datum/component/surgery_initiator/proc/surgery_needs_exposure(datum/surgery/surgery, mob/living/target) - var/mob/living/user = last_user_ref?.resolve() - if (isnull(user)) - return FALSE - if(surgery.surgery_flags & SURGERY_IGNORE_CLOTHES) - return FALSE - - return !get_location_accessible(target, user.zone_selected) - -/** - * Adds context sensitivy directly to the surgery initator file for screentips - * Arguments: - * * source - the surgery drapes, cloak, or bedsheet calling surgery initator - * * context - Preparing Surgery, the component has a lot of ballon alerts to deal with most contexts - * * target - the living target mob you are doing surgery on - * * user - refers to user who will see the screentip when the drapes are on a living target - */ -/datum/component/surgery_initiator/proc/add_item_context(obj/item/source, list/context, atom/target, mob/living/user,) - SIGNAL_HANDLER - - if(!isliving(target)) - return NONE - - context[SCREENTIP_CONTEXT_LMB] = "Prepare Surgery" - return CONTEXTUAL_SCREENTIP_SET diff --git a/code/datums/elements/examine_lore.dm b/code/datums/elements/examine_lore.dm index 00066ef51c5..1ed3f998bb2 100644 --- a/code/datums/elements/examine_lore.dm +++ b/code/datums/elements/examine_lore.dm @@ -11,11 +11,11 @@ /datum/element/examine_lore/Attach(datum/target, lore_hint, lore) . = ..() - src.lore_hint = lore_hint + src.lore_hint = lore_hint || span_notice("You can [EXAMINE_HINT("look closer")] to learn a little more about [target].") src.lore = lore - if(!lore_hint || !lore) - stack_trace("[type] initialized without lore or a lore hint! Double-check element addition?") + if(!lore) + stack_trace("[type] initialized without lore! Double-check element addition?") return ELEMENT_INCOMPATIBLE RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) diff --git a/code/datums/elements/noticable_organ.dm b/code/datums/elements/noticable_organ.dm index a6247d18bb5..7feac28d8d7 100644 --- a/code/datums/elements/noticable_organ.dm +++ b/code/datums/elements/noticable_organ.dm @@ -34,9 +34,7 @@ /// Proc that returns true or false if the organ should show its examine check. /datum/element/noticable_organ/proc/should_show_text(mob/living/carbon/examined) - if(body_zone && (body_zone in examined.get_covered_body_zones())) - return FALSE - return TRUE + return examined.is_location_accessible(body_zone) /datum/element/noticable_organ/proc/enable_description(obj/item/organ/target, mob/living/carbon/receiver) SIGNAL_HANDLER diff --git a/code/datums/elements/surgery_aid.dm b/code/datums/elements/surgery_aid.dm new file mode 100644 index 00000000000..ca351cc6a56 --- /dev/null +++ b/code/datums/elements/surgery_aid.dm @@ -0,0 +1,151 @@ +/// Item can be applied to mobs to prepare them for surgery (allowing people to operate on them) +/datum/element/surgery_aid + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// The name of the aid, for examine and messages, in plural form + var/aid_name + +/datum/element/surgery_aid/Attach(datum/target, aid_name = "things") + . = ..() + if(!isitem(target)) + return ELEMENT_INCOMPATIBLE + + src.aid_name = aid_name + RegisterSignal(target, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, PROC_REF(on_context)) + RegisterSignal(target, COMSIG_ITEM_INTERACTING_WITH_ATOM, PROC_REF(on_item_interaction)) + + var/obj/item/realtarget = target + realtarget.item_flags |= ITEM_HAS_CONTEXTUAL_SCREENTIPS + +/datum/element/surgery_aid/Detach(datum/target) + . = ..() + UnregisterSignal(target, list(COMSIG_ITEM_INTERACTING_WITH_ATOM, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET)) + +/datum/element/surgery_aid/proc/on_context(obj/item/source, list/context, atom/target, mob/living/user) + SIGNAL_HANDLER + + if(!isliving(target)) + return NONE + + var/mob/living/target_mob = target + if(!target_mob.has_limbs) + context[SCREENTIP_CONTEXT_LMB] = HAS_TRAIT(source, TRAIT_READY_TO_OPERATE) ? "Remove [aid_name]" : "Prepare for surgery" + return CONTEXTUAL_SCREENTIP_SET + + var/obj/item/bodypart/precise_part = target_mob.get_bodypart(deprecise_zone(user.zone_selected)) || target_mob.get_bodypart(BODY_ZONE_CHEST) + context[SCREENTIP_CONTEXT_LMB] = HAS_TRAIT(precise_part, TRAIT_READY_TO_OPERATE) ? "Remove [aid_name]" : "Prepare [precise_part.plaintext_zone] for surgery" + return CONTEXTUAL_SCREENTIP_SET + +/datum/element/surgery_aid/proc/on_item_interaction(datum/source, mob/living/user, atom/target, ...) + SIGNAL_HANDLER + + if(!isliving(target)) + return NONE + + var/mob/living/target_mob = target + var/obj/item/bodypart/precise_part = target_mob.get_bodypart(deprecise_zone(user.zone_selected)) || target_mob.get_bodypart(BODY_ZONE_CHEST) + surgery_prep(target_mob, user, precise_part?.body_zone || BODY_ZONE_CHEST, aid_name) + return ITEM_INTERACT_SUCCESS + +/datum/element/surgery_aid/proc/surgery_prep(mob/living/target_mob, mob/living/surgeon, body_zone) + var/datum/status_effect/surgery_prepped/prep = target_mob.has_status_effect(__IMPLIED_TYPE__) + if(isnull(prep) || !(body_zone in prep.zones)) + target_mob.apply_status_effect(/datum/status_effect/surgery_prepped, body_zone, aid_name) + target_mob.balloon_alert(surgeon, "[parse_zone(body_zone)] surgery prepared") + return + prep.untrack_surgery(body_zone) + target_mob.balloon_alert(surgeon, "surgery cleared") + +/// Tracks which body zones have been prepped for surgery +/datum/status_effect/surgery_prepped + id = "surgery_prepped" + alert_type = null + status_type = STATUS_EFFECT_REFRESH + processing_speed = STATUS_EFFECT_NORMAL_PROCESS + tick_interval = 2 SECONDS + + /// Lazylist of zones being prepped, if empty we should not exist + var/list/zones + /// Lazylist of the names of all surgical aids used, for examine + var/list/surgical_aids + /// Counts movements while standing up - removes the effect if we move too much + var/movement_counter = 0 + +/datum/status_effect/surgery_prepped/on_creation(mob/living/new_owner, target_zone, aid_name = "things") + . = ..() + track_surgery(target_zone) + LAZYOR(surgical_aids, aid_name) + ADD_TRAIT(owner, TRAIT_READY_TO_OPERATE, TRAIT_STATUS_EFFECT(id)) // needs to happen after tracking starts + +/datum/status_effect/surgery_prepped/refresh(mob/living/new_owner, target_zone, aid_name = "things") + track_surgery(target_zone) + LAZYOR(surgical_aids, aid_name) + +/datum/status_effect/surgery_prepped/on_apply() + RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) + RegisterSignal(owner, COMSIG_CARBON_POST_ATTACH_LIMB, PROC_REF(on_attach_limb)) + RegisterSignal(owner, COMSIG_CARBON_POST_REMOVE_LIMB, PROC_REF(on_detach_limb)) + return TRUE + +/datum/status_effect/surgery_prepped/on_remove() + for(var/zone in zones) + untrack_surgery(zone) + REMOVE_TRAIT(owner, TRAIT_READY_TO_OPERATE, TRAIT_STATUS_EFFECT(id)) + UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_POST_ATTACH_LIMB, COMSIG_CARBON_POST_REMOVE_LIMB)) + +/datum/status_effect/surgery_prepped/get_examine_text() + var/list/zones_readable = list() + // give the body zones a consistent order, the same order as GLOB.all_body_zones + for(var/zone in GLOB.all_body_zones & zones) + zones_readable += parse_zone(zone) + + var/list/aid_readable = list() + for(var/aid in surgical_aids) + aid_readable += copytext_char(aid, -1) == "s" ? aid : "\a [aid]" + + // "They have surgial drapes and a bedsheet adorning their chest, arms, and legs." + return "[owner.p_They()] [owner.p_have()] [english_list(aid_readable)] adorning [owner.p_their()] [english_list(zones_readable)]." + +/datum/status_effect/surgery_prepped/proc/on_move(datum/source, ...) + SIGNAL_HANDLER + + if(owner.body_position == STANDING_UP) + movement_counter += 1 + if(movement_counter < 4) + return + // "The surgical drapes and bedsheets adorning John fall off!" + owner.visible_message(span_warning("The [english_list(surgical_aids)] adorning [owner] fall off!")) + qdel(src) + +/datum/status_effect/surgery_prepped/proc/on_attach_limb(datum/source, obj/item/bodypart/limb) + SIGNAL_HANDLER + + if(limb.body_zone in zones) + ADD_TRAIT(limb, TRAIT_READY_TO_OPERATE, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/surgery_prepped/proc/on_detach_limb(datum/source, obj/item/bodypart/limb) + SIGNAL_HANDLER + + REMOVE_TRAIT(limb, TRAIT_READY_TO_OPERATE, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/surgery_prepped/tick(seconds_between_ticks) + if(owner.body_position == LYING_DOWN && movement_counter > 0) + movement_counter -= 1 + +/datum/status_effect/surgery_prepped/proc/track_surgery(body_zone) + LAZYADD(zones, body_zone) + if(iscarbon(owner)) + var/obj/item/bodypart/precise_part = owner.get_bodypart(body_zone) + if(precise_part) + ADD_TRAIT(precise_part, TRAIT_READY_TO_OPERATE, TRAIT_STATUS_EFFECT(id)) + else if(body_zone != BODY_ZONE_CHEST) + stack_trace("Attempting to track surgery on a non-carbon mob with a non-chest body zone! This should not happen.") + +/datum/status_effect/surgery_prepped/proc/untrack_surgery(body_zone) + LAZYREMOVE(zones, body_zone) + if(iscarbon(owner)) + var/obj/item/bodypart/precise_part = owner.get_bodypart(body_zone) + if(precise_part) + REMOVE_TRAIT(precise_part, TRAIT_READY_TO_OPERATE, TRAIT_STATUS_EFFECT(id)) + if(!LAZYLEN(zones) && !QDELETED(src)) + qdel(src) // no more zones to track, remove the status effect diff --git a/code/datums/status_effects/debuffs/choke.dm b/code/datums/status_effects/debuffs/choke.dm index 1613c4bbad4..7f87fba7abd 100644 --- a/code/datums/status_effects/debuffs/choke.dm +++ b/code/datums/status_effects/debuffs/choke.dm @@ -163,7 +163,7 @@ /datum/status_effect/choke/proc/attempt_eat(mob/source, atom/eating) SIGNAL_HANDLER source.balloon_alert(source, "can't get it down!") - return COMSIG_CARBON_BLOCK_EAT + return BLOCK_EAT_ATTEMPT /datum/status_effect/choke/proc/helped(mob/source, mob/helping) SIGNAL_HANDLER diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm index 8b02e51fcff..5941718110a 100644 --- a/code/datums/wounds/_wounds.dm +++ b/code/datums/wounds/_wounds.dm @@ -91,8 +91,6 @@ /// What status effect we assign on application var/status_effect_type - /// If we're operating on this wound and it gets healed, we'll nix the surgery too - var/datum/surgery/attached_surgery /// if you're a lazy git and just throw them in cryo, the wound will go away after accumulating severity * [base_xadone_progress_to_qdel] power var/cryo_progress @@ -121,7 +119,6 @@ update_actionspeed_modifier() /datum/wound/Destroy() - QDEL_NULL(attached_surgery) if (limb) remove_wound(destroying = QDELING(limb)) @@ -512,18 +509,6 @@ /// Returns TRUE if the item can be used to treat our wounds. Hooks into treat() - only things that return TRUE here may be used there. /datum/wound/proc/item_can_treat(obj/item/potential_treater, mob/user) - // surgeries take priority - for(var/datum/surgery/operation as anything in victim.surgeries) - if(isnull(operation.operated_bodypart) || operation.operated_bodypart != limb) - continue - var/datum/surgery_step/next_step = operation.get_surgery_next_step() - if(isnull(next_step)) - continue - if(potential_treater.tool_behaviour in next_step.implements) - return FALSE - if(is_type_in_list(potential_treater, next_step.implements)) - return FALSE - // check if we have a valid treatable tool if(potential_treater.tool_behaviour in treatable_tools) return TRUE diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 483d487bd93..b3e3051fefd 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -88,7 +88,7 @@ active_trauma = victim.gain_trauma_type(brain_trauma_group, TRAUMA_RESILIENCE_WOUND) next_trauma_cycle = world.time + (rand(100-WOUND_BONE_HEAD_TIME_VARIANCE, 100+WOUND_BONE_HEAD_TIME_VARIANCE) * 0.01 * trauma_cycle_cooldown) - var/is_bone_limb = ((limb.biological_state & BIO_BONE) && !(limb.biological_state & BIO_FLESH)) + var/is_bone_limb = ((limb.biological_state & BIO_BONE) && !(limb.biological_state & (BIO_FLESH|BIO_CHITIN))) if(!gelled || (!taped && !is_bone_limb)) return @@ -435,6 +435,9 @@ simple_treat_text = "Bandaging the wound will slightly reduce its impact until surgically treated with bone gel and surgical tape." homemade_treat_text = "Although this is extremely difficult and slow to function, Bone gel and surgical tape may be applied directly to the wound, though this is nigh-impossible for most people to do so individually unless they've dosed themselves with one or more painkillers (Morphine and Miner's Salve have been known to help)" + /// Tracks if a surgeon has reset the bone (part one of the surgical treatment process) + VAR_FINAL/reset = FALSE + /datum/wound_pregen_data/bone/compound abstract = FALSE @@ -452,7 +455,7 @@ /// if someone is using bone gel on our wound /datum/wound/blunt/bone/proc/gel(obj/item/stack/medical/bone_gel/I, mob/user) // skellies get treated nicer with bone gel since their "reattach dismembered limbs by hand" ability sucks when it's still critically wounded - if((limb.biological_state & BIO_BONE) && !(limb.biological_state & BIO_FLESH)) + if((limb.biological_state & BIO_BONE) && !(limb.biological_state & (BIO_FLESH|BIO_CHITIN))) return skelly_gel(I, user) if(gelled) @@ -532,6 +535,10 @@ processes = TRUE return TRUE +/datum/wound/blunt/bone/item_can_treat(obj/item/potential_treater, mob/user) + // assume that - if working on a ready-to-operate limb - the surgery wants to do the real surgery instead of bone regeneration + return ..() && !HAS_TRAIT(limb, TRAIT_READY_TO_OPERATE) + /datum/wound/blunt/bone/treat(obj/item/tool, mob/user) if(istype(tool, /obj/item/stack/medical/bone_gel)) gel(tool, user) @@ -544,7 +551,7 @@ . += "
" if(severity > WOUND_SEVERITY_MODERATE) - if((limb.biological_state & BIO_BONE) && !(limb.biological_state & BIO_FLESH)) + if((limb.biological_state & BIO_BONE) && !(limb.biological_state & (BIO_FLESH|BIO_CHITIN))) if(!gelled) . += "Recommended Treatment: Apply bone gel directly to injured limb. Creatures of pure bone don't seem to mind bone gel application nearly as much as fleshed individuals. Surgical tape will also be unnecessary.\n" else diff --git a/code/datums/wounds/cranial_fissure.dm b/code/datums/wounds/cranial_fissure.dm index 62e7acf9a76..f61e5386fb5 100644 --- a/code/datums/wounds/cranial_fissure.dm +++ b/code/datums/wounds/cranial_fissure.dm @@ -40,6 +40,9 @@ severity = WOUND_SEVERITY_CRITICAL sound_effect = 'sound/effects/dismember.ogg' + /// If TRUE we have been prepped for surgery (to repair) + VAR_FINAL/prepped = FALSE + #define CRANIAL_FISSURE_FILTER_DISPLACEMENT "cranial_fissure_displacement" /datum/wound/cranial_fissure/wound_injury(datum/wound/old_wound = null, attack_direction = null) diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index c29fea75d50..0a2e98dcf8f 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -41,6 +41,8 @@ var/internal_bleeding_chance /// If we let off blood when hit, the max blood lost is this * the incoming damage var/internal_bleeding_coefficient + /// If TRUE we are ready to be mended in surgery + VAR_FINAL/mend_state = FALSE /datum/wound/pierce/bleed/wound_injury(datum/wound/old_wound = null, attack_direction = null) set_blood_flow(initial_flow) @@ -184,7 +186,7 @@ /datum/wound_pregen_data/flesh_pierce abstract = TRUE - required_limb_biostate = (BIO_FLESH) + required_limb_biostate = BIO_FLESH required_wounding_type = WOUND_PIERCE wound_series = WOUND_SERIES_FLESH_PUNCTURE_BLEED diff --git a/code/game/machinery/computer/operating_computer.dm b/code/game/machinery/computer/operating_computer.dm index f7ebf99e141..e31d028d95f 100644 --- a/code/game/machinery/computer/operating_computer.dm +++ b/code/game/machinery/computer/operating_computer.dm @@ -9,12 +9,21 @@ circuit = /obj/item/circuitboard/computer/operating interaction_flags_machine = parent_type::interaction_flags_machine | INTERACT_MACHINE_REQUIRES_STANDING - var/obj/structure/table/optable/table - var/list/advanced_surgeries = list() - var/datum/techweb/linked_techweb - light_color = LIGHT_COLOR_BLUE + light_color = LIGHT_COLOR_GREEN + /// Linked operating table, if any + var/obj/structure/table/optable/table + /// List if surgery typepaths available on this computer + var/list/advanced_surgeries = list() + /// Techweb linked to this operating computer + var/datum/techweb/linked_techweb + /// Currently selected body zone for surgery + var/target_zone = BODY_ZONE_CHEST + /// Experiment handler component reference var/datum/component/experiment_handler/experiment_handler + /// Lazyassoclist of mob weakrefs to the zone they had selected when opening the UI + /// When closing the UI we try to revert their zone selector to that zone + var/list/datum/weakref/zone_on_open /obj/machinery/computer/operating/Initialize(mapload) . = ..() @@ -26,6 +35,14 @@ if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb) CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src) + if(linked_techweb) + RegisterSignal(linked_techweb, COMSIG_TECHWEB_ADD_DESIGN, PROC_REF(on_techweb_research)) + RegisterSignal(linked_techweb, COMSIG_TECHWEB_REMOVE_DESIGN, PROC_REF(on_techweb_unresearch)) + RegisterSignal(linked_techweb, COMSIG_TECHWEB_EXPERIMENT_COMPLETED, PROC_REF(update_static_data_batched)) + + for(var/datum/design/surgery/design in linked_techweb.get_researched_design_datums()) + advanced_surgeries |= design.surgery + var/list/operating_signals = list( COMSIG_OPERATING_COMPUTER_AUTOPSY_COMPLETE = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_autopsy_experiment), ) @@ -50,25 +67,30 @@ linked_techweb = tool.buffer return TRUE -/obj/machinery/computer/operating/attackby(obj/item/O, mob/user, list/modifiers, list/attack_modifiers) - if(istype(O, /obj/item/disk/surgery)) - user.visible_message(span_notice("[user] begins to load \the [O] in \the [src]..."), \ - span_notice("You begin to load a surgery protocol from \the [O]..."), \ - span_hear("You hear the chatter of a floppy drive.")) - var/obj/item/disk/surgery/D = O - if(do_after(user, 1 SECONDS, target = src)) - advanced_surgeries |= D.surgeries - return TRUE - return ..() +/obj/machinery/computer/operating/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/disk/surgery)) + user.visible_message( + span_notice("[user] begins to load [tool] in [src]..."), + span_notice("You begin to load a surgery protocol from [tool]..."), + span_hear("You hear the chatter of a floppy drive."), + ) + var/obj/item/disk/surgery/disky = tool + if(!do_after(user, 1 SECONDS, src)) + return ITEM_INTERACT_BLOCKING + advanced_surgeries |= disky.surgeries + update_static_data_for_all_viewers() + playsound(src, 'sound/machines/compiler/compiler-stage2.ogg', 50, FALSE, SILENCED_SOUND_EXTRARANGE) + balloon_alert(user, "surgeries loaded") + return ITEM_INTERACT_SUCCESS -/obj/machinery/computer/operating/proc/sync_surgeries() - if(!linked_techweb) - return - for(var/i in linked_techweb.researched_designs) - var/datum/design/surgery/D = SSresearch.techweb_design_by_id(i) - if(!istype(D)) - continue - advanced_surgeries |= D.surgery + if((tool.item_flags & SURGICAL_TOOL) && !user.combat_mode) + ui_interact(user) + return ITEM_INTERACT_SUCCESS + + return NONE + +/obj/machinery/computer/operating/on_set_is_operational(old_value) + update_static_data_for_all_viewers() /obj/machinery/computer/operating/proc/find_table() for(var/direction in GLOB.alldirs) @@ -77,36 +99,74 @@ table.computer = src break -/obj/machinery/computer/operating/ui_state(mob/user) - return GLOB.standing_state +/obj/machinery/computer/operating/ui_status(mob/user, datum/ui_state/state) + . = ..() + if(isliving(user)) + . = min(., ui_check(user)) + +/// Checks for special ui state conditions +/obj/machinery/computer/operating/proc/ui_check(mob/living/user) + // lower states should be checked first + + // normally machines revert to "disabled" when non-operational, + // but here we outright close it so people can't gleam extra info + if(!is_operational) + return UI_CLOSE + // if you're knocked out, ie anesthetic... definitely a no-go + if(user.stat >= UNCONSCIOUS || HAS_TRAIT(user, TRAIT_KNOCKEDOUT)) + return UI_CLOSE + // the patient itself should be blocked from viewing the computer + if(user.body_position == LYING_DOWN) + return (user.loc == table?.loc) ? UI_CLOSE : UI_DISABLED + // we have a tight range check so people can't spy on surgeries from across the room + // likewise it'd be pretty lame if you could see what was going on while incapacitated + if(get_dist(user, src) > 2 || user.incapacitated) + return UI_DISABLED + + return UI_INTERACTIVE /obj/machinery/computer/operating/ui_interact(mob/user, datum/tgui/ui) . = ..() ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "OperatingComputer", name) - ui.open() + if(ui) + return + + if(ishuman(user)) + // if you are the first human to open the ui, it changes to your active zone + if(!LAZYLEN(zone_on_open)) + target_zone = user.zone_selected + // then we record what you were looking at for when you close it + LAZYSET(zone_on_open, WEAKREF(user), user.zone_selected) + + ui = new(user, src, "OperatingComputer", name) + ui.open() + +/obj/machinery/computer/operating/ui_close(mob/user) + . = ..() + // reverts zone to whatever you had going into it, typically chest, so you don't have to mess around with it + var/zone_found = LAZYACCESS(zone_on_open, WEAKREF(user)) + if(!zone_found) + return + + var/atom/movable/screen/zone_sel/selector = user.hud_used?.zone_select + selector?.set_selected_zone(zone_found, user, FALSE) + LAZYREMOVE(zone_on_open, WEAKREF(user)) + if(!LAZYLEN(zone_on_open)) + zone_on_open = initial(zone_on_open) + +/obj/machinery/computer/operating/ui_assets(mob/user) + . = ..() + . += get_asset_datum(/datum/asset/simple/body_zones) /obj/machinery/computer/operating/ui_data(mob/user) var/list/data = list() - var/list/all_surgeries = list() - for(var/datum/surgery/surgeries as anything in advanced_surgeries) - var/list/surgery = list() - surgery["name"] = initial(surgeries.name) - surgery["desc"] = initial(surgeries.desc) - all_surgeries += list(surgery) - data["surgeries"] = all_surgeries - //If there's no patient just hop to it yeah? - if(!table) - data["patient"] = null + data["has_table"] = !!table + data["target_zone"] = target_zone + if(isnull(table?.patient)) return data - data["table"] = table data["patient"] = list() - data["procedures"] = list() - if(!table.patient) - return data var/mob/living/carbon/patient = table.patient switch(patient.stat) @@ -114,7 +174,7 @@ data["patient"]["stat"] = "Conscious" data["patient"]["statstate"] = "good" if(SOFT_CRIT) - data["patient"]["stat"] = "Conscious" + data["patient"]["stat"] = "Critical Condition" data["patient"]["statstate"] = "average" if(UNCONSCIOUS, HARD_CRIT) data["patient"]["stat"] = "Unconscious" @@ -130,30 +190,80 @@ data["patient"]["fireLoss"] = patient.get_fire_loss() data["patient"]["toxLoss"] = patient.get_tox_loss() data["patient"]["oxyLoss"] = patient.get_oxy_loss() - if(patient.surgeries.len) - for(var/datum/surgery/procedure in patient.surgeries) - var/datum/surgery_step/surgery_step = GLOB.surgery_steps[procedure.steps[procedure.status]] - var/chems_needed = surgery_step.get_chem_list() - var/alternative_step - var/alt_chems_needed = "" - var/alt_chems_present = FALSE - if(surgery_step.repeatable) - var/datum/surgery_step/next_step = procedure.get_surgery_next_step() - if(next_step) - alternative_step = capitalize(next_step.name) - alt_chems_needed = next_step.get_chem_list() - alt_chems_present = next_step.chem_check(patient) - else - alternative_step = "Finish operation" - data["procedures"] += list(list( - "name" = capitalize("[patient.parse_zone_with_bodypart(procedure.location)] [procedure.name]"), - "next_step" = capitalize(surgery_step.name), - "chems_needed" = chems_needed, - "alternative_step" = alternative_step, - "alt_chems_needed" = alt_chems_needed, - "chems_present" = surgery_step.chem_check(patient), - "alt_chems_present" = alt_chems_present + data["patient"]["blood_level"] = patient.blood_volume + data["patient"]["standard_blood_level"] = BLOOD_VOLUME_NORMAL + data["patient"]["surgery_state"] = patient.get_surgery_state_as_list(deprecise_zone(target_zone)) + return data + +/obj/machinery/computer/operating/ui_static_data(mob/user) + var/list/data = list() + + data["experiments"] = list() + data["techwebs"] = list() + data["surgeries"] = list() + if(!is_operational) + return data + + if(linked_techweb) + data["techwebs"] += list(list( + "web_id" = linked_techweb.id, + "web_org" = linked_techweb.organization, + "selected" = TRUE, + "ref" = REF(linked_techweb), + "all_servers" = linked_techweb.techweb_servers, + )) + + for(var/datum/experiment/experiment as anything in linked_techweb.available_experiments) + if(istype(experiment, /datum/experiment/autopsy)) + data["experiments"] += list(experiment.to_ui_data()) + + var/list/operations = GLOB.operations.get_instances_from(GLOB.operations.unlocked | advanced_surgeries) + var/any_recommended = FALSE + for(var/datum/surgery_operation/operation as anything in operations) + var/recommend = FALSE + if(table?.patient && operation.show_as_next_step(table.patient, target_zone)) + recommend = TRUE + any_recommended = TRUE + + data["surgeries"] += list(list( + "name" = operation.rnd_name || operation.name, + "desc" = operation.rnd_desc || operation.desc, + "tool_rec" = operation.get_recommended_tool() || "error", + "requirements" = operation.get_requirements(), + "show_as_next" = recommend, + "show_in_list" = TRUE, + "priority" = operation.operation_flags & OPERATION_PRIORITY_NEXT_STEP, + "mechanic" = operation.operation_flags & OPERATION_MECHANIC, + )) + + if(!any_recommended && table?.patient) + var/obj/item/part = table.patient.get_bodypart(deprecise_zone(target_zone)) + var/just_drapes = FALSE + if(table.patient.has_limbs) + if(isnull(part)) + data["surgeries"] += list(list( + "name" = "Prepare for [/datum/surgery_operation/prosthetic_replacement::name]", + "desc" = "Prepare the patient's chest for prosthetic limb attachment.", + "tool_rec" = "operate on chest", + "show_as_next" = TRUE, + "show_in_list" = FALSE, + )) + + else if(!HAS_TRAIT(part, TRAIT_READY_TO_OPERATE)) + just_drapes = TRUE + + else if(!HAS_TRAIT(table.patient, TRAIT_READY_TO_OPERATE)) + just_drapes = TRUE + + if(just_drapes) + data["surgeries"] += list(list( + "name" = "Prepare for surgery", + "desc" = "Begin surgery by applying surgical drapes to the patient.", + "tool_rec" = /obj/item/surgical_drapes::name, + "show_as_next" = TRUE, + "show_in_list" = FALSE, )) + return data /obj/machinery/computer/operating/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) @@ -161,11 +271,39 @@ if(.) return switch(action) - if("sync") - sync_surgeries() if("open_experiments") experiment_handler.ui_interact(usr) + if("change_zone") + if(params["new_zone"] in (GLOB.all_body_zones + GLOB.all_precise_body_zones)) + target_zone = params["new_zone"] + var/atom/movable/screen/zone_sel/selector = ui.user.hud_used?.zone_select + selector?.set_selected_zone(params["new_zone"], ui.user, FALSE) + update_static_data_for_all_viewers() return TRUE +/obj/machinery/computer/operating/proc/on_techweb_research(datum/source, datum/design/surgery/design) + SIGNAL_HANDLER + + if(!istype(design)) + return + + advanced_surgeries |= design.surgery + update_static_data_batched() + +/obj/machinery/computer/operating/proc/on_techweb_unresearch(datum/source, datum/design/surgery/design) + SIGNAL_HANDLER + + if(!istype(design)) + return + + advanced_surgeries -= design.surgery + update_static_data_batched() + +/// Updates static data for all viewers after a miniscule delay (to batch multiple updates together) +/obj/machinery/computer/operating/proc/update_static_data_batched(...) + SIGNAL_HANDLER + + addtimer(CALLBACK(src, TYPE_PROC_REF(/datum, update_static_data_for_all_viewers)), 0.1 SECONDS, TIMER_UNIQUE) + #undef MENU_OPERATION #undef MENU_SURGERIES diff --git a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm index 1d9c8bdc743..1af4ad7f2ee 100644 --- a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm @@ -20,7 +20,7 @@ // so we need to delay species change until we're fully inserted RegisterSignal(inserted_organ, COMSIG_ORGAN_BODYPART_INSERTED, PROC_REF(flyify)) -/datum/status_effect/organ_set_bonus/fly/proc/flyify(obj/item/organ/source, obj/item/bodypart/limb, movement_flags) +/datum/status_effect/organ_set_bonus/fly/proc/flyify(obj/item/organ/source, obj/item/bodypart/limb) SIGNAL_HANDLER var/mob/living/carbon/human/new_fly = owner // just in case? diff --git a/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm b/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm index 96168a953ad..cbd7fda5aba 100644 --- a/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm +++ b/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm @@ -106,7 +106,7 @@ var/obj/item/bodypart/old_bodypart = victim.get_bodypart(target_zone) var/obj/item/bodypart/new_bodypart = new part_type() - new_bodypart.replace_limb(victim, TRUE) + new_bodypart.replace_limb(victim) qdel(old_bodypart) /// Contaminated sample diff --git a/code/game/objects/effects/info.dm b/code/game/objects/effects/info.dm index c9295a090c1..9ffb49a84f7 100644 --- a/code/game/objects/effects/info.dm +++ b/code/game/objects/effects/info.dm @@ -18,7 +18,7 @@ /obj/effect/abstract/info/Click() . = ..() - to_chat(usr, info_text) + to_chat(usr, boxed_message("[span_boldnotice(name)]
[span_info(info_text)]")) /obj/effect/abstract/info/MouseEntered(location, control, params) . = ..() diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm index 4550b7cf6b8..c0834a6d459 100644 --- a/code/game/objects/items/cosmetics.dm +++ b/code/game/objects/items/cosmetics.dm @@ -232,8 +232,9 @@ var/new_style = tgui_input_list(user, "Select a facial hairstyle", "Grooming", SSaccessories.facial_hairstyles_list) if(isnull(new_style)) return - if(!get_location_accessible(human_target, location)) - to_chat(user, span_warning("The headgear is in the way!")) + var/covering = human_target.is_mouth_covered() + if(covering) + to_chat(user, span_warning("[covering] is in the way!")) return if(!(noggin.head_flags & HEAD_FACIAL_HAIR)) to_chat(user, span_warning("There is no facial hair to style!")) @@ -250,8 +251,9 @@ else return else - if(!get_location_accessible(human_target, location)) - to_chat(user, span_warning("The mask is in the way!")) + var/covering = human_target.is_mouth_covered() + if(covering) + to_chat(user, span_warning("[covering] is in the way!")) return if(!(noggin.head_flags & HEAD_FACIAL_HAIR)) to_chat(user, span_warning("There is no facial hair to shave!")) @@ -288,7 +290,7 @@ var/new_style = tgui_input_list(user, "Select a hairstyle", "Grooming", SSaccessories.hairstyles_list) if(isnull(new_style)) return - if(!get_location_accessible(human_target, location)) + if(!human_target.is_location_accessible(location)) to_chat(user, span_warning("The headgear is in the way!")) return if(!(noggin.head_flags & HEAD_HAIR)) @@ -304,7 +306,7 @@ human_target.set_hairstyle(new_style, update = TRUE) return else - if(!get_location_accessible(human_target, location)) + if(!human_target.is_location_accessible(location)) to_chat(user, span_warning("The headgear is in the way!")) return if(!(noggin.head_flags & HEAD_HAIR)) diff --git a/code/game/objects/items/debug_items.dm b/code/game/objects/items/debug_items.dm index 354e628895d..a382733560d 100644 --- a/code/game/objects/items/debug_items.dm +++ b/code/game/objects/items/debug_items.dm @@ -59,72 +59,10 @@ /obj/item/debug/omnitool/attack_self(mob/user) if(!user) return - var/list/tool_list = list( - "Crowbar" = image(icon = 'icons/obj/tools.dmi', icon_state = "crowbar"), - "Multitool" = image(icon = 'icons/obj/devices/tool.dmi', icon_state = "multitool"), - "Screwdriver" = image(icon = 'icons/map_icons/items/_item.dmi', icon_state = "/obj/item/screwdriver"), - "Wirecutters" = image(icon = 'icons/obj/tools.dmi', icon_state = "cutters_map"), - "Wrench" = image(icon = 'icons/obj/tools.dmi', icon_state = "wrench"), - "Welding Tool" = image(icon = 'icons/obj/tools.dmi', icon_state = "miniwelder"), - "Analyzer" = image(icon = 'icons/obj/devices/scanner.dmi', icon_state = "analyzer"), - "Pickaxe" = image(icon = 'icons/obj/mining.dmi', icon_state = "minipick"), - "Shovel" = image(icon = 'icons/obj/mining.dmi', icon_state = "shovel"), - "Retractor" = image(icon = 'icons/obj/medical/surgery_tools.dmi', icon_state = "retractor"), - "Hemostat" = image(icon = 'icons/obj/medical/surgery_tools.dmi', icon_state = "hemostat"), - "Cautery" = image(icon = 'icons/obj/medical/surgery_tools.dmi', icon_state = "cautery"), - "Drill" = image(icon = 'icons/obj/medical/surgery_tools.dmi', icon_state = "drill"), - "Scalpel" = image(icon = 'icons/obj/medical/surgery_tools.dmi', icon_state = "scalpel"), - "Saw" = image(icon = 'icons/obj/medical/surgery_tools.dmi', icon_state = "saw"), - "Bonesetter" = image(icon = 'icons/obj/medical/surgery_tools.dmi', icon_state = "bonesetter"), - "Knife" = image(icon = 'icons/obj/service/kitchen.dmi', icon_state = "knife"), - "Blood Filter" = image(icon = 'icons/obj/medical/surgery_tools.dmi', icon_state = "bloodfilter"), - "Rolling Pin" = image(icon = 'icons/obj/service/kitchen.dmi', icon_state = "rolling_pin"), - "Wire Brush" = image(icon = 'icons/obj/tools.dmi', icon_state = "wirebrush"), - ) - var/tool_result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE) + var/tool_result = show_radial_menu(user, src, GLOB.tool_to_image, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE) if(!check_menu(user)) return - switch(tool_result) - if("Crowbar") - tool_behaviour = TOOL_CROWBAR - if("Multitool") - tool_behaviour = TOOL_MULTITOOL - if("Screwdriver") - tool_behaviour = TOOL_SCREWDRIVER - if("Wirecutters") - tool_behaviour = TOOL_WIRECUTTER - if("Wrench") - tool_behaviour = TOOL_WRENCH - if("Welding Tool") - tool_behaviour = TOOL_WELDER - if("Analyzer") - tool_behaviour = TOOL_ANALYZER - if("Pickaxe") - tool_behaviour = TOOL_MINING - if("Shovel") - tool_behaviour = TOOL_SHOVEL - if("Retractor") - tool_behaviour = TOOL_RETRACTOR - if("Hemostat") - tool_behaviour = TOOL_HEMOSTAT - if("Cautery") - tool_behaviour = TOOL_CAUTERY - if("Drill") - tool_behaviour = TOOL_DRILL - if("Scalpel") - tool_behaviour = TOOL_SCALPEL - if("Saw") - tool_behaviour = TOOL_SAW - if("Bonesetter") - tool_behaviour = TOOL_BONESET - if("Knife") - tool_behaviour = TOOL_KNIFE - if("Blood Filter") - tool_behaviour = TOOL_BLOODFILTER - if("Rolling Pin") - tool_behaviour = TOOL_ROLLINGPIN - if("Wire Brush") - tool_behaviour = TOOL_RUSTSCRAPER + tool_behaviour = tool_result /obj/item/debug/omnitool/item_spawner name = "spawntool" diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index 1e2332a7aad..1e5ef4bd23e 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -132,7 +132,7 @@ return FALSE var/obj/item/bodypart/head/the_head = target.get_bodypart(BODY_ZONE_HEAD) - if(!(the_head.biological_state & BIO_FLESH)) + if(!(the_head.biological_state & (BIO_FLESH|BIO_CHITIN))) to_chat(user, span_warning("You can't noogie [target], [target.p_they()] [target.p_have()] no skin on [target.p_their()] head!")) return diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index 189a9423268..8c450549066 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -421,7 +421,6 @@ . = ..() RegisterSignal(src, COMSIG_TRANSFORMING_PRE_TRANSFORM, PROC_REF(check_power)) AddElement(/datum/element/examine_lore, \ - lore_hint = span_notice("You can [EXAMINE_HINT("look closer")] to learn a little more about [src]."), \ lore = "This early iteration of the now infamous energy sword was, supposedly, a Waffle Corp prototype first trialed in a variety of armed conflicts \ around the interstellar frontier.
\
\ diff --git a/code/game/objects/items/robot/items/storage.dm b/code/game/objects/items/robot/items/storage.dm index d2976295d0b..8236b58108c 100644 --- a/code/game/objects/items/robot/items/storage.dm +++ b/code/game/objects/items/robot/items/storage.dm @@ -209,6 +209,23 @@ item_flags = SURGICAL_TOOL storable = list(/obj/item/organ, /obj/item/bodypart) + /// Underlay of whatever we have stored + var/image/stored_underlay + +/obj/item/borg/apparatus/organ_storage/update_overlays() + . = ..() + if(stored_underlay) + underlays -= stored_underlay + if(!stored) + return + stored_underlay = image(stored) + stored_underlay.layer = FLOAT_LAYER + stored_underlay.plane = FLOAT_PLANE + stored_underlay.pixel_w = 0 + stored_underlay.pixel_x = 0 + stored_underlay.pixel_y = 0 + stored_underlay.pixel_z = 0 + underlays += stored_underlay /obj/item/borg/apparatus/organ_storage/examine() . = ..() @@ -220,22 +237,6 @@ . += "Nothing." . += span_notice(" Alt-click will drop the currently stored organ. ") -/obj/item/borg/apparatus/organ_storage/update_overlays() - . = ..() - icon_state = null // hides the original icon (otherwise it's drawn underneath) - var/mutable_appearance/bag - if(stored) - var/mutable_appearance/stored_organ = new /mutable_appearance(stored) - stored_organ.layer = FLOAT_LAYER - stored_organ.plane = FLOAT_PLANE - stored_organ.pixel_w = 0 - stored_organ.pixel_z = 0 - . += stored_organ - bag = mutable_appearance(icon, icon_state = "evidence") // full bag - else - bag = mutable_appearance(icon, icon_state = "evidenceobj") // empty bag - . += bag - /obj/item/borg/apparatus/organ_storage/click_alt(mob/living/silicon/robot/user) if(!stored) to_chat(user, span_notice("[src] is empty.")) @@ -243,8 +244,7 @@ var/obj/item/organ = stored user.visible_message(span_notice("[user] dumps [organ] from [src]."), span_notice("You dump [organ] from [src].")) - cut_overlays() - organ.forceMove(get_turf(src)) + organ.forceMove(drop_location()) return CLICK_ACTION_SUCCESS ///Apparatus to allow Engineering/Sabo borgs to manipulate any material sheets. diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 80ae7245c88..fb4d5b34e18 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -16,6 +16,7 @@ cost = 250 source = /datum/robot_energy_storage/medical merge_type = /obj/item/stack/medical + apply_verb = "treating" /// How long it takes to apply it to yourself var/self_delay = 5 SECONDS /// How long it takes to apply it to someone else @@ -32,8 +33,6 @@ var/sanitization /// How much we add to flesh_healing for burn wounds on application var/flesh_regeneration - /// Verb used when applying this object to someone - var/apply_verb = "treating" /// Whether this item can be used on dead bodies var/works_on_dead = FALSE /// The sound this makes when starting healing with this item @@ -382,6 +381,8 @@ custom_price = PAYCHECK_CREW * 2 absorption_rate = 0.125 absorption_capacity = 5 + sanitization = 3 + flesh_regeneration = 5 splint_factor = 0.7 burn_cleanliness_bonus = 0.35 merge_type = /obj/item/stack/medical/gauze @@ -490,6 +491,13 @@ if(limb.cached_bleed_rate) add_mob_blood(patient) + + // Dressing burns provides a "one-time" bonus to sanitization and healing + // However, any notable infection will reduce the effectiveness of this bonus + for(var/datum/wound/burn/flesh/wound in limb.wounds) + wound.sanitization += sanitization * (wound.infection > 0.1 ? 0.2 : 1) + wound.flesh_healing += flesh_regeneration * (wound.infection > 0.1 ? 0 : 1) + limb.apply_gauze(src) /obj/item/stack/medical/gauze/twelve @@ -527,6 +535,8 @@ burn_cleanliness_bonus = 0.7 absorption_rate = 0.075 absorption_capacity = 4 + sanitization = 1 + flesh_regeneration = 3 merge_type = /obj/item/stack/medical/gauze/improvised /* diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 14cfaba8d2e..1c00ddcd1e8 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -58,6 +58,8 @@ // They're here instead of /stack/medical // because sticky tape can be used as a makeshift bandage or splint + /// Verb used when applying this object to someone + var/apply_verb = "applying" /// If set and this used as a splint for a broken bone wound, /// This is used as a multiplier for applicable slowdowns (lower = better) (also for speeding up burn recoveries) var/splint_factor diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm index dfa8594d64f..58196a173da 100644 --- a/code/game/objects/structures/bedsheet_bin.dm +++ b/code/game/objects/structures/bedsheet_bin.dm @@ -37,7 +37,7 @@ LINEN BINS /obj/item/bedsheet/Initialize(mapload) . = ..() - AddComponent(/datum/component/surgery_initiator) + AddElement(/datum/element/surgery_aid, "bedsheet") AddElement(/datum/element/bed_tuckable, mapload, 0, 0, 0) if(bedsheet_type == BEDSHEET_DOUBLE) stack_amount *= 2 diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 0f9403cfdf0..92f1d528c0e 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -934,6 +934,7 @@ var/static/list/loc_connections = list( COMSIG_ATOM_ENTERED = PROC_REF(mark_patient), + COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON = PROC_REF(mark_patient), COMSIG_ATOM_EXITED = PROC_REF(unmark_patient), ) AddElement(/datum/element/connect_loc, loc_connections) @@ -1042,15 +1043,15 @@ buckled.remove_offsets(type) /// Any mob that enters our tile will be marked as a potential patient. They will be turned into a patient if they lie down. -/obj/structure/table/optable/proc/mark_patient(datum/source, mob/living/carbon/potential_patient) +/obj/structure/table/optable/proc/mark_patient(datum/source, mob/living/potential_patient) SIGNAL_HANDLER if(!istype(potential_patient)) return - RegisterSignal(potential_patient, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(recheck_patient)) + RegisterSignal(potential_patient, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(recheck_patient), TRUE) recheck_patient(potential_patient) // In case the mob is already lying down before they entered. /// Unmark the potential patient. -/obj/structure/table/optable/proc/unmark_patient(datum/source, mob/living/carbon/potential_patient) +/obj/structure/table/optable/proc/unmark_patient(datum/source, mob/living/potential_patient) SIGNAL_HANDLER if(!istype(potential_patient)) return @@ -1067,34 +1068,60 @@ if(patient && patient != potential_patient) return - if(potential_patient.body_position == LYING_DOWN && potential_patient.loc == loc) + if(IS_LYING_OR_CANNOT_LIE(potential_patient) && potential_patient.loc == loc) set_patient(potential_patient) return // Find another lying mob as a replacement. - for (var/mob/living/carbon/replacement_patient in loc.contents) - if(replacement_patient.body_position == LYING_DOWN) - set_patient(replacement_patient) - return + var/found_replacement + for (var/mob/living/replacement_patient in loc) + if(!IS_LYING_OR_CANNOT_LIE(replacement_patient)) + continue + if(iscarbon(found_replacement)) + break + else if(isliving(found_replacement) && !iscarbon(replacement_patient)) + continue // Prefer carbons over non-carbons. + found_replacement = replacement_patient - set_patient(null) + set_patient(found_replacement) /obj/structure/table/optable/proc/set_patient(mob/living/carbon/new_patient) if (patient) - UnregisterSignal(patient, list(COMSIG_MOB_SURGERY_STARTED, COMSIG_MOB_SURGERY_FINISHED)) + UnregisterSignal(patient, list( + SIGNAL_ADDTRAIT(TRAIT_READY_TO_OPERATE), + SIGNAL_REMOVETRAIT(TRAIT_READY_TO_OPERATE), + COMSIG_LIVING_BEING_OPERATED_ON, + COMSIG_LIVING_SURGERY_FINISHED, + COMSIG_LIVING_UPDATING_SURGERY_STATE, + )) if (patient.external && patient.external == air_tank) patient.close_externals() patient = new_patient update_appearance() + computer?.update_static_data_for_all_viewers() if (!patient) return - RegisterSignal(patient, COMSIG_MOB_SURGERY_STARTED, PROC_REF(on_surgery_change)) - RegisterSignal(patient, COMSIG_MOB_SURGERY_FINISHED, PROC_REF(on_surgery_change)) + RegisterSignals(patient, list( + SIGNAL_ADDTRAIT(TRAIT_READY_TO_OPERATE), + SIGNAL_REMOVETRAIT(TRAIT_READY_TO_OPERATE), + COMSIG_LIVING_SURGERY_FINISHED, + COMSIG_LIVING_UPDATING_SURGERY_STATE, + ), PROC_REF(on_surgery_change)) + RegisterSignal(patient, COMSIG_LIVING_BEING_OPERATED_ON, PROC_REF(get_surgeries)) /obj/structure/table/optable/proc/on_surgery_change(datum/source) SIGNAL_HANDLER update_appearance() + computer?.update_static_data_batched() + +/obj/structure/table/optable/proc/get_surgeries(datum/source, mob/living/surgeon, list/operations) + SIGNAL_HANDLER + + if(isnull(computer)) + return + + operations |= computer.advanced_surgeries /obj/structure/table/optable/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if (istype(tool, /obj/item/clothing/mask/breath)) @@ -1275,7 +1302,7 @@ . += mutable_appearance(icon, air_tank.tank_holder_icon_state) if (breath_mask?.loc == src) . += mutable_appearance(icon, "mask_[breath_mask.icon_state]") - if (!length(patient?.surgeries)) + if (!patient || !HAS_TRAIT(patient, TRAIT_READY_TO_OPERATE)) return . += mutable_appearance(icon, "[icon_state]_[computer ? "" : "un"]linked") if (computer) diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm index 25ee36f6a81..78befca291e 100644 --- a/code/modules/antagonists/abductor/abductor.dm +++ b/code/modules/antagonists/abductor/abductor.dm @@ -114,13 +114,47 @@ break /datum/antagonist/abductor/scientist/on_gain() - owner.add_traits(list(TRAIT_ABDUCTOR_SCIENTIST_TRAINING, TRAIT_SURGEON), ABDUCTOR_ANTAGONIST) + owner.add_traits(list(TRAIT_ABDUCTOR_SCIENTIST_TRAINING), ABDUCTOR_ANTAGONIST) return ..() /datum/antagonist/abductor/scientist/on_removal() - owner.remove_traits(list(TRAIT_ABDUCTOR_SCIENTIST_TRAINING, TRAIT_SURGEON), ABDUCTOR_ANTAGONIST) + owner.remove_traits(list(TRAIT_ABDUCTOR_SCIENTIST_TRAINING), ABDUCTOR_ANTAGONIST) return ..() +/datum/antagonist/abductor/scientist/apply_innate_effects(mob/living/mob_override) + var/mob/living/glorp = mob_override || owner.current + RegisterSignal(glorp, COMSIG_LIVING_OPERATING_ON, PROC_REF(add_surgery)) + +/datum/antagonist/abductor/scientist/remove_innate_effects(mob/living/mob_override) + var/mob/living/glorp = mob_override || owner.current + UnregisterSignal(glorp, COMSIG_LIVING_OPERATING_ON) + +/datum/antagonist/abductor/scientist/proc/add_surgery(datum/source, mob/living/patient, list/possible_operations) + SIGNAL_HANDLER + + var/static/list/ayy_operations + if(!length(ayy_operations)) + ayy_operations = list() + ayy_operations += /datum/surgery_operation/basic/tend_wounds/combo/upgraded/master + ayy_operations += /datum/surgery_operation/basic/viral_bonding + ayy_operations += /datum/surgery_operation/limb/add_plastic // unlocks advanced plastic surgery + ayy_operations += /datum/surgery_operation/limb/bionecrosis + ayy_operations += /datum/surgery_operation/limb/clamp_bleeders/abductor + ayy_operations += /datum/surgery_operation/limb/close_skin/abductor + ayy_operations += /datum/surgery_operation/limb/incise_organs/abductor + ayy_operations += /datum/surgery_operation/limb/incise_skin/abductor + ayy_operations += /datum/surgery_operation/limb/organ_manipulation/external/abductor + ayy_operations += /datum/surgery_operation/limb/organ_manipulation/internal/abductor + ayy_operations += /datum/surgery_operation/limb/retract_skin/abductor + ayy_operations += /datum/surgery_operation/limb/unclamp_bleeders/abductor + ayy_operations += /datum/surgery_operation/organ/fix_wings // i guess + ayy_operations += typesof(/datum/surgery_operation/limb/bioware) + ayy_operations += typesof(/datum/surgery_operation/organ/brainwash) + ayy_operations += typesof(/datum/surgery_operation/organ/lobotomy) + ayy_operations += typesof(/datum/surgery_operation/organ/pacify) + + possible_operations |= ayy_operations + /datum/antagonist/abductor/admin_add(datum/mind/new_owner,mob/admin) var/list/current_teams = list() for(var/datum/team/abductor_team/T in GLOB.antagonist_teams) diff --git a/code/modules/antagonists/abductor/equipment/abduction_surgery.dm b/code/modules/antagonists/abductor/equipment/abduction_surgery.dm deleted file mode 100644 index 07be8836f97..00000000000 --- a/code/modules/antagonists/abductor/equipment/abduction_surgery.dm +++ /dev/null @@ -1,64 +0,0 @@ -/datum/surgery/organ_extraction - name = "Experimental organ replacement" - possible_locs = list(BODY_ZONE_CHEST) - surgery_flags = SURGERY_IGNORE_CLOTHES | SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/extract_organ, - /datum/surgery_step/gland_insert, - ) - -/datum/surgery/organ_extraction/can_start(mob/user, mob/living/carbon/target) - if(!ishuman(user)) - return FALSE - if(!..()) - return FALSE - if(isabductor(user)) - return TRUE - var/mob/living/non_abductor = user - if(locate(/obj/item/implant/abductor) in non_abductor.implants) - return TRUE - return FALSE - - -/datum/surgery_step/extract_organ - name = "remove heart" - accept_hand = 1 - time = 3.2 SECONDS - var/obj/item/organ/IC = null - var/list/organ_types = list(/obj/item/organ/heart) - -/datum/surgery_step/extract_organ/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - for(var/atom/A in target.organs) - if(A.type in organ_types) - IC = A - break - user.visible_message(span_notice("[user] starts to remove [target]'s organs."), span_notice("You start to remove [target]'s organs...")) - -/datum/surgery_step/extract_organ/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(IC) - user.visible_message(span_notice("[user] pulls [IC] out of [target]'s [target_zone]!"), span_notice("You pull [IC] out of [target]'s [target_zone].")) - user.put_in_hands(IC) - IC.Remove(target) - return 1 - else - to_chat(user, span_warning("You don't find anything in [target]'s [target_zone]!")) - return 1 - -/datum/surgery_step/gland_insert - name = "insert gland" - implements = list(/obj/item/organ/heart/gland = 100) - time = 3.2 SECONDS - -/datum/surgery_step/gland_insert/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - user.visible_message(span_notice("[user] starts to insert [tool] into [target]."), span_notice("You start to insert [tool] into [target]...")) - -/datum/surgery_step/gland_insert/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - user.visible_message(span_notice("[user] inserts [tool] into [target]."), span_notice("You insert [tool] into [target].")) - user.temporarilyRemoveItemFromInventory(tool, TRUE) - var/obj/item/organ/heart/gland/gland = tool - gland.Insert(target, special = TRUE, movement_flags = FROM_ABDUCTOR_SURGERY) - return TRUE diff --git a/code/modules/antagonists/abductor/equipment/glands/heal.dm b/code/modules/antagonists/abductor/equipment/glands/heal.dm index cbac994bc3d..4d4deeee6ae 100644 --- a/code/modules/antagonists/abductor/equipment/glands/heal.dm +++ b/code/modules/antagonists/abductor/equipment/glands/heal.dm @@ -226,7 +226,7 @@ gibs.streak(dirs) var/obj/item/bodypart/chest/new_chest = new(null) - new_chest.replace_limb(owner, TRUE) + new_chest.replace_limb(owner) qdel(chest) #undef REJECTION_VOMIT_FLAGS diff --git a/code/modules/antagonists/heretic/knowledge/starting_lore.dm b/code/modules/antagonists/heretic/knowledge/starting_lore.dm index 9d89cfd2ca4..830e6edae32 100644 --- a/code/modules/antagonists/heretic/knowledge/starting_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/starting_lore.dm @@ -168,9 +168,7 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) /datum/heretic_knowledge/living_heart/proc/is_valid_heart(obj/item/organ/new_heart) if(QDELETED(new_heart)) return FALSE - if(!new_heart.useable) - return FALSE - if(new_heart.organ_flags & (ORGAN_ROBOTIC|ORGAN_FAILING)) + if(new_heart.organ_flags & (ORGAN_UNUSABLE|ORGAN_ROBOTIC|ORGAN_FAILING)) return FALSE return TRUE diff --git a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm index f48267356eb..0f980c5e1d2 100644 --- a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm +++ b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm @@ -158,15 +158,15 @@ name = "Advanced Surgery Disk" desc = "A disk that contains advanced surgery procedures, must be loaded into an Operating Console." surgeries = list( - /datum/surgery/advanced/lobotomy, - /datum/surgery/advanced/lobotomy/mechanic, - /datum/surgery/advanced/bioware/vein_threading, - /datum/surgery/advanced/bioware/vein_threading/mechanic, - /datum/surgery/advanced/bioware/nerve_splicing, - /datum/surgery/advanced/bioware/nerve_splicing/mechanic, - /datum/surgery_step/heal/combo/upgraded, - /datum/surgery_step/pacify, - /datum/surgery_step/pacify/mechanic, + /datum/surgery_operation/organ/lobotomy, + /datum/surgery_operation/organ/lobotomy/mechanic, + /datum/surgery_operation/limb/bioware/vein_threading, + /datum/surgery_operation/limb/bioware/vein_threading/mechanic, + /datum/surgery_operation/limb/bioware/nerve_splicing, + /datum/surgery_operation/limb/bioware/nerve_splicing/mechanic, + /datum/surgery_operation/basic/tend_wounds/combo/upgraded, + /datum/surgery_operation/organ/pacify, + /datum/surgery_operation/organ/pacify/mechanic, ) //Pad & Pad Terminal diff --git a/code/modules/asset_cache/assets/body_zones.dm b/code/modules/asset_cache/assets/body_zones.dm index cbdf268f6c1..388b57b57e2 100644 --- a/code/modules/asset_cache/assets/body_zones.dm +++ b/code/modules/asset_cache/assets/body_zones.dm @@ -6,6 +6,7 @@ /datum/asset/simple/body_zones/register() assets["body_zones.base_midnight.png"] = icon('icons/hud/screen_midnight.dmi', "zone_sel") + assets["body_zones.base_slimecore.png"] = icon('icons/hud/screen_slimecore.dmi', "zone_sel") add_limb(BODY_ZONE_HEAD) add_limb(BODY_ZONE_CHEST) diff --git a/code/modules/autowiki/pages/surgery.dm b/code/modules/autowiki/pages/surgery.dm new file mode 100644 index 00000000000..029267788f9 --- /dev/null +++ b/code/modules/autowiki/pages/surgery.dm @@ -0,0 +1,125 @@ +/datum/autowiki/surgery + page = "Template:Autowiki/Content/Surgeries" + var/list/already_generated_tools = list() + +/datum/autowiki/surgery/generate() + var/output = "" + + var/list/unlocked_operations_alpha = list() + var/list/locked_operations_alpha = list() + for(var/op_type, op_datum in GLOB.operations.operations_by_typepath) + if(op_type in GLOB.operations.locked) + locked_operations_alpha += op_datum + else + unlocked_operations_alpha += op_datum + + // sorts all unlocked operations alphabetically by name, then followed by all locked operations by name + sortTim(unlocked_operations_alpha, GLOBAL_PROC_REF(cmp_name_asc)) + sortTim(locked_operations_alpha, GLOBAL_PROC_REF(cmp_name_asc)) + + for(var/datum/surgery_operation/operation as anything in unlocked_operations_alpha + locked_operations_alpha) + if(operation.operation_flags & OPERATION_NO_WIKI) + continue + + var/list/operation_data = list() + var/locked = (operation in locked_operations_alpha) + + operation_data["name"] = escape_value(capitalize(replacetext(operation.rnd_name || operation.name, "\"", """))) + operation_data["description"] = escape_value(replacetext(operation.rnd_desc || operation.desc, "\"", """)) + + var/list/raw_reqs = operation.get_requirements() + if(length(raw_reqs[2]) == 1) + raw_reqs[1] += raw_reqs[2] + raw_reqs[2] = list() + + operation_data["hard_requirements"] = format_requirement_list(raw_reqs[1]) + operation_data["soft_requirements"] = format_requirement_list(raw_reqs[2]) + operation_data["optional_requirements"] = format_requirement_list(raw_reqs[3]) + operation_data["blocker_requirements"] = format_requirement_list(raw_reqs[4]) + + operation_data["tools"] = format_tool_list(operation) + + var/type_id = LOWER_TEXT(replacetext("[operation.type]", "[/datum/surgery_operation]", "")) + var/filename = "surgery_[SANITIZE_FILENAME(escape_value(type_id))]" + operation_data["icon"] = filename + + var/image/radial_base = image('icons/hud/screen_alert.dmi', "template") + var/image/radial_overlay = operation.get_default_radial_image() + radial_overlay.plane = radial_base.plane + radial_overlay.layer = radial_base.layer + 1 + radial_base.overlays += radial_overlay + + upload_icon(getFlatIcon(radial_base, no_anim = TRUE), filename) + + operation_data["cstyle"] = "" + if(locked && (operation.operation_flags & OPERATION_MECHANIC)) + operation_data["cstyle"] = "background: linear-gradient(115deg, [COLOR_VOID_PURPLE] 50%, [COLOR_DARK_MODERATE_LIME_GREEN] 50%); color: [COLOR_WHITE];" + else if(locked) + operation_data["cstyle"] = "background-color: [COLOR_VOID_PURPLE]; color: [COLOR_WHITE];" + else if(operation.operation_flags & OPERATION_MECHANIC) + operation_data["cstyle"] = "background-color: [COLOR_DARK_MODERATE_LIME_GREEN]; color: [COLOR_WHITE];" + + output += include_template("Autowiki/SurgeryTemplate", operation_data) + + return include_template("Autowiki/SurgeryTableTemplate", list("content" = output)) + +/datum/autowiki/surgery/proc/format_requirement_list(list/requirements) + var/output + for(var/requirement in requirements) + output += "
  • [escape_value(capitalize(requirement))]
  • " + + return output ? "" : "" + +/datum/autowiki/surgery/proc/format_tool_list(datum/surgery_operation/operation) + var/output = "" + + // tools which should not show up in the tools list + var/list/blacklisted_tool_types = list( + /obj/item/shovel/giant_wrench, // easter egg interaction + ) + + for(var/tool, multiplier in operation.implements) + if(tool in blacklisted_tool_types) + continue + + var/list/tool_info = list() + + tool_info["tool_multiplier"] = multiplier + + var/tool_name = escape_value(get_tool_name(operation, tool)) + tool_info["tool_name"] = tool_name + + var/tool_id = LOWER_TEXT(replacetext("[tool_name]", " ", "_")) + var/tool_icon = "surgery_tool_[SANITIZE_FILENAME(tool_id)]" // already escaped + tool_info["tool_icon"] = tool_icon + + if(!already_generated_tools[tool_icon]) + already_generated_tools[tool_icon] = TRUE + var/image/tool_image = get_tool_icon(tool) + upload_icon(getFlatIcon(tool_image, no_anim = TRUE), tool_icon) + + output += include_template("Autowiki/SurgeryToolTemplate", tool_info) + + return output + +/datum/autowiki/surgery/proc/get_tool_name(datum/surgery_operation/operation, obj/item/tool) + if(istext(tool)) + return capitalize(tool) + if(tool == /obj/item) + return operation.get_any_tool() + return capitalize(format_text(tool::name)) + +/datum/autowiki/surgery/proc/get_tool_icon(obj/item/tool) + if(tool == IMPLEMENT_HAND) + return image(/obj/item/hand_item) + if(istext(tool)) + return GLOB.tool_to_image[tool] || image('icons/effects/random_spawners.dmi', "questionmark") + if(tool == /obj/item) + return image('icons/effects/random_spawners.dmi', "questionmark") + if(ispath(tool, /obj/item/melee/energy)) // snowflake for soul reasons + return image(tool::icon, "[tool::icon_state]_on") + if(ispath(tool, /obj/item/bodypart)) // snowflake for readability + return image('icons/obj/medical/surgery_ui.dmi', "surgery_limbs") + if(ispath(tool, /obj/item/organ)) // snowflake for readability + return image('icons/obj/medical/surgery_ui.dmi', "surgery_chest") + return image(tool) diff --git a/code/modules/client/preferences/species_features/lizard.dm b/code/modules/client/preferences/species_features/lizard.dm index 1fe7df9fc00..10236d9e415 100644 --- a/code/modules/client/preferences/species_features/lizard.dm +++ b/code/modules/client/preferences/species_features/lizard.dm @@ -102,7 +102,7 @@ if(!path) continue var/obj/item/bodypart/new_part = new path() - new_part.replace_limb(target, TRUE) + new_part.replace_limb(target) new_part.update_limb(is_creating = TRUE) qdel(old_part) diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm index 07318432965..f1262789608 100644 --- a/code/modules/clothing/neck/_neck.dm +++ b/code/modules/clothing/neck/_neck.dm @@ -218,7 +218,7 @@ /obj/item/clothing/neck/robe_cape/Initialize(mapload) . = ..() - AddComponent(/datum/component/surgery_initiator) + AddElement(/datum/element/surgery_aid, "cape") /obj/item/clothing/neck/tie/detective name = "loose tie" diff --git a/code/modules/clothing/suits/cloaks.dm b/code/modules/clothing/suits/cloaks.dm index 925b5aad88f..5f793aa272a 100644 --- a/code/modules/clothing/suits/cloaks.dm +++ b/code/modules/clothing/suits/cloaks.dm @@ -13,7 +13,7 @@ /obj/item/clothing/neck/cloak/Initialize(mapload) . = ..() - AddComponent(/datum/component/surgery_initiator) + AddElement(/datum/element/surgery_aid, "cloak") /obj/item/clothing/neck/cloak/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is strangling [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) @@ -86,4 +86,3 @@ desc = "Worn by the wisest of veteran employees, this legendary cloak is only attainable by maintaining a living employment agreement with Nanotrasen for over five thousand hours. This status symbol represents a being is better than you in nearly every quantifiable way, simple as that." icon_state = "playercloak" element_type = /datum/element/skill_reward/veteran - diff --git a/code/modules/experisci/experiment/handlers/experiment_handler.dm b/code/modules/experisci/experiment/handlers/experiment_handler.dm index f6c80a17248..0e944fb67a5 100644 --- a/code/modules/experisci/experiment/handlers/experiment_handler.dm +++ b/code/modules/experisci/experiment/handlers/experiment_handler.dm @@ -337,16 +337,7 @@ for (var/datum/experiment/experiment as anything in linked_web.available_experiments) if(!can_select_experiment(experiment)) continue - var/list/data = list( - name = experiment.name, - description = experiment.description, - tag = experiment.exp_tag, - selected = selected_experiment == experiment, - progress = experiment.check_progress(), - performance_hint = experiment.performance_hint, - ref = REF(experiment) - ) - .["experiments"] += list(data) + .["experiments"] += list(list("selected" = selected_experiment == experiment) + experiment.to_ui_data()) /datum/component/experiment_handler/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() diff --git a/code/modules/experisci/experiment/types/experiment.dm b/code/modules/experisci/experiment/types/experiment.dm index 494655694b2..02bb6c42ea7 100644 --- a/code/modules/experisci/experiment/types/experiment.dm +++ b/code/modules/experisci/experiment/types/experiment.dm @@ -125,3 +125,14 @@ for(var/points_type in points_reward) english_list_keys += "[points_reward[points_type]] [points_type]" return "[english_list(english_list_keys)] points" + +/datum/experiment/proc/to_ui_data() + return list( + "name" = name, + "description" = description, + "tag" = exp_tag, + "progress" = check_progress(), + "completed" = completed, + "performance_hint" = performance_hint, + "ref" = REF(src) + ) diff --git a/code/modules/fishing/sources/subtypes/surgery.dm b/code/modules/fishing/sources/subtypes/surgery.dm index 0ee2b06d856..2192d2ed5c8 100644 --- a/code/modules/fishing/sources/subtypes/surgery.dm +++ b/code/modules/fishing/sources/subtypes/surgery.dm @@ -25,14 +25,12 @@ var/mob/living/carbon/carbon = fishing_spot var/list/possible_organs = list() - for(var/datum/surgery/organ_manipulation/operation in carbon.surgeries) - var/datum/surgery_step/manipulate_organs/manip_step = GLOB.surgery_steps[operation.steps[operation.status]] - if(!istype(manip_step)) + for(var/obj/item/organ/fishable as anything in carbon.organs) + if(fishable.organ_flags & (ORGAN_UNREMOVABLE|ORGAN_EXTERNAL|ORGAN_VITAL)) continue - for(var/obj/item/organ/organ in operation.operated_bodypart) - if(organ.organ_flags & ORGAN_UNREMOVABLE || !manip_step.can_use_organ(organ)) - continue - possible_organs |= organ + if(!LIMB_HAS_SURGERY_STATE(fishable.bodypart_owner, ALL_SURGERY_FISH_STATES(fishable.zone))) + continue + possible_organs += fishable if(!length(possible_organs)) return null @@ -48,7 +46,7 @@ FISH_SOURCE_AUTOWIKI_NAME = "Organs", FISH_SOURCE_AUTOWIKI_DUD = "", FISH_SOURCE_AUTOWIKI_WEIGHT = 100, - FISH_SOURCE_AUTOWIKI_NOTES = "A random organ from an ongoing organ manipulation surgery.", + FISH_SOURCE_AUTOWIKI_NOTES = "A random organ from an ongoing surgical operation.", )) return data diff --git a/code/modules/library/skill_learning/generic_skillchips/self_surgery.dm b/code/modules/library/skill_learning/generic_skillchips/self_surgery.dm index ba17761b8b7..52d0b0edadd 100644 --- a/code/modules/library/skill_learning/generic_skillchips/self_surgery.dm +++ b/code/modules/library/skill_learning/generic_skillchips/self_surgery.dm @@ -12,18 +12,3 @@ /obj/item/skillchip/self_surgery/Initialize(mapload, is_removable) . = ..() ADD_TRAIT(src, TRAIT_CONTRABAND, INNATE_TRAIT) - -/obj/item/skillchip/self_surgery/on_activate(mob/living/carbon/user, silent) - . = ..() - RegisterSignal(user, COMSIG_LIVING_INITIATE_SURGERY_STEP, PROC_REF(apply_surgery_penalty)) - -/obj/item/skillchip/self_surgery/on_deactivate(mob/living/carbon/user, silent) - . = ..() - UnregisterSignal(user, COMSIG_LIVING_INITIATE_SURGERY_STEP) - -/obj/item/skillchip/self_surgery/proc/apply_surgery_penalty(mob/living/carbon/_source, mob/living/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, datum/surgery_step/step, list/modifiers) - SIGNAL_HANDLER - if(user != target) - return - modifiers[FAIL_PROB_INDEX] += 33 - modifiers[SPEED_MOD_INDEX] *= 1.5 diff --git a/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm b/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm index 2fb0633bf53..3131dce530d 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm @@ -78,7 +78,12 @@ GLOBAL_VAR_INIT(fscpassword, generate_password()) /obj/item/disk/surgery/forgottenship name = "Advanced Surgery Disk" desc = "A disk that contains advanced surgery procedures, must be loaded into an Operating Console." - surgeries = list(/datum/surgery/advanced/lobotomy, /datum/surgery/advanced/bioware/vein_threading, /datum/surgery/advanced/bioware/nerve_splicing) + surgeries = list( + /datum/surgery_operation/organ/lobotomy, + /datum/surgery_operation/organ/lobotomy/mechanic, + /datum/surgery_operation/limb/bioware/vein_threading, + /datum/surgery_operation/limb/bioware/nerve_splicing, + ) /obj/structure/fluff/empty_sleeper/syndicate/captain icon_state = "sleeper_s-open" diff --git a/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm index 1c2173949a7..8a002f4a818 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm @@ -11,7 +11,7 @@ /obj/machinery/rnd/server/oldstation/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = ..() - if(held_item && istype(held_item, /obj/item/research_notes)) + if(istype(held_item, /obj/item/research_notes)) context[SCREENTIP_CONTEXT_LMB] = "Generate research points" return CONTEXTUAL_SCREENTIP_SET @@ -23,16 +23,17 @@ . += span_notice("Insert [EXAMINE_HINT("Research Notes")] to generate points.") -/obj/machinery/rnd/server/oldstation/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) - if(istype(attacking_item, /obj/item/research_notes) && stored_research) - var/obj/item/research_notes/research_notes = attacking_item +/obj/machinery/rnd/server/oldstation/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/research_notes) && stored_research) + var/obj/item/research_notes/research_notes = tool stored_research.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = research_notes.value)) playsound(src, 'sound/machines/copier.ogg', 50, TRUE) qdel(research_notes) - return + return ITEM_INTERACT_SUCCESS + return ..() ///Ancient computer that starts with dissection to tell players they have it. /obj/machinery/computer/operating/oldstation name = "ancient operating computer" - advanced_surgeries = list(/datum/surgery/advanced/experimental_dissection) + advanced_surgeries = list(/datum/surgery_operation/basic/dissection) diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm index 2877bac8d21..1d2b157ce6c 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot.dm @@ -99,6 +99,8 @@ var/skin = "generic" /// How much healing do we do at a time? var/heal_amount = 2.5 + /// How much healing multiplier do we have from upgrades? + VAR_FINAL/heal_multiplier = 1.0 /// Start healing when they have this much damage in a category var/heal_threshold = 10 /// What damage type does this bot support. Because the default is brute, if the medkit is brute-oriented there is a slight bonus to healing. set to "all" for it to heal any of the 4 base damage types @@ -163,6 +165,13 @@ if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb) CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src) + if(linked_techweb) + RegisterSignal(linked_techweb, COMSIG_TECHWEB_ADD_DESIGN, PROC_REF(on_techweb_research)) + RegisterSignal(linked_techweb, COMSIG_TECHWEB_REMOVE_DESIGN, PROC_REF(on_techweb_unresearch)) + + for(var/datum/design/medibot_upgrade/design in linked_techweb.get_researched_design_datums()) + heal_multiplier += design.additive_multiplier + /mob/living/basic/bot/medbot/on_craft_completion(list/components, datum/crafting_recipe/current_recipe, atom/crafter) . = ..() var/obj/item/storage/medkit/medkit = locate() in contents @@ -214,7 +223,6 @@ data["custom_controls"]["speaker"] = medical_mode_flags & MEDBOT_SPEAK_MODE data["custom_controls"]["crit_alerts"] = medical_mode_flags & MEDBOT_DECLARE_CRIT data["custom_controls"]["stationary_mode"] = medical_mode_flags & MEDBOT_STATIONARY_MODE - data["custom_controls"]["sync_tech"] = TRUE return data // Actions received from TGUI @@ -237,21 +245,6 @@ medical_mode_flags ^= MEDBOT_DECLARE_CRIT if("stationary_mode") medical_mode_flags ^= MEDBOT_STATIONARY_MODE - if("sync_tech") - if(!linked_techweb) - to_chat(user, span_notice("No research techweb connected.")) - return - var/oldheal_amount = heal_amount - var/tech_boosters - for(var/index in linked_techweb.researched_designs) - var/datum/design/surgery/healing/design = SSresearch.techweb_design_by_id(index) - if(!istype(design)) - continue - tech_boosters++ - if(tech_boosters) - heal_amount = (round(tech_boosters * 0.5, 0.1) * initial(heal_amount)) + initial(heal_amount) //every 2 tend wounds tech gives you an extra 100% healing, adjusting for unique branches (combo is bonus) - if(oldheal_amount < heal_amount) - speak("New knowledge found! Surgical efficacy improved to [round(heal_amount/initial(heal_amount)*100)]%!") update_appearance() @@ -341,7 +334,7 @@ if(!do_after(src, delay = 2 SECONDS, target = patient, interaction_key = TEND_DAMAGE_INTERACTION)) update_bot_mode(new_mode = BOT_IDLE) return - var/modified_heal_amount = heal_amount + var/modified_heal_amount = heal_amount * heal_multiplier var/done_healing = FALSE if(damage_type_healer == BRUTE && medkit_type == /obj/item/storage/medkit/brute) modified_heal_amount *= 1.1 @@ -370,6 +363,24 @@ if(patient.IsReachableBy(src)) melee_attack(patient) +/mob/living/basic/bot/medbot/proc/on_techweb_research(datum/source, datum/design/medibot_upgrade/design) + SIGNAL_HANDLER + + if(!istype(design)) + return + + heal_multiplier += design.additive_multiplier + INVOKE_ASYNC(src, PROC_REF(speak), "New knowledge found! Surgical efficacy improved to [round(heal_multiplier * 100)]%!") + +/mob/living/basic/bot/medbot/proc/on_techweb_unresearch(datum/source, datum/design/medibot_upgrade/design) + SIGNAL_HANDLER + + if(!istype(design)) + return + + heal_multiplier -= design.additive_multiplier + INVOKE_ASYNC(src, PROC_REF(speak), "Error! Surgical efficacy decreased to [round(heal_multiplier * 100)]%!") + /datum/id_trim/medibot assignment = JOB_MEDIBOT trim_state = "trim_paramedic" diff --git a/code/modules/mob/living/basic/ruin_defender/flesh.dm b/code/modules/mob/living/basic/ruin_defender/flesh.dm index 89854ee8eea..d88c896f34e 100644 --- a/code/modules/mob/living/basic/ruin_defender/flesh.dm +++ b/code/modules/mob/living/basic/ruin_defender/flesh.dm @@ -153,7 +153,7 @@ var/obj/item/bodypart/new_bodypart = new part_type() forceMove(new_bodypart) - new_bodypart.replace_limb(target, TRUE) + new_bodypart.replace_limb(target) register_to_limb(new_bodypart) /mob/living/basic/living_limb_flesh/proc/owner_shocked(datum/source, shock_damage, shock_source, siemens_coeff, flags) diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm index e46501a8445..189e83593ad 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -77,14 +77,8 @@ /obj/item/organ/body_egg/alien_embryo/egg_process() if(stage == 6 && prob(50)) - for(var/datum/surgery/operations as anything in owner.surgeries) - if(operations.location != BODY_ZONE_CHEST) - continue - if(!ispath(operations.steps[operations.status], /datum/surgery_step/manipulate_organs/internal)) - continue - attempt_grow(gib_on_success = FALSE) - return - attempt_grow() + // If we are mid surgery we won't gib the mob, isn't that neat? + attempt_grow(gib_on_success = !LIMB_HAS_SURGERY_STATE(bodypart_owner, SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED)) /// Attempt to burst an alien outside of the host, getting a ghost to play as the xeno. /obj/item/organ/body_egg/alien_embryo/proc/attempt_grow(gib_on_success = TRUE) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 15d2a78fb84..ad6ce68286d 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -753,6 +753,8 @@ if(heal_flags & HEAL_LIMBS) regenerate_limbs() + for(var/obj/item/bodypart/limb as anything in bodyparts) + limb.remove_surgical_state(ALL) if(heal_flags & (HEAL_REFRESH_ORGANS|HEAL_ORGANS)) regenerate_organs(remove_hazardous = !!(heal_flags & HEAL_REFRESH_ORGANS)) @@ -899,6 +901,7 @@ synchronize_bodytypes() synchronize_bodyshapes() + ///Proc to hook behavior on bodypart removals. Do not directly call. You're looking for [/obj/item/bodypart/proc/drop_limb()]. /mob/living/carbon/proc/remove_bodypart(obj/item/bodypart/old_bodypart, special) SHOULD_NOT_OVERRIDE(TRUE) @@ -995,7 +998,7 @@ if("replace") var/limb2add = input(usr, "Select a bodypart type to add", "Add/Replace Bodypart") as null|anything in sort_list(limbtypes) var/obj/item/bodypart/new_bp = new limb2add() - if(new_bp.replace_limb(src, special = TRUE)) + if(new_bp.replace_limb(src)) admin_ticket_log("key_name_admin(usr)] has replaced [src]'s [part.type] with [new_bp.type]") qdel(part) else diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 66b9f9e6d9b..7fa78d6c7a0 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -688,7 +688,7 @@ if (picked_user_part && BODYTYPE_CAN_BE_BIOSCRAMBLED(picked_user_part.bodytype)) changed_something = TRUE new_part = new new_part() - new_part.replace_limb(src, special = TRUE) + new_part.replace_limb(src) if (picked_user_part) qdel(picked_user_part) diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index b195e0a9d38..baf95f04fc4 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -70,6 +70,11 @@ for(var/datum/wound/iter_wound as anything in body_part.wounds) . += span_danger(iter_wound.get_examine_description(user)) + var/surgery_examine = body_part.get_surgery_examine() + if(surgery_examine) + . += surgery_examine + + for(var/obj/item/bodypart/body_part as anything in disabled) var/damage_text if(HAS_TRAIT(body_part, TRAIT_DISABLED_BY_WOUND)) diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 01642d9b37f..63acd159ca6 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -1973,7 +1973,7 @@ GLOBAL_LIST_EMPTY(features_by_species) var/obj/item/bodypart/new_part if(path) new_part = new path() - new_part.replace_limb(target, TRUE) + new_part.replace_limb(target) new_part.update_limb(is_creating = TRUE) new_part.set_initial_damage(old_part.brute_dam, old_part.burn_dam) qdel(old_part) diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 218e8544d9f..e7c6575bd18 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -418,24 +418,46 @@ RETURN_TYPE(/list) SHOULD_NOT_OVERRIDE(TRUE) - var/covered_flags = NONE - var/list/all_worn_items = get_equipped_items(INCLUDE_ABSTRACT) - for(var/obj/item/worn_item in all_worn_items) - covered_flags |= worn_item.body_parts_covered - - return cover_flags2body_zones(covered_flags) + return cover_flags2body_zones(get_all_covered_flags()) ///Returns a bitfield of all zones covered by clothing /mob/living/carbon/proc/get_all_covered_flags() SHOULD_NOT_OVERRIDE(TRUE) var/covered_flags = NONE - var/list/all_worn_items = get_equipped_items(INCLUDE_ABSTRACT) - for(var/obj/item/worn_item in all_worn_items) + for(var/obj/item/worn_item in get_equipped_items(INCLUDE_ABSTRACT)) covered_flags |= worn_item.body_parts_covered return covered_flags +/mob/living/carbon/is_location_accessible(location, exluded_equipment_slots = NONE) + switch(location) + // Snowflake checks for these precise zones + if(BODY_ZONE_PRECISE_EYES) + if(is_eyes_covered(~exluded_equipment_slots) || (obscured_slots & (HIDEEYES|HIDEFACE))) + return FALSE + if(BODY_ZONE_PRECISE_MOUTH) + if(is_mouth_covered(~exluded_equipment_slots) || (obscured_slots & HIDEFACE)) + return FALSE + + var/covered_flags = NONE + for(var/obj/item/worn_item in get_equipped_items(INCLUDE_ABSTRACT)) + if(worn_item.slot_flags & exluded_equipment_slots) + continue + covered_flags |= worn_item.body_parts_covered + + // NB: we have to convert covered_flags via cover_flags2body_zones here + // instead of converting location via body_zones2cover_flags + // + // our coverage might look something like GROIN|LEGS, which would convert to list(BODY_ZONE_GROIN, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + // so if we were checking "is BODY_ZONE_CHEST accessible", we would pass - this is correct! + // + // however, if we convert the location to body zone, we would get CHEST|GROIN + // then we would check (CHEST|GROIN) & (GROIN|LEGS) and return FALSE - which is incorrect, the chest is perfectly accessible! + // checking for ((CHEST|GROIN) & (GROIN|LEGS)) == (CHEST|GROIN) would also be incorrect, + // as it would imply your chest is accessible from lacking groin coverage + return !(location in cover_flags2body_zones(covered_flags)) + /// Attempts to equip the given item in a conspicious place. /// This is used when, for instance, a character spawning with an item /// in their hands would be a dead giveaway that they are an antagonist. diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 2e8fcb0d308..723f8f8f420 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -195,11 +195,10 @@ return // I don't care about you anymore /mob/living/carbon/human/send_death_moodlets(datum/mood_event/moodlet) - for(var/datum/surgery/organ_manipulation/manipulation in surgeries) - // This check exists so debraining someone doesn't make the surgeon super sad - if(manipulation.location == BODY_ZONE_HEAD && body_position == LYING_DOWN) - return - + // Deaths of people undergoing surgery don't count + // otherwise surgeons would be depressed and that would be too realistic + if(HAS_TRAIT(src, TRAIT_READY_TO_OPERATE)) + return . = ..() var/memory_type = ispath(moodlet, /datum/mood_event/see_death/gibbed) ? /datum/memory/witness_gib : /datum/memory/witnessed_death add_memory_in_range(src, 7, memory_type, protagonist = src) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index bd936af1b48..123d0841284 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -45,7 +45,6 @@ imaginary_group -= src QDEL_LIST(imaginary_group) QDEL_LAZYLIST(diseases) - QDEL_LIST(surgeries) QDEL_LAZYLIST(quirks) return ..() diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 555a31bf53e..caa270fb11f 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -100,6 +100,20 @@ /mob/living/proc/is_ears_covered() return null +/** + * Check if the passed body zone is covered by some clothes + * + * * location: body zone to check + * ([BODY_ZONE_CHEST], [BODY_ZONE_HEAD], etc) + * * exluded_equipment_slots: equipment slots to ignore when checking coverage + * (for example, if you want to ignore helmets, pass [ITEM_SLOT_HEAD]) + * + * Returns TRUE if the location is accessible (not covered) + * Returns FALSE if the location is covered by something + */ +/mob/living/proc/is_location_accessible(location, exluded_equipment_slots = NONE) + return TRUE + /mob/living/bullet_act(obj/projectile/proj, def_zone, piercing_hit = FALSE, blocked = 0) . = ..() if (. != BULLET_ACT_HIT) @@ -459,13 +473,8 @@ if(.) return TRUE - for(var/datum/surgery/operations as anything in surgeries) - if(user.combat_mode) - break - if(IS_IN_INVALID_SURGICAL_POSITION(src, operations)) - continue - if(operations.next_step(user, modifiers)) - return TRUE + if(!combat_mode && HAS_TRAIT(src, TRAIT_READY_TO_OPERATE) && user.perform_surgery(src)) + return TRUE return FALSE diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 3470fcf5e66..4aa9fe381e7 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -94,10 +94,8 @@ /// Lazylist of all typepaths of personalities the mob has. var/list/personalities - ///a list of surgery datums. generally empty, they're added when the player wants them. - var/list/surgeries = list() - ///Mob specific surgery speed modifier - var/mob_surgery_speed_mod = 1 + /// Lazylist of surgery speed modifiers - id to number - 2 = 2x faster, 0.5x = 0.5x slower + var/list/mob_surgery_speed_mods /// Used by [living/Bump()][/mob/living/proc/Bump] and [living/PushAM()][/mob/living/proc/PushAM] to prevent potential infinite loop. var/now_pushing = null diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index f433bf02c02..3d725c96dc8 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -3,7 +3,7 @@ spark_system.set_up(5, 0, src) spark_system.attach(src) - add_traits(list(TRAIT_CAN_STRIP, TRAIT_FORCED_STANDING, TRAIT_KNOW_ENGI_WIRES), INNATE_TRAIT) + add_traits(list(TRAIT_CAN_STRIP, TRAIT_FORCED_STANDING, TRAIT_KNOW_ENGI_WIRES, TRAIT_IGNORE_SURGERY_MODIFIERS), INNATE_TRAIT) AddComponent(/datum/component/tippable, \ tip_time = 3 SECONDS, \ untip_time = 2 SECONDS, \ diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm index 103b584d094..16e3bbe9061 100644 --- a/code/modules/mod/modules/modules_medical.dm +++ b/code/modules/mod/modules/modules_medical.dm @@ -191,37 +191,26 @@ organ = null return ..() +/obj/projectile/organ/Exited(atom/movable/gone, direction) + . = ..() + if(gone == organ) + organ = null + /obj/projectile/organ/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() - if(!isliving(target)) + if(!isliving(target) || (organ.organ_flags & ORGAN_UNUSABLE)) organ.forceMove(drop_location()) - organ = null return - var/mob/living/carbon/human/organ_receiver = target - var/succeed = FALSE - if(organ_receiver.surgeries.len) - for(var/datum/surgery/organ_manipulation/procedure in organ_receiver.surgeries) - if(procedure.location != organ.zone) - continue - if(!ispath(procedure.steps[procedure.status], /datum/surgery_step/manipulate_organs)) - continue - succeed = TRUE - break - - if(!succeed) + var/mob/living/organ_receiver = target + // bodyparts actually *do* hit a specific bodypart, but random variance would make this projectile unusable + // so we just fake it, and assume the organ always hits the place it needs to go + var/obj/item/bodypart/fake_hit_part = organ_receiver.get_bodypart(length(organ.valid_zones) ? pick(organ.valid_zones) : deprecise_zone(organ.zone)) + if(!LIMB_HAS_SURGERY_STATE(fake_hit_part, SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT|SURGERY_BONE_SAWED)) organ.forceMove(drop_location()) - organ = null return - var/list/organs_to_boot_out = organ_receiver.get_organ_slot(organ.slot) - for(var/obj/item/organ/organ_evacced as anything in organs_to_boot_out) - if(organ_evacced.organ_flags & ORGAN_UNREMOVABLE) - continue - organ_evacced.Remove(target, special = TRUE) - organ_evacced.forceMove(get_turf(target)) - + // handles swapping any existing organ out for us organ.Insert(target) - organ = null ///Patrient Transport - Generates hardlight bags you can put people in. /obj/item/mod/module/criminalcapture/patienttransport @@ -402,26 +391,27 @@ /obj/item/surgical_processor/mod/preloaded loaded_surgeries = list( - /datum/surgery/advanced/pacify, - /datum/surgery/healing/combo/upgraded/femto, - /datum/surgery/advanced/brainwashing, - /datum/surgery/advanced/brainwashing/mechanic, - /datum/surgery/advanced/bioware/nerve_splicing, - /datum/surgery/advanced/bioware/nerve_splicing/mechanic, - /datum/surgery/advanced/bioware/nerve_grounding, - /datum/surgery/advanced/bioware/nerve_grounding/mechanic, - /datum/surgery/advanced/bioware/vein_threading, - /datum/surgery/advanced/bioware/vein_threading/mechanic, - /datum/surgery/advanced/bioware/muscled_veins, - /datum/surgery/advanced/bioware/muscled_veins/mechanic, - /datum/surgery/advanced/bioware/ligament_hook, - /datum/surgery/advanced/bioware/ligament_hook/mechanic, - /datum/surgery/advanced/bioware/ligament_reinforcement, - /datum/surgery/advanced/bioware/ligament_reinforcement/mechanic, - /datum/surgery/advanced/bioware/cortex_imprint, - /datum/surgery/advanced/bioware/cortex_imprint/mechanic, - /datum/surgery/advanced/bioware/cortex_folding, - /datum/surgery/advanced/bioware/cortex_folding/mechanic, + /datum/surgery_operation/basic/tend_wounds/combo/upgraded/master, + /datum/surgery_operation/limb/bioware/cortex_folding, + /datum/surgery_operation/limb/bioware/cortex_folding/mechanic, + /datum/surgery_operation/limb/bioware/cortex_imprint, + /datum/surgery_operation/limb/bioware/cortex_imprint/mechanic, + /datum/surgery_operation/limb/bioware/ligament_hook, + /datum/surgery_operation/limb/bioware/ligament_hook/mechanic, + /datum/surgery_operation/limb/bioware/ligament_reinforcement, + /datum/surgery_operation/limb/bioware/ligament_reinforcement/mechanic, + /datum/surgery_operation/limb/bioware/muscled_veins, + /datum/surgery_operation/limb/bioware/muscled_veins/mechanic, + /datum/surgery_operation/limb/bioware/nerve_grounding, + /datum/surgery_operation/limb/bioware/nerve_grounding/mechanic, + /datum/surgery_operation/limb/bioware/nerve_splicing, + /datum/surgery_operation/limb/bioware/nerve_splicing/mechanic, + /datum/surgery_operation/limb/bioware/vein_threading, + /datum/surgery_operation/limb/bioware/vein_threading/mechanic, + /datum/surgery_operation/organ/brainwash, + /datum/surgery_operation/organ/brainwash/mechanic, + /datum/surgery_operation/organ/pacify, + /datum/surgery_operation/organ/pacify/mechanic, ) /obj/item/mod/module/surgical_processor/emergency @@ -431,14 +421,6 @@ /obj/item/surgical_processor/mod/emergency loaded_surgeries = list( - /datum/surgery/healing/combo/upgraded/femto, - /datum/surgery/blood_filter, - /datum/surgery/brain_surgery, - /datum/surgery/coronary_bypass, - /datum/surgery/ear_surgery, - /datum/surgery/eye_surgery, - /datum/surgery/hepatectomy, - /datum/surgery/revival, - /datum/surgery/stomach_pump, - /datum/surgery/advanced/wing_reconstruction, + /datum/surgery_operation/basic/tend_wounds/combo/upgraded/master, + /datum/surgery_operation/organ/fix_wings, ) diff --git a/code/modules/modular_computers/file_system/programs/techweb.dm b/code/modules/modular_computers/file_system/programs/techweb.dm index 25bfd9a326b..b3b255f0682 100644 --- a/code/modules/modular_computers/file_system/programs/techweb.dm +++ b/code/modules/modular_computers/file_system/programs/techweb.dm @@ -87,16 +87,8 @@ var/list/exp_to_process = stored_research.available_experiments.Copy() for (var/comp_experi in stored_research.completed_experiments) exp_to_process += stored_research.completed_experiments[comp_experi] - for (var/process_experi in exp_to_process) - var/datum/experiment/unf_experi = process_experi - data["experiments"][unf_experi.type] = list( - "name" = unf_experi.name, - "description" = unf_experi.description, - "tag" = unf_experi.exp_tag, - "progress" = unf_experi.check_progress(), - "completed" = unf_experi.completed, - "performance_hint" = unf_experi.performance_hint - ) + for (var/datum/experiment/unf_experi as anything in exp_to_process) + data["experiments"][unf_experi.type] = unf_experi.to_ui_data() return data /datum/computer_file/program/science/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) diff --git a/code/modules/projectiles/ammunition/ballistic/rifle.dm b/code/modules/projectiles/ammunition/ballistic/rifle.dm index ceef56677b0..554ca4c57a1 100644 --- a/code/modules/projectiles/ammunition/ballistic/rifle.dm +++ b/code/modules/projectiles/ammunition/ballistic/rifle.dm @@ -149,8 +149,8 @@ var/healing = -healing_per_second * seconds_per_tick update_health += owner.adjust_brute_loss(healing, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC) update_health += owner.adjust_fire_loss(healing, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC) - update_health += owner.adjust_tox_loss(healing, updating_health = FALSE, required_biotype = BODYTYPE_ORGANIC) - update_health += owner.adjust_oxy_loss(healing, updating_health = FALSE, required_biotype = BODYTYPE_ORGANIC) + update_health += owner.adjust_tox_loss(healing, updating_health = FALSE, required_biotype = MOB_ORGANIC) + update_health += owner.adjust_oxy_loss(healing, updating_health = FALSE, required_biotype = MOB_ORGANIC) if (update_health) owner.updatehealth() if (can_sleep && (owner.mob_biotypes & MOB_ORGANIC)) diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index 6bbb5e250e9..fbdc1f6f3f1 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -392,7 +392,6 @@ . = ..() AddComponent(/datum/component/scope, range_modifier = 2) AddElement(/datum/element/examine_lore, \ - lore_hint = span_notice("You can [EXAMINE_HINT("look closer")] to learn a little more about [src]."), \ lore = "The BR-38 is Nanotrasen's latest foray into entirely in-house, standard-issue-ready, accelerator-assisted ballistic firearms.
    \
    \ The acceleration rail built into the barrel assembly boosts fired projectiles to higher velocities than unassisted bullets, \ diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm index 620a6668e1f..9a16feceebb 100644 --- a/code/modules/projectiles/guns/ballistic/pistol.dm +++ b/code/modules/projectiles/guns/ballistic/pistol.dm @@ -75,7 +75,6 @@ . = ..() underbarrel = new /obj/item/gun/energy/recharge/fisher(src) AddElement(/datum/element/examine_lore, \ - lore_hint = span_notice("You can [EXAMINE_HINT("look closer")] to learn a little more about [src]."), \ lore = "The Ansem/SC is a Scarborough Arms overhaul suite for their own Ansem handgun, designed for special operators who operate operationally, \ especially against people who like using lightbulbs.
    \
    \ diff --git a/code/modules/projectiles/guns/energy/recharge.dm b/code/modules/projectiles/guns/energy/recharge.dm index 7c47e7b3b83..4289cee1ff3 100644 --- a/code/modules/projectiles/guns/energy/recharge.dm +++ b/code/modules/projectiles/guns/energy/recharge.dm @@ -163,7 +163,6 @@ /obj/item/gun/energy/recharge/fisher/Initialize(mapload) . = ..() AddElement(/datum/element/examine_lore, \ - lore_hint = span_notice("You can [EXAMINE_HINT("look closer")] to learn a little more about [src]."), \ lore = "The SC/FISHER is an illegally-modified kinetic accelerator that's been cut down and refit into a miniature energy gun chassis, \ optimized for temporary, but effective, electronic warfare.
    \
    \ diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm index ff3da8941ae..c4858c55e68 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm @@ -103,9 +103,7 @@ if(methods & (TOUCH|VAPOR|PATCH)) exposed_mob.adjust_fire_stacks(reac_volume / 15) - var/sterilizing_power = boozepwr / 650 // Weak alcohol has less sterilizing power - for(var/datum/surgery/surgery as anything in exposed_mob.surgeries) - surgery.speed_modifier = min(1 - sterilizing_power, surgery.speed_modifier) + exposed_mob.add_surgery_speed_mod("alcohol", round(1 - (boozepwr / 650), 0.05), min(reac_volume * 1 MINUTES, 5 MINUTES)) // Weak alcohol has less sterilizing power /datum/reagent/consumable/ethanol/beer name = "Beer" diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 69e8ce50a76..c1307d3bf7f 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -830,12 +830,10 @@ /datum/reagent/consumable/honey/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() - if(!iscarbon(exposed_mob) || !(methods & (TOUCH|VAPOR|PATCH))) + if(!(methods & (TOUCH|VAPOR|PATCH))) return - var/mob/living/carbon/exposed_carbon = exposed_mob - for(var/datum/surgery/surgery as anything in exposed_carbon.surgeries) - surgery.speed_modifier = min(0.4, surgery.speed_modifier) + exposed_mob.add_surgery_speed_mod(type, 0.6, min(reac_volume * 1 MINUTES, 5 MINUTES)) /datum/reagent/consumable/mayonnaise name = "Mayonnaise" diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm index af730ac30e3..9f86275f74a 100644 --- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm @@ -950,13 +950,12 @@ Basically, we fill the time between now and 2s from now with hands based off the metabolized_traits = list(TRAIT_ANALGESIA) tox_damage = 0 -/datum/reagent/inverse/krokodil/expose_mob(mob/living/carbon/exposed_carbon, methods=TOUCH, reac_volume) +/datum/reagent/inverse/krokodil/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() if(!(methods & (TOUCH|VAPOR|PATCH))) return - for(var/datum/surgery/surgery as anything in exposed_carbon.surgeries) - surgery.speed_modifier = min(0.7, surgery.speed_modifier) + exposed_mob.add_surgery_speed_mod(type, 0.7, min(reac_volume * 1 MINUTES, 5 MINUTES)) /datum/reagent/inverse/krokodil/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index ebd21ce691f..cbc4d3721ac 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -371,12 +371,9 @@ to_chat(exposed_mob, span_warning("Your stomach feels empty and cramps!")) if(methods & (PATCH|TOUCH)) - var/mob/living/carbon/exposed_carbon = exposed_mob - for(var/datum/surgery/surgery as anything in exposed_carbon.surgeries) - surgery.speed_modifier = min(0.9, surgery.speed_modifier) - + exposed_mob.add_surgery_speed_mod(type, 0.9, min(reac_volume * 1 MINUTES, 5 MINUTES)) if(show_message) - to_chat(exposed_carbon, span_danger("You feel your injuries fade away to nothing!") ) + to_chat(exposed_mob, span_danger("You feel your injuries fade away to nothing!") ) /datum/reagent/medicine/mine_salve/on_mob_metabolize(mob/living/affected_mob) . = ..() @@ -1308,13 +1305,12 @@ . = UPDATE_MOB_HEALTH affected_mob.adjust_drunk_effect(-10 * REM * seconds_per_tick * normalise_creation_purity()) -/datum/reagent/medicine/antihol/expose_mob(mob/living/carbon/exposed_carbon, methods=TOUCH, reac_volume) +/datum/reagent/medicine/antihol/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() if(!(methods & (TOUCH|VAPOR|PATCH))) return - for(var/datum/surgery/surgery as anything in exposed_carbon.surgeries) - surgery.speed_modifier = min(surgery.speed_modifier + 0.1, 1.1) + exposed_mob.add_surgery_speed_mod(type, 1.1, min(reac_volume * 1 MINUTES, 5 MINUTES)) /datum/reagent/medicine/stimulants name = "Stimulants" diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 07e9be721b5..a528338da7e 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1094,13 +1094,12 @@ ph = 10.5 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_AFFECTS_WOUNDS -/datum/reagent/space_cleaner/sterilizine/expose_mob(mob/living/carbon/exposed_carbon, methods=TOUCH, reac_volume) +/datum/reagent/space_cleaner/sterilizine/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() if(!(methods & (TOUCH|VAPOR|PATCH))) return - for(var/datum/surgery/surgery as anything in exposed_carbon.surgeries) - surgery.speed_modifier = min(0.8, surgery.speed_modifier) + exposed_mob.add_surgery_speed_mod(type, 0.8, min(reac_volume * 1 MINUTES, 5 MINUTES)) /datum/reagent/space_cleaner/sterilizine/on_burn_wound_processing(datum/wound/burn/flesh/burn_wound) burn_wound.sanitization += 0.9 diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index c38e90ead2f..a708f858eeb 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -228,12 +228,13 @@ /datum/reagent/cryostylane/on_mob_add(mob/living/affected_mob, amount) . = ..() - affected_mob.mob_surgery_speed_mod = 1-((CRYO_SPEED_PREFACTOR * (1 - creation_purity))+CRYO_SPEED_CONSTANT) //10% - 30% slower + // Between a 1.1x and a 1.5x to surgery time depending on purity + affected_mob.add_surgery_speed_mod(type, 1 + ((CRYO_SPEED_PREFACTOR * (1 - creation_purity)) + CRYO_SPEED_CONSTANT), min(amount * 1 MINUTES, 5 MINUTES)) affected_mob.color = COLOR_CYAN /datum/reagent/cryostylane/on_mob_delete(mob/living/affected_mob) . = ..() - affected_mob.mob_surgery_speed_mod = 1 + affected_mob.remove_surgery_speed_mod(type) affected_mob.color = COLOR_WHITE //Pauses decay! Does do something, I promise. diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm index ddc19152df3..cd9fc647a16 100644 --- a/code/modules/religion/burdened/psyker.dm +++ b/code/modules/religion/burdened/psyker.dm @@ -71,7 +71,7 @@ if(stat == DEAD || !old_head || !old_brain) return FALSE var/obj/item/bodypart/head/psyker/psyker_head = new() - if(!psyker_head.replace_limb(src, special = TRUE)) + if(!psyker_head.replace_limb(src)) return FALSE qdel(old_head) var/obj/item/organ/brain/psyker/psyker_brain = new() /// turns out if you make a flashing monochromatic outline against black background that refreshes on inconsistant intervals, it hurts peoples eyes. Who'da thunk. diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index d2bca7ccce5..89212e6215d 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -1152,240 +1152,213 @@ id = "ci-gloweyes-moth" build_path = /obj/item/organ/eyes/robotic/glow/moth +/datum/design/medibot_upgrade + name = "Medibot Upgrade" + desc = "Automatically upgrades the effectiveness of all medibots linked to the research network." + id = "medibot_upgrade" + research_icon = 'icons/mob/silicon/aibots.dmi' + research_icon_state = "medbot_generic_idle" + /// Medibot healing starts at a 1x multiplier. For every tech researched, it goes up by this amount additively. + var/additive_multiplier = 1 + +/datum/design/medibot_upgrade/tier_two + id = "medibot_upgrade_two" + research_icon_state = "medbot_adv_idle" + +/datum/design/medibot_upgrade/tier_three + id = "medibot_upgrade_three" + research_icon_state = "medbot_adv_idle" + +/datum/design/medibot_upgrade/tier_four + id = "medibot_upgrade_four" + research_icon_state = "medbot_bezerk_idle" // alien tech + ///////////////////// ///Surgery Designs/// ///////////////////// /datum/design/surgery - name = "Surgery Design" - desc = "what" + abstract_type = /datum/design/surgery + id = DESIGN_ID_IGNORE + name = null + desc = null research_icon = 'icons/obj/medical/surgery_ui.dmi' research_icon_state = "surgery_any" - var/surgery + /// Typepath of what operation this design unlocks + var/datum/surgery_operation/surgery + +/datum/design/surgery/New() + . = ..() + if(isnull(name)) + name = surgery::rnd_name || capitalize(surgery::name) + if(isnull(desc)) + desc = surgery::rnd_desc || surgery::desc /datum/design/surgery/lobotomy - name = "Lobotomy" - desc = "An invasive surgical procedure which guarantees removal of almost all brain traumas, but might cause another permanent trauma in return." id = "surgery_lobotomy" - surgery = /datum/surgery/advanced/lobotomy + surgery = /datum/surgery_operation/organ/lobotomy research_icon_state = "surgery_head" /datum/design/surgery/lobotomy/mechanic - name = "Wetware OS Destructive Defragmentation" - desc = "A destructive robotic defragmentation method which guarantees removal of almost all brain traumas, but might cause another permanent trauma in return." id = "surgery_lobotomy_mechanic" - surgery = /datum/surgery/advanced/lobotomy/mechanic + surgery = /datum/surgery_operation/organ/lobotomy/mechanic /datum/design/surgery/pacify - name = "Pacification" - desc = "A surgical procedure which permanently inhibits the aggression center of the brain, making the patient unwilling to cause direct harm." id = "surgery_pacify" - surgery = /datum/surgery/advanced/pacify + surgery = /datum/surgery_operation/organ/pacify research_icon_state = "surgery_head" /datum/design/surgery/pacify/mechanic - name = "Aggression Suppression Programming" - desc = "Malware which permanently inhibits the aggression programming of the patient's neural network, making the patient unwilling to cause direct harm." id = "surgery_pacify_mechanic" - surgery = /datum/surgery/advanced/pacify/mechanic + surgery = /datum/surgery_operation/organ/pacify/mechanic /datum/design/surgery/viral_bonding - name = "Viral Bonding" - desc = "A surgical procedure that forces a symbiotic relationship between a virus and its host. The patient must be dosed with spaceacillin, virus food, and formaldehyde." id = "surgery_viral_bond" - surgery = /datum/surgery/advanced/viral_bonding + surgery = /datum/surgery_operation/basic/viral_bonding research_icon_state = "surgery_chest" -/datum/design/surgery/healing //PLEASE ACCOUNT FOR UNIQUE HEALING BRANCHES IN THE hptech HREF (currently 2 for Brute/Burn; Combo is bonus) - name = "Tend Wounds" - desc = "An upgraded version of the original surgery." - id = "surgery_healing_base" //holder because CI cries otherwise. Not used in techweb unlocks. - surgery = /datum/surgery/healing +/datum/design/surgery/tend_wounds_upgrade + name = "Tend Wounds Upgrade" + desc = "Upgrade the efficiency of the individual tend wound operations." + id = "surgery_heal_upgrade" + surgery = /datum/surgery_operation/basic/tend_wounds/upgraded research_icon_state = "surgery_chest" -/datum/design/surgery/healing/brute_upgrade - name = "Tend Wounds (Brute) Upgrade" - surgery = /datum/surgery/healing/brute/upgraded - id = "surgery_heal_brute_upgrade" +/datum/design/surgery/tend_wounds_upgrade/femto + name = "Tend Wounds Upgrade" + surgery = /datum/surgery_operation/basic/tend_wounds/upgraded/master + id = "surgery_heal_upgrade_femto" -/datum/design/surgery/healing/brute_upgrade_2 - name = "Tend Wounds (Brute) Upgrade" - surgery = /datum/surgery/healing/brute/upgraded/femto - id = "surgery_heal_brute_upgrade_femto" - -/datum/design/surgery/healing/burn_upgrade - name = "Tend Wounds (Burn) Upgrade" - surgery = /datum/surgery/healing/burn/upgraded - id = "surgery_heal_burn_upgrade" - -/datum/design/surgery/healing/burn_upgrade_2 - name = "Tend Wounds (Burn) Upgrade" - surgery = /datum/surgery/healing/burn/upgraded/femto - id = "surgery_heal_burn_upgrade_femto" - -/datum/design/surgery/healing/combo - name = "Tend Wounds (Physical)" - desc = "A surgical procedure that repairs both bruises and burns. Repair efficiency is not as high as the individual surgeries but it is faster." - surgery = /datum/surgery/healing/combo +/datum/design/surgery/tend_wounds_combo + name = "Tend Wounds Combo" + desc = "An alternative wound treatment operation that treats both bruises and burns at the same time, albeit less effectively than their individual counterparts." + surgery = /datum/surgery_operation/basic/tend_wounds/combo id = "surgery_heal_combo" + research_icon_state = "surgery_chest" -/datum/design/surgery/healing/combo_upgrade - name = "Tend Wounds (Physical) Upgrade" - surgery = /datum/surgery/healing/combo/upgraded +/datum/design/surgery/tend_wounds_combo/upgrade + name = "Tend Wounds Combo Upgrade" + surgery = /datum/surgery_operation/basic/tend_wounds/combo/upgraded id = "surgery_heal_combo_upgrade" -/datum/design/surgery/healing/combo_upgrade_2 - name = "Tend Wounds (Physical) Upgrade" - desc = "A surgical procedure that repairs both bruises and burns faster than their individual counterparts. It is more effective than both the individual surgeries." - surgery = /datum/surgery/healing/combo/upgraded/femto +/datum/design/surgery/tend_wounds_combo/upgrade/femto + name = "Tend Wounds Combo Upgrade" + desc = "The ultimate in wound treatment operations, treating both bruises and burns simultaneous and faster than their individual counterparts." + surgery = /datum/surgery_operation/basic/tend_wounds/combo/upgraded/master id = "surgery_heal_combo_upgrade_femto" /datum/design/surgery/brainwashing - name = "Brainwashing" - desc = "A surgical procedure which directly implants a directive into the patient's brain, making it their absolute priority. It can be cleared using a mindshield implant." id = "surgery_brainwashing" - surgery = /datum/surgery/advanced/brainwashing + surgery = /datum/surgery_operation/organ/brainwash research_icon_state = "surgery_head" /datum/design/surgery/brainwashing/mechanic - name = "Reprogramming" - desc = "Malware which directly implants a directive into the robotic patient's operating system, making it their absolute priority. It can be cleared using a mindshield implant." id = "surgery_brainwashing_mechanic" - surgery = /datum/surgery/advanced/brainwashing/mechanic + surgery = /datum/surgery_operation/organ/brainwash/mechanic /datum/design/surgery/nerve_splicing - name = "Nerve Splicing" desc = "A surgical procedure which splices the patient's nerves, making them more resistant to stuns." id = "surgery_nerve_splice" - surgery = /datum/surgery/advanced/bioware/nerve_splicing + surgery = /datum/surgery_operation/limb/bioware/nerve_splicing research_icon_state = "surgery_chest" /datum/design/surgery/nerve_splicing/mechanic - name = "System Automatic Reset Subroutine" desc = "A robotic upgrade which upgrades a robotic patient's automatic systems, making them more resistant to stuns." id = "surgery_nerve_splice_mechanic" - surgery = /datum/surgery/advanced/bioware/nerve_splicing/mechanic + surgery = /datum/surgery_operation/limb/bioware/nerve_splicing/mechanic /datum/design/surgery/nerve_grounding - name = "Nerve Grounding" desc = "A surgical procedure which makes the patient's nerves act as grounding rods, protecting them from electrical shocks." id = "surgery_nerve_ground" - surgery = /datum/surgery/advanced/bioware/nerve_grounding + surgery = /datum/surgery_operation/limb/bioware/nerve_grounding research_icon_state = "surgery_chest" /datum/design/surgery/nerve_grounding/mechanic - name = "System Shock Dampening" desc = "A robotic upgrade which installs grounding rods into the robotic patient's system, protecting them from electrical shocks." id = "surgery_nerve_ground_mechanic" - surgery = /datum/surgery/advanced/bioware/nerve_grounding/mechanic + surgery = /datum/surgery_operation/limb/bioware/nerve_grounding/mechanic /datum/design/surgery/vein_threading - name = "Vein Threading" desc = "A surgical procedure which severely reduces the amount of blood lost in case of injury." id = "surgery_vein_thread" - surgery = /datum/surgery/advanced/bioware/vein_threading + surgery = /datum/surgery_operation/limb/bioware/vein_threading research_icon_state = "surgery_chest" /datum/design/surgery/vein_threading/mechanic - name = "Hydraulics Routing Optimization" desc = "A robotic upgrade which severely reduces the amount of hydraulic fluid lost in case of injury." id = "surgery_vein_thread_mechanic" - surgery = /datum/surgery/advanced/bioware/vein_threading/mechanic + surgery = /datum/surgery_operation/limb/bioware/vein_threading/mechanic /datum/design/surgery/muscled_veins - name = "Vein Muscle Membrane" desc = "A surgical procedure which adds a muscled membrane to blood vessels, allowing a patient to pump blood without a heart." id = "surgery_muscled_veins" - surgery = /datum/surgery/advanced/bioware/muscled_veins + surgery = /datum/surgery_operation/limb/bioware/muscled_veins research_icon_state = "surgery_chest" /datum/design/surgery/muscled_veins/mechanic - name = "Hydraulics Redundancy Subroutine" desc = "A robotic upgrade which adds sophisticated hydraulics redundancies, allowing a patient to pump hydraulic fluid without an engine." id = "surgery_muscled_veins_mechanic" - surgery = /datum/surgery/advanced/bioware/muscled_veins/mechanic + surgery = /datum/surgery_operation/limb/bioware/muscled_veins/mechanic /datum/design/surgery/ligament_hook - name = "Ligament Hook" desc = "A surgical procedure which reshapes the connections between torso and limbs, making it so limbs can be attached manually if severed. \ - However this weakens the connection, making them easier to detach as well." + However, this weakens the connection, making them easier to detach as well." id = "surgery_ligament_hook" - surgery = /datum/surgery/advanced/bioware/ligament_hook + surgery = /datum/surgery_operation/limb/bioware/ligament_hook research_icon_state = "surgery_chest" /datum/design/surgery/ligament_hook/mechanic - name = "Anchor Point Snaplocks" desc = "A robotic upgrade which installs rapid detachment anchor points, making it so limbs can be attached manually if detached. \ - However this weakens the connection, making them easier to detach as well." + However, this weakens the connection, making them easier to detach as well." id = "surgery_ligament_hook_mechanic" - surgery = /datum/surgery/advanced/bioware/ligament_hook/mechanic + surgery = /datum/surgery_operation/limb/bioware/ligament_hook/mechanic /datum/design/surgery/ligament_reinforcement - name = "Ligament Reinforcement" desc = "A surgical procedure which adds a protective tissue and bone cage around the connections between the torso and limbs, preventing dismemberment. \ - However, the nerve connections as a result are more easily interrupted, making it easier to disable limbs with damage." + However, the nerve connections as a result are more easily interrupted, making it easier to disable limbs with damage." id = "surgery_ligament_reinforcement" - surgery = /datum/surgery/advanced/bioware/ligament_reinforcement + surgery = /datum/surgery_operation/limb/bioware/ligament_reinforcement research_icon_state = "surgery_chest" /datum/design/surgery/ligament_reinforcement/mechanic - name = "Anchor Point Reinforcement" desc = "A surgical procedure which adds reinforced limb anchor points to the patient's chassis, preventing dismemberment. \ However, the nerve connections as a result are more easily interrupted, making it easier to disable limbs with damage." id = "surgery_ligament_reinforcement_mechanic" - surgery = /datum/surgery/advanced/bioware/ligament_reinforcement/mechanic + surgery = /datum/surgery_operation/limb/bioware/ligament_reinforcement/mechanic /datum/design/surgery/cortex_imprint - name = "Cortex Imprint" desc = "A surgical procedure which modifies the cerebral cortex into a redundant neural pattern, making the brain able to bypass damage caused by minor brain traumas." id = "surgery_cortex_imprint" - surgery = /datum/surgery/advanced/bioware/cortex_imprint + surgery = /datum/surgery_operation/limb/bioware/cortex_imprint research_icon_state = "surgery_head" /datum/design/surgery/cortex_imprint/mechanic - name = "Wetware OS Ver 2.0" - desc = "A surgical procedure which updates the patient's operating system to the 'latest version', whatever that means, making the brain able to bypass damage caused by minor brain traumas. \ - Shame about all the adware." + desc = "A surgical procedure which updates the patient's operating system to the 'latest version', whatever that means, making the brain able to bypass damage caused by minor brain traumas." id = "surgery_cortex_imprint_mechanic" - surgery = /datum/surgery/advanced/bioware/cortex_imprint/mechanic + surgery = /datum/surgery_operation/limb/bioware/cortex_imprint/mechanic /datum/design/surgery/cortex_folding - name = "Cortex Folding" desc = "A surgical procedure which modifies the cerebral cortex into a complex fold, giving space to non-standard neural patterns." id = "surgery_cortex_folding" - surgery = /datum/surgery/advanced/bioware/cortex_folding + surgery = /datum/surgery_operation/limb/bioware/cortex_folding research_icon_state = "surgery_head" /datum/design/surgery/cortex_folding/mechanic - name = "Wetware OS Labyrinthian Programming" desc = "A robotic upgrade which reprograms the patient's neural network in a downright eldritch programming language, giving space to non-standard neural patterns." id = "surgery_cortex_folding_mechanic" - surgery = /datum/surgery/advanced/bioware/cortex_folding/mechanic + surgery = /datum/surgery_operation/limb/bioware/cortex_folding/mechanic /datum/design/surgery/necrotic_revival - name = "Necrotic Revival" - desc = "An experimental surgical procedure that stimulates the growth of a Romerol tumor inside the patient's brain. Requires zombie powder or rezadone." id = "surgery_zombie" - surgery = /datum/surgery/advanced/necrotic_revival + surgery = /datum/surgery_operation/limb/bionecrosis research_icon_state = "surgery_head" /datum/design/surgery/wing_reconstruction - name = "Wing Reconstruction" - desc = "An experimental surgical procedure that reconstructs the damaged wings of moth people. Requires Synthflesh." id = "surgery_wing_reconstruction" - surgery = /datum/surgery/advanced/wing_reconstruction + surgery = /datum/surgery_operation/organ/fix_wings research_icon_state = "surgery_chest" -/datum/design/surgery/advanced_plastic_surgery - name = "Advanced Plastic Surgery" - desc = "An advanced form of the plastic surgery, allowing oneself to remodel someone's face and voice based off a picture of someones face" - surgery = /datum/surgery/plastic_surgery/advanced - id = "surgery_advanced_plastic_surgery" - research_icon_state = "surgery_head" - /datum/design/surgery/experimental_dissection - name = "Experimental Dissection" - desc = "An experimental surgical procedure that dissects bodies in exchange for research points at ancient R&D consoles." id = "surgery_oldstation_dissection" - surgery = /datum/surgery/advanced/experimental_dissection + surgery = /datum/surgery_operation/basic/dissection research_icon_state = "surgery_chest" diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index 3a0a4cad642..32793bfe8ab 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -243,16 +243,8 @@ Nothing else in the console has ID requirements. var/list/exp_to_process = stored_research.available_experiments.Copy() for (var/e in stored_research.completed_experiments) exp_to_process += stored_research.completed_experiments[e] - for (var/e in exp_to_process) - var/datum/experiment/ex = e - data["experiments"][ex.type] = list( - "name" = ex.name, - "description" = ex.description, - "tag" = ex.exp_tag, - "progress" = ex.check_progress(), - "completed" = ex.completed, - "performance_hint" = ex.performance_hint, - ) + for (var/datum/experiment/ex as anything in exp_to_process) + data["experiments"][ex.type] = ex.to_ui_data() return data /** diff --git a/code/modules/research/techweb/_techweb.dm b/code/modules/research/techweb/_techweb.dm index bf297eb7052..ade382a0265 100644 --- a/code/modules/research/techweb/_techweb.dm +++ b/code/modules/research/techweb/_techweb.dm @@ -321,6 +321,7 @@ result_text += points_rewarded result_text += "!" + SEND_SIGNAL(src, COMSIG_TECHWEB_EXPERIMENT_COMPLETED, completed_experiment) log_research("[completed_experiment.name] ([completed_experiment.type]) has been completed on techweb [id]/[organization][refund ? ", refunding [refund] points" : ""][points_rewarded].") return result_text @@ -561,3 +562,10 @@ experiment.finish_experiment(linked_web_override = src) return TRUE + +/// Returns a flat list of all design datums this techweb has researched. +/datum/techweb/proc/get_researched_design_datums() + var/list/designs = list() + for(var/id in researched_designs) + designs += SSresearch.techweb_design_by_id(id) + return designs diff --git a/code/modules/research/techweb/nodes/alien_nodes.dm b/code/modules/research/techweb/nodes/alien_nodes.dm index 78fd3791e5c..c5027b32bd6 100644 --- a/code/modules/research/techweb/nodes/alien_nodes.dm +++ b/code/modules/research/techweb/nodes/alien_nodes.dm @@ -82,6 +82,7 @@ ABDUCTOR_SUBTYPE_UNLOCKS(/datum/techweb_node/alien_engi) "alien_retractor", "alien_saw", "alien_scalpel", + "medibot_upgrade_four", "surgery_brainwashing", "surgery_brainwashing_mechanic", "surgery_heal_combo_upgrade_femto", diff --git a/code/modules/research/techweb/nodes/surgery_nodes.dm b/code/modules/research/techweb/nodes/surgery_nodes.dm index bf6f104cf1e..9123179509f 100644 --- a/code/modules/research/techweb/nodes/surgery_nodes.dm +++ b/code/modules/research/techweb/nodes/surgery_nodes.dm @@ -16,8 +16,8 @@ description = "Who would have known being more gentle with a hemostat decreases patient pain?" prereq_ids = list(TECHWEB_NODE_MEDBAY_EQUIP) design_ids = list( - "surgery_heal_brute_upgrade", - "surgery_heal_burn_upgrade", + "surgery_heal_upgrade", + "medibot_upgrade", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) announce_channels = list(RADIO_CHANNEL_MEDICAL) @@ -29,9 +29,9 @@ prereq_ids = list(TECHWEB_NODE_SURGERY) design_ids = list( "harvester", - "surgery_heal_brute_upgrade_femto", - "surgery_heal_burn_upgrade_femto", + "medibot_upgrade_two", "surgery_heal_combo", + "surgery_heal_upgrade_femto", "surgery_lobotomy", "surgery_lobotomy_mechanic", "surgery_wing_reconstruction", @@ -46,6 +46,7 @@ description = "When evolution isn't fast enough." prereq_ids = list(TECHWEB_NODE_SURGERY_ADV) design_ids = list( + "medibot_upgrade_three", "surgery_cortex_folding", "surgery_cortex_folding_mechanic", "surgery_cortex_imprint", diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm index c47ea89eaac..f041cba42bd 100644 --- a/code/modules/research/xenobiology/crossbreeding/_misc.dm +++ b/code/modules/research/xenobiology/crossbreeding/_misc.dm @@ -33,7 +33,7 @@ Slimecrossing Items if(QDELETED(saved_part.old_part)) saved_part.old_part = new saved_part.bodypart_type if(!already || already != saved_part.old_part) - saved_part.old_part.replace_limb(src, TRUE) + saved_part.old_part.replace_limb(src) saved_part.old_part.heal_damage(INFINITY, INFINITY, null, FALSE) saved_part.old_part.receive_damage(saved_part.brute_dam, saved_part.burn_dam, wound_bonus=CANT_WOUND) dont_chop[zone] = TRUE diff --git a/code/modules/surgery/advanced/bioware/bioware_surgery.dm b/code/modules/surgery/advanced/bioware/bioware_surgery.dm deleted file mode 100644 index 2b15e0b0f1c..00000000000 --- a/code/modules/surgery/advanced/bioware/bioware_surgery.dm +++ /dev/null @@ -1,30 +0,0 @@ -/datum/surgery/advanced/bioware - name = "Enhancement surgery" - /// What status effect is gained when the surgery is successful? - /// Used to check against other bioware types to prevent stacking. - var/status_effect_gained = /datum/status_effect/bioware - -/datum/surgery/advanced/bioware/can_start(mob/user, mob/living/carbon/human/target) - if(!..()) - return FALSE - if(!istype(target)) - return FALSE - if(target.has_status_effect(status_effect_gained)) - return FALSE - return TRUE - -/datum/surgery_step/apply_bioware - accept_hand = TRUE - time = 12.5 SECONDS - surgery_effects_mood = TRUE - -/datum/surgery_step/apply_bioware/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/advanced/bioware/surgery, default_display_results) - . = ..() - if(!.) - return - if(!istype(surgery)) - return - - target.apply_status_effect(surgery.status_effect_gained) - if(target.ckey) - SSblackbox.record_feedback("tally", "bioware", 1, surgery.type) diff --git a/code/modules/surgery/advanced/bioware/cortex_folding.dm b/code/modules/surgery/advanced/bioware/cortex_folding.dm deleted file mode 100644 index 503c10919c4..00000000000 --- a/code/modules/surgery/advanced/bioware/cortex_folding.dm +++ /dev/null @@ -1,80 +0,0 @@ -/datum/surgery/advanced/bioware/cortex_folding - name = "Cortex Folding" - desc = "A surgical procedure which modifies the cerebral cortex into a complex fold, giving space to non-standard neural patterns." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/incise, - /datum/surgery_step/apply_bioware/fold_cortex, - /datum/surgery_step/close, - ) - - status_effect_gained = /datum/status_effect/bioware/cortex/folded - -/datum/surgery/advanced/bioware/cortex_folding/mechanic - name = "Wetware OS Labyrinthian Programming" - desc = "A robotic upgrade which reprograms the patient's neural network in a downright eldritch programming language, giving space to non-standard neural patterns." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/apply_bioware/fold_cortex, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/advanced/bioware/cortex_folding/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/brain/target_brain = target.get_organ_slot(ORGAN_SLOT_BRAIN) - if(!target_brain) - return FALSE - return ..() - -/datum/surgery_step/apply_bioware/fold_cortex - name = "fold cortex (hand)" - -/datum/surgery_step/apply_bioware/fold_cortex/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You start folding [target]'s outer cerebral cortex into a fractal pattern."), - span_notice("[user] starts folding [target]'s outer cerebral cortex into a fractal pattern."), - span_notice("[user] begins to perform surgery on [target]'s brain."), - ) - display_pain(target, "Your head throbs with gruesome pain, it's nearly too much to handle!") - -/datum/surgery_step/apply_bioware/fold_cortex/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - . = ..() - if(!.) - return - - display_results( - user, - target, - span_notice("You fold [target]'s outer cerebral cortex into a fractal pattern!"), - span_notice("[user] folds [target]'s outer cerebral cortex into a fractal pattern!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "Your brain feels stronger... more flexible!") - -/datum/surgery_step/apply_bioware/fold_cortex/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(target.get_organ_slot(ORGAN_SLOT_BRAIN)) - display_results( - user, - target, - span_warning("You screw up, damaging the brain!"), - span_warning("[user] screws up, damaging the brain!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "Your brain throbs with intense pain; thinking hurts!") - target.adjust_organ_loss(ORGAN_SLOT_BRAIN, 60) - target.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) - else - user.visible_message(span_warning("[user] suddenly notices that the brain [user.p_they()] [user.p_were()] working on is not there anymore."), span_warning("You suddenly notice that the brain you were working on is not there anymore.")) - return FALSE diff --git a/code/modules/surgery/advanced/bioware/cortex_imprint.dm b/code/modules/surgery/advanced/bioware/cortex_imprint.dm deleted file mode 100644 index a168224c7b0..00000000000 --- a/code/modules/surgery/advanced/bioware/cortex_imprint.dm +++ /dev/null @@ -1,81 +0,0 @@ -/datum/surgery/advanced/bioware/cortex_imprint - name = "Cortex Imprint" - desc = "A surgical procedure which modifies the cerebral cortex into a redundant neural pattern, making the brain able to bypass impediments caused by minor brain traumas." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/incise, - /datum/surgery_step/apply_bioware/imprint_cortex, - /datum/surgery_step/close, - ) - - status_effect_gained = /datum/status_effect/bioware/cortex/imprinted - -/datum/surgery/advanced/bioware/cortex_imprint/mechanic - name = "Wetware OS Ver 2.0" - desc = "A robotic upgrade which updates the patient's operating system to the 'latest version', whatever that means, making the brain able to bypass damage caused by minor brain traumas. \ - Shame about all the adware." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/apply_bioware/imprint_cortex, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/advanced/bioware/cortex_imprint/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/brain/target_brain = target.get_organ_slot(ORGAN_SLOT_BRAIN) - if(!target_brain) - return FALSE - return ..() - -/datum/surgery_step/apply_bioware/imprint_cortex - name = "imprint cortex (hand)" - -/datum/surgery_step/apply_bioware/imprint_cortex/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You start carving [target]'s outer cerebral cortex into a self-imprinting pattern."), - span_notice("[user] starts carving [target]'s outer cerebral cortex into a self-imprinting pattern."), - span_notice("[user] begins to perform surgery on [target]'s brain."), - ) - display_pain(target, "Your head throbs with gruesome pain, it's nearly too much to handle!") - -/datum/surgery_step/apply_bioware/imprint_cortex/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - . = ..() - if(!.) - return - - display_results( - user, - target, - span_notice("You reshape [target]'s outer cerebral cortex into a self-imprinting pattern!"), - span_notice("[user] reshapes [target]'s outer cerebral cortex into a self-imprinting pattern!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "Your brain feels stronger... more resillient!") - -/datum/surgery_step/apply_bioware/imprint_cortex/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(target.get_organ_slot(ORGAN_SLOT_BRAIN)) - display_results( - user, - target, - span_warning("You screw up, damaging the brain!"), - span_warning("[user] screws up, damaging the brain!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "Your brain throbs with intense pain; Thinking hurts!") - target.adjust_organ_loss(ORGAN_SLOT_BRAIN, 60) - target.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) - else - user.visible_message(span_warning("[user] suddenly notices that the brain [user.p_they()] [user.p_were()] working on is not there anymore."), span_warning("You suddenly notice that the brain you were working on is not there anymore.")) - return FALSE diff --git a/code/modules/surgery/advanced/bioware/ligament_hook.dm b/code/modules/surgery/advanced/bioware/ligament_hook.dm deleted file mode 100644 index 057adda873a..00000000000 --- a/code/modules/surgery/advanced/bioware/ligament_hook.dm +++ /dev/null @@ -1,60 +0,0 @@ -/datum/surgery/advanced/bioware/ligament_hook - name = "Ligament Hook" - desc = "A surgical procedure which reshapes the connections between torso and limbs, making it so limbs can be attached manually if severed. \ - However this weakens the connection, making them easier to detach as well." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/incise, - /datum/surgery_step/apply_bioware/reshape_ligaments, - /datum/surgery_step/close, - ) - - status_effect_gained = /datum/status_effect/bioware/ligaments/hooked - -/datum/surgery/advanced/bioware/ligament_hook/mechanic - name = "Anchor Point Snaplocks" - desc = "A robotic upgrade which installs rapid detachment anchor points, making it so limbs can be attached manually if detached. \ - However this weakens the connection, making them easier to detach as well." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/apply_bioware/reshape_ligaments, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery_step/apply_bioware/reshape_ligaments - name = "reshape ligaments (hand)" - -/datum/surgery_step/apply_bioware/reshape_ligaments/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You start reshaping [target]'s ligaments into a hook-like shape."), - span_notice("[user] starts reshaping [target]'s ligaments into a hook-like shape."), - span_notice("[user] starts manipulating [target]'s ligaments."), - ) - display_pain(target, "Your limbs burn with severe pain!") - -/datum/surgery_step/apply_bioware/reshape_ligaments/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - . = ..() - if(!.) - return - - display_results( - user, - target, - span_notice("You reshape [target]'s ligaments into a connective hook!"), - span_notice("[user] reshapes [target]'s ligaments into a connective hook!"), - span_notice("[user] finishes manipulating [target]'s ligaments."), - ) - display_pain(target, "Your limbs feel... strangely loose.") diff --git a/code/modules/surgery/advanced/bioware/ligament_reinforcement.dm b/code/modules/surgery/advanced/bioware/ligament_reinforcement.dm deleted file mode 100644 index 105c8d2330f..00000000000 --- a/code/modules/surgery/advanced/bioware/ligament_reinforcement.dm +++ /dev/null @@ -1,60 +0,0 @@ -/datum/surgery/advanced/bioware/ligament_reinforcement - name = "Ligament Reinforcement" - desc = "A surgical procedure which adds a protective tissue and bone cage around the connections between the torso and limbs, preventing dismemberment. \ - However, the nerve connections as a result are more easily interrupted, making it easier to disable limbs with damage." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/incise, - /datum/surgery_step/apply_bioware/reinforce_ligaments, - /datum/surgery_step/close, - ) - - status_effect_gained = /datum/status_effect/bioware/ligaments/reinforced - -/datum/surgery/advanced/bioware/ligament_reinforcement/mechanic - name = "Anchor Point Reinforcement" - desc = "A surgical procedure which adds reinforced limb anchor points to the patient's chassis, preventing dismemberment. \ - However, the nerve connections as a result are more easily interrupted, making it easier to disable limbs with damage." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/apply_bioware/reinforce_ligaments, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery_step/apply_bioware/reinforce_ligaments - name = "reinforce ligaments (hand)" - -/datum/surgery_step/apply_bioware/reinforce_ligaments/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You start reinforcing [target]'s ligaments."), - span_notice("[user] starts reinforce [target]'s ligaments."), - span_notice("[user] starts manipulating [target]'s ligaments."), - ) - display_pain(target, "Your limbs burn with severe pain!") - -/datum/surgery_step/apply_bioware/reinforce_ligaments/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - . = ..() - if(!.) - return - - display_results( - user, - target, - span_notice("You reinforce [target]'s ligaments!"), - span_notice("[user] reinforces [target]'s ligaments!"), - span_notice("[user] finishes manipulating [target]'s ligaments."), - ) - display_pain(target, "Your limbs feel more secure, but also more frail.") diff --git a/code/modules/surgery/advanced/bioware/muscled_veins.dm b/code/modules/surgery/advanced/bioware/muscled_veins.dm deleted file mode 100644 index 2c312647b8f..00000000000 --- a/code/modules/surgery/advanced/bioware/muscled_veins.dm +++ /dev/null @@ -1,58 +0,0 @@ -/datum/surgery/advanced/bioware/muscled_veins - name = "Vein Muscle Membrane" - desc = "A surgical procedure which adds a muscled membrane to blood vessels, allowing them to pump blood without a heart." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/incise, - /datum/surgery_step/apply_bioware/muscled_veins, - /datum/surgery_step/close, - ) - - status_effect_gained = /datum/status_effect/bioware/heart/muscled_veins - -/datum/surgery/advanced/bioware/muscled_veins/mechanic - name = "Hydraulics Redundancy Subroutine" - desc = "A robotic upgrade which adds sophisticated hydraulics redundancies, allowing a patient to pump hydraulic fluid without an engine." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/apply_bioware/muscled_veins, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery_step/apply_bioware/muscled_veins - name = "shape vein muscles (hand)" - -/datum/surgery_step/apply_bioware/muscled_veins/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You start wrapping muscles around [target]'s circulatory system."), - span_notice("[user] starts wrapping muscles around [target]'s circulatory system."), - span_notice("[user] starts manipulating [target]'s circulatory system."), - ) - display_pain(target, "Your entire body burns in agony!") - -/datum/surgery_step/apply_bioware/muscled_veins/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - . = ..() - if(!.) - return - - display_results( - user, - target, - span_notice("You reshape [target]'s circulatory system, adding a muscled membrane!"), - span_notice("[user] reshapes [target]'s circulatory system, adding a muscled membrane!"), - span_notice("[user] finishes manipulating [target]'s circulatory system."), - ) - display_pain(target, "You can feel your heartbeat's powerful pulses ripple through your body!") diff --git a/code/modules/surgery/advanced/bioware/nerve_grounding.dm b/code/modules/surgery/advanced/bioware/nerve_grounding.dm deleted file mode 100644 index 0ad64218c76..00000000000 --- a/code/modules/surgery/advanced/bioware/nerve_grounding.dm +++ /dev/null @@ -1,56 +0,0 @@ -/datum/surgery/advanced/bioware/nerve_grounding - name = "Nerve Grounding" - desc = "A surgical procedure which makes the patient's nerves act as grounding rods, protecting them from electrical shocks." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/incise, - /datum/surgery_step/apply_bioware/ground_nerves, - /datum/surgery_step/close, - ) - - status_effect_gained = /datum/status_effect/bioware/nerves/grounded - -/datum/surgery/advanced/bioware/nerve_grounding/mechanic - name = "System Shock Dampening" - desc = "A robotic upgrade which installs grounding rods into the robotic patient's system, protecting them from electrical shocks." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/apply_bioware/ground_nerves, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery_step/apply_bioware/ground_nerves - name = "ground nerves (hand)" - time = 15.5 SECONDS - -/datum/surgery_step/apply_bioware/ground_nerves/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You start rerouting [target]'s nerves."), - span_notice("[user] starts rerouting [target]'s nerves."), - span_notice("[user] starts manipulating [target]'s nervous system."), - ) - display_pain(target, "Your entire body goes numb!") - -/datum/surgery_step/apply_bioware/ground_nerves/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - display_results( - user, - target, - span_notice("You successfully reroute [target]'s nervous system!"), - span_notice("[user] successfully reroutes [target]'s nervous system!"), - span_notice("[user] finishes manipulating [target]'s nervous system."), - ) - display_pain(target, "You regain feeling in your body! You feel energzed!") - return ..() diff --git a/code/modules/surgery/advanced/bioware/nerve_splicing.dm b/code/modules/surgery/advanced/bioware/nerve_splicing.dm deleted file mode 100644 index addfdfe953b..00000000000 --- a/code/modules/surgery/advanced/bioware/nerve_splicing.dm +++ /dev/null @@ -1,59 +0,0 @@ -/datum/surgery/advanced/bioware/nerve_splicing - name = "Nerve Splicing" - desc = "A surgical procedure which splices the patient's nerves, making them more resistant to stuns." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/incise, - /datum/surgery_step/apply_bioware/splice_nerves, - /datum/surgery_step/close, - ) - - status_effect_gained = /datum/status_effect/bioware/nerves/spliced - -/datum/surgery/advanced/bioware/nerve_splicing/mechanic - name = "System Automatic Reset Subroutine" - desc = "A robotic upgrade which upgrades a robotic patient's automatic systems, making them more resistant to stuns." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/apply_bioware/splice_nerves, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery_step/apply_bioware/splice_nerves - name = "splice nerves (hand)" - time = 15.5 SECONDS - -/datum/surgery_step/apply_bioware/splice_nerves/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You start splicing together [target]'s nerves."), - span_notice("[user] starts splicing together [target]'s nerves."), - span_notice("[user] starts manipulating [target]'s nervous system."), - ) - display_pain(target, "Your entire body goes numb!") - -/datum/surgery_step/apply_bioware/splice_nerves/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - . = ..() - if(!.) - return - - display_results( - user, - target, - span_notice("You successfully splice [target]'s nervous system!"), - span_notice("[user] successfully splices [target]'s nervous system!"), - span_notice("[user] finishes manipulating [target]'s nervous system."), - ) - display_pain(target, "You regain feeling in your body; It feels like everything's happening around you in slow motion!") diff --git a/code/modules/surgery/advanced/bioware/vein_threading.dm b/code/modules/surgery/advanced/bioware/vein_threading.dm deleted file mode 100644 index 52852991f18..00000000000 --- a/code/modules/surgery/advanced/bioware/vein_threading.dm +++ /dev/null @@ -1,58 +0,0 @@ -/datum/surgery/advanced/bioware/vein_threading - name = "Vein Threading" - desc = "A surgical procedure which severely reduces the amount of blood lost in case of injury." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/incise, - /datum/surgery_step/apply_bioware/thread_veins, - /datum/surgery_step/close, - ) - - status_effect_gained = /datum/status_effect/bioware/heart/threaded_veins - -/datum/surgery/advanced/bioware/vein_threading/mechanic - name = "Hydraulics Routing Optimization" - desc = "A robotic upgrade which severely reduces the amount of hydraulic fluid lost in case of injury." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/apply_bioware/thread_veins, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery_step/apply_bioware/thread_veins - name = "thread veins (hand)" - -/datum/surgery_step/apply_bioware/thread_veins/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You start weaving [target]'s circulatory system."), - span_notice("[user] starts weaving [target]'s circulatory system."), - span_notice("[user] starts manipulating [target]'s circulatory system."), - ) - display_pain(target, "Your entire body burns in agony!") - -/datum/surgery_step/apply_bioware/thread_veins/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - . = ..() - if(!.) - return - - display_results( - user, - target, - span_notice("You weave [target]'s circulatory system into a resistant mesh!"), - span_notice("[user] weaves [target]'s circulatory system into a resistant mesh!"), - span_notice("[user] finishes manipulating [target]'s circulatory system."), - ) - display_pain(target, "You can feel your blood pumping through reinforced veins!") diff --git a/code/modules/surgery/advanced/brainwashing.dm b/code/modules/surgery/advanced/brainwashing.dm deleted file mode 100644 index 90a81bc3797..00000000000 --- a/code/modules/surgery/advanced/brainwashing.dm +++ /dev/null @@ -1,116 +0,0 @@ -/obj/item/disk/surgery/brainwashing - name = "Brainwashing Surgery Disk" - desc = "The disk provides instructions on how to impress an order on a brain, making it the primary objective of the patient." - surgeries = list( - /datum/surgery/advanced/brainwashing, - /datum/surgery/advanced/brainwashing/mechanic, - ) - -/datum/surgery/advanced/brainwashing - name = "Brainwashing" - desc = "A surgical procedure which directly implants a directive into the patient's brain, making it their absolute priority. It can be cleared using a mindshield implant." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/brainwash, - /datum/surgery_step/close, - ) - -/datum/surgery/advanced/brainwashing/mechanic - name = "Reprogramming" - desc = "Malware which directly implants a directive into the robotic patient's operating system, making it their absolute priority. It can be cleared using a mindshield implant." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/brainwash/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/advanced/brainwashing/can_start(mob/user, mob/living/carbon/target) - if(!..()) - return FALSE - var/obj/item/organ/brain/target_brain = target.get_organ_slot(ORGAN_SLOT_BRAIN) - if(!target_brain) - return FALSE - return TRUE - -/datum/surgery_step/brainwash - name = "brainwash (hemostat)" - implements = list( - TOOL_HEMOSTAT = 85, - TOOL_WIRECUTTER = 50, - /obj/item/stack/package_wrap = 35, - /obj/item/stack/cable_coil = 15) - time = 20 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - success_sound = 'sound/items/handling/surgery/hemostat1.ogg' - failure_sound = 'sound/items/handling/surgery/organ2.ogg' - var/objective - -/datum/surgery_step/brainwash/mechanic - name = "reprogram (multitool)" - implements = list( - TOOL_MULTITOOL = 85, - TOOL_HEMOSTAT = 50, - TOOL_WIRECUTTER = 50, - /obj/item/stack/package_wrap = 35, - /obj/item/stack/cable_coil = 15) - preop_sound = 'sound/items/taperecorder/tape_flip.ogg' - success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' - -/datum/surgery_step/brainwash/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - objective = tgui_input_text(user, "Choose the objective to imprint on your victim's brain", "Brainwashing", max_length = MAX_MESSAGE_LEN) - if(!objective) - return SURGERY_STEP_FAIL - display_results( - user, - target, - span_notice("You begin to brainwash [target]..."), - span_notice("[user] begins to fix [target]'s brain."), - span_notice("[user] begins to perform surgery on [target]'s brain."), - ) - display_pain(target, "Your head pounds with unimaginable pain!") // Same message as other brain surgeries - -/datum/surgery_step/brainwash/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(!target.mind) - to_chat(user, span_warning("[target] doesn't respond to the brainwashing, as if [target.p_they()] lacked a mind...")) - return FALSE - if(HAS_MIND_TRAIT(target, TRAIT_UNCONVERTABLE)) - to_chat(user, span_warning("You hear a faint buzzing from a device inside [target]'s brain, and the brainwashing is erased.")) - return FALSE - display_results( - user, - target, - span_notice("You succeed in brainwashing [target]."), - span_notice("[user] successfully fixes [target]'s brain!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - to_chat(target, span_userdanger("A new compulsion fills your mind... you feel forced to obey it!")) - brainwash(target, objective) - message_admins("[ADMIN_LOOKUPFLW(user)] surgically brainwashed [ADMIN_LOOKUPFLW(target)] with the objective '[objective]'.") - user.log_message("has brainwashed [key_name(target)] with the objective '[objective]' using brainwashing surgery.", LOG_ATTACK) - target.log_message("has been brainwashed with the objective '[objective]' by [key_name(user)] using brainwashing surgery.", LOG_VICTIM, log_globally=FALSE) - user.log_message("surgically brainwashed [key_name(target)] with the objective '[objective]'.", LOG_GAME) - return ..() - -/datum/surgery_step/brainwash/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(target.get_organ_slot(ORGAN_SLOT_BRAIN)) - display_results( - user, - target, - span_warning("You screw up, bruising the brain tissue!"), - span_warning("[user] screws up, causing brain damage!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "Your head throbs with horrible pain!") - target.adjust_organ_loss(ORGAN_SLOT_BRAIN, 40) - else - user.visible_message(span_warning("[user] suddenly notices that the brain [user.p_they()] [user.p_were()] working on is not there anymore."), span_warning("You suddenly notice that the brain you were working on is not there anymore.")) - return FALSE diff --git a/code/modules/surgery/advanced/lobotomy.dm b/code/modules/surgery/advanced/lobotomy.dm deleted file mode 100644 index e15228976f0..00000000000 --- a/code/modules/surgery/advanced/lobotomy.dm +++ /dev/null @@ -1,129 +0,0 @@ -/datum/surgery/advanced/lobotomy - name = "Lobotomy" - desc = "An invasive surgical procedure which guarantees removal of almost all brain traumas, but might cause another permanent trauma in return." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/lobotomize, - /datum/surgery_step/close, - ) - -/datum/surgery/advanced/lobotomy/mechanic - name = "Wetware OS Destructive Defragmentation" - desc = "A destructive robotic defragmentation method which guarantees removal of almost all brain traumas, but might cause another permanent trauma in return." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/lobotomize/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/advanced/lobotomy/can_start(mob/user, mob/living/carbon/target) - . = ..() - if(!.) - return FALSE - var/obj/item/organ/brain/target_brain = target.get_organ_slot(ORGAN_SLOT_BRAIN) - if(!target_brain) - return FALSE - return TRUE - -/datum/surgery_step/lobotomize - name = "perform lobotomy (scalpel)" - implements = list( - TOOL_SCALPEL = 85, - /obj/item/melee/energy/sword = 55, - /obj/item/knife = 35, - /obj/item/shard = 25, - /obj/item = 20, - ) - time = 10 SECONDS - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/scalpel2.ogg' - failure_sound = 'sound/items/handling/surgery/organ2.ogg' - surgery_effects_mood = TRUE - -/datum/surgery_step/lobotomize/mechanic - name = "execute neural defragging (multitool)" - implements = list( - TOOL_MULTITOOL = 85, - /obj/item/melee/energy/sword = 55, - /obj/item/knife = 35, - /obj/item/shard = 25, - /obj/item = 20, - ) - preop_sound = 'sound/items/taperecorder/tape_flip.ogg' - success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' - -/datum/surgery_step/lobotomize/tool_check(mob/user, obj/item/tool) - if(implement_type == /obj/item && !tool.get_sharpness()) - return FALSE - return TRUE - -/datum/surgery_step/lobotomize/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to perform a lobotomy on [target]'s brain..."), - span_notice("[user] begins to perform a lobotomy on [target]'s brain."), - span_notice("[user] begins to perform surgery on [target]'s brain."), - ) - display_pain(target, "Your head pounds with unimaginable pain!") - -/datum/surgery_step/lobotomize/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - display_results( - user, - target, - span_notice("You succeed in lobotomizing [target]."), - span_notice("[user] successfully lobotomizes [target]!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "Your head goes totally numb for a moment, the pain is overwhelming!") - - target.cure_all_traumas(TRAUMA_RESILIENCE_LOBOTOMY) - if(target.mind && target.mind.has_antag_datum(/datum/antagonist/brainwashed)) - target.mind.remove_antag_datum(/datum/antagonist/brainwashed) - if(prob(75)) // 75% chance to get a trauma from this - switch(rand(1, 3))//Now let's see what hopefully-not-important part of the brain we cut off - if(1) - target.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_MAGIC) - if(2) - if(HAS_TRAIT(target, TRAIT_SPECIAL_TRAUMA_BOOST) && prob(50)) - target.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) - else - target.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_MAGIC) - if(3) - target.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) - return ..() - -/datum/surgery_step/lobotomize/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/obj/item/organ/brain/target_brain = target.get_organ_slot(ORGAN_SLOT_BRAIN) - if(target_brain) - display_results( - user, - target, - span_warning("You remove the wrong part, causing more damage!"), - span_notice("[user] successfully lobotomizes [target]!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "The pain in your head only seems to get worse!") - target_brain.apply_organ_damage(80) - switch(rand(1,3)) - if(1) - target.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_MAGIC) - if(2) - if(HAS_TRAIT(target, TRAIT_SPECIAL_TRAUMA_BOOST) && prob(50)) - target.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) - else - target.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_MAGIC) - if(3) - target.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) - else - user.visible_message(span_warning("[user] suddenly notices that the brain [user.p_they()] [user.p_were()] working on is not there anymore."), span_warning("You suddenly notice that the brain you were working on is not there anymore.")) - return FALSE diff --git a/code/modules/surgery/advanced/necrotic_revival.dm b/code/modules/surgery/advanced/necrotic_revival.dm deleted file mode 100644 index a791a70494d..00000000000 --- a/code/modules/surgery/advanced/necrotic_revival.dm +++ /dev/null @@ -1,52 +0,0 @@ -/datum/surgery/advanced/necrotic_revival - name = "Necrotic Revival" - desc = "An experimental surgical procedure that stimulates the growth of a Romerol tumor inside the patient's brain. Requires zombie powder or rezadone." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/bionecrosis, - /datum/surgery_step/close, - ) - -/datum/surgery/advanced/necrotic_revival/can_start(mob/user, mob/living/carbon/target) - . = ..() - var/obj/item/organ/zombie_infection/z_infection = target.get_organ_slot(ORGAN_SLOT_ZOMBIE) - if(z_infection) - return FALSE - -/datum/surgery_step/bionecrosis - name = "start bionecrosis (syringe)" - implements = list( - /obj/item/reagent_containers/syringe = 100, - /obj/item/pen = 30) - time = 5 SECONDS - chems_needed = list(/datum/reagent/toxin/zombiepowder, /datum/reagent/medicine/rezadone) - require_all_chems = FALSE - -/datum/surgery_step/bionecrosis/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to grow a romerol tumor on [target]'s brain..."), - span_notice("[user] begins to tinker with [target]'s brain..."), - span_notice("[user] begins to perform surgery on [target]'s brain."), - ) - display_pain(target, "Your head pounds with unimaginable pain!") // Same message as other brain surgeries - -/datum/surgery_step/bionecrosis/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - display_results( - user, - target, - span_notice("You succeed in growing a romerol tumor on [target]'s brain."), - span_notice("[user] successfully grows a romerol tumor on [target]'s brain!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "Your head goes totally numb for a moment, the pain is overwhelming!") - if(!target.get_organ_slot(ORGAN_SLOT_ZOMBIE)) - var/obj/item/organ/zombie_infection/z_infection = new() - z_infection.Insert(target) - return ..() diff --git a/code/modules/surgery/advanced/pacification.dm b/code/modules/surgery/advanced/pacification.dm deleted file mode 100644 index 0c4f0e7371d..00000000000 --- a/code/modules/surgery/advanced/pacification.dm +++ /dev/null @@ -1,89 +0,0 @@ -/datum/surgery/advanced/pacify - name = "Pacification" - desc = "A surgical procedure which permanently inhibits the aggression center of the brain, making the patient unwilling to cause direct harm." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/pacify, - /datum/surgery_step/close, - ) - -/datum/surgery/advanced/pacify/mechanic - name = "Aggression Suppression Programming" - desc = "Malware which permanently inhibits the aggression programming of the patient's neural network, making the patient unwilling to cause direct harm." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/pacify/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/advanced/pacify/can_start(mob/user, mob/living/carbon/target) - . = ..() - var/obj/item/organ/brain/target_brain = target.get_organ_slot(ORGAN_SLOT_BRAIN) - if(!target_brain) - return FALSE - -/datum/surgery_step/pacify - name = "rewire brain (hemostat)" - implements = list( - TOOL_HEMOSTAT = 100, - TOOL_SCREWDRIVER = 35, - /obj/item/pen = 15, - ) - time = 4 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - success_sound = 'sound/items/handling/surgery/hemostat1.ogg' - failure_sound = 'sound/items/handling/surgery/organ2.ogg' - -/datum/surgery_step/pacify/mechanic - name = "delete aggression programming (multitool)" - implements = list( - TOOL_MULTITOOL = 100, - TOOL_HEMOSTAT = 35, - TOOL_SCREWDRIVER = 35, - /obj/item/pen = 15, - ) - preop_sound = 'sound/items/taperecorder/tape_flip.ogg' - success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' - -/datum/surgery_step/pacify/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to pacify [target]..."), - span_notice("[user] begins to fix [target]'s brain."), - span_notice("[user] begins to perform surgery on [target]'s brain."), - ) - display_pain(target, "Your head pounds with unimaginable pain!") - -/datum/surgery_step/pacify/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - display_results( - user, - target, - span_notice("You succeed in neurologically pacifying [target]."), - span_notice("[user] successfully fixes [target]'s brain!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "Your head pounds... the concept of violence flashes in your head, and nearly makes you hurl!") - target.gain_trauma(/datum/brain_trauma/severe/pacifism, TRAUMA_RESILIENCE_LOBOTOMY) - return ..() - -/datum/surgery_step/pacify/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You screw up, rewiring [target]'s brain the wrong way around..."), - span_warning("[user] screws up, causing brain damage!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "Your head pounds, and it feels like it's getting worse!") - target.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) - return FALSE diff --git a/code/modules/surgery/advanced/viral_bonding.dm b/code/modules/surgery/advanced/viral_bonding.dm deleted file mode 100644 index be7e2969a0b..00000000000 --- a/code/modules/surgery/advanced/viral_bonding.dm +++ /dev/null @@ -1,60 +0,0 @@ -/datum/surgery/advanced/viral_bonding - name = "Viral Bonding" - desc = "A surgical procedure that forces a symbiotic relationship between a virus and its host. The patient must be dosed with spaceacillin, virus food, and formaldehyde." - surgery_flags = SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/viral_bond, - /datum/surgery_step/close, - ) - -/datum/surgery/advanced/viral_bonding/can_start(mob/user, mob/living/carbon/target) - . = ..() - if(!.) - return FALSE - if(!LAZYLEN(target.diseases)) - return FALSE - return TRUE - -/datum/surgery_step/viral_bond - name = "viral bond (cautery)" - implements = list( - TOOL_CAUTERY = 100, - TOOL_WELDER = 50, - /obj/item = 30) // 30% success with any hot item. - time = 10 SECONDS - chems_needed = list(/datum/reagent/medicine/spaceacillin,/datum/reagent/consumable/virus_food,/datum/reagent/toxin/formaldehyde) - -/datum/surgery_step/viral_bond/tool_check(mob/user, obj/item/tool) - if(implement_type == TOOL_WELDER || implement_type == /obj/item) - return tool.get_temperature() - - return TRUE - -/datum/surgery_step/viral_bond/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You start heating [target]'s bone marrow with [tool]..."), - span_notice("[user] starts heating [target]'s bone marrow with [tool]..."), - span_notice("[user] starts heating something in [target]'s chest with [tool]..."), - ) - display_pain(target, "You feel a searing heat spread through your chest!") - -/datum/surgery_step/viral_bond/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) - display_results( - user, - target, - span_notice("[target]'s bone marrow begins pulsing slowly. The viral bonding is complete."), - span_notice("[target]'s bone marrow begins pulsing slowly."), - span_notice("[user] finishes the operation."), - ) - display_pain(target, "You feel a faint throbbing in your chest.") - for(var/datum/disease/infected_disease as anything in target.diseases) - if(infected_disease.severity != DISEASE_SEVERITY_UNCURABLE) //no curing quirks, sweaty - infected_disease.carrier = TRUE - return TRUE diff --git a/code/modules/surgery/advanced/wingreconstruction.dm b/code/modules/surgery/advanced/wingreconstruction.dm deleted file mode 100644 index 3a27a19c391..00000000000 --- a/code/modules/surgery/advanced/wingreconstruction.dm +++ /dev/null @@ -1,59 +0,0 @@ -/datum/surgery/advanced/wing_reconstruction - name = "Wing Reconstruction" - desc = "An experimental surgical procedure that reconstructs the damaged wings of moth people. Requires Synthflesh." - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/wing_reconstruction, - ) - -/datum/surgery/advanced/wing_reconstruction/can_start(mob/user, mob/living/carbon/target) - if(!istype(target)) - return FALSE - var/obj/item/organ/wings/moth/wings = target.get_organ_slot(ORGAN_SLOT_EXTERNAL_WINGS) - if(!istype(wings, /obj/item/organ/wings/moth)) - return FALSE - return ..() && wings?.burnt - -/datum/surgery_step/wing_reconstruction - name = "start wing reconstruction (hemostat)" - implements = list( - TOOL_HEMOSTAT = 85, - TOOL_SCREWDRIVER = 35, - /obj/item/pen = 15) - time = 20 SECONDS - chems_needed = list(/datum/reagent/medicine/c2/synthflesh) - require_all_chems = FALSE - -/datum/surgery_step/wing_reconstruction/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to fix [target]'s charred wing membranes..."), - span_notice("[user] begins to fix [target]'s charred wing membranes."), - span_notice("[user] begins to perform surgery on [target]'s charred wing membranes."), - ) - display_pain(target, "Your wings sting like hell!") - -/datum/surgery_step/wing_reconstruction/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(ishuman(target)) - var/mob/living/carbon/human/human_target = target - display_results( - user, - target, - span_notice("You succeed in reconstructing [target]'s wings."), - span_notice("[user] successfully reconstructs [target]'s wings!"), - span_notice("[user] completes the surgery on [target]'s wings."), - ) - display_pain(target, "You can feel your wings again!") - var/obj/item/organ/wings/moth/wings = target.get_organ_slot(ORGAN_SLOT_EXTERNAL_WINGS) - if(istype(wings, /obj/item/organ/wings/moth)) //make sure we only heal moth wings. - wings.heal_wings(user, ALL) - - var/obj/item/organ/antennae/antennae = target.get_organ_slot(ORGAN_SLOT_EXTERNAL_ANTENNAE) //i mean we might aswell heal their antennae too - antennae?.heal_antennae(user, ALL) - - human_target.update_body_parts() - return ..() diff --git a/code/modules/surgery/amputation.dm b/code/modules/surgery/amputation.dm deleted file mode 100644 index be4fbf7ea5f..00000000000 --- a/code/modules/surgery/amputation.dm +++ /dev/null @@ -1,113 +0,0 @@ - -/datum/surgery/amputation - name = "Amputation" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_MORBID_CURIOSITY - possible_locs = list( - BODY_ZONE_R_ARM, - BODY_ZONE_L_ARM, - BODY_ZONE_L_LEG, - BODY_ZONE_R_LEG, - BODY_ZONE_HEAD, - ) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/sever_limb, - ) - -/datum/surgery/amputation/mechanic - name = "Disassemble" - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/sever_limb/mechanic, //The benefit of being robotic; people can pull you apart in an instant! Wait, that's not a benefit... - ) - -/datum/surgery/amputation/peg - name = "Detach" - requires_bodypart_type = BODYTYPE_PEG - steps = list( - /datum/surgery_step/sever_limb/peg, //Easy come, easy go - ) - -/datum/surgery/amputation/can_start(mob/user, mob/living/patient) - if(HAS_TRAIT(patient, TRAIT_NODISMEMBER)) - return FALSE - return ..() - -/datum/surgery_step/sever_limb - name = "sever limb (circular saw)" - implements = list( - /obj/item/shears = 300, - TOOL_SCALPEL = 100, - TOOL_SAW = 100, - /obj/item/shovel/serrated = 75, - /obj/item/melee/arm_blade = 80, - /obj/item/fireaxe = 50, - /obj/item/hatchet = 40, - /obj/item/knife/butcher = 25, - ) - time = 6.4 SECONDS - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/organ2.ogg' - surgery_effects_mood = TRUE - -/datum/surgery_step/sever_limb/mechanic - name = "detach limb (wrench or crowbar)" - implements = list( - /obj/item/shovel/giant_wrench = 300, - TOOL_WRENCH = 100, - TOOL_CROWBAR = 100, - TOOL_SCALPEL = 50, - TOOL_SAW = 50, - ) - time = 20 //WAIT I NEED THAT!! - preop_sound = 'sound/items/tools/ratchet.ogg' - preop_sound = 'sound/machines/airlock/doorclick.ogg' - -/datum/surgery_step/sever_limb/peg - name = "detach limb (circular saw)" - implements = list( - TOOL_SAW = 100, - /obj/item/shovel/serrated = 100, - /obj/item/fireaxe = 90, - /obj/item/hatchet = 75, - TOOL_SCALPEL = 25, - ) - time = 3 SECONDS - preop_sound = 'sound/items/handling/surgery/saw.ogg' - success_sound = 'sound/items/handling/materials/wood_drop.ogg' - -/datum/surgery_step/sever_limb/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to sever [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to sever [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - span_notice("[user] begins to sever [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - display_pain(target, "You feel a gruesome pain in your [parse_zone(target_zone)]'s joint!") - - -/datum/surgery_step/sever_limb/success(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - display_results( - user, - target, - span_notice("You sever [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] severs [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - span_notice("[user] severs [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - display_pain(target, "You can no longer feel your severed [target.parse_zone_with_bodypart(target_zone)]!") - - if(HAS_MIND_TRAIT(user, TRAIT_MORBID) && ishuman(user)) - var/mob/living/carbon/human/morbid_weirdo = user - morbid_weirdo.add_mood_event("morbid_dismemberment", /datum/mood_event/morbid_dismemberment) - - if(surgery.operated_bodypart) - var/obj/item/bodypart/target_limb = surgery.operated_bodypart - target_limb.drop_limb() - return ..() diff --git a/code/modules/surgery/asthmatic_bypass.dm b/code/modules/surgery/asthmatic_bypass.dm deleted file mode 100644 index 7e69c99a7bd..00000000000 --- a/code/modules/surgery/asthmatic_bypass.dm +++ /dev/null @@ -1,96 +0,0 @@ -/datum/surgery/asthmatic_bypass - name = "Asthmatic Bypass" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB - requires_bodypart_type = NONE - organ_to_manipulate = ORGAN_SLOT_LUNGS - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/expand_windpipe, - /datum/surgery_step/close, - ) - -/datum/surgery/asthmatic_bypass/can_start(mob/user, mob/living/patient) - . = ..() - - if (!.) - return - - return (patient.has_quirk(/datum/quirk/item_quirk/asthma)) - -/datum/surgery_step/expand_windpipe - name = "force open windpipe (retractor)" - implements = list( - TOOL_RETRACTOR = 80, - TOOL_WIRECUTTER = 45, - ) - time = 8 SECONDS - repeatable = TRUE - preop_sound = 'sound/items/handling/surgery/retractor1.ogg' - success_sound = 'sound/items/handling/surgery/retractor2.ogg' - - /// The amount of inflammation a failure or success of this surgery will reduce. - var/inflammation_reduction = 75 - -/datum/surgery_step/expand_windpipe/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You start to stretch [target]'s windpipe, trying your best to avoid nearby blood vessels..."), - span_notice("[user] begins to stretch [target]'s windpipe, taking care to avoid any nearby blood vessels."), - span_notice("[user] begins to stretch [target]'s windpipe."), - ) - display_pain(target, "You feel an agonizing stretching sensation in your neck!") - -/datum/surgery_step/expand_windpipe/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = TRUE) - if (!reduce_inflammation(user, target, tool, surgery)) - return - - default_display_results = FALSE - display_results( - user, - target, - span_notice("You stretch [target]'s windpipe with [tool], managing to avoid the nearby blood vessels and arteries."), - span_notice("[user] succeeds at stretching [target]'s windpipe with [tool], avoiding the nearby blood vessels and arteries."), - span_notice("[user] finishes stretching [target]'s windpipe.") - ) - - return ..() - -/datum/surgery_step/expand_windpipe/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob) - if (!reduce_inflammation(user, target, tool, surgery)) - return - - display_results( - user, - target, - span_bolddanger("You stretch [target]'s windpipe with [tool], but accidentally clip a few arteries!"), - span_bolddanger("[user] succeeds at stretching [target]'s windpipe with [tool], but accidentally clips a few arteries!"), - span_bolddanger("[user] finishes stretching [target]'s windpipe, but screws up!") - ) - - target.losebreath++ - - if (iscarbon(target)) - var/mob/living/carbon/carbon_patient = target - var/wound_bonus = tool.wound_bonus - var/obj/item/bodypart/head/patient_chest = carbon_patient.get_bodypart(BODY_ZONE_CHEST) - if (patient_chest) - if (prob(30)) - carbon_patient.cause_wound_of_type_and_severity(WOUND_SLASH, patient_chest, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL, WOUND_PICK_LOWEST_SEVERITY, tool) - patient_chest.receive_damage(brute = 10, wound_bonus = wound_bonus, sharpness = SHARP_EDGED, damage_source = tool) - - return FALSE - -/// Reduces the asthmatic's inflammation by [inflammation_reduction]. Called by both success and failure. -/datum/surgery_step/expand_windpipe/proc/reduce_inflammation(mob/user, mob/living/target, obj/item/tool, datum/surgery/surgery) - var/datum/quirk/item_quirk/asthma/asthma_quirk = locate(/datum/quirk/item_quirk/asthma) in target.quirks - if (isnull(asthma_quirk)) - qdel(surgery) // not really an error cause quirks can get removed during surgery? - return FALSE - - asthma_quirk.adjust_inflammation(-inflammation_reduction) - return TRUE diff --git a/code/modules/surgery/autopsy.dm b/code/modules/surgery/autopsy.dm deleted file mode 100644 index f04f45fe582..00000000000 --- a/code/modules/surgery/autopsy.dm +++ /dev/null @@ -1,66 +0,0 @@ -/datum/surgery/autopsy - name = "Autopsy" - surgery_flags = SURGERY_IGNORE_CLOTHES | SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/autopsy, - /datum/surgery_step/close, - ) - -/datum/surgery/autopsy/mechanic - name = "System Failure Analysis" - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/autopsy, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/autopsy/can_start(mob/user, mob/living/patient) - if(!..()) - return FALSE - if(patient.stat != DEAD) - return FALSE - if(HAS_TRAIT_FROM(patient, TRAIT_DISSECTED, AUTOPSY_TRAIT)) - return FALSE - return TRUE - -/datum/surgery_step/autopsy - name = "Perform Autopsy (autopsy scanner)" - implements = list(/obj/item/autopsy_scanner = 100) - time = 10 SECONDS - success_sound = 'sound/machines/printer.ogg' - -/datum/surgery_step/autopsy/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin performing an autopsy on [target]..."), - span_notice("[user] uses [tool] to perform an autopsy on [target]."), - span_notice("[user] uses [tool] on [target]'s chest."), - ) - display_pain(target, "You feel a burning sensation in your chest!") - -/datum/surgery_step/autopsy/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/autopsy_scanner/tool, datum/surgery/surgery, default_display_results = FALSE) - ADD_TRAIT(target, TRAIT_DISSECTED, AUTOPSY_TRAIT) - ADD_TRAIT(target, TRAIT_SURGICALLY_ANALYZED, AUTOPSY_TRAIT) - tool.scan_cadaver(user, target) - var/obj/machinery/computer/operating/operating_computer = surgery.locate_operating_computer(get_turf(target)) - if (!isnull(operating_computer)) - SEND_SIGNAL(operating_computer, COMSIG_OPERATING_COMPUTER_AUTOPSY_COMPLETE, target) - if(HAS_MIND_TRAIT(user, TRAIT_MORBID)) - user.add_mood_event("morbid_dissection_success", /datum/mood_event/morbid_dissection_success) - return ..() - -/datum/surgery_step/autopsy/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_warning("You screw up, bruising [target]'s chest!"), - span_warning("[user] screws up, brusing [target]'s chest!"), - span_warning("[user] screws up!"), - ) - target.adjust_brute_loss(5) diff --git a/code/modules/surgery/blood_filter.dm b/code/modules/surgery/blood_filter.dm deleted file mode 100644 index 7cebacd1606..00000000000 --- a/code/modules/surgery/blood_filter.dm +++ /dev/null @@ -1,106 +0,0 @@ -/datum/surgery/blood_filter - name = "Filter blood" - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/incise, - /datum/surgery_step/filter_blood, - /datum/surgery_step/close, - ) - - -/datum/surgery/blood_filter/mechanic - name = "Hydraulics Purge" - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/filter_blood, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/blood_filter/can_start(mob/user, mob/living/carbon/target) - if(HAS_TRAIT(target, TRAIT_HUSK)) //You can filter the blood of a dead person just not husked - return FALSE - return ..() - -/datum/surgery_step/filter_blood/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) - display_results( - user, - target, - span_notice("You begin filtering [target]'s blood..."), - span_notice("[user] uses [tool] to filter [target]'s blood."), - span_notice("[user] uses [tool] on [target]'s chest."), - ) - if(!..()) - return - while(has_filterable_chems(target, tool)) - if(!..()) - break - -/** - * Checks if the mob contains chems we can filter - * - * If the blood filter's whitelist is empty this checks if the mob contains any chems - * If the whitelist contains chems it checks if any chems in the mob match chems in the whitelist - * - * Arguments: - * * target - The mob to check the chems of - * * bloodfilter - The blood filter to check the whitelist of - */ -/datum/surgery_step/filter_blood/proc/has_filterable_chems(mob/living/carbon/target, obj/item/blood_filter/bloodfilter) - if(!length(target.reagents?.reagent_list)) - bloodfilter.audible_message(span_notice("[bloodfilter] pings as it reports no chemicals detected in [target]'s blood.")) - playsound(get_turf(target), 'sound/machines/ping.ogg', 75, TRUE, falloff_exponent = 12, falloff_distance = 1) - return FALSE - - if(!length(bloodfilter.whitelist)) - return TRUE - - for(var/datum/reagent/chem as anything in target.reagents.reagent_list) - if(chem.type in bloodfilter.whitelist) - return TRUE - - return FALSE - -/datum/surgery_step/filter_blood - name = "Filter blood (blood filter)" - implements = list(/obj/item/blood_filter = 95) - repeatable = TRUE - time = 2.5 SECONDS - success_sound = 'sound/machines/card_slide.ogg' - -/datum/surgery_step/filter_blood/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_pain(target, "You feel a throbbing pain in your chest!") - -/datum/surgery_step/filter_blood/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/obj/item/blood_filter/bloodfilter = tool - if(target.reagents?.total_volume) - for(var/datum/reagent/chem as anything in target.reagents.reagent_list) - if(!length(bloodfilter.whitelist) || !(chem.type in bloodfilter.whitelist)) - target.reagents.remove_reagent(chem.type, clamp(round(chem.volume * 0.22, 0.2), 0.4, 10)) - display_results( - user, - target, - span_notice("\The [tool] completes a cycle filtering [target]'s blood."), - span_notice("\The [tool] whirrs as it filters [target]'s blood."), - span_notice("\The [tool] whirrs as it pumps."), - ) - - if(locate(/obj/item/healthanalyzer) in user.held_items) - chemscan(user, target) - - return ..() - -/datum/surgery_step/filter_blood/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_warning("You screw up, bruising [target]'s chest!"), - span_warning("[user] screws up, brusing [target]'s chest!"), - span_warning("[user] screws up!"), - ) - target.adjust_brute_loss(5) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 74266fe033f..f3f11396842 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -96,9 +96,6 @@ var/brutestate = 0 var/burnstate = 0 - ///Gradually increases while burning when at full damage, destroys the limb when at 100 - var/cremation_progress = 0 - //Multiplicative damage modifiers /// Brute damage gets multiplied by this on receive_damage() var/brute_modifier = 1 @@ -227,6 +224,9 @@ /// The cached info about the blood this organ belongs to, set during on_removal() var/list/blood_dna_info + /// What state is the bodypart in for determining surgery availability + VAR_FINAL/surgery_state = NONE + /obj/item/bodypart/apply_fantasy_bonuses(bonus) . = ..() unarmed_damage_low = modify_fantasy_variable("unarmed_damage_low", unarmed_damage_low, bonus, minimum = 1) @@ -260,6 +260,16 @@ else blood_dna_info = list("Unknown DNA" = get_blood_type(BLOOD_TYPE_O_PLUS)) + var/innate_state = NONE + if(!LIMB_HAS_SKIN(src)) + innate_state |= SKINLESS_SURGERY_STATES + if(!LIMB_HAS_BONES(src)) + innate_state |= BONELESS_SURGERY_STATES + if(!LIMB_HAS_VESSELS(src)) + innate_state |= VESSELLESS_SURGERY_STATES + if(innate_state) + add_surgical_state(innate_state) + name = "[limb_id] [parse_zone(body_zone)]" update_icon_dropped() refresh_bleed_rate() @@ -386,6 +396,10 @@ if(wound_desc) check_list += "\t[wound_desc]" + var/surgery_check = get_surgery_self_check() + if(surgery_check) + check_list += "\t[surgery_check]" + for(var/obj/item/embedded_thing as anything in embedded_objects) if(embedded_thing.get_embed().stealthy_embed) continue @@ -400,18 +414,121 @@ if(current_gauze) check_list += span_notice("\tThere is some [current_gauze.name] wrapped around it.") else if(can_bleed()) + var/bleed_text = "" switch(cached_bleed_rate) if(0.2 to 1) - check_list += span_warning("\tIt's lightly bleeding.") + bleed_text = span_warning("It's lightly bleeding.") if(1 to 2) - check_list += span_warning("\tIt's bleeding.") + bleed_text = span_warning("It's bleeding.") if(3 to 4) - check_list += span_warning("\tIt's bleeding heavily!") + bleed_text = span_warning("It's bleeding heavily!") if(4 to INFINITY) - check_list += span_warning("\tIt's bleeding profusely!") + bleed_text = span_warning("It's bleeding profusely!") + + if(bleed_text) + check_list += "\t[span_tooltip("You are loosing blood. You should wrap your limb in gauze \ + or apply pressure to it by grabbing yourself (while targeting the limb) to stem the flow.", bleed_text)]" return jointext(check_list, "
    ") +/// Returns surgery self-check information for this bodypart +/obj/item/bodypart/proc/get_surgery_self_check() + var/list/surgery_message = list() + var/reported_state = surgery_state + if(!LIMB_HAS_SKIN(src)) + reported_state &= ~SKINLESS_SURGERY_STATES + if(!LIMB_HAS_BONES(src)) + reported_state &= ~BONELESS_SURGERY_STATES + if(!LIMB_HAS_VESSELS(src)) + reported_state &= ~VESSELLESS_SURGERY_STATES + + if(HAS_SURGERY_STATE(reported_state, SURGERY_SKIN_CUT)) + surgery_message += "skin has been incised" + if(HAS_SURGERY_STATE(reported_state, SURGERY_SKIN_OPEN)) + surgery_message += "skin is opened" + + // We can only see these if the skin is open + // And we check the real state rather than reported_state + if(LIMB_HAS_ANY_SURGERY_STATE(src, ALL_SURGERY_SKIN_STATES)) + if(HAS_SURGERY_STATE(reported_state, SURGERY_VESSELS_UNCLAMPED)) + surgery_message += "blood vessels are unclamped and bleeding" + if(HAS_SURGERY_STATE(reported_state, SURGERY_VESSELS_CLAMPED)) + surgery_message += "blood vessels are clamped shut" + if(HAS_SURGERY_STATE(reported_state, SURGERY_ORGANS_CUT)) + surgery_message += "organs have been incised" + if(HAS_SURGERY_STATE(reported_state, SURGERY_BONE_SAWED)) + surgery_message += "bones have been sawed apart" + if(HAS_SURGERY_STATE(reported_state, SURGERY_BONE_DRILLED)) + surgery_message += "bones have been drilled through" + + if(HAS_SURGERY_STATE(reported_state, SURGERY_PROSTHETIC_UNSECURED)) + surgery_message += "prosthetic item is unsecured" + if(HAS_SURGERY_STATE(reported_state, SURGERY_PLASTIC_APPLIED)) + surgery_message += "got a layer of plastic applied to it" + if(HAS_SURGERY_STATE(reported_state, SURGERY_CAVITY_WIDENED)) + surgery_message += "chest cavity is wide open" + + if(length(surgery_message)) + return span_tooltip("Your limb is undergoing surgery. If no doctors are around, \ + you could suture or cauterize yourself to cancel it.", span_warning("Its [english_list(surgery_message)]!")) + return "" + +/// Returns surgery examine information for this bodypart +/obj/item/bodypart/proc/get_surgery_examine() + var/t_his = owner.p_their() + var/t_His = owner.p_Their() + var/single_message = "" + var/list/sub_messages = list() + var/reported_state = surgery_state + if(!LIMB_HAS_SKIN(src)) + reported_state &= ~SKINLESS_SURGERY_STATES + if(!LIMB_HAS_BONES(src)) + reported_state &= ~BONELESS_SURGERY_STATES + if(!LIMB_HAS_VESSELS(src)) + reported_state &= ~VESSELLESS_SURGERY_STATES + + if(HAS_SURGERY_STATE(reported_state, SURGERY_SKIN_CUT)) + sub_messages += "skin has been incised" + single_message = "The skin on [t_his] [plaintext_zone] has been incised." + if(HAS_SURGERY_STATE(reported_state, SURGERY_SKIN_OPEN)) + sub_messages += "skin is opened" + single_message = "The skin on [t_his] [plaintext_zone] is opened." + + // We can only see these if the skin is open + // And we check the real state rather than reported_state + if(LIMB_HAS_ANY_SURGERY_STATE(src, ALL_SURGERY_SKIN_STATES)) + if(HAS_SURGERY_STATE(reported_state, SURGERY_VESSELS_UNCLAMPED)) + sub_messages += "blood vessels are unclamped[cached_bleed_rate ? " and bleeding" : ""]" + single_message = "The blood vessels in [t_his] [plaintext_zone] are unclamped[cached_bleed_rate ? " and bleeding!" : "."]" + if(HAS_SURGERY_STATE(reported_state, SURGERY_VESSELS_CLAMPED)) + sub_messages += "blood vessels are clamped shut" + single_message = "The blood vessels in [t_his] [plaintext_zone] are clamped shut." + if(HAS_SURGERY_STATE(reported_state, SURGERY_ORGANS_CUT)) + sub_messages += "the organs within have been incised" + single_message = "The organs in [t_his] [plaintext_zone] have been incised." + if(HAS_SURGERY_STATE(reported_state, SURGERY_BONE_SAWED)) + sub_messages += "the bones within have been sawed apart" + single_message = "The bones in [t_his] [plaintext_zone] have been sawed apart." + if(HAS_SURGERY_STATE(reported_state, SURGERY_BONE_DRILLED)) + sub_messages += "the bones within have been drilled through" + single_message = "The bones in [t_his] [plaintext_zone] have been drilled through." + + if(HAS_SURGERY_STATE(reported_state, SURGERY_PROSTHETIC_UNSECURED)) + sub_messages += "prosthetic item is unsecured" + single_message = "[t_His] [plaintext_zone] is unsecured and loose!" + if(HAS_SURGERY_STATE(reported_state, SURGERY_PLASTIC_APPLIED)) + sub_messages += "got a layer of plastic applied to it" + single_message = "A layer of plastic has been applied to [t_his] [plaintext_zone]." + if(HAS_SURGERY_STATE(reported_state, SURGERY_CAVITY_WIDENED)) + sub_messages += "the chest cavity is wide open" + single_message = "[t_His] chest cavity is wide open!" + + if(length(sub_messages) >= 2) + return span_danger("[t_His] [plaintext_zone]'s [english_list(sub_messages)].") + if(single_message) + return span_danger(single_message) + return "" + /obj/item/bodypart/blob_act() receive_damage(max_damage, wound_bonus = CANT_WOUND) @@ -723,7 +840,6 @@ update_disabled() if(updating_health) owner.updatehealth() - cremation_progress = min(0, cremation_progress - ((brute_dam + burn_dam)*(100/max_damage))) return update_bodypart_damage_state() ///Sets the damage of a bodypart when it is created. @@ -847,6 +963,9 @@ UnregisterSignal(old_owner, list(COMSIG_ATOM_RESTYLE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_LIVING_SET_BODY_POSITION)) + if(LIMB_HAS_SURGERY_STATE(src, ALL_SURGERY_FISH_STATES(body_zone))) + qdel(old_owner.GetComponent(/datum/component/fishing_spot)) + /// Apply ownership of a limb to someone, giving the appropriate traits, updates and signals /obj/item/bodypart/proc/apply_ownership(mob/living/carbon/new_owner) SHOULD_CALL_PARENT(TRUE) @@ -880,6 +999,9 @@ forceMove(owner) RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_forced_removal)) //this must be set after we moved, or we insta gib + if(LIMB_HAS_SURGERY_STATE(src, ALL_SURGERY_FISH_STATES(body_zone))) + owner.AddComponent(/datum/component/fishing_spot, /datum/fish_source/surgery) + /// Called on addition of a bodypart /obj/item/bodypart/proc/on_adding(mob/living/carbon/new_owner) SHOULD_CALL_PARENT(TRUE) @@ -1334,6 +1456,28 @@ if(generic_bleedstacks > 0) cached_bleed_rate += 0.5 + // In 99% of situations we won't get to this point if we aren't wired or blooded + // But I'm covering my ass in case someone adds some weird new species + if(biological_state & BIOSTATE_HAS_VESSELS) + var/surgery_bloodloss = 0 + // better clamp those up quick + if(HAS_ANY_SURGERY_STATE(surgery_state, SURGERY_VESSELS_UNCLAMPED)) + surgery_bloodloss += 1.5 + // better, but still not exactly ideal + else if(HAS_ANY_SURGERY_STATE(surgery_state, SURGERY_VESSELS_CLAMPED|SURGERY_ORGANS_CUT)) + surgery_bloodloss += 0.2 + + // modify rate so cutting everything open won't nuke people + if(body_zone == BODY_ZONE_HEAD) + surgery_bloodloss *= 0.5 + else if(body_zone != BODY_ZONE_CHEST) + surgery_bloodloss *= 0.25 + // bonus for being gauzed up + if(current_gauze) + surgery_bloodloss *= 0.4 + + cached_bleed_rate += surgery_bloodloss + for(var/obj/item/embeddies as anything in embedded_objects) if(!embeddies.get_embed().is_harmless()) cached_bleed_rate += 0.25 @@ -1518,6 +1662,8 @@ return "flesh" if (biological_state & BIO_WIRED) return "wiring" + if (biological_state & BIO_CHITIN) + return "chitin" return "error" @@ -1531,6 +1677,8 @@ return "shreds of ligaments" if (biological_state & BIO_WOOD) return "splinters of poorly manufactured wood" + if (biological_state & BIO_CHITIN) + return "fragments of chitin" return "error" @@ -1547,3 +1695,56 @@ if(isnull(owner)) return REMOVE_TRAIT(owner, old_trait, bodypart_trait_source) + +/// Add one or multiple surgical states to the bodypart +/obj/item/bodypart/proc/add_surgical_state(new_states) + if(!new_states) + CRASH("add_surgical_state called with no new states to add") + if((surgery_state & new_states) == new_states) + return + + var/old_states = surgery_state + surgery_state |= new_states + update_surgical_state(old_states, new_states) + +/// Remove one or multiple surgical states from the bodypart +/obj/item/bodypart/proc/remove_surgical_state(removing_states) + if(!removing_states) + CRASH("remove_surgical_state called with no states to remove") + if(!(surgery_state & removing_states)) + return + + // inherent to the biostate, don't remove them + if(!LIMB_HAS_SKIN(src)) + removing_states &= ~SKINLESS_SURGERY_STATES + if(!LIMB_HAS_BONES(src)) + removing_states &= ~BONELESS_SURGERY_STATES + if(!LIMB_HAS_VESSELS(src)) + removing_states &= ~VESSELLESS_SURGERY_STATES + if(!removing_states) + return + + var/old_states = surgery_state + surgery_state &= ~removing_states + update_surgical_state(old_states, removing_states) + +/// Called when surgical state changes so we can react to it +/obj/item/bodypart/proc/update_surgical_state(old_state, changed_states) + if(HAS_ANY_SURGERY_STATE(changed_states, SURGERY_ORGANS_CUT|ALL_SURGERY_VESSEL_STATES)) + refresh_bleed_rate() + + if(isnull(owner)) + return + SEND_SIGNAL(owner, COMSIG_LIVING_UPDATING_SURGERY_STATE, old_state, surgery_state, changed_states) + if(HAS_SURGERY_STATE(surgery_state, ALL_SURGERY_FISH_STATES(body_zone))) + owner.AddComponent(/datum/component/fishing_spot, /datum/fish_source/surgery) // no-op if they already have one + else if(HAS_SURGERY_STATE(old_state, ALL_SURGERY_FISH_STATES(body_zone))) + qdel(owner.GetComponent(/datum/component/fishing_spot)) + +/obj/item/bodypart/vv_edit_var(vname, vval) + if(vname != NAMEOF(src, surgery_state)) + return ..() + + var/old_state = surgery_state + . = ..() + update_surgical_state(old_state, surgery_state ^ old_state) diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 73f2372501d..2960dee5577 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -106,12 +106,6 @@ for(var/datum/wound/wound as anything in wounds) wound.remove_wound(TRUE) - for(var/datum/surgery/surgery as anything in phantom_owner.surgeries) //if we had an ongoing surgery on that limb, we stop it. - if(surgery.operated_bodypart == src) - phantom_owner.surgeries -= surgery - qdel(surgery) - break - if(!phantom_owner.has_embedded_objects()) phantom_owner.clear_alert(ALERT_EMBEDDED_OBJECT) phantom_owner.clear_mood_event("embedded") @@ -246,17 +240,17 @@ return ..() -///Try to attach this bodypart to a mob, while replacing one if it exists, does nothing if it fails. -/obj/item/bodypart/proc/replace_limb(mob/living/carbon/limb_owner, special) +///Try to attach this bodypart to a mob, while replacing one if it exists, does nothing if it fails. Returns TRUE on success, FALSE on failure. +/obj/item/bodypart/proc/replace_limb(mob/living/carbon/limb_owner) if(!istype(limb_owner)) - return + return FALSE var/obj/item/bodypart/old_limb = limb_owner.get_bodypart(body_zone) - if(old_limb) - old_limb.drop_limb(TRUE) + old_limb?.drop_limb(TRUE) - . = try_attach_limb(limb_owner, special) - if(!.) //If it failed to replace, re-attach their old limb as if nothing happened. - old_limb.try_attach_limb(limb_owner, TRUE) + if(!try_attach_limb(limb_owner, TRUE)) //If it failed to replace, re-attach their old limb as if nothing happened. + old_limb?.try_attach_limb(limb_owner, TRUE) + return FALSE + return TRUE ///Checks if a limb qualifies as a BODYPART_IMPLANTED /obj/item/bodypart/proc/check_for_frankenstein(mob/living/carbon/human/monster) @@ -295,16 +289,11 @@ LAZYREMOVE(new_limb_owner.body_zone_dismembered_by, body_zone) if(special) //non conventional limb attachment - for(var/datum/surgery/attach_surgery as anything in new_limb_owner.surgeries) //if we had an ongoing surgery to attach a new limb, we stop it. - var/surgery_zone = check_zone(attach_surgery.location) - if(surgery_zone == body_zone) - new_limb_owner.surgeries -= attach_surgery - qdel(attach_surgery) - break - for(var/obj/item/organ/organ as anything in new_limb_owner.organs) if(deprecise_zone(organ.zone) != body_zone) continue + if(organ.bodypart_owner == src) // someone manually updated the organs already + continue organ.bodypart_insert(src) for(var/datum/wound/wound as anything in wounds) diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index a814f2719cb..6a269d8f528 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -267,6 +267,7 @@ burn_modifier = LIMB_ALIEN_BURN_DAMAGE_MULTIPLIER bodytype = BODYTYPE_ALIEN | BODYTYPE_ORGANIC bodyshape = BODYSHAPE_HUMANOID + biological_state = BIO_STANDARD_ALIEN /obj/item/bodypart/head/larva icon = 'icons/mob/human/species/alien/bodyparts.dmi' diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index d6086208672..c6ec6b73cb0 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -116,6 +116,7 @@ burn_modifier = LIMB_ALIEN_BURN_DAMAGE_MULTIPLIER acceptable_bodyshape = BODYSHAPE_HUMANOID wing_types = null + biological_state = BIO_STANDARD_ALIEN /obj/item/bodypart/chest/larva icon = 'icons/mob/human/species/alien/bodyparts.dmi' @@ -297,6 +298,7 @@ burn_modifier = LIMB_ALIEN_BURN_DAMAGE_MULTIPLIER should_draw_greyscale = FALSE appendage_noun = "scythe-like hand" + biological_state = BIO_STANDARD_ALIEN /obj/item/bodypart/arm/right name = "right arm" @@ -396,6 +398,7 @@ burn_modifier = LIMB_ALIEN_BURN_DAMAGE_MULTIPLIER should_draw_greyscale = FALSE appendage_noun = "scythe-like hand" + biological_state = BIO_STANDARD_ALIEN /// Parent Type for legs, should not appear in game. /obj/item/bodypart/leg @@ -532,6 +535,7 @@ max_damage = LIMB_MAX_HP_ALIEN_LIMBS burn_modifier = LIMB_ALIEN_BURN_DAMAGE_MULTIPLIER should_draw_greyscale = FALSE + biological_state = BIO_STANDARD_ALIEN /obj/item/bodypart/leg/right name = "right leg" @@ -623,3 +627,4 @@ max_damage = LIMB_MAX_HP_ALIEN_LIMBS burn_modifier = LIMB_ALIEN_BURN_DAMAGE_MULTIPLIER should_draw_greyscale = FALSE + biological_state = BIO_STANDARD_ALIEN diff --git a/code/modules/surgery/bone_mending.dm b/code/modules/surgery/bone_mending.dm deleted file mode 100644 index 24cea41ce30..00000000000 --- a/code/modules/surgery/bone_mending.dm +++ /dev/null @@ -1,268 +0,0 @@ - -/////BONE FIXING SURGERIES////// - -///// Repair Hairline Fracture (Severe) -/datum/surgery/repair_bone_hairline - name = "Repair bone fracture (hairline)" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - targetable_wound = /datum/wound/blunt/bone/severe - possible_locs = list( - BODY_ZONE_R_ARM, - BODY_ZONE_L_ARM, - BODY_ZONE_R_LEG, - BODY_ZONE_L_LEG, - BODY_ZONE_CHEST, - BODY_ZONE_HEAD, - ) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/repair_bone_hairline, - /datum/surgery_step/close, - ) - -///// Repair Compound Fracture (Critical) -/datum/surgery/repair_bone_compound - name = "Repair Compound Fracture" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - targetable_wound = /datum/wound/blunt/bone/critical - possible_locs = list( - BODY_ZONE_R_ARM, - BODY_ZONE_L_ARM, - BODY_ZONE_R_LEG, - BODY_ZONE_L_LEG, - BODY_ZONE_CHEST, - BODY_ZONE_HEAD, - ) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/reset_compound_fracture, - /datum/surgery_step/repair_bone_compound, - /datum/surgery_step/close, - ) - -//SURGERY STEPS - -///// Repair Hairline Fracture (Severe) -/datum/surgery_step/repair_bone_hairline - name = "repair hairline fracture (bonesetter/bone gel/tape)" - implements = list( - TOOL_BONESET = 100, - /obj/item/stack/medical/bone_gel = 100, - /obj/item/stack/sticky_tape/surgical = 100, - /obj/item/stack/sticky_tape/super = 50, - /obj/item/stack/sticky_tape = 30) - time = 4 SECONDS - -/datum/surgery_step/repair_bone_hairline/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(surgery.operated_wound) - display_results( - user, - target, - span_notice("You begin to repair the fracture in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]..."), - span_notice("[user] begins to repair the fracture in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)] with [tool]."), - span_notice("[user] begins to repair the fracture in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), - ) - display_pain(target, "Your [target.parse_zone_with_bodypart(user.zone_selected)] aches with pain!") - else - user.visible_message(span_notice("[user] looks for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), span_notice("You look for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]...")) - -/datum/surgery_step/repair_bone_hairline/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(surgery.operated_wound) - if(isstack(tool)) - var/obj/item/stack/used_stack = tool - used_stack.use(1) - display_results( - user, - target, - span_notice("You successfully repair the fracture in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] successfully repairs the fracture in [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), - span_notice("[user] successfully repairs the fracture in [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - log_combat(user, target, "repaired a hairline fracture in", addition="COMBAT_MODE: [uppertext(user.combat_mode)]") - qdel(surgery.operated_wound) - else - to_chat(user, span_warning("[target] has no hairline fracture there!")) - return ..() - -/datum/surgery_step/repair_bone_hairline/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob = 0) - ..() - if(isstack(tool)) - var/obj/item/stack/used_stack = tool - used_stack.use(1) - - - -///// Reset Compound Fracture (Crticial) -/datum/surgery_step/reset_compound_fracture - name = "reset bone (bonesetter)" - implements = list( - TOOL_BONESET = 100, - /obj/item/stack/sticky_tape/surgical = 60, - /obj/item/stack/sticky_tape/super = 40, - /obj/item/stack/sticky_tape = 20) - time = 4 SECONDS - -/datum/surgery_step/reset_compound_fracture/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(surgery.operated_wound) - display_results( - user, - target, - span_notice("You begin to reset the bone in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]..."), - span_notice("[user] begins to reset the bone in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)] with [tool]."), - span_notice("[user] begins to reset the bone in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), - ) - display_pain(target, "The aching pain in your [target.parse_zone_with_bodypart(user.zone_selected)] is overwhelming!") - else - user.visible_message(span_notice("[user] looks for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), span_notice("You look for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]...")) - -/datum/surgery_step/reset_compound_fracture/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(surgery.operated_wound) - if(isstack(tool)) - var/obj/item/stack/used_stack = tool - used_stack.use(1) - display_results( - user, - target, - span_notice("You successfully reset the bone in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] successfully resets the bone in [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), - span_notice("[user] successfully resets the bone in [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - log_combat(user, target, "reset a compound fracture in", addition="COMBAT MODE: [uppertext(user.combat_mode)]") - else - to_chat(user, span_warning("[target] has no compound fracture there!")) - return ..() - -/datum/surgery_step/reset_compound_fracture/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob = 0) - ..() - if(isstack(tool)) - var/obj/item/stack/used_stack = tool - used_stack.use(1) - -#define IMPLEMENTS_THAT_FIX_BONES list( \ - /obj/item/stack/medical/bone_gel = 100, \ - /obj/item/stack/sticky_tape/surgical = 100, \ - /obj/item/stack/sticky_tape/super = 50, \ - /obj/item/stack/sticky_tape = 30, \ -) - - -///// Repair Compound Fracture (Crticial) -/datum/surgery_step/repair_bone_compound - name = "repair compound fracture (bone gel/tape)" - implements = IMPLEMENTS_THAT_FIX_BONES - time = 4 SECONDS - -/datum/surgery_step/repair_bone_compound/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(surgery.operated_wound) - display_results( - user, - target, - span_notice("You begin to repair the fracture in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]..."), - span_notice("[user] begins to repair the fracture in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)] with [tool]."), - span_notice("[user] begins to repair the fracture in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), - ) - display_pain(target, "The aching pain in your [target.parse_zone_with_bodypart(user.zone_selected)] is overwhelming!") - else - user.visible_message(span_notice("[user] looks for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), span_notice("You look for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]...")) - -/datum/surgery_step/repair_bone_compound/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(surgery.operated_wound) - if(isstack(tool)) - var/obj/item/stack/used_stack = tool - used_stack.use(1) - display_results( - user, - target, - span_notice("You successfully repair the fracture in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] successfully repairs the fracture in [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), - span_notice("[user] successfully repairs the fracture in [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - log_combat(user, target, "repaired a compound fracture in", addition="COMBAT MODE: [uppertext(user.combat_mode)]") - qdel(surgery.operated_wound) - else - to_chat(user, span_warning("[target] has no compound fracture there!")) - return ..() - -/datum/surgery_step/repair_bone_compound/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob = 0) - ..() - if(isstack(tool)) - var/obj/item/stack/used_stack = tool - used_stack.use(1) - -/// Surgery to repair cranial fissures -/datum/surgery/cranial_reconstruction - name = "Cranial reconstruction" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - targetable_wound = /datum/wound/cranial_fissure - possible_locs = list( - BODY_ZONE_HEAD, - ) - steps = list( - /datum/surgery_step/clamp_bleeders/discard_skull_debris, - /datum/surgery_step/repair_skull - ) - -/datum/surgery_step/clamp_bleeders/discard_skull_debris - name = "discard skull debris (hemostat)" - implements = list( - TOOL_HEMOSTAT = 100, - TOOL_WIRECUTTER = 40, - TOOL_SCREWDRIVER = 40, - ) - time = 2.4 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - -/datum/surgery_step/clamp_bleeders/discard_skull_debris/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to discard the smaller skull debris in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to discard the smaller skull debris in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to poke around in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - ) - - display_pain(target, "Your brain feels like it's getting stabbed by little shards of glass!") - -/datum/surgery_step/repair_skull - name = "repair skull (bone gel/tape)" - implements = IMPLEMENTS_THAT_FIX_BONES - time = 4 SECONDS - -/datum/surgery_step/repair_skull/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - ASSERT(surgery.operated_wound, "Repairing skull without a wound") - - display_results( - user, - target, - span_notice("You begin to repair [target]'s skull as best you can..."), - span_notice("[user] begins to repair [target]'s skull with [tool]."), - span_notice("[user] begins to repair [target]'s skull."), - ) - - display_pain(target, "You can feel pieces of your skull rubbing against your brain!") - -/datum/surgery_step/repair_skull/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) - if (isnull(surgery.operated_wound)) - to_chat(user, span_warning("[target]'s skull is fine!")) - return ..() - - - if (isstack(tool)) - var/obj/item/stack/used_stack = tool - used_stack.use(1) - - display_results( - user, - target, - span_notice("You successfully repair [target]'s skull."), - span_notice("[user] successfully repairs [target]'s skull with [tool]."), - span_notice("[user] successfully repairs [target]'s skull.") - ) - - qdel(surgery.operated_wound) - - return ..() - -#undef IMPLEMENTS_THAT_FIX_BONES diff --git a/code/modules/surgery/brain_surgery.dm b/code/modules/surgery/brain_surgery.dm deleted file mode 100644 index 0b1c3862627..00000000000 --- a/code/modules/surgery/brain_surgery.dm +++ /dev/null @@ -1,92 +0,0 @@ -/datum/surgery/brain_surgery - name = "Brain surgery" - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/fix_brain, - /datum/surgery_step/close, - ) - -/datum/surgery/brain_surgery/mechanic - name = "Wetware OS Diagnostics" - requires_bodypart_type = BODYTYPE_ROBOTIC - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/fix_brain/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery_step/fix_brain - name = "fix brain (hemostat)" - implements = list( - TOOL_HEMOSTAT = 85, - TOOL_SCREWDRIVER = 35, - /obj/item/pen = 15) //don't worry, pouring some alcohol on their open brain will get that chance to 100 - repeatable = TRUE - time = 100 //long and complicated - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - success_sound = 'sound/items/handling/surgery/hemostat1.ogg' - failure_sound = 'sound/items/handling/surgery/organ2.ogg' - -/datum/surgery_step/fix_brain/mechanic - name = "perform neural debugging (hemostat or multitool)" - implements = list( - TOOL_HEMOSTAT = 85, - TOOL_MULTITOOL = 85, - TOOL_SCREWDRIVER = 35, - /obj/item/pen = 15) - preop_sound = 'sound/items/taperecorder/tape_flip.ogg' - success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' - -/datum/surgery/brain_surgery/can_start(mob/user, mob/living/carbon/target) - return target.get_organ_slot(ORGAN_SLOT_BRAIN) && ..() - -/datum/surgery_step/fix_brain/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to fix [target]'s brain..."), - span_notice("[user] begins to fix [target]'s brain."), - span_notice("[user] begins to perform surgery on [target]'s brain."), - ) - display_pain(target, "Your head pounds with unimaginable pain!") - -/datum/surgery_step/fix_brain/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - display_results( - user, - target, - span_notice("You succeed in fixing [target]'s brain."), - span_notice("[user] successfully fixes [target]'s brain!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "The pain in your head receeds, thinking becomes a bit easier!") - if(target.mind?.has_antag_datum(/datum/antagonist/brainwashed)) - target.mind.remove_antag_datum(/datum/antagonist/brainwashed) - target.set_organ_loss(ORGAN_SLOT_BRAIN, target.get_organ_loss(ORGAN_SLOT_BRAIN) - 50) //we set damage in this case in order to clear the "failing" flag - target.cure_all_traumas(TRAUMA_RESILIENCE_SURGERY) - if(target.get_organ_loss(ORGAN_SLOT_BRAIN) > 0) - to_chat(user, "[target]'s brain looks like it could be fixed further.") - return ..() - -/datum/surgery_step/fix_brain/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(target.get_organ_slot(ORGAN_SLOT_BRAIN)) - display_results( - user, - target, - span_warning("You screw up, causing more damage!"), - span_warning("[user] screws up, causing brain damage!"), - span_notice("[user] completes the surgery on [target]'s brain."), - ) - display_pain(target, "Your head throbs with horrible pain; thinking hurts!") - target.adjust_organ_loss(ORGAN_SLOT_BRAIN, 60) - target.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) - else - user.visible_message(span_warning("[user] suddenly notices that the brain [user.p_they()] [user.p_were()] working on is not there anymore."), span_warning("You suddenly notice that the brain you were working on is not there anymore.")) - return FALSE diff --git a/code/modules/surgery/burn_dressing.dm b/code/modules/surgery/burn_dressing.dm deleted file mode 100644 index f4bc0d95a98..00000000000 --- a/code/modules/surgery/burn_dressing.dm +++ /dev/null @@ -1,181 +0,0 @@ - -/////BURN FIXING SURGERIES////// - -///// Debride burnt flesh -/datum/surgery/debride - name = "Debride burnt flesh" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - targetable_wound = /datum/wound/burn/flesh - possible_locs = list( - BODY_ZONE_R_ARM, - BODY_ZONE_L_ARM, - BODY_ZONE_R_LEG, - BODY_ZONE_L_LEG, - BODY_ZONE_CHEST, - BODY_ZONE_HEAD, - ) - steps = list( - /datum/surgery_step/debride, - /datum/surgery_step/dress, - ) - -/datum/surgery/debride/can_start(mob/living/user, mob/living/carbon/target) - . = ..() - if(!.) - return . - - var/datum/wound/burn/flesh/burn_wound = target.get_bodypart(user.zone_selected).get_wound_type(targetable_wound) - // Should be guaranteed to have the wound by this point - ASSERT(burn_wound, "[type] on [target] has no burn wound when it should have been guaranteed to have one by can_start") - return burn_wound.infection > 0 - -//SURGERY STEPS - -///// Debride -/datum/surgery_step/debride - name = "excise infection (hemostat)" - implements = list( - TOOL_HEMOSTAT = 100, - TOOL_SCALPEL = 85, - TOOL_SAW = 60, - TOOL_WIRECUTTER = 40) - time = 3 SECONDS - repeatable = TRUE - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/retractor2.ogg' - failure_sound = 'sound/items/handling/surgery/organ1.ogg' - surgery_effects_mood = TRUE - /// How much sanitization is added per step - var/sanitization_added = 0.5 - /// How much infestation is removed per step (positive number) - var/infestation_removed = 4 - -/// To give the surgeon a heads up how much work they have ahead of them -/datum/surgery_step/debride/proc/get_progress(mob/user, mob/living/carbon/target, datum/wound/burn/flesh/burn_wound) - if(!burn_wound?.infection || !infestation_removed) - return - var/estimated_remaining_steps = burn_wound.infection / infestation_removed - var/progress_text - - switch(estimated_remaining_steps) - if(-INFINITY to 1) - return - if(1 to 2) - progress_text = ", preparing to remove the last remaining bits of infection" - if(2 to 4) - progress_text = ", steadily narrowing the remaining bits of infection" - if(5 to INFINITY) - progress_text = ", though there's still quite a lot to excise" - - return progress_text - -/datum/surgery_step/debride/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(surgery.operated_wound) - var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound - if(burn_wound.infection <= 0) - to_chat(user, span_notice("[target]'s [target.parse_zone_with_bodypart(user.zone_selected)] has no infected flesh to remove!")) - surgery.status++ - repeatable = FALSE - return - display_results( - user, - target, - span_notice("You begin to excise infected flesh from [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]..."), - span_notice("[user] begins to excise infected flesh from [target]'s [target.parse_zone_with_bodypart(user.zone_selected)] with [tool]."), - span_notice("[user] begins to excise infected flesh from [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), - ) - display_pain(target, "The infection in your [target.parse_zone_with_bodypart(user.zone_selected)] stings like hell! It feels like you're being stabbed!") - else - user.visible_message(span_notice("[user] looks for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), span_notice("You look for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]...")) - -/datum/surgery_step/debride/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound - if(burn_wound) - var/progress_text = get_progress(user, target, burn_wound) - display_results( - user, - target, - span_notice("You successfully excise some of the infected flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)][progress_text]."), - span_notice("[user] successfully excises some of the infected flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), - span_notice("[user] successfully excises some of the infected flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - log_combat(user, target, "excised infected flesh in", addition="COMBAT MODE: [uppertext(user.combat_mode)]") - target.apply_damage(3, BRUTE, surgery.operated_bodypart, wound_bonus = CANT_WOUND, sharpness = SHARP_EDGED, attacking_item = tool) - burn_wound.infection -= infestation_removed - burn_wound.sanitization += sanitization_added - if(burn_wound.infection <= 0) - repeatable = FALSE - else - to_chat(user, span_warning("[target] has no infected flesh there!")) - return ..() - -/datum/surgery_step/debride/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob = 0) - ..() - display_results( - user, - target, - span_notice("You carve away some of the healthy flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] carves away some of the healthy flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), - span_notice("[user] carves away some of the healthy flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - target.apply_damage(rand(4, 8), BRUTE, surgery.operated_bodypart, sharpness = SHARP_EDGED, attacking_item = tool) - -/datum/surgery_step/debride/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) - if(!..()) - return - var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound - while(burn_wound && burn_wound.infection > 0.25) - if(!..()) - break - -///// Dressing burns -/datum/surgery_step/dress - name = "bandage burns (gauze/tape)" - implements = list( - /obj/item/stack/medical/gauze = 100, - /obj/item/stack/sticky_tape/surgical = 100) - time = 4 SECONDS - /// How much sanitization is added - var/sanitization_added = 3 - /// How much flesh healing is added - var/flesh_healing_added = 5 - - -/datum/surgery_step/dress/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound - if(burn_wound) - display_results( - user, - target, - span_notice("You begin to dress the burns on [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]..."), - span_notice("[user] begins to dress the burns on [target]'s [target.parse_zone_with_bodypart(user.zone_selected)] with [tool]."), - span_notice("[user] begins to dress the burns on [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), - ) - display_pain(target, "The burns on your [target.parse_zone_with_bodypart(user.zone_selected)] sting like hell!") - else - user.visible_message(span_notice("[user] looks for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), span_notice("You look for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]...")) - -/datum/surgery_step/dress/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound - if(burn_wound) - display_results( - user, - target, - span_notice("You successfully wrap [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]."), - span_notice("[user] successfully wraps [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), - span_notice("[user] successfully wraps [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - log_combat(user, target, "dressed burns in", addition="COMBAT MODE: [uppertext(user.combat_mode)]") - burn_wound.sanitization += sanitization_added - burn_wound.flesh_healing += flesh_healing_added - var/obj/item/bodypart/the_part = target.get_bodypart(target_zone) - the_part.apply_gauze(tool) - else - to_chat(user, span_warning("[target] has no burns there!")) - return ..() - -/datum/surgery_step/dress/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob = 0) - ..() - if(isstack(tool)) - var/obj/item/stack/used_stack = tool - used_stack.use(1) diff --git a/code/modules/surgery/cavity_implant.dm b/code/modules/surgery/cavity_implant.dm deleted file mode 100644 index cfab9cf7424..00000000000 --- a/code/modules/surgery/cavity_implant.dm +++ /dev/null @@ -1,87 +0,0 @@ -/datum/surgery/cavity_implant - name = "Cavity implant" - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/handle_cavity, - /datum/surgery_step/close) - -GLOBAL_LIST_INIT(heavy_cavity_implants, typecacheof(list(/obj/item/transfer_valve))) - -//handle cavity -/datum/surgery_step/handle_cavity - name = "implant item" - accept_hand = 1 - implements = list(/obj/item = 100) - repeatable = TRUE - time = 3.2 SECONDS - preop_sound = 'sound/items/handling/surgery/organ1.ogg' - success_sound = 'sound/items/handling/surgery/organ2.ogg' - var/obj/item/item_for_cavity - -/datum/surgery_step/handle_cavity/tool_check(mob/user, obj/item/tool) - if(tool.tool_behaviour == TOOL_CAUTERY || istype(tool, /obj/item/gun/energy/laser)) - return FALSE - return !tool.get_temperature() - -/datum/surgery_step/handle_cavity/preop(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/obj/item/bodypart/chest/target_chest = target.get_bodypart(BODY_ZONE_CHEST) - item_for_cavity = target_chest.cavity_item - if(tool) - display_results( - user, - target, - span_notice("You begin to insert [tool] into [target]'s [target_zone]..."), - span_notice("[user] begins to insert [tool] into [target]'s [target_zone]."), - span_notice("[user] begins to insert [tool.w_class > WEIGHT_CLASS_SMALL ? tool : "something"] into [target]'s [target_zone]."), - ) - display_pain(target, "You can feel something being inserted into your [target_zone], it hurts like hell!") - return - - display_results( - user, - target, - span_notice("You check for items in [target]'s [target_zone]..."), - span_notice("[user] checks for items in [target]'s [target_zone]."), - span_notice("[user] looks for something in [target]'s [target_zone]."), - ) - -/datum/surgery_step/handle_cavity/success(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery = FALSE) - var/obj/item/bodypart/chest/target_chest = target.get_bodypart(BODY_ZONE_CHEST) - if(tool) - if(item_for_cavity || ((tool.w_class > WEIGHT_CLASS_NORMAL) && !is_type_in_typecache(tool, GLOB.heavy_cavity_implants)) || HAS_TRAIT(tool, TRAIT_NODROP) || (tool.item_flags & ABSTRACT) || isorgan(tool)) - to_chat(user, span_warning("You can't seem to fit [tool] in [target]'s [target_zone]!")) - return FALSE - - display_results( - user, - target, - span_notice("You stuff [tool] into [target]'s [target_zone]."), - span_notice("[user] stuffs [tool] into [target]'s [target_zone]!"), - span_notice("[user] stuffs [tool.w_class > WEIGHT_CLASS_SMALL ? tool : "something"] into [target]'s [target_zone]."), - ) - - if (!user.transferItemToLoc(tool, target, TRUE)) - return FALSE - - target_chest.cavity_item = tool - return ..() - - if(!item_for_cavity) - to_chat(user, span_warning("You don't find anything in [target]'s [target_zone].")) - return FALSE - - display_results( - user, - target, - span_notice("You pull [item_for_cavity] out of [target]'s [target_zone]."), - span_notice("[user] pulls [item_for_cavity] out of [target]'s [target_zone]!"), - span_notice("[user] pulls [item_for_cavity.w_class > WEIGHT_CLASS_SMALL ? item_for_cavity : "something"] out of [target]'s [target_zone]."), - ) - display_pain(target, "Something is pulled out of your [target_zone]! It hurts like hell!") - user.put_in_hands(item_for_cavity) - target_chest.cavity_item = null - return ..() diff --git a/code/modules/surgery/core_removal.dm b/code/modules/surgery/core_removal.dm deleted file mode 100644 index 890d62413f2..00000000000 --- a/code/modules/surgery/core_removal.dm +++ /dev/null @@ -1,51 +0,0 @@ -/datum/surgery/core_removal - name = "Core removal" - target_mobtypes = list(/mob/living/basic/slime) - surgery_flags = SURGERY_IGNORE_CLOTHES - possible_locs = list( - BODY_ZONE_R_ARM, - BODY_ZONE_L_ARM, - BODY_ZONE_R_LEG, - BODY_ZONE_L_LEG, - BODY_ZONE_CHEST, - BODY_ZONE_HEAD, - ) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/extract_core, - ) - -/datum/surgery/core_removal/can_start(mob/user, mob/living/target) - return target.stat == DEAD && ..() - -//extract brain -/datum/surgery_step/extract_core - name = "extract core (hemostat/crowbar)" - implements = list( - TOOL_HEMOSTAT = 100, - TOOL_CROWBAR = 100) - time = 1.6 SECONDS - -/datum/surgery_step/extract_core/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to extract a core from [target]..."), - span_notice("[user] begins to extract a core from [target]."), - span_notice("[user] begins to extract a core from [target]."), - ) - -/datum/surgery_step/extract_core/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/mob/living/basic/slime/target_slime = target - var/core_count = target_slime.cores - if(core_count && target_slime.try_extract_cores(count = core_count)) - display_results( - user, - target, - span_notice("You successfully extract [core_count] core\s from [target]."), - span_notice("[user] successfully extracts [core_count] core\s from [target]!"), - span_notice("[user] successfully extracts [core_count] core\s from [target]!"), - ) - return TRUE - to_chat(user, span_warning("There aren't any cores left in [target]!")) - return ..() diff --git a/code/modules/surgery/coronary_bypass.dm b/code/modules/surgery/coronary_bypass.dm deleted file mode 100644 index 0f6136ad4e1..00000000000 --- a/code/modules/surgery/coronary_bypass.dm +++ /dev/null @@ -1,168 +0,0 @@ -/datum/surgery/coronary_bypass - name = "Coronary Bypass" - organ_to_manipulate = ORGAN_SLOT_HEART - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise_heart, - /datum/surgery_step/coronary_bypass, - /datum/surgery_step/close, - ) - -/datum/surgery/coronary_bypass/mechanic - name = "Engine Diagnostic" - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/incise_heart/mechanic, - /datum/surgery_step/coronary_bypass/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/coronary_bypass/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/heart/target_heart = target.get_organ_slot(ORGAN_SLOT_HEART) - if(isnull(target_heart) || target_heart.damage < 60 || target_heart.operated) - return FALSE - return ..() - - -//an incision but with greater bleed, and a 90% base success chance -/datum/surgery_step/incise_heart - name = "incise heart (scalpel)" - implements = list( - TOOL_SCALPEL = 90, - /obj/item/melee/energy/sword = 45, - /obj/item/knife = 45, - /obj/item/shard = 25) - time = 1.6 SECONDS - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/scalpel2.ogg' - failure_sound = 'sound/items/handling/surgery/organ2.ogg' - surgery_effects_mood = TRUE - -/datum/surgery_step/incise_heart/mechanic - name = "access engine internals (scalpel or crowbar)" - implements = list( - TOOL_SCALPEL = 95, - TOOL_CROWBAR = 95, - /obj/item/melee/energy/sword = 65, - /obj/item/knife = 45, - /obj/item/shard = 35) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' - -/datum/surgery_step/incise_heart/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to make an incision in [target]'s heart..."), - span_notice("[user] begins to make an incision in [target]'s heart."), - span_notice("[user] begins to make an incision in [target]'s heart."), - ) - display_pain(target, "You feel a horrendous pain in your heart, it's almost enough to make you pass out!") - -/datum/surgery_step/incise_heart/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(ishuman(target)) - var/mob/living/carbon/human/target_human = target - if (target_human.can_bleed()) - var/blood_name = target_human.get_bloodtype()?.get_blood_name() || "Blood" - display_results( - user, - target, - span_notice("[blood_name] pools around the incision in [target_human]'s heart."), - span_notice("[blood_name] pools around the incision in [target_human]'s heart."), - span_notice("[blood_name] pools around the incision in [target_human]'s heart."), - ) - var/obj/item/bodypart/target_bodypart = target_human.get_bodypart(target_zone) - target_bodypart.adjustBleedStacks(10) - target_human.adjust_brute_loss(10) - return ..() - -/datum/surgery_step/incise_heart/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(ishuman(target)) - var/mob/living/carbon/human/target_human = target - var/blood_name = LOWER_TEXT(target_human.get_bloodtype()?.get_blood_name()) || "blood" - display_results( - user, - target, - span_warning("You screw up, cutting too deeply into the heart!"), - span_warning("[user] screws up, causing [blood_name] to spurt out of [target_human]'s chest!"), - span_warning("[user] screws up, causing [blood_name] to spurt out of [target_human]'s chest!"), - ) - var/obj/item/bodypart/target_bodypart = target_human.get_bodypart(target_zone) - target_bodypart.adjustBleedStacks(10) - target_human.adjust_organ_loss(ORGAN_SLOT_HEART, 10) - target_human.adjust_brute_loss(10) - -//grafts a coronary bypass onto the individual's heart, success chance is 90% base again -/datum/surgery_step/coronary_bypass - name = "graft coronary bypass (hemostat)" - implements = list( - TOOL_HEMOSTAT = 90, - TOOL_WIRECUTTER = 35, - /obj/item/stack/package_wrap = 15, - /obj/item/stack/cable_coil = 5) - time = 9 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - success_sound = 'sound/items/handling/surgery/hemostat1.ogg' - failure_sound = 'sound/items/handling/surgery/organ2.ogg' - -/datum/surgery_step/coronary_bypass/mechanic - name = "perform maintenance (hemostat or wrench)" - implements = list( - TOOL_HEMOSTAT = 90, - TOOL_WRENCH = 90, - TOOL_WIRECUTTER = 35, - /obj/item/stack/package_wrap = 15, - /obj/item/stack/cable_coil = 5) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' - -/datum/surgery_step/coronary_bypass/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to graft a bypass onto [target]'s heart..."), - span_notice("[user] begins to graft something onto [target]'s heart!"), - span_notice("[user] begins to graft something onto [target]'s heart!"), - ) - display_pain(target, "The pain in your chest is unbearable! You can barely take it anymore!") - -/datum/surgery_step/coronary_bypass/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - target.set_organ_loss(ORGAN_SLOT_HEART, 60) - var/obj/item/organ/heart/target_heart = target.get_organ_slot(ORGAN_SLOT_HEART) - if(target_heart) //slightly worrying if we lost our heart mid-operation, but that's life - target_heart.operated = TRUE - if(target_heart.organ_flags & ORGAN_EMP) //If our organ is failing due to an EMP, fix that - target_heart.organ_flags &= ~ORGAN_EMP - display_results( - user, - target, - span_notice("You successfully graft a bypass onto [target]'s heart."), - span_notice("[user] finishes grafting something onto [target]'s heart."), - span_notice("[user] finishes grafting something onto [target]'s heart."), - ) - display_pain(target, "The pain in your chest throbs, but your heart feels better than ever!") - return ..() - -/datum/surgery_step/coronary_bypass/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(ishuman(target)) - var/mob/living/carbon/human/target_human = target - display_results( - user, - target, - span_warning("You screw up in attaching the graft, and it tears off, tearing part of the heart!"), - span_warning("[user] screws up, causing blood to spurt out of [target_human]'s chest profusely!"), - span_warning("[user] screws up, causing blood to spurt out of [target_human]'s chest profusely!"), - ) - display_pain(target, "Your chest burns; you feel like you're going insane!") - target_human.adjust_organ_loss(ORGAN_SLOT_HEART, 20) - var/obj/item/bodypart/target_bodypart = target_human.get_bodypart(target_zone) - target_bodypart.adjustBleedStacks(30) - return FALSE diff --git a/code/modules/surgery/dental_implant.dm b/code/modules/surgery/dental_implant.dm deleted file mode 100644 index f12064a42c1..00000000000 --- a/code/modules/surgery/dental_implant.dm +++ /dev/null @@ -1,117 +0,0 @@ -#define MARK_TOOTH 1 - -/datum/surgery/dental_implant - name = "Dental implant" - possible_locs = list(BODY_ZONE_PRECISE_MOUTH) - steps = list( - /datum/surgery_step/drill/pill, - /datum/surgery_step/insert_pill, - /datum/surgery_step/search_teeth, - /datum/surgery_step/close, - ) - -/datum/surgery_step/drill/pill/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - . = ..() - var/count = 0 - var/obj/item/bodypart/head/teeth_receptangle = target.get_bodypart(BODY_ZONE_HEAD) - - ASSERT(teeth_receptangle) - - for(var/obj/item/reagent_containers/applicator/pill/dental in teeth_receptangle) - count++ - - if(teeth_receptangle.teeth_count == 0) - to_chat(user, span_notice("[target] has no teeth, doofus!")) - return SURGERY_STEP_FAIL - - if(count >= teeth_receptangle.teeth_count) - to_chat(user, span_notice("[target]'s teeth have all been replaced with pills already!")) - return SURGERY_STEP_FAIL - -/datum/surgery_step/insert_pill - name = "insert pill" - implements = list(/obj/item/reagent_containers/applicator/pill = 100) - time = 1.6 SECONDS - -/datum/surgery_step/insert_pill/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - - display_results( - user, - target, - span_notice("You begin to wedge [tool] in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to wedge \the [tool] in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to wedge something in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "Something's being jammed into your [target.parse_zone_with_bodypart(target_zone)]!") - -/datum/surgery_step/insert_pill/success(mob/user, mob/living/carbon/target, target_zone, obj/item/reagent_containers/applicator/pill/tool, datum/surgery/surgery, default_display_results = FALSE) - if(!istype(tool)) - return FALSE - - // Pills go into head - user.transferItemToLoc(tool, target.get_bodypart(BODY_ZONE_HEAD), TRUE) - - var/datum/action/item_action/activate_pill/pill_action = new(tool) - pill_action.name = "Activate [tool.name]" - pill_action.build_all_button_icons() - pill_action.target = tool - pill_action.Grant(target) //The pill never actually goes in an inventory slot, so the owner doesn't inherit actions from it - - display_results( - user, - target, - span_notice("You wedge [tool] into [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] wedges \the [tool] into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - span_notice("[user] wedges something into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - return ..() - -/datum/action/item_action/activate_pill - name = "Activate Pill" - check_flags = NONE - -/datum/action/item_action/activate_pill/IsAvailable(feedback) - if(owner.stat > SOFT_CRIT) - return FALSE - return ..() - -/datum/action/item_action/activate_pill/do_effect(trigger_flags) - owner.balloon_alert_to_viewers("[owner] grinds their teeth!", "You grit your teeth.") - if(!do_after(owner, owner.stat * (2.5 SECONDS), owner, IGNORE_USER_LOC_CHANGE | IGNORE_INCAPACITATED)) - return FALSE - var/obj/item/item_target = target - to_chat(owner, span_notice("You grit your teeth and burst the implanted [item_target.name]!")) - owner.log_message("swallowed an implanted pill, [target]", LOG_ATTACK) - if(item_target.reagents.total_volume) - item_target.reagents.trans_to(owner, item_target.reagents.total_volume, transferred_by = owner, methods = INGEST) - qdel(target) - return TRUE - -/datum/surgery_step/search_teeth - name = "search teeth (hand)" - accept_hand = TRUE - time = 2 SECONDS - repeatable = TRUE - -/datum/surgery_step/search_teeth/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin looking in [target]'s mouth for implantable teeth..."), - span_notice("[user] begins to look in [target]'s mouth."), - span_notice("[user] begins to examine [target]'s teeth."), - ) - display_pain(target, "You feel fingers poke around at your teeth.") - -/datum/surgery_step/search_teeth/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - display_results( - user, - target, - span_notice("[user] marks a tooth in [target]'s mouth."), - span_notice("[user] marks a tooth in [target]'s mouth."), - span_notice("[user] prods a tooth in [target]'s mouth."), - ) - surgery.status = MARK_TOOTH - return ..() - -#undef MARK_TOOTH diff --git a/code/modules/surgery/ear_surgery.dm b/code/modules/surgery/ear_surgery.dm deleted file mode 100644 index bbe98395078..00000000000 --- a/code/modules/surgery/ear_surgery.dm +++ /dev/null @@ -1,74 +0,0 @@ -//Head surgery to fix the ears organ -/datum/surgery/ear_surgery - name = "Ear surgery" - requires_bodypart_type = NONE - organ_to_manipulate = ORGAN_SLOT_EARS - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/fix_ears, - /datum/surgery_step/close, - ) - -//fix ears -/datum/surgery_step/fix_ears - name = "fix ears (hemostat)" - implements = list( - TOOL_HEMOSTAT = 100, - TOOL_SCREWDRIVER = 45, - /obj/item/pen = 25) - time = 6.4 SECONDS - -/datum/surgery/ear_surgery/can_start(mob/user, mob/living/carbon/target) - return target.get_organ_slot(ORGAN_SLOT_EARS) && ..() - -/datum/surgery_step/fix_ears/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to fix [target]'s ears..."), - span_notice("[user] begins to fix [target]'s ears."), - span_notice("[user] begins to perform surgery on [target]'s ears."), - ) - display_pain(target, "You feel a dizzying pain in your head!") - -/datum/surgery_step/fix_ears/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/obj/item/organ/ears/target_ears = target.get_organ_slot(ORGAN_SLOT_EARS) - display_results( - user, - target, - span_notice("You succeed in fixing [target]'s ears."), - span_notice("[user] successfully fixes [target]'s ears!"), - span_notice("[user] completes the surgery on [target]'s ears."), - ) - display_pain(target, "Your head swims, but it seems like you can feel your hearing coming back!") - target_ears.set_organ_damage(0) - ///makes you temporarily deaf for a duration post-surgery - var/deaf_change = 40 SECONDS - target_ears.temporary_deafness - target_ears.adjust_temporary_deafness(deaf_change) - return ..() - -/datum/surgery_step/fix_ears/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(target.get_organ_by_type(/obj/item/organ/brain)) - display_results( - user, - target, - span_warning("You accidentally stab [target] right in the brain!"), - span_warning("[user] accidentally stabs [target] right in the brain!"), - span_warning("[user] accidentally stabs [target] right in the brain!"), - ) - display_pain(target, "You feel a visceral stabbing pain right through your head, into your brain!") - target.adjust_organ_loss(ORGAN_SLOT_BRAIN, 70) - else - display_results( - user, - target, - span_warning("You accidentally stab [target] right in the brain! Or would have, if [target] had a brain."), - span_warning("[user] accidentally stabs [target] right in the brain! Or would have, if [target] had a brain."), - span_warning("[user] accidentally stabs [target] right in the brain!"), - ) - display_pain(target, "You feel a visceral stabbing pain right through your head!") // dunno who can feel pain w/o a brain but may as well be consistent. - return FALSE diff --git a/code/modules/surgery/experimental_dissection.dm b/code/modules/surgery/experimental_dissection.dm deleted file mode 100644 index fc60b26f4d5..00000000000 --- a/code/modules/surgery/experimental_dissection.dm +++ /dev/null @@ -1,153 +0,0 @@ -///How many research points you gain from dissecting a Human. -#define BASE_HUMAN_REWARD 10 - -/datum/surgery/advanced/experimental_dissection - name = "Experimental Dissection" - desc = "A surgical procedure which analyzes the biology of a corpse, and automatically adds new findings to the research database." - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/experimental_dissection, - /datum/surgery_step/close, - ) - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_CHEST) - target_mobtypes = list(/mob/living) - -/datum/surgery/advanced/experimental_dissection/can_start(mob/user, mob/living/target) - . = ..() - if(!.) - return . - if(HAS_TRAIT_FROM(target, TRAIT_DISSECTED, EXPERIMENTAL_SURGERY_TRAIT)) - return FALSE - if(target.stat != DEAD) - return FALSE - return . - -/datum/surgery_step/experimental_dissection - name = "dissection" - implements = list( - /obj/item/autopsy_scanner = 100, - TOOL_SCALPEL = 60, - TOOL_KNIFE = 20, - /obj/item/shard = 10, - ) - time = 12 SECONDS - silicons_obey_prob = TRUE - -/datum/surgery_step/experimental_dissection/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - user.visible_message(span_notice("[user] starts dissecting [target]."), span_notice("You start dissecting [target].")) - -/datum/surgery_step/experimental_dissection/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/points_earned = check_value(target) - user.visible_message(span_notice("[user] dissects [target], discovering [points_earned] point\s of data!"), span_notice("You dissect [target], finding [points_earned] point\s worth of discoveries, you also write a few notes.")) - - var/obj/item/research_notes/the_dossier = new /obj/item/research_notes(user.loc, points_earned, "biology") - if(!user.put_in_hands(the_dossier) && istype(user.get_inactive_held_item(), /obj/item/research_notes)) - var/obj/item/research_notes/hand_dossier = user.get_inactive_held_item() - hand_dossier.merge(the_dossier) - - target.apply_damage(80, BRUTE, BODY_ZONE_CHEST) - ADD_TRAIT(target, TRAIT_DISSECTED, EXPERIMENTAL_SURGERY_TRAIT) - return ..() - -/datum/surgery_step/experimental_dissection/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/points_earned = round(check_value(target) * 0.01) - user.visible_message( - span_notice("[user] dissects [target]!"), - span_notice("You dissect [target], but do not find anything particularly interesting."), - ) - - var/obj/item/research_notes/the_dossier = new /obj/item/research_notes(user.loc, points_earned, "biology") - if(!user.put_in_hands(the_dossier) && istype(user.get_inactive_held_item(), /obj/item/research_notes)) - var/obj/item/research_notes/hand_dossier = user.get_inactive_held_item() - hand_dossier.merge(the_dossier) - - target.apply_damage(80, BRUTE, BODY_ZONE_CHEST) - return TRUE - -///Calculates how many research points dissecting 'target' is worth. -/datum/surgery_step/experimental_dissection/proc/check_value(mob/living/target) - var/cost = BASE_HUMAN_REWARD - - if(ishuman(target)) - var/mob/living/carbon/human/human_target = target - if(human_target.dna?.species) - if(ismonkey(human_target)) - cost /= 5 - else if(isabductor(human_target)) - cost *= 4 - else if(isgolem(human_target) || iszombie(human_target)) - cost *= 3 - else if(isjellyperson(human_target) || ispodperson(human_target)) - cost *= 2 - else if(isalienroyal(target)) - cost *= 10 - else if(isalienadult(target)) - cost *= 5 - else - cost /= 6 - - return cost - -#undef BASE_HUMAN_REWARD - -/obj/item/research_notes - name = "research notes" - desc = "Valuable scientific data. Use it in an ancient research server to turn it in." - icon = 'icons/obj/service/bureaucracy.dmi' - icon_state = "paper" - w_class = WEIGHT_CLASS_SMALL - ///research points it holds - var/value = 100 - ///origin of the research - var/origin_type = "debug" - ///if it ws merged with different origins to apply a bonus - var/mixed = FALSE - -/obj/item/research_notes/Initialize(mapload, value, origin_type) - . = ..() - if(value) - src.value = value - if(origin_type) - src.origin_type = origin_type - change_vol() - -/obj/item/research_notes/examine(mob/user) - . = ..() - . += span_notice("It is worth [value] research points.") - -/obj/item/research_notes/attackby(obj/item/attacking_item, mob/living/user, list/modifiers, list/attack_modifiers) - if(istype(attacking_item, /obj/item/research_notes)) - var/obj/item/research_notes/notes = attacking_item - value = value + notes.value - change_vol() - qdel(notes) - return - return ..() - -/// proc that changes name and icon depending on value -/obj/item/research_notes/proc/change_vol() - if(value >= 10000) - name = "revolutionary discovery in the field of [origin_type]" - icon_state = "docs_verified" - else if(value >= 2500) - name = "essay about [origin_type]" - icon_state = "paper_words" - else if(value >= 100) - name = "notes of [origin_type]" - icon_state = "paperslip_words" - else - name = "fragmentary data of [origin_type]" - icon_state = "scrap" - -///proc when you slap research notes into another one, it applies a bonus if they are of different origin (only applied once) -/obj/item/research_notes/proc/merge(obj/item/research_notes/new_paper) - var/bonus = min(value , new_paper.value) - value = value + new_paper.value - if(origin_type != new_paper.origin_type && !mixed) - value += bonus * 0.3 - origin_type = "[origin_type] and [new_paper.origin_type]" - mixed = TRUE - change_vol() - qdel(new_paper) diff --git a/code/modules/surgery/eye_surgery.dm b/code/modules/surgery/eye_surgery.dm deleted file mode 100644 index 92f883e3e21..00000000000 --- a/code/modules/surgery/eye_surgery.dm +++ /dev/null @@ -1,72 +0,0 @@ -/datum/surgery/eye_surgery - name = "Eye surgery" - requires_bodypart_type = NONE - organ_to_manipulate = ORGAN_SLOT_EYES - possible_locs = list(BODY_ZONE_PRECISE_EYES) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/fix_eyes, - /datum/surgery_step/close, - ) - -//fix eyes -/datum/surgery_step/fix_eyes - name = "fix eyes (hemostat)" - implements = list( - TOOL_HEMOSTAT = 100, - TOOL_SCREWDRIVER = 45, - /obj/item/pen = 25) - time = 6.4 SECONDS - -/datum/surgery/eye_surgery/can_start(mob/user, mob/living/carbon/target) - return target.get_organ_slot(ORGAN_SLOT_EYES) && ..() - -/datum/surgery_step/fix_eyes/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to fix [target]'s eyes..."), - span_notice("[user] begins to fix [target]'s eyes."), - span_notice("[user] begins to perform surgery on [target]'s eyes."), - ) - display_pain(target, "You feel a stabbing pain in your eyes!") - -/datum/surgery_step/fix_eyes/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/obj/item/organ/eyes/target_eyes = target.get_organ_slot(ORGAN_SLOT_EYES) - user.visible_message(span_notice("[user] successfully fixes [target]'s eyes!"), span_notice("You succeed in fixing [target]'s eyes.")) - display_results( - user, - target, - span_notice("You succeed in fixing [target]'s eyes."), - span_notice("[user] successfully fixes [target]'s eyes!"), - span_notice("[user] completes the surgery on [target]'s eyes."), - ) - display_pain(target, "Your vision blurs, but it seems like you can see a little better now!") - target.remove_status_effect(/datum/status_effect/temporary_blindness) - target.set_eye_blur_if_lower(70 SECONDS) //this will fix itself slowly. - target_eyes.set_organ_damage(0) // heals nearsightedness and blindness from eye damage - return ..() - -/datum/surgery_step/fix_eyes/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(target.get_organ_by_type(/obj/item/organ/brain)) - display_results( - user, - target, - span_warning("You accidentally stab [target] right in the brain!"), - span_warning("[user] accidentally stabs [target] right in the brain!"), - span_warning("[user] accidentally stabs [target] right in the brain!"), - ) - display_pain(target, "You feel a visceral stabbing pain right through your head, into your brain!") - target.adjust_organ_loss(ORGAN_SLOT_BRAIN, 70) - else - display_results( - user, - target, - span_warning("You accidentally stab [target] right in the brain! Or would have, if [target] had a brain."), - span_warning("[user] accidentally stabs [target] right in the brain! Or would have, if [target] had a brain."), - span_warning("[user] accidentally stabs [target] right in the brain!"), - ) - display_pain(target, "You feel a visceral stabbing pain right through your head!") // dunno who can feel pain w/o a brain but may as well be consistent. - return FALSE diff --git a/code/modules/surgery/gastrectomy.dm b/code/modules/surgery/gastrectomy.dm deleted file mode 100644 index 69634b688b1..00000000000 --- a/code/modules/surgery/gastrectomy.dm +++ /dev/null @@ -1,99 +0,0 @@ -/datum/surgery/gastrectomy - name = "Gastrectomy" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - organ_to_manipulate = ORGAN_SLOT_STOMACH - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/gastrectomy, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/close, - ) - -/datum/surgery/gastrectomy/mechanic - name = "Nutrient Processing System Diagnostic" - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/gastrectomy/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/gastrectomy/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/stomach/target_stomach = target.get_organ_slot(ORGAN_SLOT_STOMACH) - if(isnull(target_stomach) || target_stomach.damage < 50 || target_stomach.operated) - return FALSE - return ..() - -////Gastrectomy, because we truly needed a way to repair stomachs. -//95% chance of success to be consistent with most organ-repairing surgeries. -/datum/surgery_step/gastrectomy - name = "remove lower duodenum (scalpel)" - implements = list( - TOOL_SCALPEL = 95, - /obj/item/melee/energy/sword = 65, - /obj/item/knife = 45, - /obj/item/shard = 35) - time = 5.2 SECONDS - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/organ1.ogg' - failure_sound = 'sound/items/handling/surgery/organ2.ogg' - surgery_effects_mood = TRUE - -/datum/surgery_step/gastrectomy/mechanic - name = "perform maintenance (scalpel or wrench)" - implements = list( - TOOL_SCALPEL = 95, - TOOL_WRENCH = 95, - /obj/item/melee/energy/sword = 65, - /obj/item/knife = 45, - /obj/item/shard = 35) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' - -/datum/surgery_step/gastrectomy/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to cut out a damaged piece of [target]'s stomach..."), - span_notice("[user] begins to make an incision in [target]."), - span_notice("[user] begins to make an incision in [target]."), - ) - display_pain(target, "You feel a horrible stab in your gut!") - -/datum/surgery_step/gastrectomy/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/mob/living/carbon/human/target_human = target - var/obj/item/organ/stomach/target_stomach = target.get_organ_slot(ORGAN_SLOT_STOMACH) - target_human.set_organ_loss(ORGAN_SLOT_STOMACH, 20) // Stomachs have a threshold for being able to even digest food, so I might tweak this number - if(target_stomach) - target_stomach.operated = TRUE - if(target_stomach.organ_flags & ORGAN_EMP) //If our organ is failing due to an EMP, fix that - target_stomach.organ_flags &= ~ORGAN_EMP - display_results( - user, - target, - span_notice("You successfully remove the damaged part of [target]'s stomach."), - span_notice("[user] successfully removes the damaged part of [target]'s stomach."), - span_notice("[user] successfully removes the damaged part of [target]'s stomach."), - ) - display_pain(target, "The pain in your gut ebbs and fades somewhat.") - return ..() - -/datum/surgery_step/gastrectomy/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery) - var/mob/living/carbon/human/target_human = target - target_human.adjust_organ_loss(ORGAN_SLOT_STOMACH, 15) - display_results( - user, - target, - span_warning("You cut the wrong part of [target]'s stomach!"), - span_warning("[user] cuts the wrong part of [target]'s stomach!"), - span_warning("[user] cuts the wrong part of [target]'s stomach!"), - ) - display_pain(target, "Your stomach throbs with pain; it's not getting any better!") diff --git a/code/modules/surgery/healing.dm b/code/modules/surgery/healing.dm deleted file mode 100644 index 3516c74840e..00000000000 --- a/code/modules/surgery/healing.dm +++ /dev/null @@ -1,356 +0,0 @@ -/datum/surgery/healing - target_mobtypes = list(/mob/living) - requires_bodypart_type = NONE - replaced_by = /datum/surgery - surgery_flags = SURGERY_IGNORE_CLOTHES | SURGERY_REQUIRE_RESTING - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/incise, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/heal, - /datum/surgery_step/close, - ) - - var/healing_step_type - var/antispam = FALSE - -/datum/surgery/healing/can_start(mob/user, mob/living/patient) - . = ..() - if(!.) - return . - if(!(patient.mob_biotypes & (MOB_ORGANIC|MOB_HUMANOID))) - return FALSE - return . - -/datum/surgery/healing/New(surgery_target, surgery_location, surgery_bodypart) - ..() - if(healing_step_type) - steps = list( - /datum/surgery_step/incise/nobleed, - healing_step_type, //hehe cheeky - /datum/surgery_step/close, - ) - -/datum/surgery_step/heal - name = "repair body (hemostat)" - implements = list( - TOOL_HEMOSTAT = 100, - TOOL_SCREWDRIVER = 65, - TOOL_WIRECUTTER = 60, - /obj/item/pen = 55) - repeatable = TRUE - time = 2.5 SECONDS - success_sound = 'sound/items/handling/surgery/retractor2.ogg' - failure_sound = 'sound/items/handling/surgery/organ2.ogg' - var/brutehealing = 0 - var/burnhealing = 0 - var/brute_multiplier = 0 //multiplies the damage that the patient has. if 0 the patient wont get any additional healing from the damage he has. - var/burn_multiplier = 0 - -/// Returns a string letting the surgeon know roughly how much longer the surgery is estimated to take at the going rate -/datum/surgery_step/heal/proc/get_progress(mob/user, mob/living/carbon/target, brute_healed, burn_healed) - return - -/datum/surgery_step/heal/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/woundtype - if(brutehealing && burnhealing) - woundtype = "wounds" - else if(brutehealing) - woundtype = "bruises" - else //why are you trying to 0,0...? - woundtype = "burns" - if(istype(surgery,/datum/surgery/healing)) - var/datum/surgery/healing/the_surgery = surgery - if(!the_surgery.antispam) - display_results( - user, - target, - span_notice("You attempt to patch some of [target]'s [woundtype]."), - span_notice("[user] attempts to patch some of [target]'s [woundtype]."), - span_notice("[user] attempts to patch some of [target]'s [woundtype]."), - ) - display_pain(target, "Your [woundtype] sting like hell!") - -/datum/surgery_step/heal/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) - if(!..()) - return - while((brutehealing && target.get_brute_loss()) || (burnhealing && target.get_fire_loss())) - if(!..()) - break - -/datum/surgery_step/heal/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/user_msg = "You succeed in fixing some of [target]'s wounds" //no period, add initial space to "addons" - var/target_msg = "[user] fixes some of [target]'s wounds" //see above - var/brute_healed = brutehealing - var/burn_healed = burnhealing - var/dead_patient = FALSE - if(target.stat == DEAD) //dead patients get way less additional heal from the damage they have. - brute_healed += round((target.get_brute_loss() * (brute_multiplier * 0.2)),0.1) - burn_healed += round((target.get_fire_loss() * (burn_multiplier * 0.2)),0.1) - dead_patient = TRUE - else - brute_healed += round((target.get_brute_loss() * brute_multiplier),0.1) - burn_healed += round((target.get_fire_loss() * burn_multiplier),0.1) - dead_patient = FALSE - if(!get_location_accessible(target, target_zone)) - brute_healed *= 0.55 - burn_healed *= 0.55 - user_msg += " as best as you can while [target.p_they()] [target.p_have()] clothing on" - target_msg += " as best as [user.p_they()] can while [target.p_they()] [target.p_have()] clothing on" - target.heal_bodypart_damage(brute_healed,burn_healed) - - user_msg += get_progress(user, target, brute_healed, burn_healed) - - if(HAS_MIND_TRAIT(user, TRAIT_MORBID) && ishuman(user) && !dead_patient) //Morbid folk don't care about tending the dead as much as tending the living - var/mob/living/carbon/human/morbid_weirdo = user - morbid_weirdo.add_mood_event("morbid_tend_wounds", /datum/mood_event/morbid_tend_wounds) - - display_results( - user, - target, - span_notice("[user_msg]."), - span_notice("[target_msg]."), - span_notice("[target_msg]."), - ) - if(istype(surgery, /datum/surgery/healing)) - var/datum/surgery/healing/the_surgery = surgery - the_surgery.antispam = TRUE - return ..() - -/datum/surgery_step/heal/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_warning("You screwed up!"), - span_warning("[user] screws up!"), - span_notice("[user] fixes some of [target]'s wounds."), - target_detailed = TRUE, - ) - var/brute_dealt = brutehealing * 0.8 - var/burn_dealt = burnhealing * 0.8 - brute_dealt += round((target.get_brute_loss() * (brute_multiplier * 0.5)),0.1) - burn_dealt += round((target.get_fire_loss() * (burn_multiplier * 0.5)),0.1) - target.take_bodypart_damage(brute_dealt, burn_dealt, wound_bonus=CANT_WOUND) - return FALSE - -/***************************BRUTE***************************/ -/datum/surgery/healing/brute - name = "Tend Wounds (Bruises)" - -/datum/surgery/healing/brute/basic - name = "Tend Wounds (Bruises, Basic)" - replaced_by = /datum/surgery/healing/brute/upgraded - healing_step_type = /datum/surgery_step/heal/brute/basic - desc = "A surgical procedure that provides basic treatment for a patient's brute traumas. Heals slightly more when the patient is severely injured." - -/datum/surgery/healing/brute/upgraded - name = "Tend Wounds (Bruises, Adv.)" - replaced_by = /datum/surgery/healing/brute/upgraded/femto - requires_tech = TRUE - healing_step_type = /datum/surgery_step/heal/brute/upgraded - desc = "A surgical procedure that provides advanced treatment for a patient's brute traumas. Heals more when the patient is severely injured." - -/datum/surgery/healing/brute/upgraded/femto - name = "Tend Wounds (Bruises, Exp.)" - replaced_by = /datum/surgery/healing/combo/upgraded/femto - requires_tech = TRUE - healing_step_type = /datum/surgery_step/heal/brute/upgraded/femto - desc = "A surgical procedure that provides experimental treatment for a patient's brute traumas. Heals considerably more when the patient is severely injured." - -/********************BRUTE STEPS********************/ -/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.get_brute_loss() / brute_healed - var/progress_text - - if(locate(/obj/item/healthanalyzer) in user.held_items) - progress_text = ". Remaining brute: [target.get_brute_loss()]" - 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/brute/basic - name = "tend bruises (hemostat)" - brutehealing = 5 - brute_multiplier = 0.07 - -/datum/surgery_step/heal/brute/upgraded - brutehealing = 5 - brute_multiplier = 0.1 - -/datum/surgery_step/heal/brute/upgraded/femto - brutehealing = 5 - brute_multiplier = 0.2 - -/***************************BURN***************************/ -/datum/surgery/healing/burn - name = "Tend Wounds (Burn)" - -/datum/surgery/healing/burn/basic - name = "Tend Wounds (Burn, Basic)" - replaced_by = /datum/surgery/healing/burn/upgraded - healing_step_type = /datum/surgery_step/heal/burn/basic - desc = "A surgical procedure that provides basic treatment for a patient's burns. Heals slightly more when the patient is severely injured." - -/datum/surgery/healing/burn/upgraded - name = "Tend Wounds (Burn, Adv.)" - replaced_by = /datum/surgery/healing/burn/upgraded/femto - requires_tech = TRUE - healing_step_type = /datum/surgery_step/heal/burn/upgraded - desc = "A surgical procedure that provides advanced treatment for a patient's burns. Heals more when the patient is severely injured." - -/datum/surgery/healing/burn/upgraded/femto - name = "Tend Wounds (Burn, Exp.)" - replaced_by = /datum/surgery/healing/combo/upgraded/femto - requires_tech = TRUE - healing_step_type = /datum/surgery_step/heal/burn/upgraded/femto - desc = "A surgical procedure that provides experimental treatment for a patient's burns. Heals considerably more when the patient is severely injured." - -/********************BURN STEPS********************/ -/datum/surgery_step/heal/burn/get_progress(mob/user, mob/living/carbon/target, brute_healed, burn_healed) - if(!burn_healed) - return - var/estimated_remaining_steps = target.get_fire_loss() / burn_healed - var/progress_text - - if(locate(/obj/item/healthanalyzer) in user.held_items) - progress_text = ". Remaining burn: [target.get_fire_loss()]" - 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 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 - -/datum/surgery_step/heal/burn/basic - name = "tend burn wounds (hemostat)" - burnhealing = 5 - burn_multiplier = 0.07 - -/datum/surgery_step/heal/burn/upgraded - burnhealing = 5 - burn_multiplier = 0.1 - -/datum/surgery_step/heal/burn/upgraded/femto - burnhealing = 5 - burn_multiplier = 0.2 - -/***************************COMBO***************************/ -/datum/surgery/healing/combo - - -/datum/surgery/healing/combo - name = "Tend Wounds (Mixture, Basic)" - replaced_by = /datum/surgery/healing/combo/upgraded - requires_tech = TRUE - healing_step_type = /datum/surgery_step/heal/combo - desc = "A surgical procedure that provides basic treatment for a patient's burns and brute traumas. Heals slightly more when the patient is severely injured." - -/datum/surgery/healing/combo/upgraded - name = "Tend Wounds (Mixture, Adv.)" - replaced_by = /datum/surgery/healing/combo/upgraded/femto - healing_step_type = /datum/surgery_step/heal/combo/upgraded - desc = "A surgical procedure that provides advanced treatment for a patient's burns and brute traumas. Heals more when the patient is severely injured." - - -/datum/surgery/healing/combo/upgraded/femto //no real reason to type it like this except consistency, don't worry you're not missing anything - name = "Tend Wounds (Mixture, Exp.)" - replaced_by = null - healing_step_type = /datum/surgery_step/heal/combo/upgraded/femto - desc = "A surgical procedure that provides experimental treatment for a patient's burns and brute traumas. Heals considerably more when the patient is severely injured." - -/********************COMBO STEPS********************/ -/datum/surgery_step/heal/combo/get_progress(mob/user, mob/living/carbon/target, brute_healed, burn_healed) - var/estimated_remaining_steps = 0 - if(brute_healed > 0) - estimated_remaining_steps = max(0, (target.get_brute_loss() / brute_healed)) - if(burn_healed > 0) - estimated_remaining_steps = max(estimated_remaining_steps, (target.get_fire_loss() / burn_healed)) // whichever is higher between brute or burn steps - - var/progress_text - - if(locate(/obj/item/healthanalyzer) in user.held_items) - if(target.get_brute_loss()) - progress_text = ". Remaining brute: [target.get_brute_loss()]" - if(target.get_fire_loss()) - progress_text += ". Remaining burn: [target.get_fire_loss()]" - else - switch(estimated_remaining_steps) - if(-INFINITY to 1) - return - if(1 to 3) - progress_text = ", finishing up the last few signs of damage" - if(3 to 6) - progress_text = ", counting down the last few patches of trauma" - if(6 to 9) - progress_text = ", continuing to plug away at [target.p_their()] extensive injuries" - 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 smooshed baby food than a person" - if(15 to INFINITY) - progress_text = ", though you feel like you're barely making a dent in treating [target.p_their()] broken body" - - return progress_text - -/datum/surgery_step/heal/combo - name = "tend physical wounds (hemostat)" - brutehealing = 3 - burnhealing = 3 - brute_multiplier = 0.07 - burn_multiplier = 0.07 - time = 1 SECONDS - -/datum/surgery_step/heal/combo/upgraded - brutehealing = 3 - burnhealing = 3 - brute_multiplier = 0.1 - burn_multiplier = 0.1 - -/datum/surgery_step/heal/combo/upgraded/femto - brutehealing = 1 - burnhealing = 1 - brute_multiplier = 0.4 - burn_multiplier = 0.4 - -/datum/surgery_step/heal/combo/upgraded/femto/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_warning("You screwed up!"), - span_warning("[user] screws up!"), - span_notice("[user] fixes some of [target]'s wounds."), - target_detailed = TRUE, - ) - target.take_bodypart_damage(5,5) diff --git a/code/modules/surgery/hepatectomy.dm b/code/modules/surgery/hepatectomy.dm deleted file mode 100644 index e9b7e94eae4..00000000000 --- a/code/modules/surgery/hepatectomy.dm +++ /dev/null @@ -1,98 +0,0 @@ -/datum/surgery/hepatectomy - name = "Hepatectomy" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - organ_to_manipulate = ORGAN_SLOT_LIVER - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/hepatectomy, - /datum/surgery_step/close, - ) - -/datum/surgery/hepatectomy/mechanic - name = "Impurity Management System Diagnostic" - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/hepatectomy/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/hepatectomy/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/liver/target_liver = target.get_organ_slot(ORGAN_SLOT_LIVER) - if(isnull(target_liver) || target_liver.damage < 50 || target_liver.operated) - return FALSE - return ..() - -////hepatectomy, removes damaged parts of the liver so that the liver may regenerate properly -//95% chance of success, not 100 because organs are delicate -/datum/surgery_step/hepatectomy - name = "remove damaged liver section (scalpel)" - implements = list( - TOOL_SCALPEL = 95, - /obj/item/melee/energy/sword = 65, - /obj/item/knife = 45, - /obj/item/shard = 35) - time = 5.2 SECONDS - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/organ1.ogg' - failure_sound = 'sound/items/handling/surgery/organ2.ogg' - surgery_effects_mood = TRUE - -/datum/surgery_step/hepatectomy/mechanic - name = "perform maintenance (scalpel or wirecutter)" - implements = list( - TOOL_SCALPEL = 95, - TOOL_WRENCH = 95, - /obj/item/melee/energy/sword = 65, - /obj/item/knife = 45, - /obj/item/shard = 35) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' - -/datum/surgery_step/hepatectomy/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to cut out a damaged piece of [target]'s liver..."), - span_notice("[user] begins to make an incision in [target]."), - span_notice("[user] begins to make an incision in [target]."), - ) - display_pain(target, "Your abdomen burns in horrific stabbing pain!") - -/datum/surgery_step/hepatectomy/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/mob/living/carbon/human/human_target = target - var/obj/item/organ/liver/target_liver = target.get_organ_slot(ORGAN_SLOT_LIVER) - human_target.set_organ_loss(ORGAN_SLOT_LIVER, 10) //not bad, not great - if(target_liver) - target_liver.operated = TRUE - if(target_liver.organ_flags & ORGAN_EMP) //If our organ is failing due to an EMP, fix that - target_liver.organ_flags &= ~ORGAN_EMP - display_results( - user, - target, - span_notice("You successfully remove the damaged part of [target]'s liver."), - span_notice("[user] successfully removes the damaged part of [target]'s liver."), - span_notice("[user] successfully removes the damaged part of [target]'s liver."), - ) - display_pain(target, "The pain receeds slightly.") - return ..() - -/datum/surgery_step/hepatectomy/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery) - var/mob/living/carbon/human/human_target = target - human_target.adjust_organ_loss(ORGAN_SLOT_LIVER, 15) - display_results( - user, - target, - span_warning("You cut the wrong part of [target]'s liver!"), - span_warning("[user] cuts the wrong part of [target]'s liver!"), - span_warning("[user] cuts the wrong part of [target]'s liver!"), - ) - display_pain(target, "You feel a sharp stab inside your abdomen!") diff --git a/code/modules/surgery/implant_removal.dm b/code/modules/surgery/implant_removal.dm deleted file mode 100644 index 6ce2f81ad6d..00000000000 --- a/code/modules/surgery/implant_removal.dm +++ /dev/null @@ -1,93 +0,0 @@ -/datum/surgery/implant_removal - name = "Implant Removal" - target_mobtypes = list(/mob/living) - possible_locs = list(BODY_ZONE_CHEST) - surgery_flags = SURGERY_REQUIRE_RESTING - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/retract_skin, - /datum/surgery_step/extract_implant, - /datum/surgery_step/close, - ) - -//extract implant -/datum/surgery_step/extract_implant - name = "extract implant (hemostat)" - implements = list( - TOOL_HEMOSTAT = 100, - TOOL_CROWBAR = 65, - /obj/item/kitchen/fork = 35) - time = 6.4 SECONDS - success_sound = 'sound/items/handling/surgery/hemostat1.ogg' - var/obj/item/implant/implant - -/datum/surgery_step/extract_implant/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - implant = LAZYACCESS(target.implants, 1) - if(implant) - display_results( - user, - target, - span_notice("You begin to extract [implant] from [target]'s [target_zone]..."), - span_notice("[user] begins to extract [implant] from [target]'s [target_zone]."), - span_notice("[user] begins to extract something from [target]'s [target_zone]."), - ) - display_pain(target, "You feel a serious pain in your [target_zone]!") - else - display_results( - user, - target, - span_notice("You look for an implant in [target]'s [target_zone]..."), - span_notice("[user] looks for an implant in [target]'s [target_zone]."), - span_notice("[user] looks for something in [target]'s [target_zone]."), - ) - -/datum/surgery_step/extract_implant/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(implant) - display_results( - user, - target, - span_notice("You successfully remove [implant] from [target]'s [target_zone]."), - span_notice("[user] successfully removes [implant] from [target]'s [target_zone]!"), - span_notice("[user] successfully removes something from [target]'s [target_zone]!"), - ) - display_pain(target, "You can feel your [implant.name] pulled out of you!") - implant.removed(target) - - if (!QDELETED(implant)) - var/obj/item/implantcase/case - for(var/obj/item/implantcase/implant_case in user.held_items) - case = implant_case - break - if(!case) - case = locate(/obj/item/implantcase) in get_turf(target) - if(case && !case.imp) - case.imp = implant - implant.forceMove(case) - case.update_appearance() - display_results( - user, - target, - span_notice("You place [implant] into [case]."), - span_notice("[user] places [implant] into [case]!"), - span_notice("[user] places it into [case]!"), - ) - else - qdel(implant) - implant = null - else - to_chat(user, span_warning("You can't find anything in [target]'s [target_zone]!")) - return ..() - -/datum/surgery/implant_removal/mechanic - name = "Implant Removal" - requires_bodypart_type = BODYTYPE_ROBOTIC - target_mobtypes = list(/mob/living/carbon/human) // Simpler mobs don't have bodypart types - surgery_flags = parent_type::surgery_flags | SURGERY_REQUIRE_LIMB - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/extract_implant, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close) diff --git a/code/modules/surgery/limb_augmentation.dm b/code/modules/surgery/limb_augmentation.dm deleted file mode 100644 index b9b1356db89..00000000000 --- a/code/modules/surgery/limb_augmentation.dm +++ /dev/null @@ -1,96 +0,0 @@ - -/////AUGMENTATION SURGERIES////// - - -//SURGERY STEPS - -/datum/surgery_step/replace_limb - name = "replace limb" - implements = list( - /obj/item/bodypart = 100, - /obj/item/borg/apparatus/organ_storage = 100) - time = 3.2 SECONDS - var/obj/item/bodypart/target_limb - - -/datum/surgery_step/replace_limb/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(HAS_TRAIT(target, TRAIT_NO_AUGMENTS)) - to_chat(user, span_warning("[target] cannot be augmented!")) - return SURGERY_STEP_FAIL - if(istype(tool, /obj/item/borg/apparatus/organ_storage) && istype(tool.contents[1], /obj/item/bodypart)) - tool = tool.contents[1] - var/obj/item/bodypart/aug = tool - if(IS_ORGANIC_LIMB(aug)) - to_chat(user, span_warning("That's not an augment, silly!")) - return SURGERY_STEP_FAIL - if(aug.body_zone != target_zone) - to_chat(user, span_warning("[tool] isn't the right type for [target.parse_zone_with_bodypart(target_zone)].")) - return SURGERY_STEP_FAIL - target_limb = surgery.operated_bodypart - if(target_limb) - display_results( - user, - target, - span_notice("You begin to augment [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]..."), - span_notice("[user] begins to augment [target]'s [target.parse_zone_with_bodypart(user.zone_selected)] with [aug]."), - span_notice("[user] begins to augment [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), - ) - display_pain(target, "You feel a horrible pain in your [target.parse_zone_with_bodypart(user.zone_selected)]!") - else - user.visible_message(span_notice("[user] looks for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), span_notice("You look for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]...")) - - -//ACTUAL SURGERIES - -/datum/surgery/augmentation - name = "Augmentation" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - possible_locs = list( - BODY_ZONE_R_ARM, - BODY_ZONE_L_ARM, - BODY_ZONE_R_LEG, - BODY_ZONE_L_LEG, - BODY_ZONE_CHEST, - BODY_ZONE_HEAD, - ) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/replace_limb, - ) - -//SURGERY STEP SUCCESSES - -/datum/surgery_step/replace_limb/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/bodypart/tool, datum/surgery/surgery, default_display_results = FALSE) - if(target_limb) - if(istype(tool, /obj/item/borg/apparatus/organ_storage)) - tool.icon_state = initial(tool.icon_state) - tool.desc = initial(tool.desc) - tool.cut_overlays() - tool = tool.contents[1] - if(istype(tool) && user.temporarilyRemoveItemFromInventory(tool)) - if(!tool.replace_limb(target)) - display_results( - user, - target, - span_warning("You fail in replacing [target]'s [target.parse_zone_with_bodypart(target_zone)]! Their body has rejected [tool]!"), - span_warning("[user] fails to replace [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - span_warning("[user] fails to replaces [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - tool.forceMove(target.loc) - return - if(tool.check_for_frankenstein(target)) - tool.bodypart_flags |= BODYPART_IMPLANTED - display_results( - user, - target, - span_notice("You successfully augment [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] successfully augments [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), - span_notice("[user] successfully augments [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - display_pain(target, "Your [target.parse_zone_with_bodypart(target_zone)] comes awash with synthetic sensation!", mechanical_surgery = TRUE) - log_combat(user, target, "augmented", addition="by giving him new [target.parse_zone_with_bodypart(target_zone)] COMBAT MODE: [uppertext(user.combat_mode)]") - else - to_chat(user, span_warning("[target] has no organic [target.parse_zone_with_bodypart(target_zone)] there!")) - return ..() diff --git a/code/modules/surgery/lipoplasty.dm b/code/modules/surgery/lipoplasty.dm deleted file mode 100644 index 3183c968f70..00000000000 --- a/code/modules/surgery/lipoplasty.dm +++ /dev/null @@ -1,143 +0,0 @@ -/datum/surgery/lipoplasty - name = "Lipoplasty" - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/cut_fat, - /datum/surgery_step/remove_fat, - /datum/surgery_step/close, - ) - -/datum/surgery/lipoplasty/mechanic - name = "Nutrient Reserve Expulsion" - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/cut_fat/mechanic, - /datum/surgery_step/remove_fat/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/lipoplasty/can_start(mob/user, mob/living/carbon/target) - if(!HAS_TRAIT_FROM(target, TRAIT_FAT, OBESITY) || target.nutrition < NUTRITION_LEVEL_WELL_FED) - return FALSE - return ..() - - -//cut fat -/datum/surgery_step/cut_fat - name = "cut excess fat (circular saw)" - implements = list( - TOOL_SAW = 100, - /obj/item/shovel/serrated = 75, - /obj/item/hatchet = 35, - /obj/item/knife/butcher = 25, - ) - time = 6.4 SECONDS - surgery_effects_mood = TRUE - preop_sound = list( - /obj/item/circular_saw = 'sound/items/handling/surgery/saw.ogg', - /obj/item = 'sound/items/handling/surgery/scalpel1.ogg', - ) - -/datum/surgery_step/cut_fat/mechanic - name = "open fat containers (wrench or crowbar)" - implements = list( - TOOL_WRENCH = 95, - TOOL_CROWBAR = 95, - TOOL_SAW = 65, - /obj/item/melee/energy/sword = 65, - /obj/item/knife = 45, - /obj/item/shard = 35, - ) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' - -/datum/surgery_step/cut_fat/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - user.visible_message(span_notice("[user] begins to cut away [target]'s excess fat."), span_notice("You begin to cut away [target]'s excess fat...")) - display_results( - user, - target, - span_notice("You begin to cut away [target]'s excess fat..."), - span_notice("[user] begins to cut away [target]'s excess fat."), - span_notice("[user] begins to cut [target]'s [target_zone] with [tool]."), - ) - display_pain(target, "You feel a stabbing in your [target_zone]!") - -/datum/surgery_step/cut_fat/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) - display_results( - user, - target, - span_notice("You cut [target]'s excess fat loose."), - span_notice("[user] cuts [target]'s excess fat loose!"), - span_notice("[user] finishes the cut on [target]'s [target_zone]."), - ) - display_pain(target, "The fat in your [target_zone] comes loose, dangling and hurting like hell!") - return TRUE - -//remove fat -/datum/surgery_step/remove_fat - name = "remove loose fat (retractor)" - implements = list( - TOOL_RETRACTOR = 100, - TOOL_SCREWDRIVER = 45, - TOOL_WIRECUTTER = 35, - ) - time = 3.2 SECONDS - preop_sound = 'sound/items/handling/surgery/retractor1.ogg' - success_sound = 'sound/items/handling/surgery/retractor2.ogg' - -/datum/surgery_step/remove_fat/mechanic - name = "engage expulsion valve (screwdriver or wrench)" //gross - implements = list( - TOOL_SCREWDRIVER = 100, - TOOL_WRENCH = 100, - TOOL_WIRECUTTER = 35, - TOOL_RETRACTOR = 35, - ) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/items/handling/surgery/organ2.ogg' - -/datum/surgery_step/remove_fat/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to extract [target]'s loose fat..."), - span_notice("[user] begins to extract [target]'s loose fat!"), - span_notice("[user] begins to extract something from [target]'s [target_zone]."), - ) - display_pain(target, "You feel an oddly painless tugging on your loose fat!") - -/datum/surgery_step/remove_fat/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - display_results( - user, - target, - span_notice("You extract [target]'s fat."), - span_notice("[user] extracts [target]'s fat!"), - span_notice("[user] extracts [target]'s fat!"), - ) - target.overeatduration = 0 //patient is unfatted - var/removednutriment = target.nutrition - target.set_nutrition(NUTRITION_LEVEL_WELL_FED) - removednutriment -= NUTRITION_LEVEL_WELL_FED //whatever was removed goes into the meat - var/mob/living/carbon/human/human = target - var/typeofmeat = /obj/item/food/meat/slab/human - - if(target.flags_1 & HOLOGRAM_1) - typeofmeat = null - else if(human.dna && human.dna.species) - typeofmeat = human.dna.species.meat - - if(typeofmeat) - var/obj/item/food/meat/slab/human/newmeat = new typeofmeat - newmeat.name = "fatty meat" - newmeat.desc = "Extremely fatty tissue taken from a patient." - newmeat.subjectname = human.real_name - newmeat.subjectjob = human.job - newmeat.reagents.add_reagent (/datum/reagent/consumable/nutriment, (removednutriment / 15)) //To balance with nutriment_factor of nutriment - newmeat.forceMove(target.loc) - return ..() diff --git a/code/modules/surgery/lobectomy.dm b/code/modules/surgery/lobectomy.dm deleted file mode 100644 index da9bb7a278e..00000000000 --- a/code/modules/surgery/lobectomy.dm +++ /dev/null @@ -1,99 +0,0 @@ -/datum/surgery/lobectomy - name = "Lobectomy" //not to be confused with lobotomy - organ_to_manipulate = ORGAN_SLOT_LUNGS - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/lobectomy, - /datum/surgery_step/close, - ) - -/datum/surgery/lobectomy/mechanic - name = "Air Filtration Diagnostic" - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/lobectomy/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/lobectomy/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/lungs/target_lungs = target.get_organ_slot(ORGAN_SLOT_LUNGS) - if(isnull(target_lungs) || target_lungs.damage < 60 || target_lungs.operated) - return FALSE - return ..() - -//lobectomy, removes the most damaged lung lobe with a 95% base success chance -/datum/surgery_step/lobectomy - name = "excise damaged lung node (scalpel)" - implements = list( - TOOL_SCALPEL = 95, - /obj/item/melee/energy/sword = 65, - /obj/item/knife = 45, - /obj/item/shard = 35) - time = 4.2 SECONDS - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/organ1.ogg' - failure_sound = 'sound/items/handling/surgery/organ2.ogg' - surgery_effects_mood = TRUE - -/datum/surgery_step/lobectomy/mechanic - name = "Perform maintenance (scalpel or wrench)" - implements = list( - TOOL_SCALPEL = 95, - TOOL_WRENCH = 95, - /obj/item/melee/energy/sword = 65, - /obj/item/knife = 45, - /obj/item/shard = 35) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' - -/datum/surgery_step/lobectomy/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to make an incision in [target]'s lungs..."), - span_notice("[user] begins to make an incision in [target]."), - span_notice("[user] begins to make an incision in [target]."), - ) - display_pain(target, "You feel a stabbing pain in your chest!") - -/datum/surgery_step/lobectomy/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(ishuman(target)) - var/mob/living/carbon/human/human_target = target - var/obj/item/organ/lungs/target_lungs = human_target.get_organ_slot(ORGAN_SLOT_LUNGS) - human_target.set_organ_loss(ORGAN_SLOT_LUNGS, 60) - if(target_lungs) - target_lungs.operated = TRUE - if(target_lungs.organ_flags & ORGAN_EMP) //If our organ is failing due to an EMP, fix that - target_lungs.organ_flags &= ~ORGAN_EMP - display_results( - user, - target, - span_notice("You successfully excise [human_target]'s most damaged lobe."), - span_notice("Successfully removes a piece of [human_target]'s lungs."), - "", - ) - display_pain(target, "Your chest hurts like hell, but breathing becomes slightly easier.") - return ..() - -/datum/surgery_step/lobectomy/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(ishuman(target)) - var/mob/living/carbon/human/human_target = target - display_results( - user, - target, - span_warning("You screw up, failing to excise [human_target]'s damaged lobe!"), - span_warning("[user] screws up!"), - span_warning("[user] screws up!"), - ) - display_pain(target, "You feel a sharp stab in your chest; the wind is knocked out of you and it hurts to catch your breath!") - human_target.losebreath += 4 - human_target.adjust_organ_loss(ORGAN_SLOT_LUNGS, 10) - return FALSE diff --git a/code/modules/surgery/mechanic_steps.dm b/code/modules/surgery/mechanic_steps.dm deleted file mode 100644 index dab8730c2ed..00000000000 --- a/code/modules/surgery/mechanic_steps.dm +++ /dev/null @@ -1,153 +0,0 @@ -//open shell -/datum/surgery_step/mechanic_open - name = "unscrew shell (screwdriver or scalpel)" - implements = list( - TOOL_SCREWDRIVER = 100, - TOOL_SCALPEL = 75, // med borgs could try to unscrew shell with scalpel - /obj/item/knife = 50, - /obj/item = 10) // 10% success with any sharp item. - time = 2.4 SECONDS - preop_sound = 'sound/items/tools/screwdriver.ogg' - success_sound = 'sound/items/tools/screwdriver2.ogg' - -/datum/surgery_step/mechanic_open/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to unscrew the shell of [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to unscrew the shell of [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to unscrew the shell of [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You can feel your [target.parse_zone_with_bodypart(target_zone)] grow numb as the sensory panel is unscrewed.", TRUE) - -/datum/surgery_step/mechanic_open/tool_check(mob/user, obj/item/tool) - if(implement_type == /obj/item && !tool.get_sharpness()) - return FALSE - if(tool.usesound) - preop_sound = tool.usesound - - return TRUE - -//close shell -/datum/surgery_step/mechanic_close - name = "screw shell (screwdriver or scalpel)" - implements = list( - TOOL_SCREWDRIVER = 100, - TOOL_SCALPEL = 75, - /obj/item/knife = 50, - /obj/item = 10) // 10% success with any sharp item. - time = 2.4 SECONDS - preop_sound = 'sound/items/tools/screwdriver.ogg' - success_sound = 'sound/items/tools/screwdriver2.ogg' - -/datum/surgery_step/mechanic_close/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to screw the shell of [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to screw the shell of [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to screw the shell of [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You feel the faint pricks of sensation return as your [target.parse_zone_with_bodypart(target_zone)]'s panel is screwed in.", TRUE) - -/datum/surgery_step/mechanic_close/tool_check(mob/user, obj/item/tool) - if(implement_type == /obj/item && !tool.get_sharpness()) - return FALSE - if(tool.usesound) - preop_sound = tool.usesound - - return TRUE - -//prepare electronics -/datum/surgery_step/prepare_electronics - name = "prepare electronics (multitool or hemostat)" - implements = list( - TOOL_MULTITOOL = 100, - TOOL_HEMOSTAT = 75) - time = 2.4 SECONDS - preop_sound = 'sound/items/taperecorder/tape_flip.ogg' - success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' - -/datum/surgery_step/prepare_electronics/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to prepare electronics in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to prepare electronics in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to prepare electronics in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You can feel a faint buzz in your [target.parse_zone_with_bodypart(target_zone)] as the electronics reboot.", TRUE) - -//unwrench -/datum/surgery_step/mechanic_unwrench - name = "unwrench bolts (wrench or retractor)" - implements = list( - TOOL_WRENCH = 100, - TOOL_RETRACTOR = 75) - time = 2.4 SECONDS - preop_sound = 'sound/items/tools/ratchet.ogg' - -/datum/surgery_step/mechanic_unwrench/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to unwrench some bolts in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to unwrench some bolts in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to unwrench some bolts in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You feel a jostle in your [target.parse_zone_with_bodypart(target_zone)] as the bolts begin to loosen.", TRUE) - -/datum/surgery_step/mechanic_unwrench/tool_check(mob/user, obj/item/tool) - if(tool.usesound) - preop_sound = tool.usesound - - return TRUE - -//wrench -/datum/surgery_step/mechanic_wrench - name = "wrench bolts (wrench or retractor)" - implements = list( - TOOL_WRENCH = 100, - TOOL_RETRACTOR = 75) - time = 2.4 SECONDS - preop_sound = 'sound/items/tools/ratchet.ogg' - -/datum/surgery_step/mechanic_wrench/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to wrench some bolts in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to wrench some bolts in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to wrench some bolts in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You feel a jostle in your [target.parse_zone_with_bodypart(target_zone)] as the bolts begin to tighten.", TRUE) - -/datum/surgery_step/mechanic_wrench/tool_check(mob/user, obj/item/tool) - if(tool.usesound) - preop_sound = tool.usesound - - return TRUE - -//open hatch -/datum/surgery_step/open_hatch - name = "open the hatch (hand)" - accept_hand = TRUE - time = 1 SECONDS - preop_sound = 'sound/items/tools/ratchet.ogg' - preop_sound = 'sound/machines/airlock/doorclick.ogg' - -/datum/surgery_step/open_hatch/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to open the hatch holders in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to open the hatch holders in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to open the hatch holders in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "The last faint pricks of tactile sensation fade from your [target.parse_zone_with_bodypart(target_zone)] as the hatch is opened.", TRUE) - -/datum/surgery_step/open_hatch/tool_check(mob/user, obj/item/tool) - if(tool.usesound) - preop_sound = tool.usesound - - return TRUE diff --git a/code/modules/surgery/operations/_basic_surgery_state.dm b/code/modules/surgery/operations/_basic_surgery_state.dm new file mode 100644 index 00000000000..196062c381a --- /dev/null +++ b/code/modules/surgery/operations/_basic_surgery_state.dm @@ -0,0 +1,34 @@ +/// Used to track the state of surgeries on a mob generically rather than a bodypart +/datum/status_effect/basic_surgery_state + id = "surgery_state" + + alert_type = null + tick_interval = STATUS_EFFECT_NO_TICK + status_type = STATUS_EFFECT_REFRESH + + var/surgery_state = NONE + +/datum/status_effect/basic_surgery_state/on_creation(mob/living/new_owner, added_state = NONE, removed_state = NONE) + . = ..() + surgery_state = (added_state & ~removed_state) + SEND_SIGNAL(owner, COMSIG_LIVING_UPDATING_SURGERY_STATE, NONE, surgery_state, added_state) + +/datum/status_effect/basic_surgery_state/on_apply() + . = ..() + if(owner.has_limbs) + stack_trace("Applied a basic surgery state to [owner], which has limbs. This status effect is intended for limbless mobs.") + +/datum/status_effect/basic_surgery_state/get_examine_text() + if(HAS_SURGERY_STATE(surgery_state, SURGERY_SKIN_OPEN)) + return "[owner.p_Their()] skin is open[HAS_SURGERY_STATE(surgery_state, SURGERY_BONE_SAWED) ? " and bones are sawed opened" : ""]." + // other states are not yet supported + stack_trace("Surgery state holder had unsupported state(s): [jointext(bitfield_to_list(surgery_state, SURGERY_STATE_READABLE), ", ")]") + return null + +/datum/status_effect/basic_surgery_state/refresh(mob/living/old_owner, added_state = NONE, removed_state = NONE) + var/old_state = surgery_state + surgery_state |= added_state + surgery_state &= ~removed_state + SEND_SIGNAL(owner, COMSIG_LIVING_UPDATING_SURGERY_STATE, old_state, surgery_state, added_state | removed_state) + if(!surgery_state) + qdel(src) diff --git a/code/modules/surgery/operations/_operation.dm b/code/modules/surgery/operations/_operation.dm new file mode 100644 index 00000000000..aa785b95713 --- /dev/null +++ b/code/modules/surgery/operations/_operation.dm @@ -0,0 +1,1317 @@ +/** + * Attempts to perform a surgery with whatever tool is passed + * + * * src - the surgeon + * * patient - the mob being operated on + * * potential_tool - the tool being used for the operation (can be null / IMPLEMENT_HAND) + * * intentionally_fail - if TRUE, forces the operation to fail (for testing purposes) + * + * Returns an ITEM_INTERACT_* flag + */ +/mob/living/proc/perform_surgery(mob/living/patient, potential_tool = IMPLEMENT_HAND, intentionally_fail = FALSE) + if(DOING_INTERACTION(src, (HAS_TRAIT(src, TRAIT_HIPPOCRATIC_OATH) ? patient : DOAFTER_SOURCE_SURGERY))) + patient.balloon_alert(src, "already performing surgery!") + return ITEM_INTERACT_BLOCKING + + // allow cyborgs to use "hands" + if(istype(potential_tool, /obj/item/borg/cyborghug)) + potential_tool = IMPLEMENT_HAND + + var/operating_zone = zone_selected + var/list/operations = get_available_operations(patient, potential_tool, operating_zone) + + // we failed to undertake any operations? + if(!length(operations)) + if(!isitem(potential_tool)) + return NONE + var/obj/item/realtool = potential_tool + // try self-cauterization if applicable + if(src == patient) + var/manual_cauterization = try_manual_cauterize(realtool) + if(manual_cauterization & ITEM_INTERACT_ANY_BLOCKER) + return manual_cauterization + // for surgical tools specifically, we have some special handling + if(!(realtool.item_flags & SURGICAL_TOOL)) + return NONE + // if the targeted limb isn't prepped for surgery, i suppose we can allow an attack + var/obj/item/bodypart/operating = patient.get_bodypart(operating_zone) + if(operating && !HAS_TRAIT(operating, TRAIT_READY_TO_OPERATE)) + return NONE + // at this point we can be relatively sure they messed up so let's give a feedback message... + if(!patient.is_location_accessible(operating_zone, IGNORED_OPERATION_CLOTHING_SLOTS)) + patient.balloon_alert(src, "operation site is obstructed!") + else if(!IS_LYING_OR_CANNOT_LIE(patient)) + patient.balloon_alert(src, "not lying down!") + else + patient.balloon_alert(src, "nothing to do with [realtool.name]!") + // ...then, block attacking. prevents the surgeon from viciously stabbing the patient on a mistake + return ITEM_INTERACT_BLOCKING + + var/list/radial_operations = list() + for(var/radial_slice in operations) + radial_operations[radial_slice] = radial_slice // weird but makes it easier to index later + + sortTim(radial_operations, GLOBAL_PROC_REF(cmp_name_asc)) + + var/picked = show_radial_menu( + user = src, + anchor = patient, + choices = radial_operations, + require_near = TRUE, + autopick_single_option = TRUE, + radius = 56, + custom_check = CALLBACK(src, PROC_REF(surgery_check), potential_tool, patient), + ) + if(isnull(picked)) + return ITEM_INTERACT_BLOCKING + + var/datum/surgery_operation/picked_op = operations[picked][1] + var/atom/movable/operating_on = operations[picked][2] + var/list/op_info = operations[picked][3] + op_info[OPERATION_TARGET_ZONE] = operating_zone + op_info[OPERATION_FORCE_FAIL] = intentionally_fail + + return picked_op.try_perform(operating_on, src, potential_tool, op_info) + +/** + * Returns a list of all surgery operations the mob can currently perform on the patient with the potential tool + * + * * src - the surgeon + * * patient - the mob being operated on + * * potential_tool - the tool being used for the operation (can be null / IMPLEMENT_HAND) + * * operating_zone - the body zone being operated on + * + * Returns a list where the keys are radial menu slices and the values are lists of: + * * [0] - the operation datum + * * [1] - the atom being operated on + * * [2] - a list of option-specific info + */ +/mob/living/proc/get_available_operations(mob/living/patient, potential_tool = IMPLEMENT_HAND, operating_zone = zone_selected) + // List of typepaths of operations we *can* do + var/list/possible_operations = GLOB.operations.unlocked.Copy() + // Signals can add operation types to the list to unlock special ones + SEND_SIGNAL(src, COMSIG_LIVING_OPERATING_ON, patient, possible_operations) + SEND_SIGNAL(patient, COMSIG_LIVING_BEING_OPERATED_ON, patient, possible_operations) + + var/list/operations = list() + for(var/datum/surgery_operation/operation as anything in GLOB.operations.get_instances_from(possible_operations)) + var/atom/movable/operate_on = operation.get_operation_target(patient, operating_zone) + if(!operation.check_availability(patient, operate_on, src, potential_tool, operating_zone)) + continue + var/potential_options = operation.get_radial_options(operate_on, potential_tool, operating_zone) + if(!islist(potential_options)) + potential_options = list(potential_options) + for(var/datum/radial_menu_choice/radial_slice as anything in potential_options) + if(operations[radial_slice]) + stack_trace("Duplicate radial surgery option '[radial_slice.name]' detected for operation '[operation.type]'.") + continue + var/option_specific_info = potential_options[radial_slice] || list("[OPERATION_ACTION]" = "default") + operations[radial_slice] = list(operation, operate_on, option_specific_info) + + return operations + +/// Callback for checking if the surgery radial can be kept open +/mob/living/proc/surgery_check(obj/item/tool, mob/living/patient) + var/obj/item/holding = get_active_held_item() + + if(tool == IMPLEMENT_HAND) + return isnull(holding) || istype(holding, /obj/item/borg/cyborghug) // still holding nothing (or "hands") + if(QDELETED(holding)) + return FALSE // i dunno, a stack item? not our problem + + return tool == holding.get_proxy_attacker_for(patient, src) // tool (or its proxy) is still being held + +/// src attempts to cauterize themselves to reset their surgery state. Basically a manual form of the real "close skin" operation +/mob/living/proc/try_manual_cauterize(obj/item/tool) + var/cauterize_zone = deprecise_zone(zone_selected) + var/obj/item/bodypart/limb = get_bodypart(cauterize_zone) + if(!manual_cauterize_check(tool, limb)) + return NONE + if(DOING_INTERACTION_WITH_TARGET(src, src)) + return ITEM_INTERACT_BLOCKING + + visible_message( + span_notice("[src] attempts to close [p_their()] own [limb.plaintext_zone] with [tool]..."), + span_notice("You attempt to close your own [limb.plaintext_zone] with [tool]..."), + span_hear("You hear singing."), + vision_distance = 5, + visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, + ) + playsound(src, istype(tool, /obj/item/stack/medical/suture) ? SFX_SUTURE_BEGIN : 'sound/items/handling/surgery/cautery1.ogg', 50, TRUE) + if(!do_after( + user = src, + delay = /datum/surgery_operation/limb/close_skin::time * 2 * tool.toolspeed, + target = src, + extra_checks = CALLBACK(src, PROC_REF(manual_cauterize_check), tool, limb), + )) + return ITEM_INTERACT_BLOCKING + + visible_message( + span_notice("[src] closes [p_their()] own [limb.plaintext_zone] with [tool]."), + span_notice("You close your own [limb.plaintext_zone] with [tool]."), + span_hear("You hear singing."), + vision_distance = 5, + visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, + ) + playsound(src, istype(tool, /obj/item/stack/medical/suture) ? SFX_SUTURE_END : 'sound/items/handling/surgery/cautery2.ogg', 50, TRUE) + limb.remove_surgical_state(ALL_SURGERY_STATES_UNSET_ON_CLOSE) + if(istype(tool, /obj/item/stack/medical/suture)) + var/obj/item/stack/medical/suture/suture_tool = tool + suture_tool.use(1) + else + limb.receive_damage(burn = 5, wound_bonus = CANT_WOUND, damage_source = tool) + return ITEM_INTERACT_SUCCESS + +/// Callback for checking if the cauterization do-after can continue +/mob/living/proc/manual_cauterize_check(obj/item/tool, obj/item/bodypart/limb) + PRIVATE_PROC(TRUE) + + if(QDELETED(limb) || limb.owner != src) + return FALSE + + if(QDELETED(tool)) + return FALSE + else if(istype(tool, /obj/item/stack/medical/suture)) + var/obj/item/stack/medical/suture/suture_tool = tool + if(suture_tool.amount <= 0) + return FALSE + else if(tool.tool_behaviour != TOOL_CAUTERY) + if(tool.get_temperature() <= 0) + return FALSE + + // we need to have a surgery state worth closing + var/states_to_check = ALL_SURGERY_STATES_UNSET_ON_CLOSE + if(!LIMB_HAS_BONES(limb)) + states_to_check &= ~BONELESS_SURGERY_STATES + if(!LIMB_HAS_VESSELS(limb)) + states_to_check &= ~VESSELLESS_SURGERY_STATES + if(!LIMB_HAS_SKIN(limb)) + states_to_check &= ~SKINLESS_SURGERY_STATES + if(!states_to_check || !LIMB_HAS_ANY_SURGERY_STATE(limb, states_to_check)) + return FALSE + + // skin has to be open or cut to do anything (we can't have a negative state without also having skin open anyways) + if(!LIMB_HAS_ANY_SURGERY_STATE(limb, ALL_SURGERY_SKIN_STATES)) + return FALSE + + return TRUE + +/// Debug proc to print all surgeries available to whoever called the proc +/mob/living/proc/debug_get_all_available_surgeries() + var/mob/living/surgeon = usr + if(!isliving(surgeon)) + return + + var/list/operations = surgeon.get_available_operations(src, surgeon.get_active_held_item()) + if(!length(operations)) + to_chat(surgeon, boxed_message(span_info("No available surgeries."))) + return + + var/list/operations_info = list() + for(var/radial_slice in operations) + var/datum/surgery_operation/operation = operations[radial_slice][1] + var/atom/movable/operating_on = operations[radial_slice][2] + operations_info += "[radial_slice]: [operation.name] on [operating_on]" + + to_chat(surgeon, boxed_message(span_info("Available surgeries:

    [jointext(operations_info, "
    ")]"))) + +/// Takes a target zone and returns a list of readable surgery states for that zone. +/// Example output may be list("Skin is cut", "Blood vessels are unclamped", "Bone is sawed") +/mob/living/proc/get_surgery_state_as_list(target_zone) + var/list/state = list() + if(has_limbs) + var/obj/item/bodypart/part = get_bodypart(target_zone) + if(isnull(part)) + return list("Bodypart missing") + + if(HAS_TRAIT(part, TRAIT_READY_TO_OPERATE)) + state += "Ready for surgery" + if(!is_location_accessible(target_zone, IGNORED_OPERATION_CLOTHING_SLOTS)) + state += "Bodypart is obstructed by clothing" + + var/part_state = part?.surgery_state || NONE + + if(!LIMB_HAS_BONES(part)) + part_state &= ~BONELESS_SURGERY_STATES + state += "Bodypart lacks bones (counts as [jointext(bitfield_to_list(BONELESS_SURGERY_STATES, SURGERY_STATE_READABLE), ", ")])" + if(!LIMB_HAS_VESSELS(part)) + part_state &= ~VESSELLESS_SURGERY_STATES + state += "Bodypart lacks blood vessels (counts as [jointext(bitfield_to_list(VESSELLESS_SURGERY_STATES, SURGERY_STATE_READABLE), ", ")])" + if(!LIMB_HAS_SKIN(part)) + part_state &= ~SKINLESS_SURGERY_STATES + state += "Bodypart lacks skin (counts as [jointext(bitfield_to_list(SKINLESS_SURGERY_STATES, SURGERY_STATE_READABLE), ", ")])" + + state += bitfield_to_list(part_state, SURGERY_STATE_READABLE) + + else + if(HAS_TRAIT(src, TRAIT_READY_TO_OPERATE)) + state += "Ready for surgery" + + var/datum/status_effect/basic_surgery_state/state_holder = has_status_effect(__IMPLIED_TYPE__) + state += bitfield_to_list(state_holder?.surgery_state, SURGERY_STATE_READABLE) + + return state + +/** + * Adds a speed modifier to this mob + * + * * id - id of the modifier, string + * * amount - the multiplier to apply to surgery speed. + * This is multiplicative with other modifiers. + * * duration - how long the modifier should last in deciseconds. + * If null, it will be permanent until removed. + */ +/mob/living/proc/add_surgery_speed_mod(id, amount, duration) + ASSERT(!isnull(id), "Surgery speed mod ID cannot be null") + ASSERT(isnum(amount), "Surgery speed mod amount must be a number") + ASSERT(isnum(duration) || isnull(duration), "Surgery speed mod duration must be a number or null") + + var/existing = LAZYACCESS(mob_surgery_speed_mods, id) + if(existing == amount) + return + + if(isnum(existing)) + if(amount > 1 && existing > 1) + // both are speed decreases, take the better one + LAZYSET(mob_surgery_speed_mods, id, max(amount, existing)) + else if(amount < 1 && existing < 1) + // both are speed increases, take the better one + LAZYSET(mob_surgery_speed_mods, id, min(amount, existing)) + else + // one of each, just multiply them + LAZYSET(mob_surgery_speed_mods, id, amount * existing) + else + LAZYSET(mob_surgery_speed_mods, id, amount) + + if(isnum(duration)) + addtimer(CALLBACK(src, PROC_REF(remove_surgery_speed_mod), id), duration, TIMER_DELETE_ME|TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_NO_HASH_WAIT) + +/** + * Removes a speed modifier from this mob + * + * * id - id of the modifier to remove, string + */ +/mob/living/proc/remove_surgery_speed_mod(id) + LAZYREMOVE(mob_surgery_speed_mods, id) + +GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) + +/// Singleton containing all surgery operation, as well as some helpers for organizing them +/datum/operation_holder + /// All operation singletons, indexed by typepath + /// It is recommended to use get_instances_from() where possible, rather than accessing this directly + var/list/operations_by_typepath + /// All operation typepaths which are unlocked by default, indexed by typepath + var/list/unlocked + /// All operation typepaths which are locked by something, indexed by typepath + var/list/locked + +/datum/operation_holder/New() + . = ..() + operations_by_typepath = list() + unlocked = list() + locked = list() + + for(var/operation_type in valid_subtypesof(/datum/surgery_operation)) + var/datum/surgery_operation/operation = new operation_type() + if(isnull(operation.name)) + stack_trace("Surgery operation '[operation_type]' is missing a name!") + + operations_by_typepath[operation_type] = operation + if(operation.operation_flags & OPERATION_LOCKED) + locked += operation_type + else + unlocked += operation_type + +/// Takes in a list of operation typepaths and returns their singleton instances. Optionally can filter out replaced surgeries and by certain operation flags. +/datum/operation_holder/proc/get_instances_from(list/typepaths, filter_replaced = TRUE) + var/list/result = list() + for(var/datum/surgery_operation/operation_type as anything in typepaths) + var/datum/surgery_operation/operation = operations_by_typepath[operation_type] + if(isnull(operation)) + continue + if(filter_replaced && is_replaced(operation, typepaths)) + continue + result += operation + return result + +/// Check if the passed operation has been replaced by a typepath in the provided operation pool +/datum/operation_holder/proc/is_replaced(datum/surgery_operation/operation, list/operation_pool) + if(isnull(operation.replaced_by) || !length(operation_pool)) + return FALSE + if(operation.replaced_by == operation.type) + return FALSE + if(operation.replaced_by in operation_pool) + return TRUE + // recursively check if the operation that replaces us is itself replaced + var/datum/surgery_operation/next_highest_operation = operations_by_typepath[operation.replaced_by] + if(isnull(next_highest_operation)) + return FALSE + return is_replaced(next_highest_operation, operation_pool) + +/** + * ## Surgery operation datum + * + * A singleton datum which represents a surgical operation that can be performed on a mob. + * + * Surgery operations can be something simple, like moving between surgery states (tend wounds, clamp vessels), + * or more complex, like outright replacing limbs or organs. As such the datum is very flexible. + * + * At most basic, you must implement the vars: + * * - [name][/datum/surgery_operation/var/name] + * * - [desc][/datum/surgery_operation/var/desc] + * * - [implements][/datum/surgery_operation/var/implements] + * And the procs: + * * - [on success][/datum/surgery_operation/proc/on_success] - put the effects of the operation here + * Other noteworthy vars and procs you probably want to implement or override: + * * - [operation flags][/datum/surgery_operation/var/operation_flags] - flags modifying the behavior of the operation + * * - [required surgery state][/datum/surgery_operation/var/all_surgery_states_required] - target must have ALL of these states to be eligible for the operation + * * - [blocked surgery state][/datum/surgery_operation/var/any_surgery_states_blocked] - target must NOT have ANY these states to be eligible for the operation + * * - [state check][/datum/surgery_operation/proc/state_check] - extra checks for if the operating target is valid + * * - [get default radial image][/datum/surgery_operation/proc/get_default_radial_image] - what icon to use for this operation on the radial menu + * + * It's recommended to work off of [/datum/surgery_operation/limb] or [/datum/surgery_operation/organ] + * as they implement a lot of common functionality for targeting limbs or organs for you. + * + * See also [/datum/surgery_operation/basic], which is a bit more complex to use + * but allows for operations to target any mob type, rather than only those with limbs or organs. + */ +/datum/surgery_operation + abstract_type = /datum/surgery_operation + /// Required - Name of the operation, keep it short and format it like an action - "amputate limb", "remove organ" + /// Don't capitalize it, it will be capitalized automatically where necessary. + var/name + /// Required - Description of the operation, keep it short and format it like an action - "Amputate a patient's limb.", "Remove a patient's organ.". + // Use "a patient" instead of "the patient" to keep it generic. + var/desc + + /// Optional - the name of the operation shown in RND consoles and the operating computer. + /// You can get fancier here, givin an official surgery name ("Lobectomy") or rephrase it to be more descriptive ("Brain Lobectomy"). + /// Capitalize it as necessary. + var/rnd_name + /// Optional - the description of the operation shown in RND consoles and the operating computer. + /// Here is where you may want to provide more information on why an operation is done ("Fixes a broken liver") or special requirements ("Requires Synthflesh"). + /// Use "the patient" instead of "a patient" to keep it specific. + var/rnd_desc + + /** + * What tool(s) can be used to perform this operation? + * + * Assoc list of item typepath, TOOL_X, or IMPLEMENT_HAND to a multiplier for how effective that tool is at performing the operation. + * For example, list(TOOL_SCALPEL = 2, TOOL_SAW = 0.5) means that you can use a scalpel to operate, and it will double the time the operation takes. + * Likewise using a saw will halve the time it takes. If a tool is not listed, it cannot be used for this operation. + * + * Order matters! If a tool matches multiple entries, the first one will always be used. + * For example, if you have list(TOOL_SCREWDRIVER = 2, /obj/item/screwdriver = 1), and use a screwdriver + * it will use the TOOL_SCREWDRIVER modifier, making your operation 2x slower, even though the latter entry would have been faster. + * + * For this, it is handy to keep in mind SURGERY_MODIFIER_FAILURE_THRESHOLD. + * While speeds are soft capped and cannot be reduced beyond this point, larger modifiers still increase failure chances. + * + * Lastly, while most operations have its main tool with a 1x modifier (representing the "intended" tool), + * some will have its main tool's multiplier above or below 1x to represent an innately easier or harder operation + */ + var/list/implements + /// Base time to perform this operation + var/time = 1 SECONDS + + /// Flags modifying the behavior of this operation + var/operation_flags = NONE + + /// The target must have ALL of these surgery states for the operation to be available + var/all_surgery_states_required = NONE + /// The target must have ANY of these surgery states for the operation to be available + var/any_surgery_states_required = NONE + /// The target must NOT have ANY of these surgery states for the operation to be available + var/any_surgery_states_blocked = NONE + + /// Typepath of a surgical operation that supersedes this one + /// If this operation and the replaced_by operation are both available, only the replaced_by one will be usable + var/datum/surgery_operation/replaced_by + + /// SFX played before the do-after begins + /// Can be a sound path or an assoc list of item typepath to sound path to make different sounds for different tools + var/preop_sound + /// SFX played on success, after the do-after + /// Can be a sound path or an assoc list of item typepath to sound path to make different sounds for different tools + var/success_sound + /// SFX played on failure, after the do-after + /// Can be a sound path or an assoc list of item typepath to sound path to make different sounds for different tools + var/failure_sound + + /// The default radial menu choice for this operation, lazily created on first use + /// Some subtypes won't have this set as they provide their own options + VAR_PRIVATE/datum/radial_menu_choice/main_option + + /// Which mood event to give the patient when surgery is starting while they're conscious. + /// This should be permanent/not have a timer until the surgery either succeeds or fails, as those states will immediately replace it. + /// Mostly just flavor text. + var/datum/mood_event/surgery/surgery_started_mood_event = /datum/mood_event/surgery + /// Which mood event to give the conscious patient when surgery succeeds. + /// Lasts far shorter than if it failed. + var/datum/mood_event/surgery/surgery_success_mood_event = /datum/mood_event/surgery/success + /// Which mood event to give the consious patient when surgery fails. + /// Lasts muuuuuch longer. + var/datum/mood_event/surgery/surgery_failure_mood_event = /datum/mood_event/surgery/failure + +/** + * Checks to see if this operation can be performed + * This is the main entry point for checking availability + */ +/datum/surgery_operation/proc/check_availability(mob/living/patient, atom/movable/operating_on, mob/living/surgeon, tool, operated_zone) + SHOULD_NOT_OVERRIDE(TRUE) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_BE_PURE(TRUE) + + if(isnull(patient) || isnull(operating_on)) + return FALSE + + if(!(operation_flags & OPERATION_STANDING_ALLOWED) && !IS_LYING_OR_CANNOT_LIE(patient)) + return FALSE + + if(!(operation_flags & OPERATION_SELF_OPERABLE) && patient == surgeon && !HAS_TRAIT(surgeon, TRAIT_SELF_SURGERY)) + return FALSE + + if(get_tool_quality(tool) <= 0) + return FALSE + + if(!is_available(operating_on, operated_zone)) + return FALSE + + return snowflake_check_availability(operating_on, surgeon, tool, operated_zone) + +/** + * Snowflake checks for surgeries which need many interconnected conditions to be met + */ +/datum/surgery_operation/proc/snowflake_check_availability(atom/movable/operating_on, mob/living/surgeon, tool, operated_zone) + PROTECTED_PROC(TRUE) + return TRUE + +/** + * Returns the quality of the passed tool for this operation + * Quality directly affects the time taken to perform the operation + * + * 0 = unusable + * 1 = standard quality + */ +/datum/surgery_operation/proc/get_tool_quality(tool = IMPLEMENT_HAND) + PROTECTED_PROC(TRUE) + if(!length(implements)) + return 1 + if(!isitem(tool)) + return implements[tool] + if(!tool_check(tool)) + return 0 + + var/obj/item/realtool = tool + return (realtool.toolspeed) * (implements[realtool.tool_behaviour] || is_type_in_list(realtool, implements, zebra = TRUE) || 0) + +/** + * Return a radial slice, a list of radial slices, or an assoc list of radial slice to operation info + * + * By default it returns a single option with the operation name and description, + * but you can override this proc to return multiple options for one operation, like selecting which organ to operate on. + */ +/datum/surgery_operation/proc/get_radial_options(atom/movable/operating_on, obj/item/tool, operating_zone) + if(!main_option) + main_option = new() + main_option.image = get_default_radial_image() + main_option.name = name + main_option.info = desc + + return main_option + +/** + * Checks to see if this operation can be performed on the provided target + */ +/datum/surgery_operation/proc/is_available(atom/movable/operating_on, operated_zone) + PROTECTED_PROC(TRUE) + SHOULD_CALL_PARENT(TRUE) + + if(all_surgery_states_required && !has_surgery_state(operating_on, all_surgery_states_required)) + return FALSE + + if(any_surgery_states_required && !has_any_surgery_state(operating_on, any_surgery_states_required)) + return FALSE + + if(any_surgery_states_blocked && has_any_surgery_state(operating_on, any_surgery_states_blocked)) + return FALSE + + if(!state_check(operating_on)) + return FALSE + + var/mob/living/patient = get_patient(operating_on) + if(!(operation_flags & OPERATION_IGNORE_CLOTHES) && !patient.is_location_accessible(operated_zone, IGNORED_OPERATION_CLOTHING_SLOTS)) + return FALSE + + return TRUE + +/// Check if the movable being operated on has all the passed surgery states +/datum/surgery_operation/proc/has_surgery_state(atom/movable/operating_on, state) + PROTECTED_PROC(TRUE) + return FALSE + +/// Check if the movable being operated on has any of the passed surgery states +/datum/surgery_operation/proc/has_any_surgery_state(atom/movable/operating_on, state) + PROTECTED_PROC(TRUE) + return FALSE + +/** + * Any operation specific state checks, such as checking for traits or more complex state requirements + */ +/datum/surgery_operation/proc/state_check(atom/movable/operating_on) + PROTECTED_PROC(TRUE) + return TRUE + +/** + * Checks to see if the provided tool is valid for this operation + * You can override this to add more specific checks, such as checking sharpness + */ +/datum/surgery_operation/proc/tool_check(obj/item/tool) + PROTECTED_PROC(TRUE) + return TRUE + +/** + * Returns the name of whatever tool is recommended for this operation, such as "hemostat" + */ +/datum/surgery_operation/proc/get_recommended_tool() + if(!length(implements)) + return null + var/recommendation = implements[1] + if(istext(recommendation)) + return recommendation // handles tools or IMPLEMENT_HAND + if(recommendation == /obj/item) + return get_any_tool() + if(ispath(recommendation, /obj/item)) + var/obj/item/tool = recommendation + return tool::name + return null + +/** + * For surgery operations that can be performed with any item, this explains what kind of item is needed + */ +/datum/surgery_operation/proc/get_any_tool() + return "Any item" + +/** + * Return a list of lists of strings indicating the various requirements for this operation + */ +/datum/surgery_operation/proc/get_requirements() + SHOULD_NOT_OVERRIDE(TRUE) + return list( + all_required_strings(), + any_required_strings(), + any_optional_strings(), + all_blocked_strings(), + ) + +/// Returns a list of strings indicating requirements for this operation +/// "All requirements" are formatted as "All of the following must be true:" +/datum/surgery_operation/proc/all_required_strings() + SHOULD_CALL_PARENT(TRUE) + . = bitfield_to_list(all_surgery_states_required, SURGERY_STATE_GUIDES("must")) + if(!(operation_flags & OPERATION_STANDING_ALLOWED)) + . += "the patient must be lying down" + +/// Returns a list of strings indicating any of the requirements for this operation +/// "Any requirements" are formatted as "At least one of the following must be true:" +/datum/surgery_operation/proc/any_required_strings() + SHOULD_CALL_PARENT(TRUE) + . = list() + // grouped states are filtered down to make it more readable + var/parsed_any_flags = any_surgery_states_required + if((parsed_any_flags & ALL_SURGERY_BONE_STATES) == ALL_SURGERY_BONE_STATES) + parsed_any_flags &= ~ALL_SURGERY_BONE_STATES + . += "the bone must be sawed or drilled" + if((parsed_any_flags & ALL_SURGERY_SKIN_STATES) == ALL_SURGERY_SKIN_STATES) + parsed_any_flags &= ~ALL_SURGERY_SKIN_STATES + . += "the skin must be cut or opened" + if((parsed_any_flags & ALL_SURGERY_VESSEL_STATES) == ALL_SURGERY_VESSEL_STATES) + parsed_any_flags &= ~ALL_SURGERY_VESSEL_STATES + . += "the blood vessels must be clamped or unclamped" // weird phrasing but whatever + + . += bitfield_to_list(parsed_any_flags, SURGERY_STATE_GUIDES("must")) + +/// Returns a list of strings indicating optional conditions for this operation +/// "Optional conditions" are formatted as "Additionally, any of the following may be true:" +/datum/surgery_operation/proc/any_optional_strings() + SHOULD_CALL_PARENT(TRUE) + . = list() + if(operation_flags & OPERATION_SELF_OPERABLE) + . += "a surgeon may perform this on themselves" + +/// Returns a list of strings indicating blocked states for this operation +/// "Blocked requirements" are formatted as "However, none of the following may be true:" +/datum/surgery_operation/proc/all_blocked_strings() + SHOULD_CALL_PARENT(TRUE) + . = list() + // grouped states are filtered down to make it more readable + var/parsed_blocked_flags = any_surgery_states_blocked + if((parsed_blocked_flags & ALL_SURGERY_BONE_STATES) == ALL_SURGERY_BONE_STATES) + parsed_blocked_flags &= ~ALL_SURGERY_BONE_STATES + . += "the bone must be intact" + if((parsed_blocked_flags & ALL_SURGERY_SKIN_STATES) == ALL_SURGERY_SKIN_STATES) + parsed_blocked_flags &= ~ALL_SURGERY_SKIN_STATES + . += "the skin must be intact" + if((parsed_blocked_flags & ALL_SURGERY_VESSEL_STATES) == ALL_SURGERY_VESSEL_STATES) + parsed_blocked_flags &= ~ALL_SURGERY_VESSEL_STATES + . += "the blood vessels must be intact" + + . += bitfield_to_list(parsed_blocked_flags, SURGERY_STATE_GUIDES("must not")) + if(!(operation_flags & OPERATION_IGNORE_CLOTHES)) + . += "the operation site must not be obstructed by clothing" + +/** + * Returns what icon this surgery uses by default on the radial wheel if it does not implement its own radial options + * + * Also used when generating icons for the wiki + */ +/datum/surgery_operation/proc/get_default_radial_image() + return image(icon = 'icons/effects/random_spawners.dmi', icon_state = "questionmark") + +/// Helper to get a generic limb radial image based on body zone +/datum/surgery_operation/proc/get_generic_limb_radial_image(body_zone) + SHOULD_NOT_OVERRIDE(TRUE) + PROTECTED_PROC(TRUE) + + if(body_zone == BODY_ZONE_HEAD || body_zone == BODY_ZONE_CHEST || body_zone == BODY_ZONE_PRECISE_EYES || body_zone == BODY_ZONE_PRECISE_MOUTH) + return image(icon = 'icons/obj/medical/surgery_ui.dmi', icon_state = "surgery_[body_zone]") + if(body_zone == BODY_ZONE_L_ARM || body_zone == BODY_ZONE_R_ARM) + return image(icon = 'icons/obj/medical/surgery_ui.dmi', icon_state = "surgery_arms") + if(body_zone == BODY_ZONE_L_LEG || body_zone == BODY_ZONE_R_LEG) + return image(icon = 'icons/obj/medical/surgery_ui.dmi', icon_state = "surgery_legs") + return get_default_radial_image() + +/** + * Helper for constructing overlays to apply to a radial image + * + * Input can be + * * - An atom typepath + * * - An atom instance + * * - Another image + * + * Returns a list of images + */ +/datum/surgery_operation/proc/add_radial_overlays(list/overlay_icons) + SHOULD_NOT_OVERRIDE(TRUE) + PROTECTED_PROC(TRUE) + + if(!islist(overlay_icons)) + overlay_icons = list(overlay_icons) + + var/list/created_list = list() + for(var/input in overlay_icons) + var/image/created = isimage(input) ? input : image(input) + created.layer = FLOAT_LAYER + created.plane = FLOAT_PLANE + created.pixel_w = 0 + created.pixel_x = 0 + created.pixel_y = 0 + created.pixel_z = 0 + created_list += created + + return created_list + + +/** + * Collates all time modifiers for this operation and returns the final modifier + */ +/datum/surgery_operation/proc/get_time_modifiers(atom/movable/operating_on, mob/living/surgeon, tool) + PROTECTED_PROC(TRUE) + var/total_mod = 1.0 + total_mod *= get_tool_quality(tool) || 1.0 + // Ignore alllll the penalties (but also all the bonuses) + if(!HAS_TRAIT(surgeon, TRAIT_IGNORE_SURGERY_MODIFIERS)) + var/mob/living/patient = get_patient(operating_on) + total_mod *= get_location_modifier(get_turf(patient)) + total_mod *= get_morbid_modifier(surgeon, tool) + total_mod *= get_mob_surgery_speed_mod(patient) + // Using TRAIT_SELF_SURGERY on a surgery which doesn't normally allow self surgery imparts a penalty + if(patient == surgeon && HAS_TRAIT(surgeon, TRAIT_SELF_SURGERY) && !(operation_flags & OPERATION_SELF_OPERABLE)) + total_mod *= 1.5 + return round(total_mod, 0.01) + +/// Returns a time modifier for morbid operations +/datum/surgery_operation/proc/get_morbid_modifier(mob/living/surgeon, obj/item/tool) + PROTECTED_PROC(TRUE) + if(!(operation_flags & OPERATION_MORBID)) + return 1.0 + if(!HAS_MIND_TRAIT(surgeon, TRAIT_MORBID)) + return 1.0 + if(!isitem(tool) || !(tool.item_flags & CRUEL_IMPLEMENT)) + return 1.0 + + return 0.7 + +/// Returns a time modifier based on the mob's status +/datum/surgery_operation/proc/get_mob_surgery_speed_mod(mob/living/patient) + PROTECTED_PROC(TRUE) + var/basemod = 1.0 + for(var/mod_id, mod_amt in patient.mob_surgery_speed_mods) + basemod *= mod_amt + if(HAS_TRAIT(patient, TRAIT_SURGICALLY_ANALYZED)) + basemod *= 0.8 + if(HAS_TRAIT(patient, TRAIT_ANALGESIA)) + basemod *= 0.8 + return basemod + +/// Gets the surgery speed modifier for a given mob, based off what sort of table/bed/whatever is on their turf. +/datum/surgery_operation/proc/get_location_modifier(turf/operation_turf) + PROTECTED_PROC(TRUE) + // Technically this IS a typecache, just not the usual kind :3 + // The order of the modifiers matter, latter entries override earlier ones + var/static/list/modifiers = zebra_typecacheof(list( + /obj/structure/table = 1.25, + /obj/structure/table/optable = 1.0, + /obj/structure/table/optable/abductor = 0.85, + /obj/machinery/stasis = 1.15, + /obj/structure/bed = 1.5, + )) + var/mod = 2.0 + for(var/obj/thingy in operation_turf) + mod = min(mod, modifiers[thingy.type] || 2.0) + return mod + +/** + * Gets what movable is being operated on by a surgeon during this operation + * Determines what gets passed into the try_perform() proc + * If null is returned, the operation cannot be performed + * + * * patient - The mob being operated on + * * body_zone - The body zone being operated on + * + * Returns the atom/movable being operated on + */ +/datum/surgery_operation/proc/get_operation_target(mob/living/patient, body_zone) + return patient + +/** + * Called by operating computers to hint that this surgery could come next given the target's current state + */ +/datum/surgery_operation/proc/show_as_next_step(mob/living/potential_patient, operated_zone) + var/atom/movable/operate_on = get_operation_target(potential_patient, operated_zone) + return !isnull(operate_on) && is_available(operate_on, operated_zone) + + +/** + * The actual chain of performing the operation + * + * * operating_on - The atom being operated on, probably a bodypart or occasionally a mob directly + * * surgeon - The mob performing the operation + * * tool - The tool being used to perform the operation. CAN BE A STRING, ie, IMPLEMENT_HAND, be careful + * * operation_args - Additional arguments passed into the operation. Contains largely niche info that only certain operations care about or can be accessed through other means + * + * Returns an item interaction flag - intended to be invoked from the interaction chain + */ +/datum/surgery_operation/proc/try_perform(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args = list()) + SHOULD_NOT_OVERRIDE(TRUE) + var/mob/living/patient = get_patient(operating_on) + + if(!check_availability(patient, operating_on, surgeon, tool, operation_args[OPERATION_TARGET_ZONE])) + return ITEM_INTERACT_BLOCKING + if(!start_operation(operating_on, surgeon, tool, operation_args)) + return ITEM_INTERACT_BLOCKING + + var/was_sleeping = (patient.stat != DEAD && HAS_TRAIT(patient, TRAIT_KNOCKEDOUT)) + var/result = NONE + + update_surgery_mood(patient, SURGERY_STATE_STARTED) + SEND_SIGNAL(patient, COMSIG_LIVING_SURGERY_STARTED, src, operating_on, tool) + + do + operation_args[OPERATION_SPEED] = get_time_modifiers(operating_on, surgeon, tool) + + if(!do_after( + user = surgeon, + // Actual delay is capped - think of the excess time as being added to failure chance instead + delay = time * min(operation_args[OPERATION_SPEED], SURGERY_MODIFIER_FAILURE_THRESHOLD), + target = patient, + extra_checks = CALLBACK(src, PROC_REF(operate_check), patient, operating_on, surgeon, tool, operation_args), + // You can only operate on one mob at a time without a hippocratic oath + interaction_key = HAS_TRAIT(surgeon, TRAIT_HIPPOCRATIC_OATH) ? patient : DOAFTER_SOURCE_SURGERY, + )) + result |= ITEM_INTERACT_BLOCKING + update_surgery_mood(patient, SURGERY_STATE_FAILURE) + break + + if(ishuman(surgeon)) + var/mob/living/carbon/human/surgeon_human = surgeon + surgeon_human.add_blood_DNA_to_items(patient.get_blood_dna_list(), ITEM_SLOT_GLOVES) + else + surgeon.add_mob_blood(patient) + + // This will annoy doctors immensely + // if(isitem(tool)) + // var/obj/item/realtool = tool + // realtool.add_mob_blood(patient) + + // We modify speed modifier here AFTER the do after to increase failure chances, that's intentional + // Think of it as modifying "effective time" rather than "real time". Failure chance goes up but the time it took is unchanged + + // Using TRAIT_SELF_SURGERY on a surgery which doesn't normally allow self surgery imparts a flat penalty + // (On top of the 1.5x real time surgery modifier, an effective time modifier of 3x under standard conditions) + if(patient == surgeon && HAS_TRAIT(surgeon, TRAIT_SELF_SURGERY) && !(operation_flags & OPERATION_SELF_OPERABLE)) + operation_args[OPERATION_SPEED] += 1.5 + + // Otherwise if we have TRAIT_IGNORE_SURGERY_MODIFIERS we cannot possibly fail, unless we specifically allow failure + if(HAS_TRAIT(surgeon, TRAIT_IGNORE_SURGERY_MODIFIERS) && !(operation_flags & OPERATION_ALWAYS_FAILABLE)) + operation_args[OPERATION_SPEED] = 0 + + if(operation_args[OPERATION_FORCE_FAIL] || prob(clamp(GET_FAILURE_CHANCE(time, operation_args[OPERATION_SPEED]), 0, 99))) + failure(operating_on, surgeon, tool, operation_args) + result |= ITEM_INTERACT_FAILURE + update_surgery_mood(patient, SURGERY_STATE_FAILURE) + else + success(operating_on, surgeon, tool, operation_args) + result |= ITEM_INTERACT_SUCCESS + update_surgery_mood(patient, SURGERY_STATE_SUCCESS) + + if(isstack(tool)) + var/obj/item/stack/tool_stack = tool + tool_stack.use(1) + + while ((operation_flags & OPERATION_LOOPING) && can_loop(patient, operating_on, surgeon, tool, operation_args)) + + SEND_SIGNAL(patient, COMSIG_LIVING_SURGERY_FINISHED, src, operating_on, tool) + + if(patient.stat == DEAD && was_sleeping) + surgeon.client?.give_award(/datum/award/achievement/jobs/sandman, surgeon) + + return result + +/// Called after an operation to check if it can be repeated/looped +/datum/surgery_operation/proc/can_loop(mob/living/patient, atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) + PROTECTED_PROC(TRUE) + return operate_check(patient, operating_on, surgeon, tool, operation_args) + +/// Called during the do-after to check if the operation can continue +/datum/surgery_operation/proc/operate_check(mob/living/patient, atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) + PROTECTED_PROC(TRUE) + + if(isstack(tool)) + var/obj/item/stack/tool_stack = tool + if(tool_stack.amount <= 0) + return FALSE + + if(!surgeon.surgery_check(tool, patient)) + return FALSE + + if(!check_availability(patient, operating_on, surgeon, tool, operation_args[OPERATION_TARGET_ZONE])) + return FALSE + + return TRUE + +/** + * Allows for any extra checks or setup when the operation starts + * If you want user input before for an operation, do it here + * + * This proc can sleep, sanity checks are automatically performed after it completes + * + * Return FALSE to cancel the operation + * Return TRUE to continue + */ +/datum/surgery_operation/proc/pre_preop(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) + PROTECTED_PROC(TRUE) + return TRUE + +/// Used to display messages to the surgeon and patient +/datum/surgery_operation/proc/display_results(mob/living/surgeon, mob/living/target, self_message, detailed_message, vague_message, target_detailed = FALSE) + SHOULD_NOT_OVERRIDE(TRUE) + PROTECTED_PROC(TRUE) + + ASSERT(istext(self_message), "[type] operation display_results must have a self_message!") + ASSERT(istext(detailed_message), "[type] operation display_results must have a detailed_message!") + ASSERT(istext(vague_message) || target_detailed, "[type] operation display_results must have either a vague_message or target_detailed = TRUE!") + + surgeon.visible_message( + message = detailed_message, + self_message = self_message, + vision_distance = 1, + ignored_mobs = target_detailed ? null : target + ) + if(target_detailed) + return + + var/you_feel = pick("a brief pain", "your body tense up", "an unnerving sensation") + target.show_message( + msg = vague_message || detailed_message || span_notice("You feel [you_feel] as you are operated on."), + type = MSG_VISUAL, + alt_msg = span_notice("You feel [you_feel] as you are operated on."), + ) + +/// Display pain message to the target based on their traits and condition +/datum/surgery_operation/proc/display_pain(mob/living/target, pain_message, mechanical_surgery = FALSE) + SHOULD_NOT_OVERRIDE(TRUE) + PROTECTED_PROC(TRUE) + + if(!pain_message) + return + + // Determine how drunk our patient is + var/drunken_patient = target.get_drunk_amount() + // Create a probability to ignore the pain based on drunkenness level + var/drunken_ignorance_probability = clamp(drunken_patient, 0, 90) + + if(target.stat >= UNCONSCIOUS || HAS_TRAIT(target, TRAIT_KNOCKEDOUT)) + return + if(HAS_TRAIT(target, TRAIT_ANALGESIA) || drunken_patient && prob(drunken_ignorance_probability)) + to_chat(target, span_notice("You feel a dull, numb sensation as your body is surgically operated on.")) + return + to_chat(target, span_userdanger(pain_message)) + if(prob(30) && !mechanical_surgery) + target.emote("scream") + +/// Plays a sound for the operation based on the tool used +/datum/surgery_operation/proc/play_operation_sound(atom/movable/operating_on, mob/living/surgeon, tool, sound_or_sound_list) + PROTECTED_PROC(TRUE) + + if(isitem(tool) && (operation_flags & OPERATION_MECHANIC)) + var/obj/item/realtool = tool + realtool.play_tool_sound(operating_on) + return + + var/sound_to_play + if(islist(sound_or_sound_list)) + var/list/sounds = sound_or_sound_list + if(isitem(tool)) + var/obj/item/realtool = tool + sound_to_play = sounds[realtool.tool_behaviour] || is_type_in_list(realtool, sounds, zebra = TRUE) + else + sound_to_play = sounds[tool] + else + sound_to_play = sound_or_sound_list + + if(sound_to_play) + playsound(surgeon, sound_to_play, 50, TRUE) + +/// Helper for getting the mob who is ultimately being operated on, given the movable that is truly being operated on. +/// For example in limb surgeries this would return the mob the limb is attached to. +/datum/surgery_operation/proc/get_patient(atom/movable/operating_on) as /mob/living + return operating_on + +/// Helper for getting an operating compupter the patient is linked to +/datum/surgery_operation/proc/locate_operating_computer(atom/movable/operating_on) + SHOULD_NOT_OVERRIDE(TRUE) + var/turf/operating_turf = get_turf(operating_on) + if(isnull(operating_turf)) + return null + + var/obj/structure/table/optable/operating_table = locate() in operating_turf + var/obj/machinery/computer/operating/operating_computer = operating_table?.computer + + if(isnull(operating_computer) || (operating_computer.machine_stat & (NOPOWER|BROKEN))) + return null + + return operating_computer + +/// Updates a patient's mood based on the surgery state and their traits +/datum/surgery_operation/proc/update_surgery_mood(mob/living/patient, surgery_state) + PROTECTED_PROC(TRUE) + if(!(operation_flags & OPERATION_AFFECTS_MOOD)) + return + + // Create a probability to ignore the pain based on drunkenness level + var/drunk_ignore_prob = clamp(patient.get_drunk_amount(), 0, 90) + + if(HAS_TRAIT(patient, TRAIT_ANALGESIA) || prob(drunk_ignore_prob)) + patient.clear_mood_event(SURGERY_MOOD_CATEGORY) //incase they gained the trait mid-surgery (or became drunk). has the added side effect that if someone has a bad surgical memory/mood and gets drunk & goes back to surgery, they'll forget they hated it, which is kinda funny imo. + return + if(patient.stat >= UNCONSCIOUS) + var/datum/mood_event/surgery/target_mood_event = patient.mob_mood?.mood_events[SURGERY_MOOD_CATEGORY] + if(!target_mood_event || target_mood_event.surgery_completed) //don't give sleeping mobs trauma. that said, if they fell asleep mid-surgery after already getting the bad mood, lets make sure they wake up to a (hopefully) happy memory. + return + switch(surgery_state) + if(SURGERY_STATE_STARTED) + patient.add_mood_event(SURGERY_MOOD_CATEGORY, surgery_started_mood_event) + if(SURGERY_STATE_SUCCESS) + patient.add_mood_event(SURGERY_MOOD_CATEGORY, surgery_success_mood_event) + if(SURGERY_STATE_FAILURE) + patient.add_mood_event(SURGERY_MOOD_CATEGORY, surgery_failure_mood_event) + else + CRASH("passed invalid surgery_state, \"[surgery_state]\".") + +/** + * Called when the operation initiates + * Don't touch this proc, override on_preop() instead + */ +/datum/surgery_operation/proc/start_operation(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + + var/preop_time = world.time + var/mob/living/patient = get_patient(operating_on) + if(!pre_preop(operating_on, surgeon, tool, operation_args)) + return FALSE + // if pre_preop slept, sanity check that everything is still valid + if(preop_time != world.time && (patient != get_patient(operating_on) || !surgeon.Adjacent(patient) || !surgeon.is_holding(tool) || !operate_check(patient, operating_on, surgeon, tool, operation_args))) + return FALSE + + play_operation_sound(operating_on, surgeon, tool, preop_sound) + on_preop(operating_on, surgeon, tool, operation_args) + return TRUE + +/** + * Used to customize behavior when the operation starts + */ +/datum/surgery_operation/proc/on_preop(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) + PROTECTED_PROC(TRUE) + var/mob/living/patient = get_patient(operating_on) + + display_results( + surgeon, + patient, + span_notice("You begin to operate on [patient]..."), + span_notice("[surgeon] begins to operate on [patient]."), + span_notice("[surgeon] begins to operate on [patient]."), + ) + +/** + * Called when the operation is successful + * Don't touch this proc, override on_success() instead + */ +/datum/surgery_operation/proc/success(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + SHOULD_NOT_SLEEP(TRUE) + + if(operation_flags & OPERATION_NOTABLE) + SSblackbox.record_feedback("tally", "surgeries_completed", 1, type) + surgeon.add_mob_memory(/datum/memory/surgery, deuteragonist = surgeon, surgery_type = name) + + SEND_SIGNAL(surgeon, COMSIG_LIVING_SURGERY_SUCCESS, src, operating_on, tool) + play_operation_sound(operating_on, surgeon, tool, success_sound) + on_success(operating_on, surgeon, tool, operation_args) + +/** + * Used to customize behavior when the operation is successful + */ +/datum/surgery_operation/proc/on_success(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) + PROTECTED_PROC(TRUE) + var/mob/living/patient = get_patient(operating_on) + + display_results( + surgeon, + patient, + span_notice("You succeed."), + span_notice("[surgeon] succeeds!"), + span_notice("[surgeon] finishes."), + ) + +/** + * Called when the operation fails + * Don't touch this proc, override on_failure() instead + */ +/datum/surgery_operation/proc/failure(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + SHOULD_NOT_SLEEP(TRUE) + + if(operation_flags & OPERATION_NOTABLE) + SSblackbox.record_feedback("tally", "surgeries_failed", 1, type) + + SEND_SIGNAL(surgeon, COMSIG_LIVING_SURGERY_FAILED, src, operating_on, tool) + play_operation_sound(operating_on, surgeon, tool, failure_sound) + on_failure(operating_on, surgeon, tool, operation_args) + +/** + * Used to customize behavior when the operation fails + * + * total_penalty_modifier is the final modifier applied to the time taken to perform the operation, + * and it can be interpreted as how badly the operation was performed + * + * At its lowest, it will be just above 2.5 (the threshold for success), and can go up to infinity (theoretically) + */ +/datum/surgery_operation/proc/on_failure(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) + PROTECTED_PROC(TRUE) + var/mob/living/patient = get_patient(operating_on) + + var/screwedmessage = "" + switch(operation_args[OPERATION_SPEED]) + if(2.5 to 3) + screwedmessage = " You almost had it, though." + if(3 to 4) + pass() + if(4 to 5) + screwedmessage = " This is hard to get right in these conditions..." + if(5 to INFINITY) + screwedmessage = " This is practically impossible in these conditions..." + if(operation_args[OPERATION_FORCE_FAIL]) + screwedmessage = " Intentionally." + + display_results( + surgeon, + patient, + span_warning("You screw up![screwedmessage]"), + span_warning("[surgeon] screws up!"), + span_notice("[surgeon] finishes."), + TRUE, //By default the patient will notice if the wrong thing has been cut + ) + +/** + * Basic operations are a simple base type for surgeries that + * 1. Target a specific zone on humans + * 2. Work on non-humans + * + * Use this as a bsae if your surgery needs to work on everyone + * + * "operating_on" is the mob being operated on, be it carbon or non-carbon. + * If the mob is carbon, we check the relevant bodypart for surgery states and traits. No bodypart, no operation. + * If the mob is non-carbon, we just check the mob directly. + */ +/datum/surgery_operation/basic + abstract_type = /datum/surgery_operation/basic + /// Biotype required to perform this operation + var/required_biotype = ~MOB_ROBOTIC + /// The zone we are expected to be working on, even if the target is a non-carbon mob + var/target_zone = BODY_ZONE_CHEST + /// When working on carbons, what bodypart are we working on? Keep it representative of the required biotype + var/required_bodytype = ~BODYTYPE_ROBOTIC + +/datum/surgery_operation/basic/all_required_strings() + . = list() + if(required_biotype) + . += "operate on [target_zone ? "[parse_zone(target_zone)] (target [parse_zone(target_zone)])" : "patient"]" + else if(target_zone) + . += "operate on [parse_zone(target_zone)] (target [parse_zone(target_zone)])" + . += ..() + +/datum/surgery_operation/basic/all_blocked_strings() + . = ..() + if(required_biotype & MOB_ROBOTIC) + . += "the patient must not be organic" + else if(required_biotype) + . += "the patient must not be robotic" + +/datum/surgery_operation/basic/is_available(mob/living/patient, operated_zone) + SHOULD_NOT_OVERRIDE(TRUE) + + if(target_zone && target_zone != operated_zone) + return FALSE + if(!HAS_TRAIT(patient, TRAIT_READY_TO_OPERATE)) + return FALSE + if(required_biotype && !(patient.mob_biotypes & required_biotype)) + return FALSE + if(!patient.has_limbs || !target_zone) + return ..() + + var/obj/item/bodypart/carbon_part = patient.get_bodypart(target_zone) + if(isnull(carbon_part)) + return FALSE + if(!HAS_TRAIT(carbon_part, TRAIT_READY_TO_OPERATE)) + return FALSE + if(required_bodytype && !(carbon_part.bodytype & required_bodytype)) + return FALSE + return ..() + +/datum/surgery_operation/basic/has_surgery_state(mob/living/patient, state) + var/obj/item/bodypart/carbon_part = patient.get_bodypart(target_zone) + if(isnull(carbon_part)) // non-carbon + var/datum/status_effect/basic_surgery_state/state_holder = patient.has_status_effect(__IMPLIED_TYPE__) + return HAS_SURGERY_STATE(state_holder?.surgery_state, state & (SURGERY_BONE_SAWED|SURGERY_SKIN_OPEN)) // these are the only states basic mobs support, update this if that changes + + return LIMB_HAS_SURGERY_STATE(carbon_part, state) + +/datum/surgery_operation/basic/has_any_surgery_state(mob/living/patient, state) + var/obj/item/bodypart/carbon_part = patient.get_bodypart(target_zone) + if(isnull(carbon_part)) // non-carbon + var/datum/status_effect/basic_surgery_state/state_holder = patient.has_status_effect(__IMPLIED_TYPE__) + return HAS_ANY_SURGERY_STATE(state_holder?.surgery_state, state) + + return LIMB_HAS_ANY_SURGERY_STATE(carbon_part, state) + +/** + * Limb opterations are a base focused on the limb the surgeon is targeting + * + * Use this if your surgery targets a specific limb on the mob + * + * "operating_on" is asserted to be a bodypart - the bodypart the surgeon is targeting. + * If there is no bodypart, there's no operation. + */ +/datum/surgery_operation/limb + abstract_type = /datum/surgery_operation/limb + /// Body type required to perform this operation + var/required_bodytype = NONE + +/datum/surgery_operation/limb/all_blocked_strings() + . = ..() + if(required_bodytype & BODYTYPE_ROBOTIC) + . += "the limb must not be organic" + else if(required_bodytype & BODYTYPE_ORGANIC) + . += "the limb must not be cybernetic" + +/datum/surgery_operation/limb/get_operation_target(mob/living/patient, body_zone) + return patient.get_bodypart(deprecise_zone(body_zone)) + +/datum/surgery_operation/limb/is_available(obj/item/bodypart/limb, operated_zone) + SHOULD_NOT_OVERRIDE(TRUE) + + // targeting groin will redirect you to the chest + if(limb.body_zone != deprecise_zone(operated_zone)) + return FALSE + if(required_bodytype && !(limb.bodytype & required_bodytype)) + return FALSE + if(!HAS_TRAIT(limb, TRAIT_READY_TO_OPERATE)) + return FALSE + + return ..() + +/datum/surgery_operation/limb/has_surgery_state(obj/item/bodypart/limb, state) + return LIMB_HAS_SURGERY_STATE(limb, state) + +/datum/surgery_operation/limb/has_any_surgery_state(obj/item/bodypart/limb, state) + return LIMB_HAS_ANY_SURGERY_STATE(limb, state) + +/datum/surgery_operation/limb/get_patient(obj/item/bodypart/limb) + return limb.owner + +/** + * Organ operations are a base focused on a specific organ typepath + * + * Use this if your surgery targets a specific organ type + * + * "operating_on" is asserted to be an organ of the type defined by target_type. + * No organ of that type, no operation. + */ +/datum/surgery_operation/organ + abstract_type = /datum/surgery_operation/organ + /// Biotype required to perform this operation + var/required_organ_flag = ORGAN_TYPE_FLAGS & ~ORGAN_ROBOTIC + /// The type of organ this operation can target + var/obj/item/organ/target_type + +/datum/surgery_operation/organ/all_required_strings() + return list("operate on [target_type::name] (target [target_type::zone])") + ..() + +/datum/surgery_operation/organ/all_blocked_strings() + . = ..() + if(required_organ_flag & BODYTYPE_ROBOTIC) + . += "the organ must not be organic" + else if(required_organ_flag & ORGAN_TYPE_FLAGS) + . += "the organ must not be cybernetic" + +/datum/surgery_operation/organ/get_default_radial_image() + return get_generic_limb_radial_image(target_type::zone) + +/datum/surgery_operation/organ/get_operation_target(mob/living/patient, body_zone) + return patient.get_organ_by_type(target_type) + +/datum/surgery_operation/organ/get_patient(obj/item/organ/organ) + return organ.owner + +/datum/surgery_operation/organ/is_available(obj/item/organ/organ, operated_zone) + SHOULD_NOT_OVERRIDE(TRUE) + + if(organ.zone != operated_zone) // this check prevents eyes from showing up in head operations + return FALSE + if(required_organ_flag && !(organ.organ_flags & required_organ_flag)) + return FALSE + if(!HAS_TRAIT(organ.bodypart_owner, TRAIT_READY_TO_OPERATE)) + return FALSE + + return ..() + +/datum/surgery_operation/organ/has_surgery_state(obj/item/organ/organ, state) + return LIMB_HAS_SURGERY_STATE(organ.bodypart_owner, state) + +/datum/surgery_operation/organ/has_any_surgery_state(obj/item/organ/organ, state) + return LIMB_HAS_ANY_SURGERY_STATE(organ.bodypart_owner, state) diff --git a/code/modules/surgery/operations/operation_add_limb.dm b/code/modules/surgery/operations/operation_add_limb.dm new file mode 100644 index 00000000000..744fdbef880 --- /dev/null +++ b/code/modules/surgery/operations/operation_add_limb.dm @@ -0,0 +1,201 @@ +#define OPERATION_REJECTION_DAMAGE "tox_damage" + +// This surgery is so snowflake that it doesn't use any of the operation subtypes, it forges its own path +/datum/surgery_operation/prosthetic_replacement + name = "prosthetic replacement" + desc = "Replace a missing limb with a prosthetic (or arbitrary) item." + implements = list( + /obj/item/bodypart = 1, + /obj/item = 1, + ) + time = 3.2 SECONDS + operation_flags = OPERATION_STANDING_ALLOWED | OPERATION_PRIORITY_NEXT_STEP | OPERATION_NOTABLE | OPERATION_IGNORE_CLOTHES + all_surgery_states_required = SURGERY_SKIN_OPEN | SURGERY_VESSELS_CLAMPED + /// List of items that are always allowed to be an arm replacement, even if they fail another requirement. + var/list/always_accepted_prosthetics = list( + /obj/item/chainsaw, // the OG, too large otherwise + /obj/item/melee/synthetic_arm_blade, // also too large otherwise + /obj/item/food/pizzaslice, // he's turning her into a papa john's + ) + /// Radial slice datums for every augment type + VAR_PRIVATE/list/cached_prosthetic_options + +/datum/surgery_operation/prosthetic_replacement/get_default_radial_image() + return image(/obj/item/bodypart/chest) + +/datum/surgery_operation/prosthetic_replacement/get_recommended_tool() + return "any limb / any item" + +/datum/surgery_operation/prosthetic_replacement/get_any_tool() + return "Any suitable arm replacement" + +/datum/surgery_operation/prosthetic_replacement/all_required_strings() + . = list() + . += "operate on chest (target chest)" + . += ..() + . += "when the chest is prepared, target the zone of the limb you are attaching" + +/datum/surgery_operation/prosthetic_replacement/any_required_strings() + return list("arms may receive any suitable item in lieu of a replacement limb") + ..() + +/datum/surgery_operation/prosthetic_replacement/get_radial_options(obj/item/bodypart/chest/chest, obj/item/tool, operating_zone) + var/datum/radial_menu_choice/option = LAZYACCESS(cached_prosthetic_options, tool.type) + if(!option) + option = new() + option.name = "attach [initial(tool.name)]" + option.info = "Replace the patient's missing limb with [initial(tool.name)]." + option.image = image(tool.type) + LAZYSET(cached_prosthetic_options, tool.type, option) + + return option + +/datum/surgery_operation/prosthetic_replacement/get_operation_target(mob/living/patient, body_zone) + // We always operate on the chest even if we're targeting left leg or w/e + return patient.get_bodypart(BODY_ZONE_CHEST) + +/datum/surgery_operation/prosthetic_replacement/has_surgery_state(obj/item/bodypart/chest/chest, state) + return LIMB_HAS_SURGERY_STATE(chest, state) + +/datum/surgery_operation/prosthetic_replacement/has_any_surgery_state(obj/item/bodypart/chest/chest, state) + return LIMB_HAS_ANY_SURGERY_STATE(chest, state) + +/datum/surgery_operation/prosthetic_replacement/get_patient(obj/item/bodypart/chest/chest) + return chest.owner + +/datum/surgery_operation/prosthetic_replacement/is_available(obj/item/bodypart/chest/chest, operated_zone) + var/real_operated_zone = deprecise_zone(operated_zone) + // Operate on the chest but target another zone + if(!HAS_TRAIT(chest, TRAIT_READY_TO_OPERATE) || real_operated_zone == BODY_ZONE_CHEST) + return FALSE + if(chest.owner.get_bodypart(real_operated_zone)) + return FALSE + return ..() + +/datum/surgery_operation/prosthetic_replacement/snowflake_check_availability(obj/item/bodypart/chest, mob/living/surgeon, obj/item/tool, operated_zone) + if(!surgeon.canUnEquip(tool)) + return FALSE + var/real_operated_zone = deprecise_zone(operated_zone) + // check bodyshape compatibility for real bodyparts + if(isbodypart(tool)) + var/obj/item/bodypart/new_limb = tool + if(real_operated_zone != new_limb.body_zone) + return FALSE + if(!new_limb.can_attach_limb(chest.owner)) + return FALSE + // arbitrary prosthetics can only be used on arms (for now) + else if(!(real_operated_zone in GLOB.arm_zones)) + return FALSE + return TRUE + +/datum/surgery_operation/prosthetic_replacement/tool_check(obj/item/tool) + if(tool.item_flags & (ABSTRACT|DROPDEL|HAND_ITEM)) + return FALSE + if(isbodypart(tool)) + return TRUE // auto pass - "intended" use case + if(is_type_in_list(tool, always_accepted_prosthetics)) + return TRUE // auto pass - soulful prosthetics + if(tool.w_class < WEIGHT_CLASS_NORMAL || tool.w_class > WEIGHT_CLASS_BULKY) + return FALSE // too large or too small items don't make sense as a limb replacement + if(HAS_TRAIT(tool, TRAIT_WIELDED)) + return FALSE + return TRUE + +/datum/surgery_operation/prosthetic_replacement/pre_preop(atom/movable/operating_on, mob/living/surgeon, tool, list/operation_args) + . = ..() + // always operate on absolute body zones + operation_args[OPERATION_TARGET_ZONE] = deprecise_zone(operation_args[OPERATION_TARGET_ZONE]) + +/datum/surgery_operation/prosthetic_replacement/on_preop(obj/item/bodypart/chest/chest, mob/living/surgeon, obj/item/tool, list/operation_args) + var/target_zone_readable = parse_zone(operation_args[OPERATION_TARGET_ZONE]) + display_results( + surgeon, + chest.owner, + span_notice("You begin to replace [chest.owner]'s missing [target_zone_readable] with [tool]..."), + span_notice("[surgeon] begins to replace [chest.owner]'s missing [target_zone_readable] with [tool]."), + span_notice("[surgeon] begins to replace [chest.owner]'s missing [target_zone_readable]."), + ) + display_pain(chest.owner, "You feel an uncomfortable sensation where your [target_zone_readable] should be!") + + operation_args[OPERATION_REJECTION_DAMAGE] = 10 + if(isbodypart(tool)) + var/obj/item/bodypart/new_limb = tool + if(IS_ROBOTIC_LIMB(new_limb)) + operation_args[OPERATION_REJECTION_DAMAGE] = 0 + else if(new_limb.check_for_frankenstein(chest.owner)) + operation_args[OPERATION_REJECTION_DAMAGE] = 30 + +/datum/surgery_operation/prosthetic_replacement/on_success(obj/item/bodypart/chest/chest, mob/living/surgeon, obj/item/tool, list/operation_args) + if(!surgeon.temporarilyRemoveItemFromInventory(tool)) + return // should never happen + if(operation_args[OPERATION_REJECTION_DAMAGE] > 0) + chest.owner.apply_damage(operation_args[OPERATION_REJECTION_DAMAGE], TOX) + if(isbodypart(tool)) + handle_bodypart(chest.owner, surgeon, tool) + return + handle_arbitrary_prosthetic(chest.owner, surgeon, tool, operation_args[OPERATION_TARGET_ZONE]) + +/datum/surgery_operation/prosthetic_replacement/proc/handle_bodypart(mob/living/carbon/patient, mob/living/surgeon, obj/item/bodypart/bodypart_to_attach) + bodypart_to_attach.try_attach_limb(patient) + if(bodypart_to_attach.check_for_frankenstein(patient)) + bodypart_to_attach.bodypart_flags |= BODYPART_IMPLANTED + display_results( + surgeon, patient, + span_notice("You succeed in replacing [patient]'s [bodypart_to_attach.plaintext_zone]."), + span_notice("[surgeon] successfully replaces [patient]'s [bodypart_to_attach.plaintext_zone] with [bodypart_to_attach]!"), + span_notice("[surgeon] successfully replaces [patient]'s [bodypart_to_attach.plaintext_zone]!"), + ) + display_pain(patient, "You feel synthetic sensation wash from your [bodypart_to_attach.plaintext_zone], which you can feel again!", TRUE) + +/datum/surgery_operation/prosthetic_replacement/proc/handle_arbitrary_prosthetic(mob/living/carbon/patient, mob/living/surgeon, obj/item/thing_to_attach, target_zone) + SSblackbox.record_feedback("tally", "arbitrary_prosthetic", 1, initial(thing_to_attach.name)) + var/obj/item/bodypart/new_limb = patient.make_item_prosthetic(thing_to_attach, target_zone, 80) + new_limb.add_surgical_state(SURGERY_PROSTHETIC_UNSECURED) + display_results( + surgeon, patient, + span_notice("You attach [thing_to_attach]."), + span_notice("[surgeon] finishes attaching [thing_to_attach]!"), + span_notice("[surgeon] finishes the attachment procedure!"), + ) + display_pain(patient, "You feel a strange sensation as [thing_to_attach] takes the place of your arm!", TRUE) + +#undef OPERATION_REJECTION_DAMAGE + +/datum/surgery_operation/limb/secure_arbitrary_prosthetic + name = "secure prosthetic" + desc = "Ensure that an arbitrary prosthetic is properly attached to a patient's body." + implements = list( + /obj/item/stack/medical/suture = 1, + /obj/item/stack/sticky_tape/surgical = 1.25, + /obj/item/stack/sticky_tape = 2, + ) + time = 4.8 SECONDS + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_STANDING_ALLOWED + all_surgery_states_required = SURGERY_PROSTHETIC_UNSECURED + +/datum/surgery_operation/limb/secure_arbitrary_prosthetic/get_default_radial_image() + return image(/obj/item/stack/medical/suture) + +/datum/surgery_operation/limb/secure_arbitrary_prosthetic/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/stack/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to [tool.singular_name] [limb] to [limb.owner]'s body."), + span_notice("[surgeon] begins to [tool.singular_name] [limb] to [limb.owner]'s body."), + span_notice("[surgeon] begins to [tool.singular_name] something to [limb.owner]'s body."), + ) + var/obj/item/bodypart/chest = limb.owner.get_bodypart(BODY_ZONE_CHEST) + display_pain(limb.owner, "[surgeon] begins to [tool.singular_name] [limb] to your body!", IS_ROBOTIC_LIMB(chest)) + +/datum/surgery_operation/limb/secure_arbitrary_prosthetic/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/stack/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You finish [tool.apply_verb] [limb] to [limb.owner]'s body."), + span_notice("[surgeon] finishes [tool.apply_verb] [limb] to [limb.owner]'s body."), + span_notice("[surgeon] finishes the [tool.apply_verb] procedure!"), + ) + var/obj/item/bodypart/chest = limb.owner.get_bodypart(BODY_ZONE_CHEST) + display_pain(limb.owner, "You feel more secure as your prosthetic is firmly attached to your body!", IS_ROBOTIC_LIMB(chest)) + limb.remove_surgical_state(SURGERY_PROSTHETIC_UNSECURED) + limb.AddComponent(/datum/component/item_as_prosthetic_limb, null, 0) // updates drop probability to zero + tool.use(1) diff --git a/code/modules/surgery/operations/operation_amputation.dm b/code/modules/surgery/operations/operation_amputation.dm new file mode 100644 index 00000000000..48b7962c9ed --- /dev/null +++ b/code/modules/surgery/operations/operation_amputation.dm @@ -0,0 +1,115 @@ +/datum/surgery_operation/limb/amputate + name = "amputate limb" + rnd_name = "Disarticulation (Amputation)" + desc = "Sever a limb from a patient's body." + operation_flags = OPERATION_MORBID | OPERATION_AFFECTS_MOOD | OPERATION_NOTABLE + required_bodytype = ~(BODYTYPE_ROBOTIC|BODYTYPE_PEG) + implements = list( + /obj/item/shears = 0.33, + TOOL_SAW = 1, + TOOL_SCALPEL = 1, + /obj/item/melee/arm_blade = 1.25, + /obj/item/shovel/serrated = 1.33, + /obj/item/fireaxe = 2, + /obj/item/hatchet = 2.5, + /obj/item/knife/butcher = 4, + ) + time = 6.4 SECONDS + preop_sound = list( + /obj/item/circular_saw = 'sound/items/handling/surgery/saw.ogg', + /obj/item = 'sound/items/handling/surgery/scalpel1.ogg', + ) + success_sound = 'sound/items/handling/surgery/organ2.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/limb/amputate/get_recommended_tool() + return TOOL_SAW + +/datum/surgery_operation/limb/amputate/get_default_radial_image() + return image(/obj/item/shears) + +/datum/surgery_operation/limb/amputate/state_check(obj/item/bodypart/limb) + if(limb.body_zone == BODY_ZONE_CHEST) + return FALSE + if(limb.bodypart_flags & BODYPART_UNREMOVABLE) + return FALSE + if(HAS_TRAIT(limb.owner, TRAIT_NODISMEMBER)) + return FALSE + return TRUE + +/datum/surgery_operation/limb/amputate/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to sever [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to sever [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to sever [limb.owner]'s [limb.plaintext_zone] with [tool]."), + ) + display_pain(limb.owner, "You feel a gruesome pain in your [limb.plaintext_zone]'s joint!") + +/datum/surgery_operation/limb/amputate/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You successfully amputate [limb.owner]'s [limb.plaintext_zone]!"), + span_notice("[surgeon] successfully amputates [limb.owner]'s [limb.plaintext_zone]!"), + span_notice("[surgeon] finishes severing [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You can no longer feel your [limb.plaintext_zone]!") + if(HAS_MIND_TRAIT(surgeon, TRAIT_MORBID)) + surgeon.add_mood_event("morbid_dissection_success", /datum/mood_event/morbid_dissection_success) + limb.drop_limb() + +/datum/surgery_operation/limb/amputate/mechanic + name = "disassemble limb" + rnd_name = "Dissassembly (Amputation)" + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + implements = list( + /obj/item/shovel/giant_wrench = 0.33, + TOOL_WRENCH = 1, + TOOL_CROWBAR = 1, + TOOL_SCALPEL = 2, + TOOL_SAW = 2, + ) + time = 2 SECONDS //WAIT I NEED THAT!! + preop_sound = 'sound/items/tools/ratchet.ogg' + preop_sound = 'sound/machines/airlock/doorclick.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN + +/datum/surgery_operation/limb/amputate/mechanic/state_check(obj/item/bodypart/limb) + // added requirement for bone sawed to prevent accidental head removals. + return ..() && (limb.body_zone != BODY_ZONE_HEAD || LIMB_HAS_SURGERY_STATE(limb, SURGERY_BONE_SAWED)) + +/datum/surgery_operation/limb/amputate/mechanic/any_required_strings() + return ..() + list( + "if operating on the head, the bone MUST be sawed", + "otherwise, the state of the bone doesn't matter", + ) + +/datum/surgery_operation/limb/amputate/mechanic/get_recommended_tool() + return "[TOOL_WRENCH] / [TOOL_SAW]" + +/datum/surgery_operation/limb/amputate/pegleg + name = "detach wooden limb" + rnd_name = "Detach Wooden Limb (Amputation)" + required_bodytype = BODYTYPE_PEG + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + implements = list( + TOOL_SAW = 1, + /obj/item/shovel/serrated = 1, + /obj/item/fireaxe = 1.15, + /obj/item/hatchet = 1.33, + TOOL_SCALPEL = 4, + ) + time = 3 SECONDS + preop_sound = list( + /obj/item/circular_saw = 'sound/items/handling/surgery/saw.ogg', + /obj/item = 'sound/items/weapons/bladeslice.ogg', + ) + success_sound = 'sound/items/handling/materials/wood_drop.ogg' + all_surgery_states_required = NONE + +/datum/surgery_operation/limb/amputate/pegleg/all_required_strings() + . = ..() + . += "the limb must be wooden" diff --git a/code/modules/surgery/operations/operation_asthma.dm b/code/modules/surgery/operations/operation_asthma.dm new file mode 100644 index 00000000000..427832b9659 --- /dev/null +++ b/code/modules/surgery/operations/operation_asthma.dm @@ -0,0 +1,72 @@ +/datum/surgery_operation/organ/asthmatic_bypass + name = "force open windpipe" + // google says the *actual* operation used to relieve asthma is called bronchial thermoplasty but this operation doesn't resemble that at all + // local doctors suggested "bronchial dilatation" instead + rnd_name = "Bronchial Dilatation (Asthmatic Bypass)" + desc = "Forcibly expand a patient's windpipe, relieving asthma symptoms." + operation_flags = OPERATION_PRIORITY_NEXT_STEP + implements = list( + TOOL_RETRACTOR = 1.25, + TOOL_WIRECUTTER = 2.25, + ) + time = 8 SECONDS + preop_sound = 'sound/items/handling/surgery/retractor1.ogg' + success_sound = 'sound/items/handling/surgery/retractor2.ogg' + target_type = /obj/item/organ/lungs + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT + /// The amount of inflammation a failure or success of this surgery will reduce. + var/inflammation_reduction = 75 + +/datum/surgery_operation/organ/asthmatic_bypass/all_required_strings() + return list("the patient must be asthmatic") + ..() + +/datum/surgery_operation/organ/asthmatic_bypass/state_check(obj/item/organ/organ) + if(!organ.owner.has_quirk(/datum/quirk/item_quirk/asthma)) + return FALSE + return TRUE + +/datum/surgery_operation/organ/asthmatic_bypass/on_preop(obj/item/organ/lungs/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to stretch [organ.owner]'s windpipe, trying your best to avoid nearby blood vessels..."), + span_notice("[surgeon] begins to stretch [organ.owner]'s windpipe, taking care to avoid any nearby blood vessels."), + span_notice("[surgeon] begins to stretch [organ.owner]'s windpipe."), + ) + display_pain(organ.owner, "You feel an agonizing stretching sensation in your neck!") + +/datum/surgery_operation/organ/asthmatic_bypass/on_success(obj/item/organ/lungs/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + var/datum/quirk/item_quirk/asthma/asthma = organ.owner.get_quirk(/datum/quirk/item_quirk/asthma) + if(isnull(asthma)) + return + + asthma.adjust_inflammation(-inflammation_reduction) + + display_results( + surgeon, + organ.owner, + span_notice("You stretch [organ.owner]'s windpipe with [tool], managing to avoid the nearby blood vessels."), + span_notice("[surgeon] succeeds at stretching [organ.owner]'s windpipe with [tool], avoiding the nearby blood vessels."), + span_notice("[surgeon] finishes stretching [organ.owner]'s windpipe.") + ) + +/datum/surgery_operation/organ/asthmatic_bypass/on_failure(obj/item/organ/lungs/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + var/datum/quirk/item_quirk/asthma/asthma = organ.owner.get_quirk(/datum/quirk/item_quirk/asthma) + if(isnull(asthma)) + return + + asthma.adjust_inflammation(-inflammation_reduction) + + display_results( + surgeon, + organ.owner, + span_warning("You stretch [organ.owner]'s windpipe with [tool], but accidentally clip a few arteries!"), + span_warning("[surgeon] succeeds at stretching [organ.owner]'s windpipe with [tool], but accidentally clips a few arteries!"), + span_warning("[surgeon] finishes stretching [organ.owner]'s windpipe, but screws up!"), + ) + + organ.owner.losebreath++ + + if(prob(30)) + organ.owner.cause_wound_of_type_and_severity(WOUND_SLASH, organ.bodypart_owner, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL, WOUND_PICK_LOWEST_SEVERITY, tool) + organ.bodypart_owner.receive_damage(brute = 10, wound_bonus = tool.wound_bonus, sharpness = SHARP_EDGED, damage_source = tool) diff --git a/code/modules/surgery/operations/operation_autopsy.dm b/code/modules/surgery/operations/operation_autopsy.dm new file mode 100644 index 00000000000..ac0af85d6cb --- /dev/null +++ b/code/modules/surgery/operations/operation_autopsy.dm @@ -0,0 +1,56 @@ +/datum/surgery_operation/limb/autopsy + name = "autopsy" + rnd_name = "Androtomy (Dissection and Autopsy)" + desc = "Perform a detailed analysis of a deceased patient's body." + implements = list(/obj/item/autopsy_scanner = 1) + time = 10 SECONDS + success_sound = 'sound/machines/printer.ogg' + required_bodytype = ~BODYTYPE_ROBOTIC + operation_flags = OPERATION_MORBID | OPERATION_IGNORE_CLOTHES + all_surgery_states_required = SURGERY_SKIN_OPEN + +/datum/surgery_operation/limb/autopsy/get_default_radial_image() + return image(/obj/item/autopsy_scanner) + +/datum/surgery_operation/limb/autopsy/all_required_strings() + . = list() + . += "operate on chest (target chest)" + . += ..() + . += "the patient must be deceased" + . += "the patient must not have been autopsied prior" + +/datum/surgery_operation/limb/autopsy/state_check(obj/item/bodypart/limb) + if(limb.body_zone != BODY_ZONE_CHEST) + return FALSE + if(limb.owner.stat != DEAD) + return FALSE + if(HAS_TRAIT_FROM(limb.owner, TRAIT_DISSECTED, AUTOPSY_TRAIT)) + return FALSE + return TRUE + +/datum/surgery_operation/limb/autopsy/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/autopsy_scanner/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin performing an autopsy on [limb.owner]..."), + span_notice("[surgeon] uses [tool] to perform an autopsy on [limb.owner]."), + span_notice("[surgeon] uses [tool] on [limb.owner]'s chest."), + ) + +/datum/surgery_operation/limb/autopsy/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/autopsy_scanner/tool, list/operation_args) + ADD_TRAIT(limb.owner, TRAIT_DISSECTED, AUTOPSY_TRAIT) + ADD_TRAIT(limb.owner, TRAIT_SURGICALLY_ANALYZED, AUTOPSY_TRAIT) + tool.scan_cadaver(surgeon, limb.owner) + var/obj/machinery/computer/operating/operating_computer = locate_operating_computer(limb) + if (!isnull(operating_computer)) + SEND_SIGNAL(operating_computer, COMSIG_OPERATING_COMPUTER_AUTOPSY_COMPLETE, limb.owner) + if(HAS_MIND_TRAIT(surgeon, TRAIT_MORBID)) + surgeon.add_mood_event("morbid_dissection_success", /datum/mood_event/morbid_dissection_success) + return ..() + +/datum/surgery_operation/limb/autopsy/mechanic + name = "system failure analysis" + rnd_name = "System Failure Analysis (Dissection and Autopsy)" + desc = "Perform a detailed analysis of a robotic patient's deactivated systems." + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC diff --git a/code/modules/surgery/operations/operation_bioware.dm b/code/modules/surgery/operations/operation_bioware.dm new file mode 100644 index 00000000000..1ea6f7ea820 --- /dev/null +++ b/code/modules/surgery/operations/operation_bioware.dm @@ -0,0 +1,341 @@ +/datum/surgery_operation/limb/bioware + abstract_type = /datum/surgery_operation/limb/bioware + implements = list( + IMPLEMENT_HAND = 1, + ) + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_NOTABLE | OPERATION_MORBID | OPERATION_LOCKED + required_bodytype = ~BODYTYPE_ROBOTIC + time = 12.5 SECONDS + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED|SURGERY_ORGANS_CUT + /// What status effect is gained when the surgery is successful? + /// Used to check against other bioware types to prevent stacking. + var/datum/status_effect/status_effect_gained = /datum/status_effect/bioware + /// Zone to operate on for this bioware + var/required_zone = BODY_ZONE_CHEST + +/datum/surgery_operation/limb/bioware/get_default_radial_image() + return image('icons/hud/implants.dmi', "lighting_bolt") + +/datum/surgery_operation/limb/bioware/all_required_strings() + return list("operate on [parse_zone(required_zone)] (target [parse_zone(required_zone)])") + ..() + +/datum/surgery_operation/limb/bioware/all_blocked_strings() + var/list/incompatible_surgeries = list() + for(var/datum/surgery_operation/limb/bioware/other_bioware as anything in subtypesof(/datum/surgery_operation/limb/bioware)) + if(other_bioware::status_effect_gained::id != status_effect_gained::id) + continue + if(other_bioware::required_bodytype != required_bodytype) + continue + incompatible_surgeries += (other_bioware.rnd_name || other_bioware.name) + + return ..() + list("the patient must not have undergone [english_list(incompatible_surgeries, and_text = " OR ")] prior") + +/datum/surgery_operation/limb/bioware/state_check(obj/item/bodypart/limb) + if(limb.body_zone != required_zone) + return FALSE + if(limb.owner.has_status_effect(status_effect_gained)) + return FALSE + return TRUE + +/datum/surgery_operation/limb/bioware/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + limb.owner.apply_status_effect(status_effect_gained) + if(limb.owner.ckey) + SSblackbox.record_feedback("tally", "bioware", 1, status_effect_gained) + +/datum/surgery_operation/limb/bioware/vein_threading + name = "thread veins" + rnd_name = "Symvasculodesis (Vein Threading)" // "together vessel fusion" + desc = "Weave a patient's veins into a reinforced mesh, reducing blood loss from injuries." + status_effect_gained = /datum/status_effect/bioware/heart/threaded_veins + +/datum/surgery_operation/limb/bioware/vein_threading/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool) + display_results( + surgeon, + limb.owner, + span_notice("You start weaving [limb.owner]'s blood vessels."), + span_notice("[surgeon] starts weaving [limb.owner]'s blood vessels."), + span_notice("[surgeon] starts manipulating [limb.owner]'s blood vessels."), + ) + display_pain(limb.owner, "Your entire body burns in agony!") + +/datum/surgery_operation/limb/bioware/vein_threading/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + display_results( + surgeon, + limb.owner, + span_notice("You weave [limb.owner]'s blood vessels into a resistant mesh!"), + span_notice("[surgeon] weaves [limb.owner]'s blood vessels into a resistant mesh!"), + span_notice("[surgeon] finishes manipulating [limb.owner]'s blood vessels."), + ) + display_pain(limb.owner, "You can feel your blood pumping through reinforced veins!") + +/datum/surgery_operation/limb/bioware/vein_threading/mechanic + rnd_name = "Hydraulics Routing Optimization (Threaded Veins)" + desc = "Optimize the routing of a robotic patient's hydraulic system, reducing fluid loss from leaks." + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/limb/bioware/muscled_veins + name = "muscled veins" + rnd_name = "Myovasculoplasty (Muscled Veins)" // "muscle vessel reshaping" + desc = "Add a muscled membrane to a patient's veins, allowing them to pump blood without a heart." + status_effect_gained = /datum/status_effect/bioware/heart/muscled_veins + +/datum/surgery_operation/limb/bioware/muscled_veins/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool) + display_results( + surgeon, + limb.owner, + span_notice("You start wrapping muscles around [limb.owner]'s blood vessels."), + span_notice("[surgeon] starts wrapping muscles around [limb.owner]'s blood vessels."), + span_notice("[surgeon] starts manipulating [limb.owner]'s blood vessels."), + ) + display_pain(limb.owner, "Your entire body burns in agony!") + +/datum/surgery_operation/limb/bioware/muscled_veins/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + display_results( + surgeon, + limb.owner, + span_notice("You reshape [limb.owner]'s blood vessels, adding a muscled membrane!"), + span_notice("[surgeon] reshapes [limb.owner]'s blood vessels, adding a muscled membrane!"), + span_notice("[surgeon] finishes manipulating [limb.owner]'s blood vessels."), + ) + display_pain(limb.owner, "You can feel your heartbeat's powerful pulses ripple through your body!") + +/datum/surgery_operation/limb/bioware/muscled_veins/mechanic + rnd_name = "Hydraulics Redundancy Subroutine (Muscled Veins)" + desc = "Add redundancies to a robotic patient's hydraulic system, allowing it to pump fluids without an engine or pump." + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/limb/bioware/nerve_splicing + name = "splice nerves" + rnd_name = "Symneurodesis (Spliced Nerves)" // "together nerve fusion" + desc = "Splice a patient's nerves together to make them more resistant to stuns." + time = 15.5 SECONDS + status_effect_gained = /datum/status_effect/bioware/nerves/spliced + +/datum/surgery_operation/limb/bioware/nerve_splicing/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool) + display_results( + surgeon, + limb.owner, + span_notice("You start splicing together [limb.owner]'s nerves."), + span_notice("[surgeon] starts splicing together [limb.owner]'s nerves."), + span_notice("[surgeon] starts manipulating [limb.owner]'s nervous system."), + ) + display_pain(limb.owner, "Your entire body goes numb!") + +/datum/surgery_operation/limb/bioware/nerve_splicing/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + display_results( + surgeon, + limb.owner, + span_notice("You successfully splice [limb.owner]'s nervous system!"), + span_notice("[surgeon] successfully splices [limb.owner]'s nervous system!"), + span_notice("[surgeon] finishes manipulating [limb.owner]'s nervous system."), + ) + display_pain(limb.owner, "You regain feeling in your body; It feels like everything's happening around you in slow motion!") + +/datum/surgery_operation/limb/bioware/nerve_splicing/mechanic + rnd_name = "System Automatic Reset Subroutine (Spliced Nerves)" + desc = "Upgrade a robotic patient's automatic systems, allowing it to better resist stuns." + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/limb/bioware/nerve_grounding + name = "ground nerves" + rnd_name = "Xanthoneuroplasty (Grounded Nerves)" // "yellow nerve reshaping". see: yellow gloves + desc = "Reroute a patient's nerves to act as grounding rods, protecting them from electrical shocks." + time = 15.5 SECONDS + status_effect_gained = /datum/status_effect/bioware/nerves/grounded + +/datum/surgery_operation/limb/bioware/nerve_grounding/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool) + display_results( + surgeon, + limb.owner, + span_notice("You start rerouting [limb.owner]'s nerves."), + span_notice("[surgeon] starts rerouting [limb.owner]'s nerves."), + span_notice("[surgeon] starts manipulating [limb.owner]'s nervous system."), + ) + display_pain(limb.owner, "Your entire body goes numb!") + +/datum/surgery_operation/limb/bioware/nerve_grounding/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + display_results( + surgeon, + limb.owner, + span_notice("You successfully reroute [limb.owner]'s nervous system!"), + span_notice("[surgeon] successfully reroutes [limb.owner]'s nervous system!"), + span_notice("[surgeon] finishes manipulating [limb.owner]'s nervous system."), + ) + display_pain(limb.owner, "You regain feeling in your body! You feel energized!") + +/datum/surgery_operation/limb/bioware/nerve_grounding/mechanic + rnd_name = "System Shock Dampening (Grounded Nerves)" + desc = "Install grounding rods into a robotic patient's nervous system, protecting it from electrical shocks." + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/limb/bioware/ligament_hook + name = "reshape ligaments" + rnd_name = "Arthroplasty (Ligament Hooks)" // "joint reshaping" + desc = "Reshape a patient's ligaments to allow limbs to be manually reattached if severed - at the cost of making them easier to detach." + status_effect_gained = /datum/status_effect/bioware/ligaments/hooked + +/datum/surgery_operation/limb/bioware/ligament_hook/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool) + display_results( + surgeon, + limb.owner, + span_notice("You start reshaping [limb.owner]'s ligaments into a hook-like shape."), + span_notice("[surgeon] starts reshaping [limb.owner]'s ligaments into a hook-like shape."), + span_notice("[surgeon] starts manipulating [limb.owner]'s ligaments."), + ) + display_pain(limb.owner, "Your limbs burn with severe pain!") + +/datum/surgery_operation/limb/bioware/ligament_hook/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + display_results( + surgeon, + limb.owner, + span_notice("You reshape [limb.owner]'s ligaments into a connective hook!"), + span_notice("[surgeon] reshapes [limb.owner]'s ligaments into a connective hook!"), + span_notice("[surgeon] finishes manipulating [limb.owner]'s ligaments."), + ) + display_pain(limb.owner, "Your limbs feel... strangely loose.") + +/datum/surgery_operation/limb/bioware/ligament_hook/mechanic + rnd_name = "Anchor Point Snaplocks (Ligament Hooks)" + desc = "Refactor a robotic patient's limb joints to allow for rapid deatchment, allowing limbs to be manually reattached if severed - \ + at the cost of making them easier to detach as well." + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/limb/bioware/ligament_reinforcement + name = "strengthen ligaments" + rnd_name = "Arthrorrhaphy (Ligament Reinforcement)" // "joint strengthening" / "joint stitching" + desc = "Strengthen a patient's ligaments to make dismemberment more difficult, at the cost of making nerve connections easier to interrupt." + status_effect_gained = /datum/status_effect/bioware/ligaments/reinforced + +/datum/surgery_operation/limb/bioware/ligament_reinforcement/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool) + display_results( + surgeon, + limb.owner, + span_notice("You start reinforcing [limb.owner]'s ligaments."), + span_notice("[surgeon] starts reinforcing [limb.owner]'s ligaments."), + span_notice("[surgeon] starts manipulating [limb.owner]'s ligaments."), + ) + display_pain(limb.owner, "Your limbs burn with severe pain!") + +/datum/surgery_operation/limb/bioware/ligament_reinforcement/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + display_results( + surgeon, + limb.owner, + span_notice("You reinforce [limb.owner]'s ligaments!"), + span_notice("[surgeon] reinforces [limb.owner]'s ligaments!"), + span_notice("[surgeon] finishes manipulating [limb.owner]'s ligaments."), + ) + display_pain(limb.owner, "Your limbs feel more secure, but also more frail.") + +/datum/surgery_operation/limb/bioware/ligament_reinforcement/mechanic + rnd_name = "Anchor Point Reinforcement (Ligament Reinforcement)" + desc = "Reinforce a robotic patient's limb joints to prevent dismemberment, at the cost of making nerve connections easier to interrupt." + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/limb/bioware/cortex_folding + name = "cortex folding" + rnd_name = "Encephalofractoplasty (Cortex Folding)" // it's a stretch - "brain fractal reshaping" + desc = "A biological upgrade which folds a patient's cerebral cortex into a fractal pattern, increasing neural density and flexibility." + status_effect_gained = /datum/status_effect/bioware/cortex/folded + required_zone = BODY_ZONE_HEAD + +/datum/surgery_operation/limb/bioware/cortex_folding/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool) + display_results( + surgeon, + limb.owner, + span_notice("You start folding [limb.owner]'s cerebral cortex."), + span_notice("[surgeon] starts folding [limb.owner]'s cerebral cortex."), + span_notice("[surgeon] starts performing surgery on [limb.owner]'s brain."), + ) + display_pain(limb.owner, "Your head throbs with gruesome pain, it's nearly too much to handle!") + +/datum/surgery_operation/limb/bioware/cortex_folding/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + display_results( + surgeon, + limb.owner, + span_notice("You fold [limb.owner]'s cerebral cortex into a fractal pattern!"), + span_notice("[surgeon] folds [limb.owner]'s cerebral cortex into a fractal pattern!"), + span_notice("[surgeon] completes the surgery on [limb.owner]'s brain."), + ) + display_pain(limb.owner, "Your brain feels stronger... and more flexible!") + +/datum/surgery_operation/limb/bioware/cortex_folding/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool) + if(!limb.owner.get_organ_slot(ORGAN_SLOT_BRAIN)) + return ..() + display_results( + surgeon, + limb.owner, + span_warning("You screw up, damaging the brain!"), + span_warning("[surgeon] screws up, damaging the brain!"), + span_notice("[surgeon] completes the surgery on [limb.owner]'s brain."), + ) + display_pain(limb.owner, "Your head throbs with excruciating pain!") + limb.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 60) + limb.owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) + +/datum/surgery_operation/limb/bioware/cortex_folding/mechanic + rnd_name = "Wetware OS Labyrinthian Programming (Cortex Folding)" + desc = "Reprogram a robotic patient's neural network in a downright eldritch programming language, giving space to non-standard neural patterns." + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/limb/bioware/cortex_imprint + name = "cortex imprinting" + rnd_name = "Encephalopremoplasty (Cortex Imprinting)" // it's a stretch - "brain print reshaping" + desc = "A biological upgrade which carves a patient's cerebral cortex into a self-imprinting pattern, increasing neural density and resilience." + status_effect_gained = /datum/status_effect/bioware/cortex/imprinted + required_zone = BODY_ZONE_HEAD + +/datum/surgery_operation/limb/bioware/cortex_imprint/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool) + display_results( + surgeon, + limb.owner, + span_notice("You start carving [limb.owner]'s outer cerebral cortex into a self-imprinting pattern."), + span_notice("[surgeon] starts carving [limb.owner]'s outer cerebral cortex into a self-imprinting pattern."), + span_notice("[surgeon] starts performing surgery on [limb.owner]'s brain."), + ) + display_pain(limb.owner, "Your head throbs with gruesome pain, it's nearly too much to handle!") + +/datum/surgery_operation/limb/bioware/cortex_imprint/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + display_results( + surgeon, + limb.owner, + span_notice("You reshape [limb.owner]'s outer cerebral cortex into a self-imprinting pattern!"), + span_notice("[surgeon] reshapes [limb.owner]'s outer cerebral cortex into a self-imprinting pattern!"), + span_notice("[surgeon] completes the surgery on [limb.owner]'s brain."), + ) + display_pain(limb.owner, "Your brain feels stronger... and more resilient!") + +/datum/surgery_operation/limb/bioware/cortex_imprint/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool) + if(!limb.owner.get_organ_slot(ORGAN_SLOT_BRAIN)) + return ..() + display_results( + surgeon, + limb.owner, + span_warning("You screw up, damaging the brain!"), + span_warning("[surgeon] screws up, damaging the brain!"), + span_notice("[surgeon] completes the surgery on [limb.owner]'s brain."), + ) + display_pain(limb.owner, "Your brain throbs with intense pain; Thinking hurts!") + limb.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 60) + limb.owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) + +/datum/surgery_operation/limb/bioware/cortex_imprint/mechanic + rnd_name = "Wetware OS Ver 2.0 (Cortex Imprinting)" + desc = "Update a robotic patient's operating system to a \"newer version\", improving overall performance and resilience. \ + Shame about all the adware." + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC diff --git a/code/modules/surgery/operations/operation_bone_repair.dm b/code/modules/surgery/operations/operation_bone_repair.dm new file mode 100644 index 00000000000..47ec6dbf44f --- /dev/null +++ b/code/modules/surgery/operations/operation_bone_repair.dm @@ -0,0 +1,225 @@ +/datum/surgery_operation/limb/repair_hairline + name = "repair hairline fracture" + desc = "Mend a hairline fracture in a patient's bone." + operation_flags = OPERATION_PRIORITY_NEXT_STEP + implements = list( + TOOL_BONESET = 1, + /obj/item/stack/medical/bone_gel = 1, + /obj/item/stack/sticky_tape/surgical = 1, + /obj/item/stack/sticky_tape/super = 2, + /obj/item/stack/sticky_tape = 3.33, + ) + time = 4 SECONDS + any_surgery_states_required = ALL_SURGERY_SKIN_STATES + +/datum/surgery_operation/limb/repair_hairline/get_default_radial_image() + return image(/obj/item/bonesetter) + +/datum/surgery_operation/limb/repair_hairline/all_required_strings() + return list("the limb must have a hairline fracture") + ..() + +/datum/surgery_operation/limb/repair_hairline/state_check(obj/item/bodypart/limb) + if(!(locate(/datum/wound/blunt/bone/severe) in limb.wounds)) + return FALSE + return TRUE + +/datum/surgery_operation/limb/repair_hairline/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to repair the fracture in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to repair the fracture in [limb.owner]'s [limb.plaintext_zone] with [tool]."), + span_notice("[surgeon] begins to repair the fracture in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "Your [limb.plaintext_zone] aches with pain!") + +/datum/surgery_operation/limb/repair_hairline/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + var/datum/wound/blunt/bone/fracture = locate() in limb.wounds + qdel(fracture) + + display_results( + surgeon, + limb.owner, + span_notice("You successfully repair the fracture in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully repairs the fracture in [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully repairs the fracture in [limb.owner]'s [limb.plaintext_zone]!"), + ) + +/datum/surgery_operation/limb/reset_compound + name = "reset compound fracture" + desc = "Reset a compound fracture in a patient's bone, preparing it for proper healing." + operation_flags = OPERATION_PRIORITY_NEXT_STEP + implements = list( + TOOL_BONESET = 1, + /obj/item/stack/sticky_tape/surgical = 1.66, + /obj/item/stack/sticky_tape/super = 2.5, + /obj/item/stack/sticky_tape = 5, + ) + time = 6 SECONDS + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/limb/reset_compound/get_default_radial_image() + return image(/obj/item/bonesetter) + +/datum/surgery_operation/limb/reset_compound/all_required_strings() + return list("the limb must have a compound fracture") + ..() + +/datum/surgery_operation/limb/reset_compound/state_check(obj/item/bodypart/limb) + var/datum/wound/blunt/bone/critical/fracture = locate() in limb.wounds + if(isnull(fracture) || fracture.reset) + return FALSE + return TRUE + +/datum/surgery_operation/limb/reset_compound/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to reset the bone in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to reset the bone in [limb.owner]'s [limb.plaintext_zone] with [tool]."), + span_notice("[surgeon] begins to reset the bone in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "The aching pain in your [limb.plaintext_zone] is overwhelming!") + +/datum/surgery_operation/limb/reset_compound/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + var/datum/wound/blunt/bone/critical/fracture = locate() in limb.wounds + fracture?.reset = TRUE + + display_results( + surgeon, + limb.owner, + span_notice("You successfully reset the bone in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully resets the bone in [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully resets the bone in [limb.owner]'s [limb.plaintext_zone]!"), + ) + +/datum/surgery_operation/limb/repair_compound + name = "repair compound fracture" + desc = "Mend a compound fracture in a patient's bone." + operation_flags = OPERATION_PRIORITY_NEXT_STEP + implements = list( + /obj/item/stack/medical/bone_gel = 1, + /obj/item/stack/sticky_tape/surgical = 1, + /obj/item/stack/sticky_tape/super = 2, + /obj/item/stack/sticky_tape = 3.33, + ) + time = 4 SECONDS + any_surgery_states_required = ALL_SURGERY_SKIN_STATES + +/datum/surgery_operation/limb/repair_compound/get_default_radial_image() + return image(/obj/item/stack/medical/bone_gel) + +/datum/surgery_operation/limb/repair_compound/all_required_strings() + return list("the limb's compound fracture has been reset") + ..() + +/datum/surgery_operation/limb/repair_compound/state_check(obj/item/bodypart/limb) + var/datum/wound/blunt/bone/critical/fracture = locate() in limb.wounds + if(isnull(fracture) || !fracture.reset) + return FALSE + return TRUE + +/datum/surgery_operation/limb/repair_compound/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to repair the fracture in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to repair the fracture in [limb.owner]'s [limb.plaintext_zone] with [tool]."), + span_notice("[surgeon] begins to repair the fracture in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "The aching pain in your [limb.plaintext_zone] is overwhelming!") + +/datum/surgery_operation/limb/repair_compound/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + var/datum/wound/blunt/bone/critical/fracture = locate() in limb.wounds + qdel(fracture) + display_results( + surgeon, + limb.owner, + span_notice("You successfully repair the fracture in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully repairs the fracture in [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully repairs the fracture in [limb.owner]'s [limb.plaintext_zone]!"), + ) + +/datum/surgery_operation/limb/prepare_cranium_repair + name = "discard skull debris" + desc = "Clear away bone fragments and debris from a patient's cranial fissure in preparation for repair." + operation_flags = OPERATION_PRIORITY_NEXT_STEP + implements = list( + TOOL_HEMOSTAT = 1, + TOOL_WIRECUTTER = 2.5, + TOOL_SCREWDRIVER = 2.5, + ) + time = 2.4 SECONDS + preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + +/datum/surgery_operation/limb/prepare_cranium_repair/get_default_radial_image() + return image(/obj/item/hemostat) + +/datum/surgery_operation/limb/prepare_cranium_repair/all_required_strings() + return list("the cranium must be fractured") + ..() + +/datum/surgery_operation/limb/prepare_cranium_repair/state_check(obj/item/bodypart/limb) + var/datum/wound/cranial_fissure/fissure = locate() in limb.wounds + if(isnull(fissure) || fissure.prepped) + return FALSE + return TRUE + +/datum/surgery_operation/limb/prepare_cranium_repair/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to discard the smaller skull debris in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to discard the smaller skull debris in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to poke around in [limb.owner]'s [limb.plaintext_zone]..."), + ) + display_pain(limb.owner, "Your brain feels like it's getting stabbed by little shards of glass!") + +/datum/surgery_operation/limb/prepare_cranium_repair/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + var/datum/wound/cranial_fissure/fissure = locate() in limb.wounds + fissure?.prepped = TRUE + +/datum/surgery_operation/limb/repair_cranium + name = "repair cranium" + desc = "Mend a cranial fissure in a patient's skull." + operation_flags = OPERATION_PRIORITY_NEXT_STEP + implements = list( + /obj/item/stack/medical/bone_gel = 1, + /obj/item/stack/sticky_tape/surgical = 1, + /obj/item/stack/sticky_tape/super = 2, + /obj/item/stack/sticky_tape = 3.33, + ) + time = 4 SECONDS + +/datum/surgery_operation/limb/repair_cranium/get_default_radial_image() + return image(/obj/item/stack/medical/bone_gel) + +/datum/surgery_operation/limb/repair_cranium/all_required_strings() + return list("the debris has been cleared from the cranial fissure") + ..() + +/datum/surgery_operation/limb/repair_cranium/state_check(obj/item/bodypart/limb) + var/datum/wound/cranial_fissure/fissure = locate() in limb.wounds + if(isnull(fissure) || !fissure.prepped) + return FALSE + return TRUE + +/datum/surgery_operation/limb/repair_cranium/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to repair [limb.owner]'s skull as best you can..."), + span_notice("[surgeon] begins to repair [limb.owner]'s skull with [tool]."), + span_notice("[surgeon] begins to repair [limb.owner]'s skull."), + ) + + display_pain(limb.owner, "You can feel pieces of your skull rubbing against your brain!") + +/datum/surgery_operation/limb/repair_cranium/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + var/datum/wound/cranial_fissure/fissure = locate() in limb.wounds + qdel(fissure) + + display_results( + surgeon, + limb.owner, + span_notice("You successfully repair [limb.owner]'s skull."), + span_notice("[surgeon] successfully repairs [limb.owner]'s skull with [tool]."), + span_notice("[surgeon] successfully repairs [limb.owner]'s skull.") + ) diff --git a/code/modules/surgery/operations/operation_brainwash.dm b/code/modules/surgery/operations/operation_brainwash.dm new file mode 100644 index 00000000000..eece83b3142 --- /dev/null +++ b/code/modules/surgery/operations/operation_brainwash.dm @@ -0,0 +1,150 @@ +#define OPERATION_OBJECTIVE "objective" + +/datum/surgery_operation/organ/brainwash + name = "brainwash" + desc = "Implant a directive into the patient's brain, making it their absolute priority." + rnd_name = "Neural Brainwashing (Brainwash)" + rnd_desc = "A surgical procedure which directly implants a directive into the patient's brain, \ + making it their absolute priority. It can be cleared using a mindshield implant." + implements = list( + TOOL_HEMOSTAT = 1.15, + TOOL_WIRECUTTER = 2, + /obj/item/stack/package_wrap = 2.85, + /obj/item/stack/cable_coil = 6.67, + ) + time = 20 SECONDS + preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + success_sound = 'sound/items/handling/surgery/hemostat1.ogg' + failure_sound = 'sound/items/handling/surgery/organ2.ogg' + operation_flags = OPERATION_MORBID | OPERATION_NOTABLE | OPERATION_LOCKED + target_type = /obj/item/organ/brain + required_organ_flag = ORGAN_TYPE_FLAGS & ~ORGAN_ROBOTIC + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT|SURGERY_BONE_SAWED + +/datum/surgery_operation/organ/brainwash/get_default_radial_image() + return image(/atom/movable/screen/alert/hypnosis::overlay_icon, /atom/movable/screen/alert/hypnosis::overlay_state) + +/datum/surgery_operation/organ/brainwash/pre_preop(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + operation_args[OPERATION_OBJECTIVE] = tgui_input_text(surgeon, "Choose the objective to imprint on your patient's brain", "Brainwashing", max_length = MAX_MESSAGE_LEN) + return !!operation_args[OPERATION_OBJECTIVE] + +/datum/surgery_operation/organ/brainwash/on_preop(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to brainwash [organ.owner]..."), + span_notice("[surgeon] begins to fix [organ.owner]'s brain."), + span_notice("[surgeon] begins to perform surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "Your head pounds with unimaginable pain!") // Same message as other brain surgeries + +/datum/surgery_operation/organ/brainwash/on_success(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + if(!organ.owner.mind) + to_chat(surgeon, span_warning("[organ.owner] doesn't respond to the brainwashing, as if [organ.owner.p_they()] lacked a mind...")) + return ..() + if(HAS_MIND_TRAIT(organ.owner, TRAIT_UNCONVERTABLE)) + to_chat(surgeon, span_warning("[organ.owner] seems resistant to the brainwashing...")) + return ..() + + display_results( + surgeon, + organ.owner, + span_notice("You successfully brainwash [organ.owner]!"), + span_notice("[surgeon] successfully brainwashes [organ.owner]!"), + span_notice("[surgeon] finishes performing surgery on [organ.owner]'s brain."), + ) + on_brainwash(organ.owner, surgeon, tool, operation_args) + +/datum/surgery_operation/organ/brainwash/proc/on_brainwash(mob/living/carbon/brainwashed, mob/living/surgeon, obj/item/tool, list/operation_args) + var/objective = operation_args[OPERATION_OBJECTIVE] || "Oooo no objective set somehow report this to an admin" + to_chat(brainwashed, span_notice("A new thought forms in your mind: '[objective]'")) + brainwash(brainwashed, objective) + message_admins("[ADMIN_LOOKUPFLW(surgeon)] surgically brainwashed [ADMIN_LOOKUPFLW(brainwashed)] with the objective '[objective]'.") + surgeon.log_message("has brainwashed [key_name(brainwashed)] with the objective '[objective]' using brainwashing surgery.", LOG_ATTACK) + brainwashed.log_message("has been brainwashed with the objective '[objective]' by [key_name(surgeon)] using brainwashing surgery.", LOG_VICTIM, log_globally=FALSE) + surgeon.log_message("surgically brainwashed [key_name(brainwashed)] with the objective '[objective]'.", LOG_GAME) + +/datum/surgery_operation/organ/brainwash/on_failure(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You screw up, bruising the brain's tissue!"), + span_notice("[surgeon] screws up, causing brain damage!"), + span_notice("[surgeon] completes the surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "Your head throbs with horrible pain!") + organ.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 40) + +/datum/surgery_operation/organ/brainwash/mechanic + name = "reprogram" + rnd_name = "Neural Reprogramming (Brainwash)" + rnd_desc = "Install malware which directly implants a directive into the robotic patient's operating system, \ + making it their absolute priority. It can be cleared using a mindshield implant." + implements = list( + TOOL_MULTITOOL = 1.15, + TOOL_HEMOSTAT = 2, + TOOL_WIRECUTTER = 2, + /obj/item/stack/package_wrap = 2.85, + /obj/item/stack/cable_coil = 6.67, + ) + preop_sound = 'sound/items/taperecorder/tape_flip.ogg' + success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' + required_organ_flag = ORGAN_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/organ/brainwash/sleeper + name = "install sleeper agent directive" + rnd_name = "Sleeper Agent Implantation (Brainwash)" + preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + success_sound = 'sound/items/handling/surgery/hemostat1.ogg' + failure_sound = 'sound/items/handling/surgery/organ2.ogg' + + var/list/possible_objectives = list( + "You love the Syndicate.", + "Do not trust Nanotrasen.", + "The Captain is a lizardperson.", + "Nanotrasen isn't real.", + "They put something in the food to make you forget.", + "You are the only real person on the station.", + "Things would be a lot better on the station if more people were screaming, someone should do something about that.", + "The people in charge around here have only ill intentions for the crew.", + "Help the crew? What have they ever done for you anyways?", + "Does your bag feel lighter? I bet those guys in Security stole something from it. Go get it back.", + "Command is incompetent, someone with some REAL authority should take over around here.", + "The cyborgs and the AI are stalking you. What are they planning?", + ) + +/datum/surgery_operation/organ/brainwash/sleeper/pre_preop(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + operation_args[OPERATION_OBJECTIVE] = pick(possible_objectives) + return TRUE + +/datum/surgery_operation/organ/brainwash/sleeper/on_preop(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to brainwash [organ.owner]..."), + span_notice("[surgeon] begins to fix [organ.owner]'s brain."), + span_notice("[surgeon] begins to perform surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "Your head pounds with unimaginable pain!") // Same message as other brain surgeries + +/datum/surgery_operation/organ/brainwash/sleeper/on_brainwash(mob/living/carbon/brainwashed, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + brainwashed.gain_trauma(new /datum/brain_trauma/mild/phobia/conspiracies(), TRAUMA_RESILIENCE_LOBOTOMY) + +/datum/surgery_operation/organ/brainwash/sleeper/mechanic + name = "install sleeper agent programming" + rnd_name = "Sleeper Agent Programming (Brainwash)" + implements = list( + TOOL_MULTITOOL = 1.15, + TOOL_HEMOSTAT = 2, + TOOL_WIRECUTTER = 2, + /obj/item/stack/package_wrap = 2.85, + /obj/item/stack/cable_coil = 6.67, + ) + preop_sound = 'sound/items/taperecorder/tape_flip.ogg' + success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' + required_organ_flag = ORGAN_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +#undef OPERATION_OBJECTIVE diff --git a/code/modules/surgery/operations/operation_cavity_implant.dm b/code/modules/surgery/operations/operation_cavity_implant.dm new file mode 100644 index 00000000000..8c54a19eed1 --- /dev/null +++ b/code/modules/surgery/operations/operation_cavity_implant.dm @@ -0,0 +1,194 @@ +/datum/surgery_operation/limb/prepare_cavity + name = "widen chest cavity" + desc = "Widen a patient's chest cavity to allow for implanting of larger items." + implements = list( + TOOL_RETRACTOR = 1, + TOOL_CROWBAR = 1.5, + ) + time = 4.8 SECONDS + preop_sound = 'sound/items/handling/surgery/retractor1.ogg' + success_sound = 'sound/items/handling/surgery/retractor2.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT + any_surgery_states_blocked = SURGERY_CAVITY_WIDENED + +/datum/surgery_operation/limb/prepare_cavity/get_default_radial_image() + return image(/obj/item/retractor) + +/datum/surgery_operation/limb/prepare_cavity/all_required_strings() + return list("operate on chest (target chest)") + ..() + +/datum/surgery_operation/limb/prepare_cavity/state_check(obj/item/bodypart/chest/limb) + return limb.body_zone == BODY_ZONE_CHEST + +/datum/surgery_operation/limb/prepare_cavity/on_preop(obj/item/bodypart/chest/limb, mob/living/surgeon, tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to open [limb.owner]'s [limb.plaintext_zone] cavity wide..."), + span_notice("[surgeon] begins to open [limb.owner]'s [limb.plaintext_zone] cavity wide."), + span_notice("[surgeon] begins to open [limb.owner]'s [limb.plaintext_zone] cavity wide."), + ) + display_pain(limb.owner, "You can feel pressure as your [limb.plaintext_zone] is being opened wide!") + +/datum/surgery_operation/limb/prepare_cavity/on_success(obj/item/bodypart/chest/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + limb.add_surgical_state(SURGERY_CAVITY_WIDENED) + +/datum/surgery_operation/limb/cavity_implant + name = "cavity implant" + desc = "Implant an item into a patient's body cavity." + operation_flags = OPERATION_NOTABLE + implements = list( + /obj/item = 1, + ) + time = 3.2 SECONDS + preop_sound = 'sound/items/handling/surgery/organ1.ogg' + success_sound = 'sound/items/handling/surgery/organ2.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT|SURGERY_CAVITY_WIDENED + /// Items that bypass normal size restrictions for cavity implantation + var/list/heavy_cavity_implants + +/datum/surgery_operation/limb/cavity_implant/New() + . = ..() + heavy_cavity_implants = typecacheof(list( + /obj/item/transfer_valve, + )) + +/datum/surgery_operation/limb/cavity_implant/all_required_strings() + return list("operate on chest (target chest)") + ..() + +/datum/surgery_operation/limb/cavity_implant/get_default_radial_image() + return image('icons/hud/screen_gen.dmi', "arrow_large_still") + +/datum/surgery_operation/limb/cavity_implant/state_check(obj/item/bodypart/chest/limb) + if(limb.body_zone != BODY_ZONE_CHEST) + return FALSE + if(!isnull(limb.cavity_item)) + return FALSE + return TRUE + +/datum/surgery_operation/limb/cavity_implant/snowflake_check_availability(obj/item/bodypart/chest/limb, mob/living/surgeon, obj/item/tool, operated_zone) + if(!surgeon.canUnEquip(tool)) + return FALSE + // Stops accidentally putting a tool you meant to operate with + // Besides who really wants to put a scalpel or a wrench inside someone that's lame + if(IS_ROBOTIC_LIMB(limb) && (tool.tool_behaviour in GLOB.all_mechanical_tools)) + return FALSE + if(IS_ORGANIC_LIMB(limb) && (tool.tool_behaviour in GLOB.all_surgical_tools)) + return FALSE + return TRUE + +/datum/surgery_operation/limb/cavity_implant/tool_check(obj/item/tool) + if(tool.w_class > WEIGHT_CLASS_NORMAL && !is_type_in_typecache(tool, heavy_cavity_implants)) + return FALSE + if(tool.item_flags & (ABSTRACT|DROPDEL|HAND_ITEM)) + return FALSE + if(isorgan(tool)) + return FALSE // use organ manipulation + return TRUE + +/datum/surgery_operation/limb/cavity_implant/on_preop(obj/item/bodypart/chest/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to insert [tool] into [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to insert [tool] into [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to insert [tool.w_class > WEIGHT_CLASS_SMALL ? tool : "something"] into [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You can feel something being inserted into your [limb.plaintext_zone], it hurts like hell!") + +/datum/surgery_operation/limb/cavity_implant/on_success(obj/item/bodypart/chest/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + if (!surgeon.transferItemToLoc(tool, limb.owner, force = TRUE)) // shouldn't fail but just in case + display_results( + surgeon, + limb.owner, + span_warning("You can't seem to fit [tool] in [limb.owner]'s [limb.plaintext_zone]!"), + span_warning("[surgeon] can't seem to fit [tool] in [limb.owner]'s [limb.plaintext_zone]!"), + span_warning("[surgeon] can't seem to fit [tool.w_class > WEIGHT_CLASS_SMALL ? tool : "something"] in [limb.owner]'s [limb.plaintext_zone]!"), + ) + return + + limb.cavity_item = tool + limb.remove_surgical_state(SURGERY_CAVITY_WIDENED) + display_results( + surgeon, + limb.owner, + span_notice("You stuff [tool] into [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] stuffs [tool] into [limb.owner]'s [limb.plaintext_zone]!"), + span_notice("[surgeon] stuffs [tool.w_class > WEIGHT_CLASS_SMALL ? tool : "something"] into [limb.owner]'s [limb.plaintext_zone]."), + ) + + +/datum/surgery_operation/limb/undo_cavity_implant + name = "remove cavity implant" + desc = "Remove an item from a body cavity." + implements = list( + IMPLEMENT_HAND = 1, + TOOL_HEMOSTAT = 2, + TOOL_CROWBAR = 2.5, + /obj/item/kitchen/fork = 5, + ) + + time = 3.2 SECONDS + preop_sound = 'sound/items/handling/surgery/organ1.ogg' + success_sound = 'sound/items/handling/surgery/organ2.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT|SURGERY_CAVITY_WIDENED + +/datum/surgery_operation/limb/undo_cavity_implant/all_required_strings() + return list("operate on chest (target chest)") + ..() + +/datum/surgery_operation/limb/undo_cavity_implant/get_default_radial_image() + return image('icons/hud/screen_gen.dmi', "arrow_large_still") + +/datum/surgery_operation/limb/undo_cavity_implant/get_radial_options(obj/item/bodypart/chest/limb, obj/item/tool, operating_zone) + // Not bothering to cache this as the chance of hitting the same cavity item in the same round is rather low + var/datum/radial_menu_choice/option = new() + option.name = "remove [limb.cavity_item]" + option.info = "Replace the [limb.cavity_item] embededd in the patient's chest cavity." + option.image = get_generic_limb_radial_image(BODY_ZONE_CHEST) + option.image.overlays += add_radial_overlays(limb.cavity_item) + return option + +/datum/surgery_operation/limb/undo_cavity_implant/state_check(obj/item/bodypart/chest/limb) + if(limb.body_zone != BODY_ZONE_CHEST) + return FALSE + // unlike implant removal, don't show the surgery as an option unless something is actually implanted + // it would stand to reason standard implants would be hidden from view (requires a search) + // while cavity implants would be blatantly visible (no search necessary) + if(isnull(limb.cavity_item)) + return FALSE + return TRUE + +/datum/surgery_operation/limb/undo_cavity_implant/on_preop(obj/item/bodypart/chest/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to extract [limb.cavity_item] from [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to extract [limb.cavity_item] from [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to extract something from [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a serious pain in your [limb.plaintext_zone]!") + +/datum/surgery_operation/limb/undo_cavity_implant/on_success(obj/item/bodypart/chest/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + if(isnull(limb.cavity_item)) // something else could have removed it mid surgery? + display_results( + surgeon, + limb.owner, + span_warning("You find nothing to remove from [limb.owner]'s [limb.plaintext_zone]."), + span_warning("[surgeon] finds nothing to remove from [limb.owner]'s [limb.plaintext_zone]."), + span_warning("[surgeon] finds nothing to remove from [limb.owner]'s [limb.plaintext_zone]."), + ) + return + + var/obj/item/implant = limb.cavity_item + limb.cavity_item = null + limb.remove_surgical_state(SURGERY_CAVITY_WIDENED) + display_results( + surgeon, + limb.owner, + span_notice("You pull [implant] out of [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] pulls [implant] out of [limb.owner]'s [limb.plaintext_zone]!"), + span_notice("[surgeon] pulls something out of [limb.owner]'s [limb.plaintext_zone]!"), + ) + display_pain(limb.owner, "You can feel [implant.name] being pulled out of you!") + surgeon.put_in_hands(implant) diff --git a/code/modules/surgery/operations/operation_core.dm b/code/modules/surgery/operations/operation_core.dm new file mode 100644 index 00000000000..78175d11ff5 --- /dev/null +++ b/code/modules/surgery/operations/operation_core.dm @@ -0,0 +1,43 @@ +/datum/surgery_operation/basic/core_removal + name = "extract core" + rnd_name = "Corectomy (Extract Core)" // source: i made it up + desc = "Remove the core from a slime." + implements = list( + TOOL_HEMOSTAT = 1, + TOOL_CROWBAR = 1, + ) + time = 1.6 SECONDS + operation_flags = OPERATION_IGNORE_CLOTHES | OPERATION_STANDING_ALLOWED + any_surgery_states_required = ALL_SURGERY_SKIN_STATES + required_biotype = NONE + +/datum/surgery_operation/basic/core_removal/get_default_radial_image() + return image(/mob/living/basic/slime) + +/datum/surgery_operation/basic/core_removal/all_required_strings() + return list("operate on a deceased slime") + ..() + +/datum/surgery_operation/basic/core_removal/state_check(mob/living/patient) + return isslime(patient) && patient.stat == DEAD + +/datum/surgery_operation/basic/core_removal/on_preop(mob/living/basic/slime/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + patient, + span_notice("You begin to extract [patient]'s core..."), + span_notice("[surgeon] begins to extract [patient]'s core."), + span_notice("[surgeon] begins to extract [patient]'s core."), + ) + +/datum/surgery_operation/basic/core_removal/on_success(mob/living/basic/slime/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + var/core_count = patient.cores + if(core_count && patient.try_extract_cores(count = core_count)) + display_results( + surgeon, + patient, + span_notice("You successfully extract [core_count] core\s from [patient]."), + span_notice("[surgeon] successfully extracts [core_count] core\s from [patient]!"), + span_notice("[surgeon] successfully extracts [core_count] core\s from [patient]!"), + ) + else + to_chat(surgeon, span_warning("There aren't any cores left in [patient]!")) diff --git a/code/modules/surgery/operations/operation_debride.dm b/code/modules/surgery/operations/operation_debride.dm new file mode 100644 index 00000000000..5283a836af8 --- /dev/null +++ b/code/modules/surgery/operations/operation_debride.dm @@ -0,0 +1,86 @@ +/datum/surgery_operation/limb/debride + name = "debride infected flesh" + rnd_name = "Debridement" + desc = "Remove infected or necrotic flesh from a patient's wound to promote healing." + implements = list( + TOOL_HEMOSTAT = 1, + TOOL_SCALPEL = 1.25, + TOOL_SAW = 1.66, + TOOL_WIRECUTTER = 2.5, + ) + time = 3 SECONDS + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_LOOPING | OPERATION_PRIORITY_NEXT_STEP + preop_sound = list( + TOOL_SCALPEL = 'sound/items/handling/surgery/scalpel1.ogg', + TOOL_HEMOSTAT = 'sound/items/handling/surgery/hemostat1.ogg', + ) + success_sound = 'sound/items/handling/surgery/retractor2.ogg' + failure_sound = 'sound/items/handling/surgery/organ1.ogg' + + /// How much infestation is removed per step (positive number) + var/infestation_removed = 4 + /// How much sanitization is added per step + var/sanitization_added = 0.5 // just enough to stop infestation from worsening + +/datum/surgery_operation/limb/debride/get_default_radial_image() + return image(/obj/item/reagent_containers/applicator/patch/aiuri) + +/datum/surgery_operation/limb/debride/all_required_strings() + return list("the limb must have a second degree or worse burn") + ..() + +/datum/surgery_operation/limb/debride/state_check(obj/item/bodypart/limb) + var/datum/wound/burn/flesh/wound = locate() in limb.wounds + return wound?.infection > 0 + +/// To give the surgeon a heads up how much work they have ahead of them +/datum/surgery_operation/limb/debride/proc/get_progress(datum/wound/burn/flesh/wound) + if(wound?.infection <= 0) + return null + + var/estimated_remaining_steps = wound.infection / infestation_removed + var/progress_text + + switch(estimated_remaining_steps) + if(-INFINITY to 1) + return null + if(1 to 2) + progress_text = ", preparing to remove the last remaining bits of infection" + if(2 to 4) + progress_text = ", steadily narrowing the remaining bits of infection" + if(5 to INFINITY) + progress_text = ", though there's still quite a lot to excise" + + return progress_text + +/datum/surgery_operation/limb/debride/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to excise infected flesh from [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to excise infected flesh from [limb.owner]'s [limb.plaintext_zone] with [tool]."), + span_notice("[surgeon] begins to excise infected flesh from [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "The infection in your [limb.plaintext_zone] stings like hell! It feels like you're being stabbed!") + +/datum/surgery_operation/limb/debride/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args, default_display_results = FALSE) + limb.receive_damage(3, wound_bonus = CANT_WOUND, sharpness = tool.get_sharpness(), damage_source = tool) + var/datum/wound/burn/flesh/wound = locate() in limb.wounds + wound?.infection -= infestation_removed + wound?.sanitization += sanitization_added + display_results( + surgeon, + limb.owner, + span_notice("You successfully excise some of the infected flesh from [limb.owner]'s [limb.plaintext_zone][get_progress(wound)]."), + span_notice("[surgeon] successfully excises some of the infected flesh from [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully excises some of the infected flesh from [limb.owner]'s [limb.plaintext_zone]!"), + ) + +/datum/surgery_operation/limb/debride/on_failure(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You carve away some of the healthy flesh from [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] carves away some of the healthy flesh from [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] carves away some of the healthy flesh from [limb.owner]'s [limb.plaintext_zone]!"), + ) + limb.receive_damage(rand(4, 8), wound_bonus = CANT_WOUND, sharpness = tool.get_sharpness(), damage_source = tool) diff --git a/code/modules/surgery/operations/operation_dental.dm b/code/modules/surgery/operations/operation_dental.dm new file mode 100644 index 00000000000..a572520a6ba --- /dev/null +++ b/code/modules/surgery/operations/operation_dental.dm @@ -0,0 +1,141 @@ +/datum/surgery_operation/limb/add_dental_implant + name = "add dental implant" + desc = "Implant a pill into a patient's teeth." + implements = list( + /obj/item/reagent_containers/applicator/pill = 1, + ) + time = 1.6 SECONDS + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_DRILLED + +/datum/surgery_operation/limb/add_dental_implant/all_required_strings() + . = list() + . += "operate on mouth (target mouth)" + . += ..() + . += "the mouth must have teeth" + +/datum/surgery_operation/limb/add_dental_implant/get_default_radial_image() + return image('icons/hud/implants.dmi', "reagents") + +/datum/surgery_operation/limb/add_dental_implant/snowflake_check_availability(atom/movable/operating_on, mob/living/surgeon, tool, operated_zone) + return ..() && surgeon.canUnEquip(tool) && operated_zone == BODY_ZONE_PRECISE_MOUTH + +/datum/surgery_operation/limb/add_dental_implant/state_check(obj/item/bodypart/head/limb) + var/obj/item/bodypart/head/teeth_receptangle = limb + if(!istype(teeth_receptangle)) + return FALSE + if(teeth_receptangle.teeth_count <= 0) + return FALSE + var/count = 0 + for(var/obj/item/reagent_containers/applicator/pill/dental in limb) + count++ + if(count >= teeth_receptangle.teeth_count) + return FALSE + return TRUE + +/datum/surgery_operation/limb/add_dental_implant/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to wedge [tool] in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to wedge \the [tool] in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to wedge something in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "Something's being jammed into your [limb.plaintext_zone]!") + +/datum/surgery_operation/limb/add_dental_implant/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + // Pills go into head + surgeon.transferItemToLoc(tool, limb, TRUE) + + var/datum/action/item_action/activate_pill/pill_action = new(tool) + pill_action.name = "Activate [tool.name]" + pill_action.build_all_button_icons() + pill_action.Grant(limb.owner) //The pill never actually goes in an inventory slot, so the owner doesn't inherit actions from it + + display_results( + surgeon, + limb.owner, + span_notice("You wedge [tool] into [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] wedges [tool] into [limb.owner]'s [limb.plaintext_zone]!"), + span_notice("[surgeon] wedges something into [limb.owner]'s [limb.plaintext_zone]!"), + ) + +/datum/surgery_operation/limb/remove_dental_implant + name = "remove dental implant" + desc = "Remove a dental implant from a patient's teeth." + implements = list( + TOOL_HEMOSTAT = 1, + IMPLEMENT_HAND = 1, + ) + time = 3.2 SECONDS + all_surgery_states_required = SURGERY_BONE_DRILLED|SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/limb/remove_dental_implant/get_default_radial_image() + return image(/obj/item/reagent_containers/applicator/pill) + +/datum/surgery_operation/limb/remove_dental_implant/snowflake_check_availability(atom/movable/operating_on, mob/living/surgeon, tool, operated_zone) + return ..() && operated_zone == BODY_ZONE_PRECISE_MOUTH + +/datum/surgery_operation/limb/remove_dental_implant/get_time_modifiers(atom/movable/operating_on, mob/living/surgeon, tool) + . = ..() + for(var/obj/item/flashlight/light in surgeon) + if(light.light_on) // Hey I can see a better! + . *= 0.8 + +/datum/surgery_operation/limb/remove_dental_implant/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin looking in [limb.owner]'s mouth for dental implants..."), + span_notice("[surgeon] begins to look in [limb.owner]'s mouth."), + span_notice("[surgeon] begins to examine [limb.owner]'s teeth."), + ) + display_pain(limb.owner, "You feel fingers poke around at your teeth.") + +/datum/surgery_operation/limb/remove_dental_implant/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + var/list/pills = list() + for(var/obj/item/reagent_containers/applicator/pill/dental in limb) + pills += dental + if(!length(pills)) + display_results( + surgeon, + limb.owner, + span_notice("You don't find any dental implants in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] doesn't find any dental implants in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] finishes examining [limb.owner]'s [limb.plaintext_zone]."), + ) + return + + var/obj/item/reagent_containers/applicator/pill/yoinked = pick(pills) + for(var/datum/action/item_action/activate_pill/associated_action in limb.owner.actions) + if(associated_action.target == yoinked) + qdel(associated_action) + + surgeon.put_in_hands(yoinked) + display_results( + surgeon, + limb.owner, + span_notice("You carefully remove [yoinked] from [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] carefully removes [yoinked] from [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] carefully removes something from [limb.owner]'s [limb.plaintext_zone]."), + ) + +// Teeth pill code +/datum/action/item_action/activate_pill + name = "Activate Pill" + check_flags = NONE + +/datum/action/item_action/activate_pill/IsAvailable(feedback) + if(owner.stat > SOFT_CRIT) + return FALSE + return ..() + +/datum/action/item_action/activate_pill/do_effect(trigger_flags) + owner.balloon_alert_to_viewers("[owner] grinds their teeth!", "you grit your teeth") + if(!do_after(owner, owner.stat * (2.5 SECONDS), owner, IGNORE_USER_LOC_CHANGE | IGNORE_INCAPACITATED)) + return FALSE + var/obj/item/pill = target + to_chat(owner, span_notice("You grit your teeth and burst the implanted [pill.name]!")) + owner.log_message("swallowed an implanted pill, [pill]", LOG_ATTACK) + pill.reagents.trans_to(owner, pill.reagents.total_volume, transferred_by = owner, methods = INGEST) + qdel(pill) + return TRUE diff --git a/code/modules/surgery/operations/operation_dissection.dm b/code/modules/surgery/operations/operation_dissection.dm new file mode 100644 index 00000000000..1756d536e02 --- /dev/null +++ b/code/modules/surgery/operations/operation_dissection.dm @@ -0,0 +1,151 @@ +/datum/surgery_operation/basic/dissection + name = "experimental dissection" + rnd_name = "Experimental Androtomy (Experimental Dissection and Autopsy)" + desc = "Perform an experimental dissection on a patient to obtain research points." + rnd_desc = "An experimental surgical procedure that dissects bodies in exchange for research points at ancient R&D consoles." + implements = list( + /obj/item/autopsy_scanner = 1, + TOOL_SCALPEL = 1.66, + TOOL_KNIFE = 5, + /obj/item/shard = 10, + ) + time = 12 SECONDS + operation_flags = OPERATION_LOCKED | OPERATION_ALWAYS_FAILABLE | OPERATION_MORBID | OPERATION_IGNORE_CLOTHES + required_biotype = NONE + any_surgery_states_required = ALL_SURGERY_SKIN_STATES + +/datum/surgery_operation/basic/dissection/get_default_radial_image() + return image(/obj/item/paper) + +/datum/surgery_operation/basic/dissection/all_required_strings() + . += ..() + . += "the patient must be deceased" + . += "the patient must not have been dissected prior" + +/datum/surgery_operation/basic/dissection/state_check(mob/living/patient) + return !HAS_TRAIT_FROM(patient, TRAIT_DISSECTED, EXPERIMENTAL_SURGERY_TRAIT) && patient.stat == DEAD + +/datum/surgery_operation/basic/dissection/on_preop(mob/living/patient, mob/living/surgeon, tool, list/operation_args) + display_results( + surgeon, + patient, + span_notice("You begin to dissect [patient]..."), + span_notice("[surgeon] begins to dissect [patient]."), + span_notice("[surgeon] begins to dissect [patient]."), + ) + +/datum/surgery_operation/basic/dissection/on_failure(mob/living/patient, mob/living/surgeon, tool, list/operation_args) + var/points_earned = round(check_value(patient) * 0.01) + display_results( + surgeon, + patient, + span_warning("You dissect [patient], but don't find anything particularly interesting."), + span_warning("[surgeon] dissects [patient]."), + span_warning("[surgeon] dissects [patient]."), + ) + give_paper(surgeon, points_earned) + patient.apply_damage(80, BRUTE, BODY_ZONE_CHEST) + ADD_TRAIT(patient, TRAIT_DISSECTED, EXPERIMENTAL_SURGERY_TRAIT) + +/datum/surgery_operation/basic/dissection/on_success(mob/living/patient, mob/living/surgeon, tool, list/operation_args) + var/points_earned = check_value(patient) + display_results( + surgeon, + patient, + span_warning("You dissect [patient], discovering [points_earned] point\s of data!"), + span_warning("[surgeon] dissects [patient]."), + span_warning("[surgeon] dissects [patient]."), + ) + give_paper(surgeon, points_earned) + patient.apply_damage(80, BRUTE, BODY_ZONE_CHEST) + ADD_TRAIT(patient, TRAIT_DISSECTED, EXPERIMENTAL_SURGERY_TRAIT) + +/datum/surgery_operation/basic/dissection/proc/give_paper(mob/living/surgeon, points) + var/obj/item/research_notes/the_dossier = new /obj/item/research_notes(surgeon.loc, points, "biology") + if(!surgeon.put_in_hands(the_dossier) && istype(surgeon.get_inactive_held_item(), /obj/item/research_notes)) + var/obj/item/research_notes/hand_dossier = surgeon.get_inactive_held_item() + hand_dossier.merge(the_dossier) + +///Calculates how many research points dissecting 'target' is worth. +/datum/surgery_operation/basic/dissection/proc/check_value(mob/living/target) + var/reward = 10 + + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + if(human_target.dna?.species) + if(ismonkey(human_target)) + reward /= 5 + else if(isabductor(human_target)) + reward *= 4 + else if(isgolem(human_target) || iszombie(human_target)) + reward *= 3 + else if(isjellyperson(human_target) || ispodperson(human_target)) + reward *= 2 + else if(isalienroyal(target)) + reward *= 10 + else if(isalienadult(target)) + reward *= 5 + else + reward /= 6 + + return reward + +/obj/item/research_notes + name = "research notes" + desc = "Valuable scientific data. Use it in an ancient research server to turn it in." + icon = 'icons/obj/service/bureaucracy.dmi' + icon_state = "paper" + w_class = WEIGHT_CLASS_SMALL + ///research points it holds + var/value = 100 + ///origin of the research + var/origin_type = "debug" + ///if it ws merged with different origins to apply a bonus + var/mixed = FALSE + +/obj/item/research_notes/Initialize(mapload, value, origin_type) + . = ..() + if(value) + src.value = value + if(origin_type) + src.origin_type = origin_type + change_vol() + +/obj/item/research_notes/examine(mob/user) + . = ..() + . += span_notice("It is worth [value] research points.") + +/obj/item/research_notes/attackby(obj/item/attacking_item, mob/living/user, list/modifiers, list/attack_modifiers) + if(istype(attacking_item, /obj/item/research_notes)) + var/obj/item/research_notes/notes = attacking_item + value = value + notes.value + change_vol() + qdel(notes) + return + return ..() + +/// proc that changes name and icon depending on value +/obj/item/research_notes/proc/change_vol() + if(value >= 10000) + name = "revolutionary discovery in the field of [origin_type]" + icon_state = "docs_verified" + else if(value >= 2500) + name = "essay about [origin_type]" + icon_state = "paper_words" + else if(value >= 100) + name = "notes of [origin_type]" + icon_state = "paperslip_words" + else + name = "fragmentary data of [origin_type]" + icon_state = "scrap" + +///proc when you slap research notes into another one, it applies a bonus if they are of different origin (only applied once) +/obj/item/research_notes/proc/merge(obj/item/research_notes/new_paper) + var/bonus = min(value , new_paper.value) + value = value + new_paper.value + if(origin_type != new_paper.origin_type && !mixed) + value += bonus * 0.3 + origin_type = "[origin_type] and [new_paper.origin_type]" + mixed = TRUE + change_vol() + qdel(new_paper) diff --git a/code/modules/surgery/operations/operation_filter.dm b/code/modules/surgery/operations/operation_filter.dm new file mode 100644 index 00000000000..539f43b8755 --- /dev/null +++ b/code/modules/surgery/operations/operation_filter.dm @@ -0,0 +1,78 @@ +/datum/surgery_operation/limb/filter_blood + name = "blood filtration" + rnd_name = "Hemodialysis (Blood Filtration)" + desc = "Remove unwanted chemicals from a patient's bloodstream." + implements = list(/obj/item/blood_filter = 1) + time = 2.5 SECONDS + operation_flags = OPERATION_LOOPING + required_bodytype = ~BODYTYPE_ROBOTIC + success_sound = 'sound/machines/card_slide.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/limb/filter_blood/all_required_strings() + . = list() + . += "operate on chest (target chest)" + . += ..() + . += "the patient must not be husked" + +/datum/surgery_operation/limb/filter_blood/get_default_radial_image() + return image(/obj/item/blood_filter) + +/datum/surgery_operation/limb/filter_blood/state_check(obj/item/bodypart/limb) + return limb.body_zone == BODY_ZONE_CHEST && !HAS_TRAIT(limb.owner, TRAIT_HUSK) + +/datum/surgery_operation/limb/filter_blood/can_loop(mob/living/patient, obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + return ..() && has_filterable_chems(limb.owner, tool) + +/datum/surgery_operation/limb/filter_blood/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + display_pain(limb.owner, "You feel a throbbing pain in your chest!") + +/datum/surgery_operation/limb/filter_blood/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + var/obj/item/blood_filter/bloodfilter = tool + for(var/datum/reagent/chem as anything in limb.owner.reagents?.reagent_list) + if(!length(bloodfilter.whitelist) || !(chem.type in bloodfilter.whitelist)) + limb.owner.reagents.remove_reagent(chem.type, clamp(round(chem.volume * 0.22, 0.2), 0.4, 10)) + + display_results( + surgeon, + limb.owner, + span_notice("[tool] completes a cycle filtering [limb.owner]'s blood."), + span_notice("[tool] whirrs as it filters [limb.owner]'s blood."), + span_notice("[tool] whirrs as it pumps."), + ) + + if(surgeon.is_holding_item_of_type(/obj/item/healthanalyzer)) + chemscan(surgeon, limb.owner) + +/datum/surgery_operation/limb/filter_blood/on_failure(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_warning("You screw up, bruising [limb.owner]'s chest!"), + span_warning("[surgeon] screws up, bruising [limb.owner]'s chest!"), + span_warning("[surgeon] screws up!"), + ) + limb.receive_damage(5, damage_source = tool) + +/datum/surgery_operation/limb/filter_blood/proc/has_filterable_chems(mob/living/carbon/target, obj/item/blood_filter/bloodfilter) + if(!length(target.reagents?.reagent_list)) + bloodfilter.audible_message(span_notice("[bloodfilter] pings as it reports no chemicals detected in [target]'s blood.")) + playsound(target, 'sound/machines/ping.ogg', 75, TRUE, falloff_exponent = 12, falloff_distance = 1) + return FALSE + + if(!length(bloodfilter.whitelist)) + return TRUE + + for(var/datum/reagent/chem as anything in target.reagents.reagent_list) + if(chem.type in bloodfilter.whitelist) + return TRUE + + return FALSE + +/datum/surgery_operation/limb/filter_blood/mechanic + name = "purge hydraulics" + rnd_name = "Hydraulics Purge (Blood Filtration)" + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC diff --git a/code/modules/surgery/operations/operation_generic.dm b/code/modules/surgery/operations/operation_generic.dm new file mode 100644 index 00000000000..762761ab7c6 --- /dev/null +++ b/code/modules/surgery/operations/operation_generic.dm @@ -0,0 +1,504 @@ +// Basic operations for moving back and forth between surgery states +/// First step of every surgery, makes an incision in the skin +/datum/surgery_operation/limb/incise_skin + name = "make skin incision" + // rnd_name = "Laparotomy / Craniotomy / Myotomy (Make Incision)" // Maybe we keep this one simple + desc = "Make an incision in the patient's skin to access internal organs. \ + Causes \"cut skin\" surgical state." + required_bodytype = ~BODYTYPE_ROBOTIC + replaced_by = /datum/surgery_operation/limb/incise_skin/abductor + implements = list( + TOOL_SCALPEL = 1, + /obj/item/melee/energy/sword = 1.33, + /obj/item/knife = 1.5, + /obj/item/shard = 2.25, + /obj/item/screwdriver = 5, + /obj/item/pen = 5, + /obj/item = 3.33, + ) + time = 1.6 SECONDS + preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' + success_sound = 'sound/items/handling/surgery/scalpel2.ogg' + operation_flags = OPERATION_AFFECTS_MOOD + any_surgery_states_blocked = ALL_SURGERY_SKIN_STATES + /// We can't cut mobs with this biostate + var/biostate_blacklist = BIO_CHITIN + +/datum/surgery_operation/limb/incise_skin/get_any_tool() + return "Any sharp edged item" + +/datum/surgery_operation/limb/incise_skin/get_default_radial_image() + return image(/obj/item/scalpel) + +/datum/surgery_operation/limb/incise_skin/tool_check(obj/item/tool) + // Require edged sharpness OR a tool behavior match + if((tool.get_sharpness() & SHARP_EDGED) || implements[tool.tool_behaviour]) + return TRUE + // these are here by popular demand, even though they don't fit the above criteria + if(istype(tool, /obj/item/pen) || istype(tool, /obj/item/screwdriver)) + return TRUE + return FALSE + +/datum/surgery_operation/limb/incise_skin/state_check(obj/item/bodypart/limb) + return !(limb.biological_state & biostate_blacklist) + +/datum/surgery_operation/limb/incise_skin/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to make an incision in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to make an incision in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to make an incision in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a stabbing in your [limb.plaintext_zone].") + +/datum/surgery_operation/limb/incise_skin/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() // default success message + limb.add_surgical_state(SURGERY_SKIN_CUT|SURGERY_VESSELS_UNCLAMPED) // ouch, cuts the vessels + if(!limb.can_bleed()) + return + + var/blood_name = limb.owner.get_bloodtype()?.get_blood_name() || "Blood" + display_results( + surgeon, + limb.owner, + span_notice("[blood_name] pools around the incision in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[blood_name] pools around the incision in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[blood_name] pools around the incision in [limb.owner]'s [limb.plaintext_zone]."), + ) + +/// Subtype for thick skinned creatures (Xenomorphs) +/datum/surgery_operation/limb/incise_skin/thick + name = "make thick skin incision" + implements = list( + TOOL_SAW = 1, + /obj/item/melee/energy/sword = 1.25, + /obj/item/fireaxe = 1.5, + /obj/item/knife/butcher = 2.5, + /obj/item = 5, + ) + biostate_blacklist = BIO_FLESH|BIO_METAL + +/datum/surgery_operation/limb/incise_skin/thick/get_any_tool() + return "Any sharp edged item with decent force" + +/datum/surgery_operation/limb/incise_skin/thick/tool_check(obj/item/tool) + return ..() && tool.force >= 10 + +/datum/surgery_operation/limb/incise_skin/abductor + operation_flags = parent_type::operation_flags | OPERATION_IGNORE_CLOTHES | OPERATION_LOCKED | OPERATION_NO_WIKI + required_bodytype = NONE + biostate_blacklist = NONE // they got laser scalpels + +/// Pulls the skin back to access internals +/datum/surgery_operation/limb/retract_skin + name = "retract skin" + desc = "Retract the patient's skin to access their internal organs. \ + Causes \"skin open\" surgical state." + required_bodytype = ~BODYTYPE_ROBOTIC + replaced_by = /datum/surgery_operation/limb/retract_skin/abductor + implements = list( + TOOL_RETRACTOR = 1, + TOOL_SCREWDRIVER = 2.25, + TOOL_WIRECUTTER = 2.85, + /obj/item/stack/rods = 2.85, + ) + time = 2.4 SECONDS + preop_sound = 'sound/items/handling/surgery/retractor1.ogg' + success_sound = 'sound/items/handling/surgery/retractor2.ogg' + all_surgery_states_required = SURGERY_SKIN_CUT + +/datum/surgery_operation/limb/retract_skin/get_default_radial_image() + return image(/obj/item/retractor) + +/datum/surgery_operation/limb/retract_skin/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to retract the skin in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to retract the skin in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to retract the skin in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a severe stinging pain spreading across your [limb.plaintext_zone] as the skin is pulled back.") + +/datum/surgery_operation/limb/retract_skin/on_success(obj/item/bodypart/limb) + . = ..() + limb.add_surgical_state(SURGERY_SKIN_OPEN) + limb.remove_surgical_state(SURGERY_SKIN_CUT) + +/datum/surgery_operation/limb/retract_skin/abductor + operation_flags = parent_type::operation_flags | OPERATION_IGNORE_CLOTHES | OPERATION_LOCKED | OPERATION_NO_WIKI + required_bodytype = NONE + +/// Closes the skin +/datum/surgery_operation/limb/close_skin + name = "mend skin incision" + desc = "Mend the incision in the patient's skin, closing it up. \ + Clears most surgical states." + required_bodytype = ~BODYTYPE_ROBOTIC + replaced_by = /datum/surgery_operation/limb/close_skin/abductor + implements = list( + TOOL_CAUTERY = 1, + /obj/item/stack/medical/suture = 1, + /obj/item/gun/energy/laser = 1.15, + TOOL_WELDER = 1.5, + /obj/item = 3.33, + ) + time = 2.4 SECONDS + preop_sound = list( + /obj/item/stack/medical/suture = SFX_SUTURE_BEGIN, + /obj/item = 'sound/items/handling/surgery/cautery1.ogg', + ) + success_sound = list( + /obj/item/stack/medical/suture = SFX_SUTURE_END, + /obj/item = 'sound/items/handling/surgery/cautery2.ogg', + ) + any_surgery_states_required = ALL_SURGERY_SKIN_STATES + +/datum/surgery_operation/limb/close_skin/get_any_tool() + return "Any heat source" + +/datum/surgery_operation/limb/close_skin/get_default_radial_image() + return image(/obj/item/cautery) + +/datum/surgery_operation/limb/close_skin/all_required_strings() + return ..() + list("the limb must have skin") + +/datum/surgery_operation/limb/close_skin/state_check(obj/item/bodypart/limb) + return LIMB_HAS_SKIN(limb) + +/datum/surgery_operation/limb/close_skin/tool_check(obj/item/tool) + if(istype(tool, /obj/item/stack/medical/suture)) + return TRUE + + if(istype(tool, /obj/item/gun/energy/laser)) + var/obj/item/gun/energy/laser/lasergun = tool + return lasergun.cell?.charge > 0 + + return tool.get_temperature() > 0 + +/datum/surgery_operation/limb/close_skin/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to mend the incision in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to mend the incision in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to mend the incision in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "Your [limb.plaintext_zone] is being [istype(tool, /obj/item/stack/medical/suture) ? "pinched" : "burned"]!") + +/datum/surgery_operation/limb/close_skin/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + if(LIMB_HAS_SURGERY_STATE(limb, SURGERY_BONE_SAWED)) + limb.heal_damage(40) + limb.remove_surgical_state(ALL_SURGERY_STATES_UNSET_ON_CLOSE) + +/datum/surgery_operation/limb/close_skin/abductor + operation_flags = parent_type::operation_flags | OPERATION_IGNORE_CLOTHES | OPERATION_LOCKED | OPERATION_NO_WIKI + required_bodytype = NONE + +/// Clamps bleeding blood vessels to prevent blood loss +/datum/surgery_operation/limb/clamp_bleeders + name = "clamp bleeders" + desc = "Clamp bleeding blood vessels in the patient's body to prevent blood loss. \ + Causes \"vessels clamped\" surgical state." + required_bodytype = ~BODYTYPE_ROBOTIC + operation_flags = OPERATION_PRIORITY_NEXT_STEP + replaced_by = /datum/surgery_operation/limb/clamp_bleeders/abductor + implements = list( + TOOL_HEMOSTAT = 1, + TOOL_WIRECUTTER = 1.67, + /obj/item/stack/package_wrap = 2.85, + /obj/item/stack/cable_coil = 6.67, + ) + time = 2.4 SECONDS + preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_UNCLAMPED + +/datum/surgery_operation/limb/clamp_bleeders/get_default_radial_image() + return image(/obj/item/hemostat) + +/datum/surgery_operation/limb/clamp_bleeders/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to clamp bleeders in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to clamp bleeders in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to clamp bleeders in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a pinch as the bleeding in your [limb.plaintext_zone] is slowed.") + +/datum/surgery_operation/limb/clamp_bleeders/on_success(obj/item/bodypart/limb) + . = ..() + // free brute healing if you do it after sawing bones + if(LIMB_HAS_SURGERY_STATE(limb, SURGERY_BONE_SAWED)) + limb.heal_damage(20) + limb.add_surgical_state(SURGERY_VESSELS_CLAMPED) + limb.remove_surgical_state(SURGERY_VESSELS_UNCLAMPED) + +/datum/surgery_operation/limb/clamp_bleeders/abductor + operation_flags = parent_type::operation_flags | OPERATION_IGNORE_CLOTHES | OPERATION_LOCKED | OPERATION_NO_WIKI + required_bodytype = NONE + +/// Unclamps blood vessels to allow blood flow again +/datum/surgery_operation/limb/unclamp_bleeders + name = "unclamp bleeders" + desc = "Unclamp blood vessels in the patient's body to allow blood flow again. \ + Clears \"vessels clamped\" surgical state." + required_bodytype = ~BODYTYPE_ROBOTIC + replaced_by = /datum/surgery_operation/limb/unclamp_bleeders/abductor + implements = list( + TOOL_HEMOSTAT = 1, + TOOL_WIRECUTTER = 1.67, + /obj/item/stack/package_wrap = 2.85, + /obj/item/stack/cable_coil = 6.67, + ) + time = 2.4 SECONDS + preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/limb/unclamp_bleeders/get_default_radial_image() + return image(/obj/item/hemostat) + +/datum/surgery_operation/limb/unclamp_bleeders/all_required_strings() + return ..() + list("the limb must have blood vessels") + +/datum/surgery_operation/limb/unclamp_bleeders/state_check(obj/item/bodypart/limb) + return LIMB_HAS_VESSELS(limb) + +/datum/surgery_operation/limb/unclamp_bleeders/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to unclamp bleeders in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to unclamp bleeders in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to unclamp bleeders in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a pressure release as blood starts flowing in your [limb.plaintext_zone] again.") + +/datum/surgery_operation/limb/unclamp_bleeders/on_success(obj/item/bodypart/limb) + . = ..() + limb.add_surgical_state(SURGERY_VESSELS_UNCLAMPED) + limb.remove_surgical_state(SURGERY_VESSELS_CLAMPED) + +/datum/surgery_operation/limb/unclamp_bleeders/abductor + operation_flags = parent_type::operation_flags | OPERATION_IGNORE_CLOTHES | OPERATION_LOCKED | OPERATION_NO_WIKI + required_bodytype = NONE + +/// Saws through bones to access organs +/datum/surgery_operation/limb/saw_bones + name = "saw limb bone" + desc = "Saw through the patient's bones to access their internal organs. \ + Causes \"bone sawed\" surgical state." + required_bodytype = ~BODYTYPE_ROBOTIC + implements = list( + TOOL_SAW = 1, + /obj/item/shovel/serrated = 1.33, + /obj/item/melee/arm_blade = 1.33, + /obj/item/fireaxe = 2, + /obj/item/hatchet = 2.85, + /obj/item/knife/butcher = 2.85, + /obj/item = 4, + ) + time = 5.4 SECONDS + preop_sound = list( + /obj/item/circular_saw = 'sound/items/handling/surgery/saw.ogg', + /obj/item/melee/arm_blade = 'sound/items/handling/surgery/scalpel1.ogg', + /obj/item/fireaxe = 'sound/items/handling/surgery/scalpel1.ogg', + /obj/item/hatchet = 'sound/items/handling/surgery/scalpel1.ogg', + /obj/item/knife/butcher = 'sound/items/handling/surgery/scalpel1.ogg', + /obj/item = 'sound/items/handling/surgery/scalpel1.ogg', + ) + success_sound = 'sound/items/handling/surgery/organ2.ogg' + operation_flags = OPERATION_AFFECTS_MOOD + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED + +/datum/surgery_operation/limb/saw_bones/get_any_tool() + return "Any sharp edged item with decent force" + +/datum/surgery_operation/limb/saw_bones/get_default_radial_image() + return image(/obj/item/circular_saw) + +/datum/surgery_operation/limb/saw_bones/tool_check(obj/item/tool) + // Require edged sharpness and sufficient force OR a tool behavior match + return (((tool.get_sharpness() & SHARP_EDGED) && tool.force >= 10) || implements[tool.tool_behaviour]) + +/datum/surgery_operation/limb/saw_bones/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to saw through the bone in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to saw through the bone in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to saw through the bone in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a horrid ache spread through the inside of your [limb.plaintext_zone]!") + +/datum/surgery_operation/limb/saw_bones/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + limb.add_surgical_state(SURGERY_BONE_SAWED) + limb.receive_damage(50, sharpness = tool.get_sharpness(), wound_bonus = CANT_WOUND, damage_source = tool) + display_results( + surgeon, + limb.owner, + span_notice("You saw [limb.owner]'s [limb.plaintext_zone] open."), + span_notice("[surgeon] saws [limb.owner]'s [limb.plaintext_zone] open!"), + span_notice("[surgeon] saws [limb.owner]'s [limb.plaintext_zone] open!"), + ) + display_pain(limb.owner, "It feels like something just broke in your [limb.plaintext_zone]!") + +/// Fixes sawed bones back together +/datum/surgery_operation/limb/fix_bones + name = "fix limb bone" + desc = "Repair a patient's cut or broken bones. \ + Clears \"bone sawed\" and \"bone drilled\" surgical states." + required_bodytype = ~BODYTYPE_ROBOTIC + implements = list( + /obj/item/stack/medical/bone_gel = 1, + /obj/item/stack/sticky_tape/surgical = 1, + /obj/item/stack/sticky_tape/super = 2, + /obj/item/stack/sticky_tape = 3.33, + ) + preop_sound = list( + /obj/item/stack/medical/bone_gel = 'sound/misc/soggy.ogg', + /obj/item/stack/sticky_tape/surgical = 'sound/items/duct_tape/duct_tape_rip.ogg', + /obj/item/stack/sticky_tape/super = 'sound/items/duct_tape/duct_tape_rip.ogg', + /obj/item/stack/sticky_tape = 'sound/items/duct_tape/duct_tape_rip.ogg', + ) + time = 4 SECONDS + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_required = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED + +/datum/surgery_operation/limb/fix_bones/get_default_radial_image() + return image(/obj/item/stack/medical/bone_gel) + +/datum/surgery_operation/limb/fix_bones/all_required_strings() + return ..() + list("the limb must have bones") + +/datum/surgery_operation/limb/fix_bones/state_check(obj/item/bodypart/limb) + return LIMB_HAS_BONES(limb) + +/datum/surgery_operation/limb/fix_bones/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to fix the bones in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to fix the bones in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to fix the bones in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a grinding sensation in your [limb.plaintext_zone] as the bones are set back in place.") + +/datum/surgery_operation/limb/fix_bones/on_success(obj/item/bodypart/limb) + . = ..() + limb.remove_surgical_state(SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED) + limb.heal_damage(40) + +/datum/surgery_operation/limb/drill_bones + name = "drill limb bone" + desc = "Drill through a patient's bones. \ + Causes \"bone drilled\" surgical state." + required_bodytype = ~BODYTYPE_ROBOTIC + implements = list( + TOOL_DRILL = 1, + /obj/item/screwdriver/power = 1.25, + /obj/item/pickaxe/drill = 1.67, + TOOL_SCREWDRIVER = 4, + /obj/item/kitchen/spoon = 5, + /obj/item = 6.67, + ) + time = 3 SECONDS + preop_sound = 'sound/items/handling/surgery/saw.ogg' + success_sound = 'sound/items/handling/surgery/organ2.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED + +/datum/surgery_operation/limb/drill_bones/get_any_tool() + return "Any sharp pointed item with decent force" + +/datum/surgery_operation/limb/drill_bones/get_default_radial_image() + return image(/obj/item/surgicaldrill) + +/datum/surgery_operation/limb/drill_bones/tool_check(obj/item/tool) + // Require pointy sharpness and sufficient force OR a tool behavior match + return (((tool.get_sharpness() & SHARP_POINTY) && tool.force >= 10) || implements[tool.tool_behaviour]) + +/datum/surgery_operation/limb/drill_bones/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to drill into the bone in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to drill into the bone in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to drill into the bone in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a horrible piercing pain in your [limb.plaintext_zone]!") + +/datum/surgery_operation/limb/drill_bones/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + limb.add_surgical_state(SURGERY_BONE_DRILLED) + display_results( + surgeon, + limb.owner, + span_notice("You drill into [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] drills into [limb.owner]'s [limb.plaintext_zone]!"), + span_notice("[surgeon] drills into [limb.owner]'s [limb.plaintext_zone]!"), + ) + +/datum/surgery_operation/limb/incise_organs + name = "incise organs" + desc = "Make an incision in patient's internal organ tissue to allow for manipulation or repair. \ + Causes \"organs cut\" surgical state." + required_bodytype = ~BODYTYPE_ROBOTIC + replaced_by = /datum/surgery_operation/limb/incise_organs/abductor + implements = list( + TOOL_SCALPEL = 1, + /obj/item/melee/energy/sword = 1.33, + /obj/item/knife = 1.5, + /obj/item/shard = 2.25, + /obj/item/pen = 5, + /obj/item = 3.33, + ) + time = 2.4 SECONDS + preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' + success_sound = 'sound/items/handling/surgery/organ1.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_ORGANS_CUT + +/datum/surgery_operation/limb/incise_organs/get_any_tool() + return "Any sharp edged item" + +/datum/surgery_operation/limb/incise_organs/get_default_radial_image() + return image(/obj/item/scalpel) + +/datum/surgery_operation/limb/incise_organs/tool_check(obj/item/tool) + // Require edged sharpness OR a tool behavior match. Also saws are a no-go, you'll rip up the organs! + return ((tool.get_sharpness() & SHARP_EDGED) || implements[tool.tool_behaviour]) && tool.tool_behaviour != TOOL_SAW + +/datum/surgery_operation/limb/incise_organs/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to make an incision in the organs within [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to make an incision in the organs within [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to make an incision in the organs within [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a stabbing in your [limb.plaintext_zone].") + +/datum/surgery_operation/limb/incise_organs/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + limb.add_surgical_state(SURGERY_ORGANS_CUT) + limb.receive_damage(10, sharpness = tool.get_sharpness(), wound_bonus = CANT_WOUND, damage_source = tool) + display_results( + surgeon, + limb.owner, + span_notice("You make an incision in the organs within [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] makes an incision in the organs within [limb.owner]'s [limb.plaintext_zone]!"), + span_notice("[surgeon] makes an incision in the organs within [limb.owner]'s [limb.plaintext_zone]!"), + ) + display_pain(limb.owner, "You feel a sharp pain from inside your [limb.plaintext_zone]!") + +/datum/surgery_operation/limb/incise_organs/abductor + operation_flags = parent_type::operation_flags | OPERATION_IGNORE_CLOTHES | OPERATION_LOCKED | OPERATION_NO_WIKI + required_bodytype = NONE + +/datum/surgery_operation/limb/incise_organs/abductor/state_check(obj/item/bodypart/limb) + return TRUE // You can incise chests without sawing ribs diff --git a/code/modules/surgery/operations/operation_generic_basic.dm b/code/modules/surgery/operations/operation_generic_basic.dm new file mode 100644 index 00000000000..8c8840ece78 --- /dev/null +++ b/code/modules/surgery/operations/operation_generic_basic.dm @@ -0,0 +1,185 @@ +// Some operations that mirror basic carbon state-moving operations but for basic mobs +/// Incision of skin for basic mobs +/datum/surgery_operation/basic/incise_skin + name = "make incision" + // rnd_name = "Laparotomy / Craniotomy / Myotomy (Make Incision)" // Maybe we keep this one simple + desc = "Make an incision in the patient's skin to access internals. \ + Causes \"cut skin\" surgical state." + implements = list( + TOOL_SCALPEL = 1, + /obj/item/melee/energy/sword = 1.33, + /obj/item/knife = 1.5, + /obj/item/shard = 2.25, + /obj/item/screwdriver = 5, + /obj/item/pen = 5, + /obj/item = 3.33, + ) + time = 1.6 SECONDS + preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' + success_sound = 'sound/items/handling/surgery/scalpel2.ogg' + operation_flags = OPERATION_AFFECTS_MOOD + any_surgery_states_blocked = ALL_SURGERY_SKIN_STATES + target_zone = null + +/datum/surgery_operation/basic/incise_skin/get_any_tool() + return "Any sharp edged item" + +/datum/surgery_operation/basic/incise_skin/all_blocked_strings() + return ..() + list("The patient must not have complex anatomy") + +/datum/surgery_operation/basic/incise_skin/get_default_radial_image() + return image(/obj/item/scalpel) + +/datum/surgery_operation/basic/incise_skin/state_check(mob/living/patient) + return !patient.has_limbs // Only for limbless mobs + +/datum/surgery_operation/basic/incise_skin/tool_check(obj/item/tool) + // Require edged sharpness OR a tool behavior match + if((tool.get_sharpness() & SHARP_EDGED) || implements[tool.tool_behaviour]) + return TRUE + // these are here by popular demand, even though they don't fit the above criteria + if(istype(tool, /obj/item/pen) || istype(tool, /obj/item/screwdriver)) + return TRUE + return FALSE + +/datum/surgery_operation/basic/incise_skin/on_preop(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + patient, + span_notice("You begin to make an incision in [patient]..."), + span_notice("[surgeon] begins to make an incision in [patient]."), + span_notice("[surgeon] begins to make an incision in [patient]."), + ) + display_pain(patient, "You feel a sharp stabbing sensation!") + +/datum/surgery_operation/basic/incise_skin/on_success(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + patient.apply_status_effect(/datum/status_effect/basic_surgery_state, SURGERY_SKIN_OPEN) + +/datum/surgery_operation/basic/saw_bone + name = "saw bone" + desc = "Saw through the patient's bones to access their internal organs. \ + Causes \"bone sawed\" surgical state." + implements = list( + TOOL_SAW = 1, + /obj/item/shovel/serrated = 1.33, + /obj/item/melee/arm_blade = 1.33, + /obj/item/fireaxe = 2, + /obj/item/hatchet = 2.85, + /obj/item/knife/butcher = 2.85, + /obj/item = 4, + ) + time = 5.4 SECONDS + preop_sound = list( + /obj/item/circular_saw = 'sound/items/handling/surgery/saw.ogg', + /obj/item/melee/arm_blade = 'sound/items/handling/surgery/scalpel1.ogg', + /obj/item/fireaxe = 'sound/items/handling/surgery/scalpel1.ogg', + /obj/item/hatchet = 'sound/items/handling/surgery/scalpel1.ogg', + /obj/item/knife/butcher = 'sound/items/handling/surgery/scalpel1.ogg', + /obj/item = 'sound/items/handling/surgery/scalpel1.ogg', + ) + success_sound = 'sound/items/handling/surgery/organ2.ogg' + operation_flags = OPERATION_AFFECTS_MOOD + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED + target_zone = null + +/datum/surgery_operation/basic/saw_bone/get_any_tool() + return "Any sharp edged item with decent force" + +/datum/surgery_operation/basic/saw_bone/all_blocked_strings() + return ..() + list("The patient must not have complex anatomy") + +/datum/surgery_operation/basic/saw_bone/get_default_radial_image() + return image(/obj/item/circular_saw) + +/datum/surgery_operation/basic/saw_bone/state_check(mob/living/patient) + return !patient.has_limbs // Only for limbless mobs + +/datum/surgery_operation/basic/saw_bone/tool_check(obj/item/tool) + // Require edged sharpness and sufficient force OR a tool behavior match + return (((tool.get_sharpness() & SHARP_EDGED) && tool.force >= 10) || implements[tool.tool_behaviour]) + +/datum/surgery_operation/basic/saw_bone/on_preop(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + patient, + span_notice("You begin to saw through [patient]'s bones..."), + span_notice("[surgeon] begins to saw through [patient]'s bones."), + span_notice("[surgeon] begins to saw through [patient]'s bones."), + ) + display_pain(patient, "You feel a horrid ache spread through your insides!") + +/datum/surgery_operation/basic/saw_bone/on_success(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + patient.apply_status_effect(/datum/status_effect/basic_surgery_state, SURGERY_BONE_SAWED) + patient.apply_damage(patient.maxHealth * 0.5, sharpness = tool.get_sharpness(), wound_bonus = CANT_WOUND, attacking_item = tool) + display_results( + surgeon, + patient, + span_notice("You saw [patient] open."), + span_notice("[surgeon] saws [patient] open!"), + span_notice("[surgeon] saws [patient] open!"), + ) + display_pain(patient, "It feels like something just broke!") + +// Closing of skin for basic mobs +/datum/surgery_operation/basic/close_skin + name = "mend incision" + desc = "Mend the incision in the patient's skin, closing it up. \ + Clears most surgical states." + implements = list( + TOOL_CAUTERY = 1, + /obj/item/stack/medical/suture = 1, + /obj/item/gun/energy/laser = 1.15, + TOOL_WELDER = 1.5, + /obj/item = 3.33, + ) + time = 2.4 SECONDS + preop_sound = list( + /obj/item/stack/medical/suture = SFX_SUTURE_BEGIN, + /obj/item = 'sound/items/handling/surgery/cautery1.ogg', + ) + success_sound = list( + /obj/item/stack/medical/suture = SFX_SUTURE_END, + /obj/item = 'sound/items/handling/surgery/cautery2.ogg', + ) + any_surgery_states_required = ALL_SURGERY_STATES_UNSET_ON_CLOSE // we're not picky + target_zone = null + +/datum/surgery_operation/basic/close_skin/get_any_tool() + return "Any heat source" + +/datum/surgery_operation/basic/close_skin/all_blocked_strings() + return ..() + list("The patient must not have complex anatomy") + +/datum/surgery_operation/basic/close_skin/get_default_radial_image() + return image(/obj/item/cautery) + +/datum/surgery_operation/basic/close_skin/state_check(mob/living/patient) + return !patient.has_limbs // Only for limbless mobs + +/datum/surgery_operation/basic/close_skin/tool_check(obj/item/tool) + if(istype(tool, /obj/item/stack/medical/suture)) + return TRUE + + if(istype(tool, /obj/item/gun/energy/laser)) + var/obj/item/gun/energy/laser/lasergun = tool + return lasergun.cell?.charge > 0 + + return tool.get_temperature() > 0 + +/datum/surgery_operation/basic/close_skin/on_preop(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + patient, + span_notice("You begin to mend the incision in [patient]..."), + span_notice("[surgeon] begins to mend the incision in [patient]."), + span_notice("[surgeon] begins to mend the incision in [patient]."), + ) + display_pain(patient, "You are being [istype(tool, /obj/item/stack/medical/suture) ? "pinched" : "burned"]!") + +/datum/surgery_operation/basic/close_skin/on_success(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + // Just nuke the status effect, wipe the slate clean + patient.remove_status_effect(/datum/status_effect/basic_surgery_state) diff --git a/code/modules/surgery/operations/operation_generic_mechanic.dm b/code/modules/surgery/operations/operation_generic_mechanic.dm new file mode 100644 index 00000000000..f8e4ec33b9a --- /dev/null +++ b/code/modules/surgery/operations/operation_generic_mechanic.dm @@ -0,0 +1,227 @@ +// Mechanical equivalents of basic surgical operations +/// Mechanical equivalent of cutting skin +/datum/surgery_operation/limb/mechanical_incision + name = "unscrew shell" + desc = "Unscrew the shell of a mechanical patient to access its internals. \ + Causes \"cut skin\" surgical state." + implements = list( + TOOL_SCREWDRIVER = 1, + TOOL_SCALPEL = 1.33, + /obj/item/knife = 2, + /obj/item = 10, // i think this amounts to a 180% chance of failure (clamped to 99%) + ) + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + required_bodytype = BODYTYPE_ROBOTIC + time = 2.4 SECONDS + preop_sound = 'sound/items/tools/screwdriver.ogg' + success_sound = 'sound/items/tools/screwdriver2.ogg' + any_surgery_states_blocked = ALL_SURGERY_SKIN_STATES + +/datum/surgery_operation/limb/mechanical_incision/get_any_tool() + return "Any sharp item" + +/datum/surgery_operation/limb/mechanical_incision/get_default_radial_image() + return image(/obj/item/screwdriver) + +/datum/surgery_operation/limb/mechanical_incision/tool_check(obj/item/tool) + // Require any sharpness OR a tool behavior match + return (tool.get_sharpness() || implements[tool.tool_behaviour]) + +/datum/surgery_operation/limb/mechanical_incision/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to unscrew the shell of [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to unscrew the shell of [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to unscrew the shell of [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel your [limb.plaintext_zone] grow numb as the shell is unscrewed.", TRUE) + +/datum/surgery_operation/limb/mechanical_incision/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + limb.add_surgical_state(SURGERY_SKIN_CUT) + +/// Mechanical equivalent of opening skin and clamping vessels +/datum/surgery_operation/limb/mechanical_open + name = "open hatch" + desc = "Open the hatch of a mechanical patient to access its internals. \ + Causes \"skin open\" and \"vessels clamped\" surgical states." + required_bodytype = BODYTYPE_ROBOTIC + implements = list( + IMPLEMENT_HAND = 1, + TOOL_CROWBAR = 1, + ) + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + time = 1 SECONDS + preop_sound = 'sound/items/tools/ratchet.ogg' + success_sound = 'sound/machines/airlock/doorclick.ogg' + all_surgery_states_required = SURGERY_SKIN_CUT + +/datum/surgery_operation/limb/mechanical_open/get_default_radial_image() + return image('icons/hud/screen_gen.dmi', "arrow_large_still") + +/datum/surgery_operation/limb/mechanical_open/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to open the hatch holders in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to open the hatch holders in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to open the hatch holders in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "The last faint pricks of tactile sensation fade from your [limb.plaintext_zone] as the hatch is opened.", TRUE) + +/datum/surgery_operation/limb/mechanical_open/on_success(obj/item/bodypart/limb) + . = ..() + // We get both vessels and skin done at the same time wowee + limb.add_surgical_state(SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED) + limb.remove_surgical_state(SURGERY_SKIN_CUT) + +/// Mechanical equivalent of cauterizing / closing skin +/datum/surgery_operation/limb/mechanical_close + name = "screw shell" + desc = "Screw the shell of a mechanical patient back into place. \ + Clears most surgical states." + required_bodytype = BODYTYPE_ROBOTIC + implements = list( + TOOL_SCREWDRIVER = 1, + TOOL_SCALPEL = 1.33, + /obj/item/knife = 2, + /obj/item = 10, + ) + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + time = 2.4 SECONDS + preop_sound = 'sound/items/tools/screwdriver.ogg' + success_sound = 'sound/items/tools/screwdriver2.ogg' + any_surgery_states_required = ALL_SURGERY_SKIN_STATES + +/datum/surgery_operation/limb/mechanical_close/get_any_tool() + return "Any sharp item" + +/datum/surgery_operation/limb/mechanical_close/get_default_radial_image() + return image(/obj/item/screwdriver) + +/datum/surgery_operation/limb/mechanical_close/tool_check(obj/item/tool) + // Require any sharpness OR a tool behavior match + return (tool.get_sharpness() || implements[tool.tool_behaviour]) + +/datum/surgery_operation/limb/mechanical_close/state_check(obj/item/bodypart/limb) + return LIMB_HAS_SKIN(limb) + +/datum/surgery_operation/limb/mechanical_close/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to screw the shell of [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to screw the shell of [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to screw the shell of [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel the faint pricks of sensation return as your [limb.plaintext_zone]'s shell is screwed in.", TRUE) + +/datum/surgery_operation/limb/mechanical_close/on_success(obj/item/bodypart/limb) + . = ..() + limb.remove_surgical_state(ALL_SURGERY_STATES_UNSET_ON_CLOSE) + +// Mechanical equivalent of cutting vessels and organs +/datum/surgery_operation/limb/prepare_electronics + name = "prepare electronics" + desc = "Prepare the internal electronics of a mechanical patient for surgery. \ + Causes \"organs cut\" surgical state." + required_bodytype = BODYTYPE_ROBOTIC + implements = list( + TOOL_MULTITOOL = 1, + TOOL_HEMOSTAT = 1.33, + ) + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + time = 2.4 SECONDS + preop_sound = 'sound/items/taperecorder/tape_flip.ogg' + success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_ORGANS_CUT + +/datum/surgery_operation/limb/prepare_electronics/get_default_radial_image() + return image(/obj/item/multitool) + +/datum/surgery_operation/limb/prepare_electronics/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to prepare electronics in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to prepare electronics in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to prepare electronics in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You can feel a faint buzz in your [limb.plaintext_zone] as the electronics reboot.", TRUE) + +/datum/surgery_operation/limb/prepare_electronics/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + limb.add_surgical_state(SURGERY_ORGANS_CUT) + +// Mechanical equivalent of sawing bone +/datum/surgery_operation/limb/mechanic_unwrench + name = "unwrench endoskeleton" + desc = "Unwrench a mechanical patient's endoskeleton to access its internals. \ + Clears \"bone sawed\" surgical state." + required_bodytype = BODYTYPE_ROBOTIC + implements = list( + TOOL_WRENCH = 1, + TOOL_RETRACTOR = 1.33, + ) + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + time = 2.4 SECONDS + preop_sound = 'sound/items/tools/ratchet.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED + +/datum/surgery_operation/limb/mechanic_unwrench/get_default_radial_image() + return image(/obj/item/wrench) + +/datum/surgery_operation/limb/mechanic_unwrench/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to unwrench some bolts in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to unwrench some bolts in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to unwrench some bolts in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to loosen.", TRUE) + +/datum/surgery_operation/limb/mechanic_unwrench/on_success(obj/item/bodypart/limb) + . = ..() + limb.add_surgical_state(SURGERY_BONE_SAWED) + +// Mechanical equivalent of unsawing bone +/datum/surgery_operation/limb/mechanic_wrench + name = "wrench endoskeleton" + desc = "Wrench a mechanical patient's endoskeleton back into place. \ + Clears \"bone sawed\" surgical state." + required_bodytype = BODYTYPE_ROBOTIC + implements = list( + TOOL_WRENCH = 1, + TOOL_RETRACTOR = 1.33, + ) + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + time = 2.4 SECONDS + preop_sound = 'sound/items/tools/ratchet.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED + +/datum/surgery_operation/limb/mechanic_wrench/state_check(obj/item/bodypart/limb) + return LIMB_HAS_BONES(limb) + +/datum/surgery_operation/limb/mechanic_wrench/all_required_strings() + return ..() + list("the limb must have bones") + +/datum/surgery_operation/limb/mechanic_wrench/get_default_radial_image() + return image(/obj/item/wrench) + +/datum/surgery_operation/limb/mechanic_wrench/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to wrench some bolts in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to wrench some bolts in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to wrench some bolts in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to tighten.", TRUE) + +/datum/surgery_operation/limb/mechanic_wrench/on_success(obj/item/bodypart/limb) + . = ..() + limb.remove_surgical_state(SURGERY_BONE_SAWED) diff --git a/code/modules/surgery/operations/operation_healing.dm b/code/modules/surgery/operations/operation_healing.dm new file mode 100644 index 00000000000..2592ab7b7fd --- /dev/null +++ b/code/modules/surgery/operations/operation_healing.dm @@ -0,0 +1,254 @@ +/// Allow brute healing operation +#define BRUTE_SURGERY (1<<0) +/// Allow burn healing operation +#define BURN_SURGERY (1<<1) +/// Allow combo healing operation +#define COMBO_SURGERY (1<<2) + +/datum/surgery_operation/basic/tend_wounds + name = "tend wounds" + rnd_name = "Tend Wounds" + desc = "Perform superficial wound care on a patient's bruises and burns." + implements = list( + TOOL_HEMOSTAT = 1, + TOOL_SCREWDRIVER = 1.5, + TOOL_WIRECUTTER = 1.67, + /obj/item/pen = 1.8, + ) + time = 2.5 SECONDS + operation_flags = OPERATION_LOOPING | OPERATION_IGNORE_CLOTHES + success_sound = 'sound/items/handling/surgery/retractor2.ogg' + failure_sound = 'sound/items/handling/surgery/organ2.ogg' + required_biotype = MOB_ORGANIC|MOB_HUMANOID + required_bodytype = NONE + any_surgery_states_required = ALL_SURGERY_SKIN_STATES + replaced_by = /datum/surgery_operation/basic/tend_wounds/upgraded + /// Radial slice datums for every healing option we can provide + VAR_PRIVATE/list/cached_healing_options + /// Bitflag of which healing types this operation can perform + var/can_heal = BRUTE_SURGERY | BURN_SURGERY + /// Flat amount of healing done per operation + var/healing_amount = 5 + /// The amount of damage healed scales based on how much damage the patient has times this multiplier + var/healing_multiplier = 0.07 + +/datum/surgery_operation/basic/tend_wounds/all_required_strings() + return ..() + list("the patient must have brute or burn damage") + +/datum/surgery_operation/basic/tend_wounds/state_check(mob/living/patient) + return patient.get_brute_loss() > 0 || patient.get_fire_loss() > 0 + +/datum/surgery_operation/basic/tend_wounds/get_default_radial_image() + return image(/obj/item/storage/medkit) + +/datum/surgery_operation/basic/tend_wounds/get_radial_options(mob/living/patient, obj/item/tool, operating_zone) + var/list/options = list() + + if(can_heal & COMBO_SURGERY) + var/datum/radial_menu_choice/all_healing = LAZYACCESS(cached_healing_options, "[COMBO_SURGERY]") + if(!all_healing) + all_healing = new() + all_healing.image = image(/obj/item/storage/medkit/advanced) + all_healing.name = "tend bruises and burns" + all_healing.info = "Heal a patient's superficial bruises, cuts, and burns." + LAZYSET(cached_healing_options, "[COMBO_SURGERY]", all_healing) + + options[all_healing] = list( + "[OPERATION_ACTION]" = "heal", + "[OPERATION_BRUTE_HEAL]" = healing_amount, + "[OPERATION_BURN_HEAL]" = healing_amount, + "[OPERATION_BRUTE_MULTIPLIER]" = healing_multiplier, + "[OPERATION_BURN_MULTIPLIER]" = healing_multiplier, + ) + + if((can_heal & BRUTE_SURGERY) && patient.get_brute_loss() > 0) + var/datum/radial_menu_choice/brute_healing = LAZYACCESS(cached_healing_options, "[BRUTE_SURGERY]") + if(!brute_healing) + brute_healing = new() + brute_healing.image = image(/obj/item/storage/medkit/brute) + brute_healing.name = "tend bruises" + brute_healing.info = "Heal a patient's superficial bruises and cuts." + LAZYSET(cached_healing_options, "[BRUTE_SURGERY]", brute_healing) + + options[brute_healing] = list( + "[OPERATION_ACTION]" = "heal", + "[OPERATION_BRUTE_HEAL]" = healing_amount, + "[OPERATION_BRUTE_MULTIPLIER]" = healing_multiplier, + ) + + if((can_heal & BURN_SURGERY) && patient.get_fire_loss() > 0) + var/datum/radial_menu_choice/burn_healing = LAZYACCESS(cached_healing_options, "[BURN_SURGERY]") + if(!burn_healing) + burn_healing = new() + burn_healing.image = image(/obj/item/storage/medkit/fire) + burn_healing.name = "tend burns" + burn_healing.info = "Heal a patient's superficial burns." + LAZYSET(cached_healing_options, "[BURN_SURGERY]", burn_healing) + + options[burn_healing] = list( + "[OPERATION_ACTION]" = "heal", + "[OPERATION_BURN_HEAL]" = healing_amount, + "[OPERATION_BURN_MULTIPLIER]" = healing_multiplier, + ) + + return options + +/datum/surgery_operation/basic/tend_wounds/can_loop(mob/living/patient, mob/living/operating_on, mob/living/surgeon, tool, list/operation_args) + . = ..() + if(!.) + return FALSE + var/brute_heal = operation_args[OPERATION_BRUTE_HEAL] > 0 + var/burn_heal = operation_args[OPERATION_BURN_HEAL] > 0 + if(brute_heal && burn_heal) + return patient.get_brute_loss() > 0 || patient.get_fire_loss() > 0 + else if(brute_heal) + return patient.get_brute_loss() > 0 + else if(burn_heal) + return patient.get_fire_loss() > 0 + return FALSE + +/datum/surgery_operation/basic/tend_wounds/on_preop(mob/living/patient, mob/living/surgeon, tool, list/operation_args) + var/woundtype + var/brute_heal = operation_args[OPERATION_BRUTE_HEAL] > 0 + var/burn_heal = operation_args[OPERATION_BURN_HEAL] > 0 + if(brute_heal && burn_heal) + woundtype = "wounds" + else if(brute_heal) + woundtype = "bruises" + else //why are you trying to 0,0...? + woundtype = "burns" + display_results( + surgeon, + patient, + span_notice("You attempt to patch some of [patient]'s [woundtype]."), + span_notice("[surgeon] attempts to patch some of [patient]'s [woundtype]."), + span_notice("[surgeon] attempts to patch some of [patient]'s [woundtype]."), + ) + display_pain(patient, "Your [woundtype] sting like hell!") + +#define CONDITIONAL_DAMAGE_MESSAGE(brute, burn, combo_msg, brute_msg, burn_msg) "[(brute > 0 && burn > 0) ? combo_msg : (brute > 0 ? brute_msg : burn_msg)]" + +/// Returns a string letting the surgeon know roughly how much longer the surgery is estimated to take at the going rate +/datum/surgery_operation/basic/tend_wounds/proc/get_progress(mob/living/surgeon, mob/living/patient, brute_healed, burn_healed) + var/estimated_remaining_steps = 0 + if(brute_healed > 0) + estimated_remaining_steps = max(0, (patient.get_brute_loss() / brute_healed)) + if(burn_healed > 0) + estimated_remaining_steps = max(estimated_remaining_steps, (patient.get_fire_loss() / burn_healed)) // whichever is higher between brute or burn steps + + var/progress_text + + if(surgeon.is_holding_item_of_type(/obj/item/healthanalyzer)) + if(brute_healed > 0 && patient.get_brute_loss() > 0) + progress_text += ". Remaining brute: [patient.get_brute_loss()]" + if(burn_healed > 0 && patient.get_fire_loss() > 0) + progress_text += ". Remaining burn: [patient.get_fire_loss()]" + return progress_text + + switch(estimated_remaining_steps) + if(-INFINITY to 1) + return + if(1 to 3) + progress_text += ", finishing up the last few [CONDITIONAL_DAMAGE_MESSAGE(brute_healed, burn_healed, "signs of damage", "scrapes", "burn marks")]" + if(3 to 6) + progress_text += ", counting down the last few [CONDITIONAL_DAMAGE_MESSAGE(brute_healed, burn_healed, "patches of trauma", "bruises", "blisters")] left to treat" + if(6 to 9) + progress_text += ", continuing to plug away at [patient.p_their()] extensive [CONDITIONAL_DAMAGE_MESSAGE(brute_healed, burn_healed, "injuries", "rupturing", "roasting")]" + if(9 to 12) + progress_text += ", steadying yourself for the long surgery ahead" + if(12 to 15) + progress_text += ", though [patient.p_they()] still look[patient.p_s()] more like [CONDITIONAL_DAMAGE_MESSAGE(brute_healed, burn_healed, "smooshed baby food", "ground beef", "burnt steak")] than a person" + if(15 to INFINITY) + progress_text += ", though you feel like you're barely making a dent in treating [patient.p_their()] [CONDITIONAL_DAMAGE_MESSAGE(brute_healed, burn_healed, "broken", "pulped", "charred")] body" + + return progress_text + +#undef CONDITIONAL_DAMAGE_MESSAGE + +/datum/surgery_operation/basic/tend_wounds/on_success(mob/living/patient, mob/living/surgeon, tool, list/operation_args) + var/user_msg = "You succeed in fixing some of [patient]'s wounds" //no period, add initial space to "addons" + var/target_msg = "[surgeon] fixes some of [patient]'s wounds" //see above + + var/brute_healed = operation_args[OPERATION_BRUTE_HEAL] + var/burn_healed = operation_args[OPERATION_BURN_HEAL] + + var/dead_multiplier = patient.stat == DEAD ? 0.2 : 1.0 + var/accessibility_modifier = 1.0 + if(!patient.is_location_accessible(BODY_ZONE_CHEST, IGNORED_OPERATION_CLOTHING_SLOTS)) + accessibility_modifier = 0.55 + user_msg += " as best as you can while [patient.p_they()] [patient.p_have()] clothing on" + target_msg += " as best as [surgeon.p_they()] can while [patient.p_they()] [patient.p_have()] clothing on" + + var/brute_multiplier = operation_args[OPERATION_BRUTE_MULTIPLIER] * dead_multiplier * accessibility_modifier + var/burn_multiplier = operation_args[OPERATION_BURN_MULTIPLIER] * dead_multiplier * accessibility_modifier + + brute_healed += round(patient.get_brute_loss() * brute_multiplier, DAMAGE_PRECISION) + burn_healed += round(patient.get_fire_loss() * burn_multiplier, DAMAGE_PRECISION) + + patient.heal_bodypart_damage(brute_healed, burn_healed) + + user_msg += get_progress(surgeon, patient, brute_healed, burn_healed) + + if(HAS_MIND_TRAIT(surgeon, TRAIT_MORBID) && patient.stat != DEAD) //Morbid folk don't care about tending the dead as much as tending the living + surgeon.add_mood_event("morbid_tend_wounds", /datum/mood_event/morbid_tend_wounds) + + display_results( + surgeon, + patient, + span_notice("[user_msg]."), + span_notice("[target_msg]."), + span_notice("[target_msg]."), + ) + +/datum/surgery_operation/basic/tend_wounds/on_failure(mob/living/patient, mob/living/surgeon, tool, list/operation_args) + display_results( + surgeon, + patient, + span_warning("You screwed up!"), + span_warning("[surgeon] screws up!"), + span_notice("[surgeon] fixes some of [patient]'s wounds."), + target_detailed = TRUE, + ) + var/brute_dealt = operation_args[OPERATION_BRUTE_HEAL] * 0.8 + var/burn_dealt = operation_args[OPERATION_BURN_HEAL] * 0.8 + var/brute_multiplier = operation_args[OPERATION_BRUTE_MULTIPLIER] * 0.5 + var/burn_multiplier = operation_args[OPERATION_BURN_MULTIPLIER] * 0.5 + + brute_dealt += round(patient.get_brute_loss() * brute_multiplier, 0.1) + burn_dealt += round(patient.get_fire_loss() * burn_multiplier, 0.1) + + patient.take_bodypart_damage(brute_dealt, burn_dealt, wound_bonus = CANT_WOUND) + +/datum/surgery_operation/basic/tend_wounds/upgraded + rnd_name = parent_type::rnd_name + "+" + operation_flags = parent_type::operation_flags | OPERATION_LOCKED + replaced_by = /datum/surgery_operation/basic/tend_wounds/upgraded/master + healing_multiplier = 0.1 + +/datum/surgery_operation/basic/tend_wounds/upgraded/master + rnd_name = parent_type::rnd_name + "+" + replaced_by = /datum/surgery_operation/basic/tend_wounds/combo/upgraded/master + healing_multiplier = 0.2 + +/datum/surgery_operation/basic/tend_wounds/combo + rnd_name = "Advanced Tend Wounds" + operation_flags = parent_type::operation_flags | OPERATION_LOCKED + replaced_by = /datum/surgery_operation/basic/tend_wounds/combo/upgraded + can_heal = COMBO_SURGERY + healing_amount = 3 + time = 1 SECONDS + +/datum/surgery_operation/basic/tend_wounds/combo/upgraded + rnd_name = parent_type::rnd_name + "+" + operation_flags = parent_type::operation_flags | OPERATION_LOCKED + replaced_by = /datum/surgery_operation/basic/tend_wounds/combo/upgraded/master + healing_multiplier = 0.1 + +/datum/surgery_operation/basic/tend_wounds/combo/upgraded/master + rnd_name = parent_type::rnd_name + "+" + healing_amount = 1 + healing_multiplier = 0.4 + +#undef BRUTE_SURGERY +#undef BURN_SURGERY +#undef COMBO_SURGERY diff --git a/code/modules/surgery/operations/operation_implant_removal.dm b/code/modules/surgery/operations/operation_implant_removal.dm new file mode 100644 index 00000000000..3b4dc93b632 --- /dev/null +++ b/code/modules/surgery/operations/operation_implant_removal.dm @@ -0,0 +1,83 @@ +/datum/surgery_operation/basic/implant_removal + name = "implant removal" + desc = "Attempt to find and remove an implant from a patient. \ + Any implant found will be destroyed unless an implant case is held or nearby." + operation_flags = OPERATION_NOTABLE + implements = list( + TOOL_HEMOSTAT = 1, + TOOL_CROWBAR = 1.5, + /obj/item/kitchen/fork = 2.85, + ) + time = 6.4 SECONDS + success_sound = 'sound/items/handling/surgery/hemostat1.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/basic/implant_removal/get_default_radial_image() + return image('icons/obj/medical/syringe.dmi', "implantcase-b") + +/datum/surgery_operation/basic/implant_removal/any_optional_strings() + return ..() + list("have an implant case below or inhand to store removed implants") + +/datum/surgery_operation/basic/implant_removal/on_preop(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + patient, + span_notice("You search for implants in [patient]..."), + span_notice("[surgeon] searches for implants in [patient]."), + span_notice("[surgeon] searches for something in [patient]."), + ) + if(LAZYLEN(patient.implants)) + display_pain(patient, "You feel a serious pain as [surgeon] digs around inside you!") + +/datum/surgery_operation/basic/implant_removal/on_success(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + var/obj/item/implant/implant = LAZYACCESS(patient.implants, 1) + if(isnull(implant)) + display_results( + surgeon, + patient, + span_warning("You find no implant to remove from [patient]."), + span_warning("[surgeon] finds no implant to remove from [patient]."), + span_warning("[surgeon] finds nothing to remove from [patient]."), + ) + return + + display_results( + surgeon, + patient, + span_notice("You successfully remove [implant] from [patient]."), + span_notice("[surgeon] successfully removes [implant] from [patient]!"), + span_notice("[surgeon] successfully removes something from [patient]!"), + ) + display_pain(patient, "You can feel your [implant.name] pulled out of you!") + implant.removed(patient) + + if(QDELETED(implant)) + return + + var/obj/item/implantcase/case = get_case(surgeon, patient) + if(isnull(case)) + return + + case.imp = implant + implant.forceMove(case) + case.update_appearance() + display_results( + surgeon, + patient, + span_notice("You place [implant] into [case]."), + span_notice("[surgeon] places [implant] into [case]."), + span_notice("[surgeon] places something into [case]."), + ) + +/datum/surgery_operation/basic/implant_removal/proc/get_case(mob/living/surgeon, mob/living/target) + var/list/locations = list( + surgeon.is_holding_item_of_type(/obj/item/implantcase), + locate(/obj/item/implantcase) in surgeon.loc, + locate(/obj/item/implantcase) in target.loc, + ) + + for(var/obj/item/implantcase/case in locations) + if(!case.imp) + return case + + return null diff --git a/code/modules/surgery/operations/operation_lipo.dm b/code/modules/surgery/operations/operation_lipo.dm new file mode 100644 index 00000000000..05dff033868 --- /dev/null +++ b/code/modules/surgery/operations/operation_lipo.dm @@ -0,0 +1,104 @@ +/datum/surgery_operation/limb/lipoplasty + name = "excise excess fat" + rnd_name = "Lipoplasty (Excise Fat)" + desc = "Remove excess fat from a patient's body." + operation_flags = OPERATION_NOTABLE | OPERATION_AFFECTS_MOOD + implements = list( + TOOL_SAW = 1, + TOOL_SCALPEL = 1.25, + /obj/item/shovel/serrated = 1.33, + /obj/item/melee/energy/sword = 1.33, + /obj/item/hatchet = 3.33, + /obj/item/knife = 3.33, + /obj/item = 5, + ) + time = 6.4 SECONDS + required_bodytype = ~BODYTYPE_ROBOTIC + preop_sound = list( + /obj/item/circular_saw = 'sound/items/handling/surgery/saw.ogg', + /obj/item = 'sound/items/handling/surgery/scalpel1.ogg', + ) + success_sound = 'sound/items/handling/surgery/organ2.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/limb/lipoplasty/get_any_tool() + return "Any sharp edged item" + +/datum/surgery_operation/limb/lipoplasty/get_default_radial_image() + return image(/obj/item/food/meat/slab/human) + +/datum/surgery_operation/limb/lipoplasty/all_required_strings() + . = list() + . += "operate on chest (target chest)" + . += ..() + . += "the patient must have excess fat to remove" + +/datum/surgery_operation/limb/lipoplasty/tool_check(obj/item/tool) + // Require edged sharpness OR a tool behavior match + return ((tool.get_sharpness() & SHARP_EDGED) || implements[tool.tool_behaviour]) + +/datum/surgery_operation/limb/lipoplasty/state_check(obj/item/bodypart/limb) + if(limb.body_zone != BODY_ZONE_CHEST) + return FALSE + if(!HAS_TRAIT_FROM(limb.owner, TRAIT_FAT, OBESITY) && limb.owner.nutrition < NUTRITION_LEVEL_WELL_FED) + return FALSE + return TRUE + +/datum/surgery_operation/limb/lipoplasty/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to cut away [limb.owner]'s excess fat..."), + span_notice("[surgeon] begins to cut away [limb.owner]'s excess fat."), + span_notice("[surgeon] begins to cut [limb.owner]'s [limb.plaintext_zone] with [tool]."), + ) + display_pain(limb.owner, "You feel a stabbing in your [limb.plaintext_zone]!") + +/datum/surgery_operation/limb/lipoplasty/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You successfully remove excess fat from [limb.owner]'s body!"), + span_notice("[surgeon] successfully removes excess fat from [limb.owner]'s body!"), + span_notice("[surgeon] finishes cutting away excess fat from [limb.owner]'s [limb.plaintext_zone]."), + ) + limb.owner.overeatduration = 0 //patient is unfatted + var/removednutriment = limb.owner.nutrition + limb.owner.set_nutrition(NUTRITION_LEVEL_WELL_FED) + removednutriment -= NUTRITION_LEVEL_WELL_FED //whatever was removed goes into the meat + + var/typeofmeat = /obj/item/food/meat/slab/human + if(limb.owner.flags_1 & HOLOGRAM_1) + typeofmeat = null + else if(limb.owner.dna?.species) + typeofmeat = limb.owner.dna.species.meat + + if(!typeofmeat) + return + + var/obj/item/food/meat/slab/newmeat = new typeofmeat() + newmeat.name = "fatty meat" + newmeat.desc = "Extremely fatty tissue taken from a patient." + newmeat.subjectname = limb.owner.real_name + newmeat.subjectjob = limb.owner.job + newmeat.reagents.add_reagent(/datum/reagent/consumable/nutriment, (removednutriment / 15)) //To balance with nutriment_factor of nutriment + newmeat.forceMove(limb.owner.drop_location()) + +/datum/surgery_operation/limb/lipoplasty/mechanic + name = "engage expulsion valve" //gross + rnd_name = "Nutrient Reserve Expulsion (Excise Fat)" + implements = list( + TOOL_WRENCH = 1.05, + TOOL_CROWBAR = 1.05, + /obj/item/shovel/serrated = 1.33, + /obj/item/melee/energy/sword = 1.33, + TOOL_SAW = 1.67, + /obj/item/hatchet = 3.33, + /obj/item/knife = 3.33, + TOOL_SCALPEL = 4, + /obj/item = 5, + ) + preop_sound = 'sound/items/tools/ratchet.ogg' + success_sound = 'sound/items/handling/surgery/organ2.ogg' + required_bodytype = BODYTYPE_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC diff --git a/code/modules/surgery/operations/operation_lobotomy.dm b/code/modules/surgery/operations/operation_lobotomy.dm new file mode 100644 index 00000000000..66d64e1cd4b --- /dev/null +++ b/code/modules/surgery/operations/operation_lobotomy.dm @@ -0,0 +1,97 @@ +/datum/surgery_operation/organ/lobotomy + name = "lobotomize" + rnd_name = "Lobotomy (Lobotomy)" + desc = "Repair most of a patient's brain traumas, with the risk of causing new permanent traumas." + rnd_desc = "An invasive surgical procedure which guarantees removal of almost all brain traumas, but might cause another permanent trauma in return." + operation_flags = OPERATION_MORBID | OPERATION_AFFECTS_MOOD | OPERATION_LOCKED | OPERATION_NOTABLE + implements = list( + TOOL_SCALPEL = 1.15, + /obj/item/melee/energy/sword = 0.55, + /obj/item/knife = 2.85, + /obj/item/shard = 4, + /obj/item = 5, + ) + target_type = /obj/item/organ/brain + required_organ_flag = ORGAN_TYPE_FLAGS & ~ORGAN_ROBOTIC + preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' + success_sound = 'sound/items/handling/surgery/scalpel2.ogg' + failure_sound = 'sound/items/handling/surgery/organ2.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_SAWED + +/datum/surgery_operation/organ/lobotomy/get_any_tool() + return "Any sharp edged item" + +/datum/surgery_operation/organ/lobotomy/tool_check(obj/item/tool) + // Require edged sharpness OR a tool behavior match + return ((tool.get_sharpness() & SHARP_EDGED) || implements[tool.tool_behaviour]) + +/datum/surgery_operation/organ/lobotomy/on_preop(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to perform a lobotomy on [organ.owner]'s brain..."), + span_notice("[surgeon] begins to perform a lobotomy on [organ.owner]'s brain."), + span_notice("[surgeon] begins to perform surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "Your head pounds with unimaginable pain!") + +/datum/surgery_operation/organ/lobotomy/on_success(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You successfully perform a lobotomy on [organ.owner]!"), + span_notice("[surgeon] successfully lobotomizes [organ.owner]!"), + span_notice("[surgeon] finishes performing surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "Your head goes totally numb for a moment, the pain is overwhelming!") + + organ.cure_all_traumas(TRAUMA_RESILIENCE_LOBOTOMY) + organ.owner.mind?.remove_antag_datum(/datum/antagonist/brainwashed) + if(!prob(75)) + return + switch(rand(1, 3))//Now let's see what hopefully-not-important part of the brain we cut off + if(1) + organ.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_MAGIC) + if(2) + if(HAS_TRAIT(organ.owner, TRAIT_SPECIAL_TRAUMA_BOOST) && prob(50)) + organ.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) + else + organ.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_MAGIC) + if(3) + organ.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) + +/datum/surgery_operation/organ/lobotomy/on_failure(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_warning("You remove the wrong part, causing more damage!"), + span_notice("[surgeon] unsuccessfully attempts to lobotomize [organ.owner]!"), + span_notice("[surgeon] completes the surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "The pain in your head only seems to get worse!") + organ.apply_organ_damage(80) + switch(rand(1, 3)) + if(1) + organ.owner.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_MAGIC) + if(2) + if(HAS_TRAIT(organ.owner, TRAIT_SPECIAL_TRAUMA_BOOST) && prob(50)) + organ.owner.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) + else + organ.owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_MAGIC) + if(3) + organ.owner.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) + +/datum/surgery_operation/organ/lobotomy/mechanic + name = "execute neural defragging" + rnd_name = "Wetware OS Destructive Defragmentation (Lobotomy)" + implements = list( + TOOL_MULTITOOL = 1.15, + /obj/item/melee/energy/sword = 1.85, + /obj/item/knife = 2.85, + /obj/item/shard = 4, + /obj/item = 5, + ) + preop_sound = 'sound/items/taperecorder/tape_flip.ogg' + success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' + required_organ_flag = ORGAN_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC diff --git a/code/modules/surgery/operations/operation_organ_manip.dm b/code/modules/surgery/operations/operation_organ_manip.dm new file mode 100644 index 00000000000..2c72e659a19 --- /dev/null +++ b/code/modules/surgery/operations/operation_organ_manip.dm @@ -0,0 +1,282 @@ +#define OPERATION_REMOVED_ORGAN "removed_organ" + +/// Adding or removing specific organs +/datum/surgery_operation/limb/organ_manipulation + name = "organ manipulation" + abstract_type = /datum/surgery_operation/limb/organ_manipulation + operation_flags = OPERATION_MORBID | OPERATION_NOTABLE + required_bodytype = ~BODYTYPE_ROBOTIC + /// Radial slice datums for every organ type we can manipulate + VAR_PRIVATE/list/cached_organ_manipulation_options + + /// Sound played when starting to insert an organ + var/insert_preop_sound = 'sound/items/handling/surgery/organ2.ogg' + /// Sound played when starting to remove an organ + var/remove_preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + /// Sound played when successfully inserting an organ + var/insert_success_sound = 'sound/items/handling/surgery/organ1.ogg' + /// Sound played when successfully removing an organ + var/remove_success_sound = 'sound/items/handling/surgery/organ2.ogg' + + /// Implements used to insert organs + var/list/insert_implements = list( + /obj/item/organ = 1, + ) + /// Implements used to remove organs + var/list/remove_implements = list( + TOOL_HEMOSTAT = 1, + TOOL_CROWBAR = 1.8, + /obj/item/kitchen/fork = 2.85, + ) + +/datum/surgery_operation/limb/organ_manipulation/New() + . = ..() + implements = remove_implements + insert_implements + +/datum/surgery_operation/limb/organ_manipulation/get_recommended_tool() + return "[..()] / organ" + +/datum/surgery_operation/limb/organ_manipulation/get_default_radial_image() + return image('icons/obj/medical/surgery_ui.dmi', "surgery_any") + +/// Checks that the passed organ can be inserted/removed +/datum/surgery_operation/limb/organ_manipulation/proc/organ_check(obj/item/bodypart/limb, obj/item/organ/organ) + return TRUE + +/// Checks that the passed organ can be inserted/removed in the specified zones +/datum/surgery_operation/limb/organ_manipulation/proc/zone_check(obj/item/organ/organ, limb_zone, operated_zone) + SHOULD_CALL_PARENT(TRUE) + + if(LAZYLEN(organ.valid_zones)) + // allows arm implants to be inserted into either arm + if(!(limb_zone in organ.valid_zones)) + return FALSE + // but disallows arm implants from being inserted into the torso + if(!(operated_zone in organ.valid_zones)) + return FALSE + else + // allows appendixes to be inserted into chest + if(limb_zone != deprecise_zone(organ.zone)) + return FALSE + // but disallows appendixes from being inserted into the chest cavity + if(operated_zone != organ.zone) + return FALSE + + return TRUE + +/// Get a list of organs that can be removed from the limb in the specified zone +/datum/surgery_operation/limb/organ_manipulation/proc/get_removable_organs(obj/item/bodypart/limb, operated_zone) + var/list/removable_organs = list() + for(var/obj/item/organ/organ in limb) + if(!organ_check(limb, organ) || (organ.organ_flags & ORGAN_UNREMOVABLE)) + continue + if(!zone_check(organ, limb.body_zone, operated_zone)) + continue + removable_organs += organ + + return removable_organs + +/// Check if removing an organ is possible +/datum/surgery_operation/limb/organ_manipulation/proc/is_remove_available(obj/item/bodypart/limb, operated_zone) + return length(get_removable_organs(limb, operated_zone)) > 0 + +/// Check if inserting an organ is possible +/datum/surgery_operation/limb/organ_manipulation/proc/is_insert_available(obj/item/bodypart/limb, obj/item/organ/organ, operated_zone) + if(!organ_check(limb, organ) || (organ.organ_flags & ORGAN_UNUSABLE)) + return FALSE + + for(var/obj/item/organ/other_organ in limb) + if(other_organ.slot == organ.slot) + return FALSE + + if(!zone_check(organ, limb.body_zone, operated_zone)) + return FALSE + + return TRUE + +/datum/surgery_operation/limb/organ_manipulation/snowflake_check_availability(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, operated_zone) + return isorgan(tool) ? is_insert_available(limb, tool, operated_zone) : is_remove_available(limb, operated_zone) + +/datum/surgery_operation/limb/organ_manipulation/get_radial_options(obj/item/bodypart/limb, obj/item/tool, operating_zone) + return isorgan(tool) ? get_insert_options(limb, tool, operating_zone) : get_remove_options(limb, operating_zone) + +/datum/surgery_operation/limb/organ_manipulation/proc/get_remove_options(obj/item/bodypart/limb, operating_zone) + var/list/options = list() + for(var/obj/item/organ/organ as anything in get_removable_organs(limb, operating_zone)) + var/datum/radial_menu_choice/option = LAZYACCESS(cached_organ_manipulation_options, "[organ.type]_remove") + if(!option) + option = new() + option.image = get_generic_limb_radial_image(limb.body_zone) + option.image.overlays += add_radial_overlays(organ.type) + option.name = "remove [initial(organ.name)]" + option.info = "Remove [initial(organ.name)] from the patient." + LAZYSET(cached_organ_manipulation_options, "[organ.type]_remove", option) + + options[option] = list("[OPERATION_ACTION]" = "remove", "[OPERATION_REMOVED_ORGAN]" = organ) + + return options + +/datum/surgery_operation/limb/organ_manipulation/proc/get_insert_options(obj/item/bodypart/limb, obj/item/organ/organ) + var/datum/radial_menu_choice/option = LAZYACCESS(cached_organ_manipulation_options, "[organ.type]_insert") + if(!option) + option = new() + option.image = get_generic_limb_radial_image(limb.body_zone) + option.image.overlays += add_radial_overlays(list(image('icons/hud/screen_gen.dmi', "arrow_large_still"), organ.type)) + option.name = "insert [initial(organ.name)]" + option.info = "insert [initial(organ.name)] into the patient." + LAZYSET(cached_organ_manipulation_options, "[organ.type]_insert", option) + + var/list/result = list() + result[option] = list("[OPERATION_ACTION]" = "insert") + return result + +/datum/surgery_operation/limb/organ_manipulation/operate_check(mob/living/patient, obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + if(!..()) + return FALSE + + switch(operation_args[OPERATION_ACTION]) + if("remove") + var/obj/item/organ/organ = operation_args[OPERATION_REMOVED_ORGAN] + if(QDELETED(organ) || !(organ in limb)) + return FALSE + if("insert") + var/obj/item/organ/organ = tool + for(var/obj/item/organ/existing_organ in limb) + if(existing_organ.slot == organ.slot) + return FALSE + + return TRUE + +/datum/surgery_operation/limb/organ_manipulation/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + switch(operation_args[OPERATION_ACTION]) + if("remove") + var/obj/item/organ = operation_args[OPERATION_REMOVED_ORGAN] + play_operation_sound(limb, surgeon, tool, remove_preop_sound) + display_results( + surgeon, + limb.owner, + span_notice("You begin to remove [organ.name] from [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to remove [organ.name] from [limb.owner]."), + span_notice("[surgeon] begins to remove something from [limb.owner]."), + ) + display_pain(limb.owner, "You feel a tugging sensation in your [limb.plaintext_zone]!") + if("insert") + play_operation_sound(limb, surgeon, tool, insert_preop_sound) + display_results( + surgeon, + limb.owner, + span_notice("You begin to insert [tool.name] into [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to insert [tool.name] into [limb.owner]."), + span_notice("[surgeon] begins to insert something into [limb.owner]."), + ) + display_pain(limb.owner, "You can feel something being placed in your [limb.plaintext_zone]!") + +/datum/surgery_operation/limb/organ_manipulation/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + switch(operation_args[OPERATION_ACTION]) + if("remove") + play_operation_sound(limb, surgeon, tool, remove_success_sound) + on_success_remove_organ(limb, surgeon, operation_args[OPERATION_REMOVED_ORGAN], tool) + if("insert") + play_operation_sound(limb, surgeon, tool, insert_success_sound) + on_success_insert_organ(limb, surgeon, tool) + if(HAS_MIND_TRAIT(surgeon, TRAIT_MORBID)) + surgeon.add_mood_event("morbid_abominable_surgery_success", /datum/mood_event/morbid_abominable_surgery_success) + +/datum/surgery_operation/limb/organ_manipulation/proc/on_success_remove_organ(obj/item/bodypart/limb, mob/living/surgeon, obj/item/organ/organ, obj/item/tool) + display_results( + surgeon, + limb.owner, + span_notice("You successfully extract [organ.name] from [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully extracts [organ.name] from [limb.owner]'s [limb.plaintext_zone]!"), + span_notice("[surgeon] successfully extracts something from [limb.owner]'s [limb.plaintext_zone]!"), + ) + display_pain(limb.owner, "Your [limb.plaintext_zone] throbs with pain, you can't feel your [organ.name] anymore!") + log_combat(surgeon, limb.owner, "surgically removed [organ.name] from") + organ.Remove(limb.owner) + organ.forceMove(limb.owner.drop_location()) + organ.on_surgical_removal(surgeon, limb, tool) + +/datum/surgery_operation/limb/organ_manipulation/proc/on_success_insert_organ(obj/item/bodypart/limb, mob/living/surgeon, obj/item/organ/organ) + surgeon.temporarilyRemoveItemFromInventory(organ, TRUE) + organ.pre_surgical_insertion(surgeon, limb, limb.body_zone) + organ.Insert(limb.owner) + organ.on_surgical_insertion(surgeon, limb, organ) + display_results( + surgeon, + limb.owner, + span_notice("You successfully insert [organ.name] into [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully inserts [organ.name] into [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully inserts something into [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "Your [limb.plaintext_zone] throbs with pain as your new [organ.name] comes to life!") + +/datum/surgery_operation/limb/organ_manipulation/internal + name = "internal organ manipulation" + desc = "Manipulate a patient's internal organs." + replaced_by = /datum/surgery_operation/limb/organ_manipulation/internal/abductor + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT + + var/bone_locked_organs = "the brain or any chest organs" + +/datum/surgery_operation/limb/organ_manipulation/internal/organ_check(obj/item/bodypart/limb, obj/item/organ/organ) + if(organ.organ_flags & ORGAN_EXTERNAL) + return FALSE + // chest organs and the brain require bone sawed + if(organ.zone == BODY_ZONE_CHEST || organ.slot == ORGAN_SLOT_BRAIN) + return !LIMB_HAS_BONES(limb) || LIMB_HAS_SURGERY_STATE(limb, SURGERY_BONE_SAWED) + return TRUE + +/datum/surgery_operation/limb/organ_manipulation/internal/any_required_strings() + return ..() + list( + "if operating on [bone_locked_organs], the bone MUST be sawed", + "otherwise, the state of the bone doesn't matter", + ) + +/datum/surgery_operation/limb/organ_manipulation/internal/mechanic + name = "prosthetic organ manipulation" + required_bodytype = BODYTYPE_ROBOTIC + remove_implements = list( + TOOL_CROWBAR = 1, + TOOL_HEMOSTAT = 1, + /obj/item/kitchen/fork = 2.85, + ) + operation_flags = parent_type::operation_flags | OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + +/// Abductor subtype that works through clothes and lets you extract the heart without sawing bones +/datum/surgery_operation/limb/organ_manipulation/internal/abductor + name = "experimental organ manipulation" + operation_flags = parent_type::operation_flags | OPERATION_IGNORE_CLOTHES | OPERATION_LOCKED | OPERATION_NO_WIKI + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + bone_locked_organs = "the brain or any chest organs EXCLUDING the heart" + +/datum/surgery_operation/limb/organ_manipulation/internal/abductor/organ_check(obj/item/bodypart/limb, obj/item/organ/organ) + return (organ.slot == ORGAN_SLOT_HEART) || ..() // Hearts can always be removed, it doesn't check for bone state + +// All external organ manipulation requires bones sawed +/datum/surgery_operation/limb/organ_manipulation/external + name = "feature manipulation" + desc = "Manipulate features of the patient, such as a moth's wings or a lizard's tail." + replaced_by = /datum/surgery_operation/limb/organ_manipulation/external/abductor + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_SAWED + +/datum/surgery_operation/limb/organ_manipulation/external/organ_check(obj/item/bodypart/limb, obj/item/organ/organ) + return (organ.organ_flags & ORGAN_EXTERNAL) + +/datum/surgery_operation/limb/organ_manipulation/external/mechanic + name = "prosthetic feature manipulation" + required_bodytype = BODYTYPE_ROBOTIC + remove_implements = list( + TOOL_CROWBAR = 1, + TOOL_HEMOSTAT = 1, + /obj/item/kitchen/fork = 2.85, + ) + operation_flags = parent_type::operation_flags | OPERATION_SELF_OPERABLE + replaced_by = null + +/// Abductor subtype that works through clothes +/datum/surgery_operation/limb/organ_manipulation/external/abductor + name = "experimental feature manipulation" + operation_flags = parent_type::operation_flags | OPERATION_IGNORE_CLOTHES | OPERATION_LOCKED | OPERATION_NO_WIKI + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + +#undef OPERATION_REMOVED_ORGAN diff --git a/code/modules/surgery/operations/operation_organ_repair.dm b/code/modules/surgery/operations/operation_organ_repair.dm new file mode 100644 index 00000000000..2fa6a8d6862 --- /dev/null +++ b/code/modules/surgery/operations/operation_organ_repair.dm @@ -0,0 +1,529 @@ +/// Repairing specific organs +/datum/surgery_operation/organ/repair + abstract_type = /datum/surgery_operation/organ/repair + name = "repair organ" + desc = "Repair a patient's damaged organ." + required_organ_flag = ORGAN_TYPE_FLAGS & ~ORGAN_ROBOTIC + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_NOTABLE + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT|SURGERY_BONE_SAWED + /// What % damage do we heal the organ to on success + /// Note that 0% damage = 100% health + var/heal_to_percent = 0.6 + /// What % damage do we apply to the organ on failure + var/failure_damage_percent = 0.2 + /// If TRUE, an organ can be repaired multiple times + var/repeatable = FALSE + +/datum/surgery_operation/organ/repair/New() + . = ..() + if(operation_flags & OPERATION_LOOPING) + repeatable = TRUE // if it's looping it would necessitate being repeatable + if(!repeatable) + desc += " This procedure can only be performed once per organ." + +/datum/surgery_operation/organ/repair/state_check(obj/item/organ/organ) + if(organ.damage < (organ.maxHealth * heal_to_percent) || (!repeatable && HAS_TRAIT(organ, TRAIT_ORGAN_OPERATED_ON))) + return FALSE // conditionally available so we don't spam the radial with useless options, alas + return TRUE + +/datum/surgery_operation/organ/repair/all_required_strings() + . = ..() + if(!repeatable) + . += "the organ must be moderately damaged" + +/datum/surgery_operation/organ/repair/all_blocked_strings() + . = ..() + if(!repeatable) + . += "the organ must not have been surgically repaired prior" + +/datum/surgery_operation/organ/repair/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + organ.set_organ_damage(organ.maxHealth * heal_to_percent) + organ.organ_flags &= ~ORGAN_EMP + ADD_TRAIT(organ, TRAIT_ORGAN_OPERATED_ON, TRAIT_GENERIC) + +/datum/surgery_operation/organ/repair/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + organ.apply_organ_damage(organ.maxHealth * failure_damage_percent) + +/datum/surgery_operation/organ/repair/lobectomy + name = "excise damaged lung lobe" + rnd_name = "Lobectomy (Lung Surgery)" + desc = "Perform repairs to a patient's damaged lung by excising the most damaged lobe." + implements = list( + TOOL_SCALPEL = 1.05, + /obj/item/melee/energy/sword = 1.5, + /obj/item/knife = 2.25, + /obj/item/shard = 2.85, + ) + time = 4.2 SECONDS + preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' + success_sound = 'sound/items/handling/surgery/organ1.ogg' + failure_sound = 'sound/items/handling/surgery/organ2.ogg' + target_type = /obj/item/organ/lungs + failure_damage_percent = 0.1 + +/datum/surgery_operation/organ/repair/lobectomy/on_preop(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to make an incision in [organ.owner]'s lungs..."), + span_notice("[surgeon] begins to make an incision in [organ.owner]."), + span_notice("[surgeon] begins to make an incision in [organ.owner]."), + ) + display_pain(organ.owner, "You feel a stabbing pain in your chest!") + +/datum/surgery_operation/organ/repair/lobectomy/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + display_results( + surgeon, + organ.owner, + span_notice("You successfully excise [organ.owner]'s most damaged lobe."), + span_notice("[surgeon] successfully excises [organ.owner]'s most damaged lobe."), + span_notice("[surgeon] successfully excises [organ.owner]'s most damaged lobe."), + ) + +/datum/surgery_operation/organ/repair/lobectomy/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + organ.owner.losebreath += 4 + display_results( + surgeon, + organ.owner, + span_warning("You screw up, failing to excise [organ.owner]'s damaged lobe!"), + span_warning("[surgeon] screws up!"), + span_warning("[surgeon] screws up!"), + ) + display_pain(organ.owner, "You feel a sharp stab in your chest; the wind is knocked out of you and it hurts to catch your breath!") + +/datum/surgery_operation/organ/repair/lobectomy/mechanic + name = "perform maintenance" + rnd_name = "Air Filtration Diagnostic (Lung Surgery)" + implements = list( + TOOL_WRENCH = 1.05, + TOOL_SCALPEL = 1.05, + /obj/item/melee/energy/sword = 1.5, + /obj/item/knife = 2.25, + /obj/item/shard = 2.85, + ) + preop_sound = 'sound/items/tools/ratchet.ogg' + success_sound = 'sound/machines/airlock/doorclick.ogg' + required_organ_flag = ORGAN_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/organ/repair/hepatectomy + name = "remove damaged liver section" + rnd_name = "Hepatectomy (Liver Surgery)" + desc = "Perform repairs to a patient's damaged liver by removing the most damaged section." + implements = list( + TOOL_SCALPEL = 1.05, + /obj/item/melee/energy/sword = 1.5, + /obj/item/knife = 2.25, + /obj/item/shard = 2.85, + ) + time = 5.2 SECONDS + preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' + success_sound = 'sound/items/handling/surgery/organ1.ogg' + failure_sound = 'sound/items/handling/surgery/organ2.ogg' + target_type = /obj/item/organ/liver + heal_to_percent = 0.1 + failure_damage_percent = 0.15 + +/datum/surgery_operation/organ/repair/hepatectomy/on_preop(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to cut out a damaged piece of [organ.owner]'s liver..."), + span_notice("[surgeon] begins to make an incision in [organ.owner]."), + span_notice("[surgeon] begins to make an incision in [organ.owner]."), + ) + display_pain(organ.owner, "Your abdomen burns in horrific stabbing pain!") + +/datum/surgery_operation/organ/repair/hepatectomy/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + display_results( + surgeon, + organ.owner, + span_notice("You successfully remove the damaged part of [organ.owner]'s liver."), + span_notice("[surgeon] successfully removes the damaged part of [organ.owner]'s liver."), + span_notice("[surgeon] successfully removes the damaged part of [organ.owner]'s liver."), + ) + display_pain(organ.owner, "The pain receeds slightly!") + +/datum/surgery_operation/organ/repair/hepatectomy/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + display_results( + surgeon, + organ.owner, + span_warning("You cut the wrong part of [organ.owner]'s liver!"), + span_warning("[surgeon] cuts the wrong part of [organ.owner]'s liver!"), + span_warning("[surgeon] cuts the wrong part of [organ.owner]'s liver!"), + ) + display_pain(organ.owner, "The pain in your abdomen intensifies!") + +/datum/surgery_operation/organ/repair/hepatectomy/mechanic + name = "perform maintenance" + rnd_name = "Impurity Management System Diagnostic (Liver Surgery)" + implements = list( + TOOL_WRENCH = 1.05, + TOOL_SCALPEL = 1.05, + /obj/item/melee/energy/sword = 1.5, + /obj/item/knife = 2.25, + /obj/item/shard = 2.85, + ) + preop_sound = 'sound/items/tools/ratchet.ogg' + success_sound = 'sound/machines/airlock/doorclick.ogg' + required_organ_flag = ORGAN_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/organ/repair/coronary_bypass + name = "graft coronary bypass" + rnd_name = "Coronary Artery Bypass Graft (Heart Surgery)" + desc = "Graft a bypass onto a patient's damaged heart to restore proper blood flow." + implements = list( + TOOL_HEMOSTAT = 1.05, + TOOL_WIRECUTTER = 2.85, + /obj/item/stack/package_wrap = 6.67, + /obj/item/stack/cable_coil = 2, + ) + time = 9 SECONDS + preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + success_sound = 'sound/items/handling/surgery/hemostat1.ogg' + failure_sound = 'sound/items/handling/surgery/organ2.ogg' + target_type = /obj/item/organ/heart + +/datum/surgery_operation/organ/repair/coronary_bypass/on_preop(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to graft a bypass onto [organ.owner]'s heart..."), + span_notice("[surgeon] begins to graft a bypass onto [organ.owner]'s heart."), + span_notice("[surgeon] begins to graft a bypass onto [organ.owner]'s heart."), + ) + display_pain(organ.owner, "The pain in your chest is unbearable! You can barely take it anymore!") + +/datum/surgery_operation/organ/repair/coronary_bypass/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + display_results( + surgeon, + organ.owner, + span_notice("You successfully graft a bypass onto [organ.owner]'s heart."), + span_notice("[surgeon] successfully grafts a bypass onto [organ.owner]'s heart."), + span_notice("[surgeon] successfully grafts a bypass onto [organ.owner]'s heart."), + ) + display_pain(organ.owner, "The pain in your chest throbs, but your heart feels better than ever!") + +/datum/surgery_operation/organ/repair/coronary_bypass/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + organ.bodypart_owner.adjustBleedStacks(30) + var/blood_name = LOWER_TEXT(organ.owner.get_bloodtype()?.get_blood_name()) || "blood" + display_results( + surgeon, + organ.owner, + span_warning("You screw up in attaching the graft, and it tears off, tearing part of the heart!"), + span_warning("[surgeon] screws up, causing [blood_name] to spurt out of [organ.owner]'s chest profusely!"), + span_warning("[surgeon] screws up, causing [blood_name] to spurt out of [organ.owner]'s chest profusely!"), + ) + display_pain(organ.owner, "Your chest burns; you feel like you're going insane!") + +/datum/surgery_operation/organ/repair/coronary_bypass/mechanic + name = "access engine internals" + rnd_name = "Engine Diagnostic (Heart Surgery)" + implements = list( + TOOL_CROWBAR = 1.05, + TOOL_SCALPEL = 1.05, + /obj/item/melee/energy/sword = 1.5, + /obj/item/knife = 2.25, + /obj/item/shard = 2.85, + ) + preop_sound = 'sound/items/tools/ratchet.ogg' + success_sound = 'sound/machines/airlock/doorclick.ogg' + required_organ_flag = ORGAN_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/organ/repair/gastrectomy + name = "remove damaged stomach section" + rnd_name = "Gastrectomy (Stomach Surgery)" + desc = "Perform repairs to a patient's stomach by removing a damaged section." + implements = list( + TOOL_SCALPEL = 1.05, + /obj/item/melee/energy/sword = 1.5, + /obj/item/knife = 2.25, + /obj/item/shard = 2.85, + /obj/item = 4, + ) + time = 5.2 SECONDS + preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' + success_sound = 'sound/items/handling/surgery/organ1.ogg' + failure_sound = 'sound/items/handling/surgery/organ2.ogg' + target_type = /obj/item/organ/stomach + heal_to_percent = 0.2 + failure_damage_percent = 0.15 + +/datum/surgery_operation/organ/repair/gastrectomy/get_any_tool() + return "Any sharp edged item" + +/datum/surgery_operation/organ/repair/gastrectomy/tool_check(obj/item/tool) + // Require edged sharpness OR a tool behavior match + return ((tool.get_sharpness() & SHARP_EDGED) || implements[tool.tool_behaviour]) + +/datum/surgery_operation/organ/repair/gastrectomy/on_preop(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to cut out a damaged piece of [organ.owner]'s stomach..."), + span_notice("[surgeon] begins to make an incision in [organ.owner]."), + span_notice("[surgeon] begins to make an incision in [organ.owner]."), + ) + display_pain(organ.owner, "You feel a horrible stab in your gut!") + +/datum/surgery_operation/organ/repair/gastrectomy/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + display_results( + surgeon, + organ.owner, + span_notice("You successfully remove the damaged part of [organ.owner]'s stomach."), + span_notice("[surgeon] successfully removes the damaged part of [organ.owner]'s stomach."), + span_notice("[surgeon] successfully removes the damaged part of [organ.owner]'s stomach."), + ) + display_pain(organ.owner, "The pain in your gut receeds slightly!") + +/datum/surgery_operation/organ/repair/gastrectomy/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + display_results( + surgeon, + organ.owner, + span_warning("You cut the wrong part of [organ.owner]'s stomach!"), + span_warning("[surgeon] cuts the wrong part of [organ.owner]'s stomach!"), + span_warning("[surgeon] cuts the wrong part of [organ.owner]'s stomach!"), + ) + display_pain(organ.owner, "The pain in your gut intensifies!") + +/datum/surgery_operation/organ/repair/gastrectomy/mechanic + name = "perform maintenance" + rnd_name = "Nutrient Processing System Diagnostic (Stomach Surgery)" + implements = list( + TOOL_WRENCH = 1.05, + TOOL_SCALPEL = 1.05, + /obj/item/melee/energy/sword = 1.5, + /obj/item/knife = 2.25, + /obj/item/shard = 2.85, + /obj/item = 4, + ) + preop_sound = 'sound/items/tools/ratchet.ogg' + success_sound = 'sound/machines/airlock/doorclick.ogg' + required_organ_flag = ORGAN_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC + +/datum/surgery_operation/organ/repair/ears + name = "ear surgery" + rnd_name = "Ototomy (Ear surgery)" // source: i made it up + desc = "Repair a patient's damaged ears to restore hearing." + operation_flags = parent_type::operation_flags & ~OPERATION_AFFECTS_MOOD + implements = list( + TOOL_HEMOSTAT = 1.05, + TOOL_SCREWDRIVER = 2.25, + /obj/item/pen = 4, + ) + target_type = /obj/item/organ/ears + time = 6.4 SECONDS + heal_to_percent = 0 + repeatable = TRUE + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/organ/repair/ears/all_blocked_strings() + return ..() + list("if the limb has bones, they must be intact") + +/datum/surgery_operation/organ/repair/ears/state_check(obj/item/organ/ears/organ) + // If bones are sawed, prevent the operation (unless we're operating on a limb with no bones) + if(LIMB_HAS_ANY_SURGERY_STATE(organ.bodypart_owner, SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED) && LIMB_HAS_BONES(organ.bodypart_owner)) + return FALSE + return TRUE // always available so you can intentionally fail it + +/datum/surgery_operation/organ/repair/ears/on_preop(obj/item/organ/ears/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to fix [organ.owner]'s ears..."), + span_notice("[surgeon] begins to fix [organ.owner]'s ears."), + span_notice("[surgeon] begins to perform surgery on [organ.owner]'s ears."), + ) + display_pain(organ.owner, "You feel a dizzying pain in your head!") + +/datum/surgery_operation/organ/repair/ears/on_success(obj/item/organ/ears/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + var/deaf_change = 40 SECONDS - organ.temporary_deafness + organ.adjust_temporary_deafness(deaf_change) + display_results( + surgeon, + organ.owner, + span_notice("You successfully fix [organ.owner]'s ears."), + span_notice("[surgeon] successfully fixes [organ.owner]'s ears."), + span_notice("[surgeon] successfully fixes [organ.owner]'s ears."), + ) + display_pain(organ.owner, "Your head swims, but it seems like you can feel your hearing coming back!") + +/datum/surgery_operation/organ/repair/ears/on_failure(obj/item/organ/ears/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + var/obj/item/organ/brain/brain = locate() in organ.bodypart_owner + if(brain) + display_results( + surgeon, + organ.owner, + span_warning("You accidentally stab [organ.owner] right in the brain!"), + span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain!"), + span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain!"), + ) + display_pain(organ.owner, "You feel a visceral stabbing pain right through your head, into your brain!") + organ.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 70) + else + display_results( + surgeon, + organ.owner, + span_warning("You accidentally stab [organ.owner] right in the brain! Or would have, if [organ.owner] had a brain."), + span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain! Or would have, if [organ.owner] had a brain."), + span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain!"), + ) + +/datum/surgery_operation/organ/repair/eyes + name = "eye surgery" + rnd_name = "Vitrectomy (Eye Surgery)" + desc = "Repair a patient's damaged eyes to restore vision." + operation_flags = parent_type::operation_flags & ~OPERATION_AFFECTS_MOOD + implements = list( + TOOL_HEMOSTAT = 1.05, + TOOL_SCREWDRIVER = 2.25, + /obj/item/pen = 4, + ) + time = 6.4 SECONDS + target_type = /obj/item/organ/eyes + heal_to_percent = 0 + repeatable = TRUE + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/organ/repair/eyes/all_blocked_strings() + return ..() + list("if the limb has bones, they must be intact") + +/datum/surgery_operation/organ/repair/eyes/state_check(obj/item/organ/organ) + // If bones are sawed, prevent the operation (unless we're operating on a limb with no bones) + if(LIMB_HAS_ANY_SURGERY_STATE(organ.bodypart_owner, SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED) && LIMB_HAS_BONES(organ.bodypart_owner)) + return FALSE + return TRUE // always available so you can intentionally fail it + +/datum/surgery_operation/organ/repair/eyes/get_default_radial_image() + return image(icon = 'icons/obj/medical/surgery_ui.dmi', icon_state = "surgery_eyes") + +/datum/surgery_operation/organ/repair/eyes/on_preop(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to fix [organ.owner]'s eyes..."), + span_notice("[surgeon] begins to fix [organ.owner]'s eyes."), + span_notice("[surgeon] begins to perform surgery on [organ.owner]'s eyes."), + ) + display_pain(organ.owner, "You feel a stabbing pain in your eyes!") + +/datum/surgery_operation/organ/repair/eyes/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + organ.owner.remove_status_effect(/datum/status_effect/temporary_blindness) + organ.owner.set_eye_blur_if_lower(70 SECONDS) //this will fix itself slowly. + display_results( + surgeon, + organ.owner, + span_notice("You successfully fix [organ.owner]'s eyes."), + span_notice("[surgeon] successfully fixes [organ.owner]'s eyes."), + span_notice("[surgeon] successfully fixes [organ.owner]'s eyes."), + ) + display_pain(organ.owner, "Your vision blurs, but it seems like you can see a little better now!") + +/datum/surgery_operation/organ/repair/eyes/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + var/obj/item/organ/brain/brain = locate() in organ.bodypart_owner + if(brain) + display_results( + surgeon, + organ.owner, + span_warning("You accidentally stab [organ.owner] right in the brain!"), + span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain!"), + span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain!"), + ) + display_pain(organ.owner, "You feel a visceral stabbing pain right through your head, into your brain!") + organ.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 70) + + else + display_results( + surgeon, + organ.owner, + span_warning("You accidentally stab [organ.owner] right in the brain! Or would have, if [organ.owner] had a brain."), + span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain! Or would have, if [organ.owner] had a brain."), + span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain!"), + ) + +/datum/surgery_operation/organ/repair/brain + name = "brain surgery" + rnd_name = "Neurosurgery (Brain Surgery)" + desc = "Repair a patient's damaged brain tissue to restore cognitive function." + implements = list( + TOOL_HEMOSTAT = 1.05, + TOOL_SCREWDRIVER = 2.85, + /obj/item/pen = 6.67, + ) + time = 10 SECONDS + preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + success_sound = 'sound/items/handling/surgery/hemostat1.ogg' + failure_sound = 'sound/items/handling/surgery/organ2.ogg' + operation_flags = parent_type::operation_flags | OPERATION_LOOPING + target_type = /obj/item/organ/brain + heal_to_percent = 0.25 + failure_damage_percent = 0.3 + repeatable = TRUE + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/organ/repair/brain/state_check(obj/item/organ/brain/organ) + return TRUE // always available so you can intentionally fail it + +/datum/surgery_operation/organ/repair/brain/on_preop(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to fix [organ.owner]'s brain..."), + span_notice("[surgeon] begins to fix [organ.owner]'s brain."), + span_notice("[surgeon] begins to perform surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "Your head pounds with unimaginable pain!") + +/datum/surgery_operation/organ/repair/brain/on_success(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + organ.apply_organ_damage(-organ.maxHealth * heal_to_percent) // no parent call, special healing for this one + display_results( + surgeon, + organ.owner, + span_notice("You succeed in fixing [organ.owner]'s brain."), + span_notice("[surgeon] successfully fixes [organ.owner]'s brain!"), + span_notice("[surgeon] completes the surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "The pain in your head receeds, thinking becomes a bit easier!") + organ.owner.mind?.remove_antag_datum(/datum/antagonist/brainwashed) + organ.cure_all_traumas(TRAUMA_RESILIENCE_SURGERY) + if(organ.damage > organ.maxHealth * 0.1) + to_chat(surgeon, "[organ.owner]'s brain looks like it could be fixed further.") + +/datum/surgery_operation/organ/repair/brain/on_failure(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + display_results( + surgeon, + organ.owner, + span_warning("You screw up, causing more damage!"), + span_warning("[surgeon] screws up, causing brain damage!"), + span_notice("[surgeon] completes the surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "Your head throbs with horrible pain; thinking hurts!") + organ.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) + +/datum/surgery_operation/organ/repair/brain/mechanic + name = "perform neural debugging" + rnd_name = "Wetware OS Diagnostics (Brain Surgery)" + implements = list( + TOOL_MULTITOOL = 1.15, + TOOL_HEMOSTAT = 1.05, + TOOL_SCREWDRIVER = 2.85, + /obj/item/pen = 6.67, + ) + preop_sound = 'sound/items/taperecorder/tape_flip.ogg' + success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' + required_organ_flag = ORGAN_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC diff --git a/code/modules/surgery/operations/operation_pacify.dm b/code/modules/surgery/operations/operation_pacify.dm new file mode 100644 index 00000000000..04f6c6c4571 --- /dev/null +++ b/code/modules/surgery/operations/operation_pacify.dm @@ -0,0 +1,69 @@ +/datum/surgery_operation/organ/pacify + name = "pacification" + rnd_name = "Paxopsy (Pacification)" + desc = "Remove aggressive tendencies from a patient's brain." + rnd_desc = "A surgical procedure which permanently inhibits the aggression center of the brain, making the patient unwilling to cause direct harm." + operation_flags = OPERATION_MORBID | OPERATION_LOCKED | OPERATION_NOTABLE + implements = list( + TOOL_HEMOSTAT = 1, + TOOL_SCREWDRIVER = 2.85, + /obj/item/pen = 6.67, + ) + time = 4 SECONDS + preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + success_sound = 'sound/items/handling/surgery/hemostat1.ogg' + failure_sound = 'sound/items/handling/surgery/organ2.ogg' + required_organ_flag = ORGAN_TYPE_FLAGS & ~ORGAN_ROBOTIC + target_type = /obj/item/organ/brain + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_SAWED + +/datum/surgery_operation/organ/pacify/get_default_radial_image() + return image(/atom/movable/screen/alert/status_effect/high::overlay_icon, /atom/movable/screen/alert/status_effect/high::overlay_state) + +/datum/surgery_operation/organ/pacify/on_preop(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to pacify [organ.owner]..."), + span_notice("[surgeon] begins to fix [organ.owner]'s brain."), + span_notice("[surgeon] begins to perform surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "Your head pounds with unimaginable pain!") + +/datum/surgery_operation/organ/pacify/on_success(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You succeed in pacifying [organ.owner]."), + span_notice("[surgeon] successfully fixes [organ.owner]!"), + span_notice("[surgeon] completes the surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "Your head pounds... the concept of violence flashes in your head, and nearly makes you hurl!") + organ.gain_trauma(/datum/brain_trauma/severe/pacifism, TRAUMA_RESILIENCE_LOBOTOMY) + +/datum/surgery_operation/organ/pacify/on_failure(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You screw up, rewiring [organ.owner]'s brain the wrong way around..."), + span_warning("[surgeon] screws up, causing brain damage!"), + span_notice("[surgeon] completes the surgery on [organ.owner]'s brain."), + ) + display_pain(organ.owner, "Your head pounds, and it feels like it's getting worse!") + organ.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) + +/datum/surgery_operation/organ/pacify/mechanic + name = "delete aggression programming" + rnd_name = "Aggression Suppression Programming (Pacification)" + rnd_desc = "Install malware which permanently inhibits the aggression programming of the patient's neural network, making the patient unwilling to cause direct harm." + implements = list( + TOOL_MULTITOOL = 1, + TOOL_HEMOSTAT = 2.85, + TOOL_SCREWDRIVER = 2.85, + /obj/item/pen = 6.67, + ) + preop_sound = 'sound/items/taperecorder/tape_flip.ogg' + success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' + failure_sound = null + required_organ_flag = ORGAN_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC diff --git a/code/modules/surgery/operations/operation_plastic_surgery.dm b/code/modules/surgery/operations/operation_plastic_surgery.dm new file mode 100644 index 00000000000..c5ca9d364d5 --- /dev/null +++ b/code/modules/surgery/operations/operation_plastic_surgery.dm @@ -0,0 +1,139 @@ +#define OPERATION_NEW_NAME "chosen_name" + +/datum/surgery_operation/limb/plastic_surgery + name = "plastic surgery" + desc = "Reshape or reconstruct a patient's face for cosmetic or functional purposes." + implements = list( + TOOL_SCALPEL = 1, + /obj/item/knife = 2, + TOOL_WIRECUTTER = 2.85, + /obj/item/pen = 5, + ) + time = 6.4 SECONDS + operation_flags = OPERATION_MORBID | OPERATION_AFFECTS_MOOD | OPERATION_NOTABLE + preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' + success_sound = 'sound/items/handling/surgery/scalpel2.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN + +/datum/surgery_operation/limb/plastic_surgery/all_required_strings() + return list("operate on head (target head)") + ..() + +/datum/surgery_operation/limb/plastic_surgery/get_default_radial_image() + return image(/obj/item/scalpel) + +/datum/surgery_operation/limb/plastic_surgery/state_check(obj/item/bodypart/limb) + return limb.body_zone == BODY_ZONE_HEAD + +/datum/surgery_operation/limb/plastic_surgery/pre_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + if(HAS_TRAIT_FROM(limb.owner, TRAIT_DISFIGURED, TRAIT_GENERIC)) + return TRUE //skip name selection if fixing disfigurement + + var/list/names = list() + if(isabductor(surgeon)) + for(var/j in 1 to 9) + names += "Subject [limb.owner.gender == MALE ? "i" : "o"]-[pick("a", "b", "c", "d", "e")]-[rand(10000, 99999)]" + names += limb.owner.generate_random_mob_name(TRUE) //give one normal name in case they want to do regular plastic surgery + + else + var/advanced = LIMB_HAS_SURGERY_STATE(limb, SURGERY_PLASTIC_APPLIED) + var/obj/item/offhand = surgeon.get_inactive_held_item() + if(istype(offhand, /obj/item/photo) && advanced) + var/obj/item/photo/disguises = offhand + for(var/namelist in disguises.picture?.names_seen) + names += namelist + else + if(advanced) + to_chat(surgeon, span_warning("You have no picture to base the appearance on!")) + for(var/i in 1 to 10) + names += limb.owner.generate_random_mob_name(TRUE) + + operation_args[OPERATION_NEW_NAME] = tgui_input_list(surgeon, "New name to assign", "Plastic Surgery", names) + return !!operation_args[OPERATION_NEW_NAME] + +/datum/surgery_operation/limb/plastic_surgery/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to alter [limb.owner]'s appearance..."), + span_notice("[surgeon] begins to alter [limb.owner]'s appearance."), + span_notice("[surgeon] begins to make an incision in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a slicing pain across your face!") + +/datum/surgery_operation/limb/plastic_surgery/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + if(HAS_TRAIT_FROM(limb.owner, TRAIT_DISFIGURED, TRAIT_GENERIC)) + REMOVE_TRAIT(limb.owner, TRAIT_DISFIGURED, TRAIT_GENERIC) + display_results( + surgeon, + limb.owner, + span_notice("You successfully restore [limb.owner]'s appearance."), + span_notice("[surgeon] successfully restores [limb.owner]'s appearance!"), + span_notice("[surgeon] finishes the operation on [limb.owner]'s face."), + ) + display_pain(limb.owner, "The pain fades, your face feels normal again!") + return + + var/oldname = limb.owner.real_name + limb.owner.real_name = operation_args[OPERATION_NEW_NAME] + var/newname = limb.owner.real_name //something about how the code handles names required that I use this instead of target.real_name + display_results( + surgeon, + limb.owner, + span_notice("You alter [oldname]'s appearance completely, [limb.owner.p_they()] is now [newname]."), + span_notice("[surgeon] alters [oldname]'s appearance completely, [limb.owner.p_they()] is now [newname]!"), + span_notice("[surgeon] finishes the operation on [limb.owner]'s face."), + ) + display_pain(limb.owner, "The pain fades, your face feels new and unfamiliar!") + if(ishuman(limb.owner)) + var/mob/living/carbon/human/human_target = limb.owner + human_target.update_ID_card() + if(HAS_MIND_TRAIT(surgeon, TRAIT_MORBID)) + surgeon.add_mood_event("morbid_abominable_surgery_success", /datum/mood_event/morbid_abominable_surgery_success) + limb.remove_surgical_state(SURGERY_PLASTIC_APPLIED) + +/datum/surgery_operation/limb/plastic_surgery/on_failure(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_warning("Your screw up, leaving [limb.owner]'s appearance disfigured!"), + span_warning("[surgeon] screws up, disfiguring [limb.owner]'s appearance!"), + span_notice("[surgeon] finishes the operation on [limb.owner]'s face."), + ) + display_pain(limb.owner, "Your face feels horribly scarred and deformed!") + ADD_TRAIT(limb.owner, TRAIT_DISFIGURED, TRAIT_GENERIC) + +#undef OPERATION_NEW_NAME + +/datum/surgery_operation/limb/add_plastic + name = "apply plastic" + desc = "Apply plastic to a patient's face to to allow for greater customization in following plastic surgery." + implements = list( + /obj/item/stack/sheet/plastic = 1, + ) + time = 4.8 SECONDS + operation_flags = OPERATION_MORBID | OPERATION_LOCKED + preop_sound = 'sound/effects/blob/blobattack.ogg' + success_sound = 'sound/effects/blob/attackblob.ogg' + failure_sound = 'sound/effects/blob/blobattack.ogg' + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_PLASTIC_APPLIED + +/datum/surgery_operation/limb/add_plastic/get_default_radial_image() + return image(/obj/item/stack/sheet/plastic) + +/datum/surgery_operation/limb/add_plastic/state_check(obj/item/bodypart/limb) + return limb.body_zone == BODY_ZONE_HEAD + +/datum/surgery_operation/limb/add_plastic/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to apply plastic to [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to apply plastic to [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] begins to perform surgery on [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a strange sensation as something is applied to your face!") + +/datum/surgery_operation/limb/add_plastic/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + . = ..() + limb.add_surgical_state(SURGERY_PLASTIC_APPLIED) diff --git a/code/modules/surgery/operations/operation_pump.dm b/code/modules/surgery/operations/operation_pump.dm new file mode 100644 index 00000000000..d1e7778d30a --- /dev/null +++ b/code/modules/surgery/operations/operation_pump.dm @@ -0,0 +1,58 @@ +/datum/surgery_operation/organ/stomach_pump + name = "pump stomach" + rnd_name = "Gastric Lavage (Stomach Pump)" + desc = "Manually pump a patient's stomach to induce vomiting and expel harmful chemicals." + operation_flags = OPERATION_NOTABLE + implements = list( + IMPLEMENT_HAND = 1, + ) + time = 2 SECONDS + required_organ_flag = ORGAN_TYPE_FLAGS & ~ORGAN_ROBOTIC + target_type = /obj/item/organ/stomach + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT + +/datum/surgery_operation/organ/stomach_pump/get_default_radial_image() + return image(/atom/movable/screen/alert/disgusted::overlay_icon, /atom/movable/screen/alert/disgusted::overlay_state) + +/datum/surgery_operation/organ/stomach_pump/all_required_strings() + return ..() + list("the patient must not be husked") + +/datum/surgery_operation/organ/stomach_pump/state_check(obj/item/organ/stomach/organ) + return !HAS_TRAIT(organ.owner, TRAIT_HUSK) + +/datum/surgery_operation/organ/stomach_pump/on_preop(obj/item/organ/stomach/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to pump [organ.owner]'s stomach..."), + span_notice("[surgeon] begins to pump [organ.owner]'s stomach."), + span_notice("[surgeon] begins to press on [organ.owner]'s abdomen."), + ) + display_pain(organ.owner, "You feel a horrible sloshing feeling in your gut! You're going to be sick!") + +/datum/surgery_operation/organ/stomach_pump/on_success(obj/item/organ/stomach/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("[surgeon] forces [organ.owner] to vomit, cleansing their stomach of some chemicals!"), + span_notice("[surgeon] forces [organ.owner] to vomit, cleansing their stomach of some chemicals!"), + span_notice("[surgeon] forces [organ.owner] to vomit!"), + ) + organ.owner.vomit((MOB_VOMIT_MESSAGE | MOB_VOMIT_STUN), lost_nutrition = 20, purge_ratio = 0.67) + +/datum/surgery_operation/organ/stomach_pump/on_failure(obj/item/organ/stomach/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_warning("You screw up, bruising [organ.owner]'s chest!"), + span_warning("[surgeon] screws up, bruising [organ.owner]'s chest!"), + span_warning("[surgeon] screws up!"), + ) + organ.apply_organ_damage(5) + organ.bodypart_owner.receive_damage(5) + +/datum/surgery_operation/organ/stomach_pump/mechanic + name = "purge nutrient processor" + rnd_name = "Nutrient Processor Purge (Stomach Pump)" + required_organ_flag = ORGAN_ROBOTIC + operation_flags = parent_type::operation_flags | OPERATION_MECHANIC diff --git a/code/modules/surgery/operations/operation_puncture.dm b/code/modules/surgery/operations/operation_puncture.dm new file mode 100644 index 00000000000..eebf3bba0c6 --- /dev/null +++ b/code/modules/surgery/operations/operation_puncture.dm @@ -0,0 +1,138 @@ +/datum/surgery_operation/limb/repair_puncture + name = "realign blood vessels" + desc = "Realign a patient's torn blood vessels to prepare for sealing." + implements = list( + TOOL_HEMOSTAT = 1, + TOOL_SCALPEL = 1.15, + TOOL_WIRECUTTER = 2.5, + ) + time = 3 SECONDS + preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_PRIORITY_NEXT_STEP + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT + +/datum/surgery_operation/limb/repair_puncture/get_default_radial_image() + return image(/obj/item/hemostat) + +/datum/surgery_operation/limb/repair_puncture/all_required_strings() + return list("the limb must have an unoperated puncture wound") + ..() + +/datum/surgery_operation/limb/repair_puncture/state_check(obj/item/bodypart/limb) + var/datum/wound/pierce/bleed/pierce_wound = locate() in limb.wounds + if(isnull(pierce_wound) || pierce_wound.blood_flow <= 0 || pierce_wound.mend_state) + return FALSE + return TRUE + +/datum/surgery_operation/limb/repair_puncture/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to realign the torn blood vessels in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to realign the torn blood vessels in [limb.owner]'s [limb.plaintext_zone] with [tool]."), + span_notice("[surgeon] begins to realign the torn blood vessels in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a horrible stabbing pain in your [limb.plaintext_zone]!") + +/datum/surgery_operation/limb/repair_puncture/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + var/datum/wound/pierce/bleed/pierce_wound = locate() in limb.wounds + pierce_wound?.adjust_blood_flow(-0.25) + limb.receive_damage(3, wound_bonus = CANT_WOUND, sharpness = tool.get_sharpness(), damage_source = tool) + + if(QDELETED(pierce_wound)) + display_results( + surgeon, + limb.owner, + span_notice("You successfully realign the last of the torn blood vessels in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully realigns the last of the torn blood vessels in [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully realigns the last of the torn blood vessels in [limb.owner]'s [limb.plaintext_zone]!"), + ) + return + + pierce_wound?.mend_state = TRUE + display_results( + surgeon, + limb.owner, + span_notice("You successfully realign some of the blood vessels in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully realigns some of the blood vessels in [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully realigns some of the blood vessels in [limb.owner]'s [limb.plaintext_zone]!"), + ) + +/datum/surgery_operation/limb/repair_puncture/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You jerk apart some of the blood vessels in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] jerks apart some of the blood vessels in [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] jerks apart some of the blood vessels in [limb.owner]'s [limb.plaintext_zone]."), + ) + limb.receive_damage(rand(4, 8), wound_bonus = 10, sharpness = SHARP_EDGED, damage_source = tool) + +/datum/surgery_operation/limb/seal_veins + name = "seal blood vessels" + // rnd_name = "Anastomosis (Seal Blood Vessels)" // doctor says this is the term to use but it fits awkwardly + desc = "Seal a patient's now-realigned blood vessels." + implements = list( + TOOL_CAUTERY = 1, + /obj/item/gun/energy/laser = 1.12, + TOOL_WELDER = 1.5, + /obj/item = 3.33, + ) + time = 3.2 SECONDS + preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_PRIORITY_NEXT_STEP + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT + +/datum/surgery_operation/limb/seal_veins/get_default_radial_image() + return image(/obj/item/cautery) + +/datum/surgery_operation/limb/seal_veins/get_any_tool() + return "Any heat source" + +/datum/surgery_operation/limb/seal_veins/all_required_strings() + return list("the limb must have an operated puncture wound") + ..() + +/datum/surgery_operation/limb/seal_veins/tool_check(obj/item/tool) + if(istype(tool, /obj/item/gun/energy/laser)) + var/obj/item/gun/energy/laser/lasergun = tool + return lasergun.cell?.charge > 0 + + return tool.get_temperature() > 0 + +/datum/surgery_operation/limb/seal_veins/state_check(obj/item/bodypart/limb) + var/datum/wound/pierce/bleed/pierce_wound = locate() in limb.wounds + if(isnull(pierce_wound) || pierce_wound.blood_flow <= 0 || !pierce_wound.mend_state) + return FALSE + return TRUE + +/datum/surgery_operation/limb/seal_veins/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to seal the realigned blood vessels in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to seal the realigned blood vessels in [limb.owner]'s [limb.plaintext_zone] with [tool]."), + span_notice("[surgeon] begins to seal the realigned blood vessels in [limb.owner]'s [limb.plaintext_zone]."), + ) + display_pain(limb.owner, "You feel a burning sensation in your [limb.plaintext_zone]!") + +/datum/surgery_operation/limb/seal_veins/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + var/datum/wound/pierce/bleed/pierce_wound = locate() in limb.wounds + pierce_wound?.adjust_blood_flow(-0.5) + + if(QDELETED(pierce_wound)) + display_results( + surgeon, + limb.owner, + span_notice("You successfully seal the last of the ruptured blood vessels in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully seals the last of the ruptured blood vessels in [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully seals the last of the ruptured blood vessels in [limb.owner]'s [limb.plaintext_zone]!"), + ) + return + + pierce_wound?.mend_state = FALSE + display_results( + surgeon, + limb.owner, + span_notice("You successfully seal some of the blood vessels in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully seals some of the blood vessels in [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully seals some of the blood vessels in [limb.owner]'s [limb.plaintext_zone]!"), + ) diff --git a/code/modules/surgery/operations/operation_replace_limb.dm b/code/modules/surgery/operations/operation_replace_limb.dm new file mode 100644 index 00000000000..47f15cf518e --- /dev/null +++ b/code/modules/surgery/operations/operation_replace_limb.dm @@ -0,0 +1,91 @@ +/datum/surgery_operation/limb/replace_limb + name = "augment limb" + rnd_name = "Augmentation" + desc = "Replace a patient's limb with a robotic or prosthetic one." + operation_flags = OPERATION_NOTABLE + implements = list( + /obj/item/bodypart = 1, + ) + time = 3.2 SECONDS + all_surgery_states_required = SURGERY_SKIN_OPEN + /// Radial slice datums for every augment type + VAR_PRIVATE/list/cached_augment_options + +/datum/surgery_operation/limb/replace_limb/get_recommended_tool() + return "cybernetic limb" + +/datum/surgery_operation/limb/replace_limb/get_default_radial_image() + return image(/obj/item/bodypart/chest/robot) + +/datum/surgery_operation/limb/replace_limb/get_radial_options(obj/item/bodypart/limb, obj/item/tool, operating_zone) + var/datum/radial_menu_choice/option = LAZYACCESS(cached_augment_options, tool.type) + if(!option) + option = new() + option.name = "augment with [initial(tool.name)]" + option.info = "Replace the patient's [initial(limb.name)] with [initial(tool.name)]." + option.image = image(tool.type) + LAZYSET(cached_augment_options, tool.type, option) + + return option + +/datum/surgery_operation/limb/replace_limb/snowflake_check_availability(obj/item/bodypart/limb, mob/living/surgeon, obj/item/bodypart/tool, operated_zone) + if(!surgeon.canUnEquip(tool)) + return FALSE + if(limb.body_zone != tool.body_zone) + return FALSE + if(!tool.can_attach_limb(limb.owner)) + return FALSE + return TRUE + +/datum/surgery_operation/limb/replace_limb/state_check(obj/item/bodypart/limb) + return !HAS_TRAIT(limb.owner, TRAIT_NO_AUGMENTS) && !(limb.bodypart_flags & BODYPART_UNREMOVABLE) + +/datum/surgery_operation/limb/replace_limb/tool_check(obj/item/bodypart/tool) + if(tool.item_flags & (ABSTRACT|DROPDEL|HAND_ITEM)) + return FALSE + if(!isbodypart(tool)) + return FALSE + var/obj/item/bodypart/limb = tool + if(!IS_ROBOTIC_LIMB(limb)) + return FALSE + return TRUE + +/datum/surgery_operation/limb/replace_limb/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/bodypart/tool, list/operation_args) + // purposefully doesn't use plaintext zone for more context on what is being replaced with what + display_results( + surgeon, + limb.owner, + span_notice("You begin to augment [limb.owner]'s [limb.name] with [tool]..."), + span_notice("[surgeon] begins to augment [limb.owner]'s [limb.name] with [tool]."), + span_notice("[surgeon] begins to augment [limb.owner]'s [limb.name]."), + ) + display_pain(limb.owner, "You feel a horrible pain in your [limb.plaintext_zone]!") + +/datum/surgery_operation/limb/replace_limb/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/bodypart/tool, list/operation_args) + if(!surgeon.temporarilyRemoveItemFromInventory(tool)) + return // should never happen + + var/mob/living/patient = limb.owner // owner's about to be nulled + if(!tool.replace_limb(patient)) + display_results( + surgeon, + patient, + span_warning("You can't seem to fit [tool] onto [patient]'s body!"), + span_warning("[surgeon] can't seem to fit [tool] onto [patient]'s body!"), + span_warning("[surgeon] can't seem to fit [tool] onto [patient]'s body!"), + ) + tool.forceMove(patient.drop_location()) + return // could possibly happen + + if(tool.check_for_frankenstein(patient)) + tool.bodypart_flags |= BODYPART_IMPLANTED + + display_results( + surgeon, + patient, + span_notice("You successfully augment [patient]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully augments [patient]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] finishes augmenting [patient]'s [limb.plaintext_zone]."), + ) + display_pain(patient, "Your [limb.plaintext_zone] comes awash with synthetic sensation!", TRUE) + log_combat(surgeon, patient, "augmented", addition = "by giving him new [tool]") diff --git a/code/modules/surgery/operations/operation_revival.dm b/code/modules/surgery/operations/operation_revival.dm new file mode 100644 index 00000000000..ea6808d8e44 --- /dev/null +++ b/code/modules/surgery/operations/operation_revival.dm @@ -0,0 +1,119 @@ +/datum/surgery_operation/basic/revival + name = "shock brain" + rnd_name = "Brain Defibrillation (Revival)" + desc = "Use a defibrillator to shock a patient's brain back to life." + implements = list( + /obj/item/shockpaddles = 1, + /obj/item/melee/touch_attack/shock = 1, + /obj/item/melee/baton/security = 1.33, + /obj/item/gun/energy = 1.67, + ) + operation_flags = OPERATION_MORBID | OPERATION_NOTABLE + time = 5 SECONDS + preop_sound = list( + /obj/item/shockpaddles = 'sound/machines/defib/defib_charge.ogg', + /obj/item = null, + ) + success_sound = 'sound/machines/defib/defib_zap.ogg' + required_biotype = NONE + target_zone = BODY_ZONE_HEAD + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED + +/datum/surgery_operation/basic/revival/get_default_radial_image() + return image(/obj/item/shockpaddles) + +/datum/surgery_operation/basic/revival/all_required_strings() + return ..() + list("the patient must be deceased", "the patient must be in a revivable state") + +/datum/surgery_operation/basic/revival/state_check(mob/living/patient) + if(patient.stat != DEAD) + return FALSE + if(HAS_TRAIT(patient, TRAIT_SUICIDED) || HAS_TRAIT(patient, TRAIT_HUSK) || HAS_TRAIT(patient, TRAIT_DEFIB_BLACKLISTED)) + return FALSE + if(patient.has_limbs) + var/obj/item/organ/brain/brain = patient.get_organ_slot(ORGAN_SLOT_BRAIN) + return !isnull(brain) && brain_check(brain) + return mob_check(patient) + +/datum/surgery_operation/basic/revival/proc/brain_check(obj/item/organ/brain/brain) + return !IS_ROBOTIC_ORGAN(brain) + +/datum/surgery_operation/basic/revival/proc/mob_check(mob/living/patient) + return !(patient.mob_biotypes & MOB_ROBOTIC) + +/datum/surgery_operation/basic/revival/tool_check(obj/item/tool) + if(istype(tool, /obj/item/shockpaddles)) + var/obj/item/shockpaddles/paddles = tool + if((paddles.req_defib && !paddles.defib.powered) || !HAS_TRAIT(paddles, TRAIT_WIELDED) || paddles.cooldown || paddles.busy) + return FALSE + + if(istype(tool, /obj/item/melee/baton/security)) + var/obj/item/melee/baton/security/baton = tool + return baton.active + + if(istype(tool, /obj/item/gun/energy)) + var/obj/item/gun/energy/egun = tool + return istype(egun.chambered, /obj/item/ammo_casing/energy/electrode) + + return TRUE + +/datum/surgery_operation/basic/revival/on_preop(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + patient, + span_notice("You prepare to give [patient]'s brain the spark of life with [tool]."), + span_notice("[surgeon] prepares to give [patient]'s brain the spark of life with [tool]."), + span_notice("[surgeon] prepares to give [patient]'s brain the spark of life."), + ) + patient.notify_revival("Someone is trying to zap your brain.", source = patient) + +/datum/surgery_operation/basic/revival/on_success(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + patient, + span_notice("You successfully shock [patient]'s brain with [tool]..."), + span_notice("[surgeon] sends a powerful shock to [patient]'s brain with [tool]..."), + span_notice("[surgeon] sends a powerful shock to [patient]'s brain..."), + ) + patient.grab_ghost() + patient.adjust_oxy_loss(-50) + if(iscarbon(patient)) + var/mob/living/carbon/carbon_patient = patient + carbon_patient.set_heartattack(FALSE) + if(!patient.revive()) + on_no_revive(surgeon, patient) + return + + on_revived(surgeon, patient) + +/// Called when you have been successfully raised from the dead +/datum/surgery_operation/basic/revival/proc/on_revived(mob/living/surgeon, mob/living/patient) + patient.visible_message(span_notice("...[patient] wakes up, alive and aware!")) + patient.emote("gasp") + if(HAS_MIND_TRAIT(surgeon, TRAIT_MORBID)) // Contrary to their typical hatred of resurrection, it wouldn't be very thematic if morbid people didn't love playing god + surgeon.add_mood_event("morbid_revival_success", /datum/mood_event/morbid_revival_success) + patient.adjust_organ_loss(ORGAN_SLOT_BRAIN, 15, 180) + +/// Called when revival fails +/datum/surgery_operation/basic/revival/proc/on_no_revive(mob/living/surgeon, mob/living/patient) + patient.visible_message(span_warning("...[patient.p_they()] convulse[patient.p_s()], then lie[patient.p_s()] still.")) + patient.adjust_organ_loss(ORGAN_SLOT_BRAIN, 50, 199) // MAD SCIENCE + +/datum/surgery_operation/basic/revival/on_failure(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + patient, + span_warning("You shock [patient]'s brain with [tool], but [patient.p_they()] don't react."), + span_warning("[surgeon] shocks [patient]'s brain with [tool], but [patient.p_they()] don't react."), + span_warning("[surgeon] shocks [patient]'s brain with [tool], but [patient.p_they()] don't react."), + ) + +/datum/surgery_operation/basic/revival/mechanic + name = "full system reboot" + required_biotype = MOB_ROBOTIC + +/datum/surgery_operation/basic/revival/mechanic/brain_check(obj/item/organ/brain/brain) + return !..() + +/datum/surgery_operation/basic/revival/mechanic/mob_check(mob/living/patient) + return !..() diff --git a/code/modules/surgery/operations/operation_virus.dm b/code/modules/surgery/operations/operation_virus.dm new file mode 100644 index 00000000000..6edbc23ff4b --- /dev/null +++ b/code/modules/surgery/operations/operation_virus.dm @@ -0,0 +1,69 @@ +/datum/surgery_operation/basic/viral_bonding + name = "viral bonding" + rnd_name = "Viroplasty (Viral Bonding)" + desc = "Force a symbiotic relationship between a patient and a virus it is infected with." + rnd_desc = "A surgical procedure that forces a symbiotic relationship between a virus and its host. \ + The patient will be completely immune to the effects of the virus, but will carry and spread it to others." + implements = list( + TOOL_CAUTERY = 1, + TOOL_WELDER = 2, + /obj/item = 3.33, + ) + time = 10 SECONDS + preop_sound = 'sound/items/handling/surgery/cautery1.ogg' + success_sound = 'sound/items/handling/surgery/cautery2.ogg' + operation_flags = OPERATION_MORBID | OPERATION_LOCKED | OPERATION_NOTABLE + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT + var/list/required_chems = list( + /datum/reagent/medicine/spaceacillin, + /datum/reagent/consumable/virus_food, + /datum/reagent/toxin/formaldehyde, + ) + +/datum/surgery_operation/basic/viral_bonding/get_any_tool() + return "Any heat source" + +/datum/surgery_operation/basic/viral_bonding/all_required_strings() + . = ..() + . += "the patient must have a virus to bond" + for(var/datum/reagent/chem as anything in required_chems) + . += "the patient must be dosed with >1u [chem::name]" + +/datum/surgery_operation/basic/viral_bonding/get_default_radial_image() + return image(/obj/item/clothing/mask/surgical) + +/datum/surgery_operation/basic/viral_bonding/state_check(mob/living/patient) + for(var/chem in required_chems) + if(patient.reagents?.get_reagent_amount(chem) < 1) + return FALSE + for(var/datum/disease/infected_disease as anything in patient.diseases) + if(infected_disease.severity != DISEASE_SEVERITY_UNCURABLE) + return TRUE + return FALSE + +/datum/surgery_operation/basic/viral_bonding/tool_check(obj/item/tool) + return tool.get_temperature() > 0 + +/datum/surgery_operation/basic/viral_bonding/on_preop(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + patient, + span_notice("You start heating [patient]'s bone marrow with [tool]..."), + span_notice("[surgeon] starts heating [patient]'s bone marrow with [tool]..."), + span_notice("[surgeon] starts heating something in [patient]'s chest with [tool]..."), + ) + display_pain(patient, "You feel a searing heat spread through your chest!") + +/datum/surgery_operation/basic/viral_bonding/on_success(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + patient, + span_notice("[patient]'s bone marrow begins pulsing slowly. The viral bonding is complete."), + span_notice("[patient]'s bone marrow begins pulsing slowly."), + span_notice("[surgeon] finishes the operation."), + ) + display_pain(patient, "You feel a faint throbbing in your chest.") + for(var/datum/disease/infected_disease as anything in patient.diseases) + if(infected_disease.severity != DISEASE_SEVERITY_UNCURABLE) //no curing quirks, sweaty + infected_disease.carrier = TRUE + return TRUE diff --git a/code/modules/surgery/operations/operation_wing_repair.dm b/code/modules/surgery/operations/operation_wing_repair.dm new file mode 100644 index 00000000000..6429bf17052 --- /dev/null +++ b/code/modules/surgery/operations/operation_wing_repair.dm @@ -0,0 +1,61 @@ +/datum/surgery_operation/organ/fix_wings + name = "repair wings" + rnd_name = "Pteroplasty (Wing Repair)" + desc = "Repair a patient's damaged wings to restore flight capability." + rnd_desc = "A surgical procedure that repairs damaged wings using Synthflesh." + implements = list( + TOOL_HEMOSTAT = 1.15, + TOOL_SCREWDRIVER = 2.85, + /obj/item/pen = 6.67, + ) + operation_flags = OPERATION_LOCKED | OPERATION_NOTABLE + time = 20 SECONDS + target_type = /obj/item/organ/wings/moth + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + +/datum/surgery_operation/organ/fix_wings/get_default_radial_image() + return image(icon = 'icons/mob/human/species/moth/moth_wings.dmi', icon_state = "m_moth_wings_monarch_BEHIND") + +/datum/surgery_operation/organ/fix_wings/all_required_strings() + return ..() + list("the wings must be burnt", "the patient must be dosed with >5u [/datum/reagent/medicine/c2/synthflesh::name]") + +/datum/surgery_operation/organ/fix_wings/all_blocked_strings() + return ..() + list("if the limb has bones, they must be intact") + +/datum/surgery_operation/organ/fix_wings/state_check(obj/item/organ/wings/moth/organ) + if(!organ.burnt) + return FALSE + // If bones are sawed, prevent the operation (unless we're operating on a limb with no bones) + if(!LIMB_HAS_ANY_SURGERY_STATE(organ.bodypart_owner, SURGERY_BONE_DRILLED|SURGERY_BONE_SAWED) && LIMB_HAS_BONES(organ.bodypart_owner)) + return FALSE + if(organ.owner.reagents?.get_reagent_amount(/datum/reagent/medicine/c2/synthflesh) < 5) + return FALSE + return TRUE + +/datum/surgery_operation/organ/fix_wings/on_preop(obj/item/organ/wings/moth/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You begin to repair [organ.owner]'s damaged wings..."), + span_notice("[surgeon] begins to repair [organ.owner]'s damaged wings."), + span_notice("[surgeon] begins to perform surgery on [organ.owner]'s damaged wings."), + ) + display_pain(organ.owner, "Your wings sting like hell!") + +/datum/surgery_operation/organ/fix_wings/on_success(obj/item/organ/wings/moth/organ, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + organ.owner, + span_notice("You succeed in repairing [organ.owner]'s wings."), + span_notice("[surgeon] successfully repairs [organ.owner]'s wings!"), + span_notice("[surgeon] completes the surgery on [organ.owner]'s wings."), + ) + display_pain(organ.owner, "You can feel your wings again!") + // heal the wings in question + organ.heal_wings(surgeon, ALL) + + // might as well heal their antennae too + var/obj/item/organ/antennae/antennae = organ.owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_ANTENNAE) + antennae?.heal_antennae(surgeon, ALL) + + organ.owner.update_body_parts() diff --git a/code/modules/surgery/operations/operation_zombie.dm b/code/modules/surgery/operations/operation_zombie.dm new file mode 100644 index 00000000000..8e81cd6491c --- /dev/null +++ b/code/modules/surgery/operations/operation_zombie.dm @@ -0,0 +1,74 @@ +/datum/surgery_operation/limb/bionecrosis + name = "induce bionecrosis" + rnd_name = "Bionecroplasty (Necrotic Revival)" + desc = "Inject reagents that stimulate the growth of a Romerol tumor inside the patient's brain." + rnd_desc = "An experimental procedure which induces the growth of a Romerol tumor inside the patient's brain." + implements = list( + /obj/item/reagent_containers/syringe = 1, + /obj/item/pen = 3.33, + ) + time = 5 SECONDS + operation_flags = OPERATION_MORBID | OPERATION_LOCKED | OPERATION_NOTABLE + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_SAWED + var/list/zombie_chems = list( + /datum/reagent/medicine/rezadone, + /datum/reagent/toxin/zombiepowder, + ) + +/datum/surgery_operation/limb/bionecrosis/get_default_radial_image() + return image(get_dynamic_human_appearance(species_path = /datum/species/zombie)) + +/datum/surgery_operation/limb/bionecrosis/all_required_strings() + . = ..() + . += "the limb must have a brain present" + +/datum/surgery_operation/limb/bionecrosis/any_required_strings() + . = ..() + for(var/datum/reagent/chem as anything in zombie_chems) + . += "the patient or tool must contain >1u [chem::name]" + +/datum/surgery_operation/limb/bionecrosis/all_blocked_strings() + . = ..() + . += "the limb must not already have a Romerol tumor" + +/datum/surgery_operation/limb/bionecrosis/state_check(obj/item/bodypart/limb) + if(locate(/obj/item/organ/zombie_infection) in limb) + return FALSE + if(!(locate(/obj/item/organ/brain) in limb)) + return FALSE + return TRUE + +/datum/surgery_operation/limb/bionecrosis/snowflake_check_availability(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, operated_zone) + for(var/chem in zombie_chems) + if(tool.reagents?.get_reagent_amount(chem) > 1) + return TRUE + if(limb.owner.reagents?.get_reagent_amount(chem) > 1) + return TRUE + return FALSE + +/datum/surgery_operation/limb/bionecrosis/on_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin to grow a romerol tumor in [limb.owner]'s brain..."), + span_notice("[surgeon] begins to tinker with [limb.owner]'s brain..."), + span_notice("[surgeon] begins to perform surgery on [limb.owner]'s brain."), + ) + display_pain(limb.owner, "Your head pounds with unimaginable pain!") // Same message as other brain surgeries + +/datum/surgery_operation/limb/bionecrosis/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You succeed in growing a romerol tumor in [limb.owner]'s brain."), + span_notice("[surgeon] successfully grows a romerol tumor in [limb.owner]'s brain!"), + span_notice("[surgeon] completes the surgery on [limb.owner]'s brain."), + ) + display_pain(limb.owner, "Your head goes totally numb for a moment, the pain is overwhelming!") + if(locate(/obj/item/organ/zombie_infection) in limb) // they got another one mid surgery? whatever + return + var/obj/item/organ/zombie_infection/z_infection = new() + z_infection.Insert(limb.owner) + for(var/chem in zombie_chems) + tool.reagents?.remove_reagent(chem, 1) + limb.owner.reagents?.remove_reagent(chem, 1) diff --git a/code/modules/surgery/organ_manipulation.dm b/code/modules/surgery/organ_manipulation.dm deleted file mode 100644 index 24504132d4a..00000000000 --- a/code/modules/surgery/organ_manipulation.dm +++ /dev/null @@ -1,353 +0,0 @@ -/datum/surgery/organ_manipulation - name = "Organ manipulation" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB | SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/manipulate_organs/internal, - /datum/surgery_step/close, - ) - -//So far, this surgery type should be the only way carbon mobs can be fishing spots, also because the comp doesn't allow dupes. -/datum/surgery/organ_manipulation/next_step(mob/living/user, modifiers) - . = ..() - if(!.) - return - - // We've completed the surgery - if(status > length(steps)) - return - - if(!ispath(steps[status], /datum/surgery_step/manipulate_organs)) - //The manipulate_organs step either hasn't been reached yet or we're already past it. - if(!HAS_TRAIT(target, TRAIT_FISHING_SPOT)) - return - remove_fishing_spot() - return - - if(HAS_TRAIT(target, TRAIT_FISHING_SPOT)) - return - - target.AddComponent(/datum/component/fishing_spot, /datum/fish_source/surgery) - -/datum/surgery/organ_manipulation/Destroy() - if(QDELETED(target) || !HAS_TRAIT(target, TRAIT_FISHING_SPOT)) - return ..() - // The surgery is not finished yet and we're currently on manipulate organs step - if(status <= length(steps) && ispath(steps[status], /datum/surgery_step/manipulate_organs)) - remove_fishing_spot() - return ..() - -/** - * The target is a fishing spot, but we're past the step that allows us to fish organs from him, so we need - * to check if there are other organ manipulation surgeries that still meet this criteria before we remove - * the component - */ -/datum/surgery/organ_manipulation/proc/remove_fishing_spot() - for(var/datum/surgery/organ_manipulation/manipulation in target.surgeries) - if(manipulation != src && ispath(manipulation.steps[manipulation.status], /datum/surgery_step/manipulate_organs)) - return - qdel(target.GetComponent(/datum/component/fishing_spot)) - -/datum/surgery/organ_manipulation/soft - possible_locs = list(BODY_ZONE_PRECISE_GROIN, BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/manipulate_organs/internal, - /datum/surgery_step/close, - ) - -/datum/surgery/organ_manipulation/external - name = "Feature manipulation" - possible_locs = list( - BODY_ZONE_CHEST, - BODY_ZONE_HEAD, - BODY_ZONE_PRECISE_GROIN, - BODY_ZONE_L_ARM, - BODY_ZONE_R_ARM, - BODY_ZONE_L_LEG, - BODY_ZONE_R_LEG, - ) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/manipulate_organs/external, - /datum/surgery_step/close, - ) - -/datum/surgery/organ_manipulation/alien - name = "Alien organ manipulation" - possible_locs = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_PRECISE_GROIN, BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) - target_mobtypes = list(/mob/living/carbon/alien/adult) - steps = list( - /datum/surgery_step/saw, - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/manipulate_organs/any, - /datum/surgery_step/close, - ) - -/datum/surgery/organ_manipulation/mechanic - name = "Hardware Manipulation" - requires_bodypart_type = BODYTYPE_ROBOTIC - surgery_flags = SURGERY_SELF_OPERABLE | SURGERY_REQUIRE_LIMB | SURGERY_CHECK_TOOL_BEHAVIOUR - possible_locs = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/manipulate_organs/internal/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/organ_manipulation/mechanic/soft - possible_locs = list( - BODY_ZONE_PRECISE_GROIN, - BODY_ZONE_PRECISE_EYES, - BODY_ZONE_PRECISE_MOUTH, - BODY_ZONE_L_ARM, - BODY_ZONE_R_ARM, - ) - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/manipulate_organs/internal/mechanic, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/organ_manipulation/mechanic/external - name = "Chassis Manipulation" - possible_locs = list( - BODY_ZONE_CHEST, - BODY_ZONE_HEAD, - BODY_ZONE_PRECISE_GROIN, - BODY_ZONE_L_ARM, - BODY_ZONE_R_ARM, - BODY_ZONE_L_LEG, - BODY_ZONE_R_LEG, - ) - steps = list( //not shorter than soft prosthetic manip because I dunno what steps could be cut here - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/manipulate_organs/external/mechanic, - /datum/surgery_step/mechanic_close, - ) - -///Organ manipulation base class. Do not use, it wont work. Use its subtypes -/datum/surgery_step/manipulate_organs - name = "manipulate organs" - repeatable = TRUE - implements = list( - /obj/item/organ = 100, - /obj/item/borg/apparatus/organ_storage = 100) - preop_sound = 'sound/items/handling/surgery/organ2.ogg' - success_sound = 'sound/items/handling/surgery/organ1.ogg' - - var/implements_extract = list(TOOL_HEMOSTAT = 100, TOOL_CROWBAR = 55, /obj/item/kitchen/fork = 35) - var/current_type - var/obj/item/organ/target_organ - -/datum/surgery_step/manipulate_organs/New() - ..() - implements = implements + implements_extract - -/datum/surgery_step/manipulate_organs/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - target_organ = null - if(istype(tool, /obj/item/borg/apparatus/organ_storage)) - preop_sound = initial(preop_sound) - success_sound = initial(success_sound) - if(!length(tool.contents)) - to_chat(user, span_warning("There is nothing inside [tool]!")) - return SURGERY_STEP_FAIL - target_organ = tool.contents[1] - if(!isorgan(target_organ)) - if (target_zone == BODY_ZONE_PRECISE_EYES) - target_zone = check_zone(target_zone) - to_chat(user, span_warning("You cannot put [target_organ] into [target]'s [target.parse_zone_with_bodypart(target_zone)]!")) - return SURGERY_STEP_FAIL - tool = target_organ - if(isorgan(tool)) - current_type = "insert" - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - success_sound = 'sound/items/handling/surgery/organ2.ogg' - target_organ = tool - if(!target_organ.pre_surgical_insertion(user, target, target_zone, tool)) - return SURGERY_STEP_FAIL - - if(target_zone != target_organ.zone || target.get_organ_slot(target_organ.slot)) - to_chat(user, span_warning("There is no room for [target_organ] in [target]'s [target.parse_zone_with_bodypart(target_zone)]!")) - return SURGERY_STEP_FAIL - - var/obj/item/organ/meatslab = tool - if(!meatslab.useable) - to_chat(user, span_warning("[target_organ] seems to have been chewed on, you can't use this!")) - return SURGERY_STEP_FAIL - - if(!can_use_organ(meatslab)) - return SURGERY_STEP_FAIL - - if (target_zone == BODY_ZONE_PRECISE_EYES) - target_zone = check_zone(target_zone) - display_results( - user, - target, - span_notice("You begin to insert [tool] into [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to insert [tool] into [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to insert something into [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You can feel something being placed in your [target.parse_zone_with_bodypart(target_zone)]!") - - - else if(implement_type in implements_extract) - current_type = "extract" - var/list/unfiltered_organs = target.get_organs_for_zone(target_zone) - var/list/organs = list() - for(var/organ in unfiltered_organs) - if(can_use_organ(organ)) - organs.Add(organ) - if (target_zone == BODY_ZONE_PRECISE_EYES) - target_zone = check_zone(target_zone) - if(!length(organs)) - to_chat(user, span_warning("There are no removable organs in [target]'s [target.parse_zone_with_bodypart(target_zone)]!")) - return SURGERY_STEP_FAIL - else - for(var/obj/item/organ/organ in organs) - organ.on_find(user) - organs -= organ - organs[organ.name] = organ - - var/chosen_organ = tgui_input_list(user, "Remove which organ?", "Surgery", sort_list(organs)) - if(isnull(chosen_organ)) - return SURGERY_STEP_FAIL - target_organ = chosen_organ - - if(user && target && user.Adjacent(target)) - //tool check - var/obj/item/held_tool = user.get_active_held_item() - if(held_tool) - held_tool = held_tool.get_proxy_attacker_for(target, user) - if(held_tool != tool) - return SURGERY_STEP_FAIL - - //organ check - target_organ = organs[target_organ] - if(!target_organ) - return SURGERY_STEP_FAIL - if(target_organ.organ_flags & ORGAN_UNREMOVABLE) - to_chat(user, span_warning("[target_organ] is too well connected to take out!")) - return SURGERY_STEP_FAIL - - //start operation - display_results( - user, - target, - span_notice("You begin to extract [target_organ] from [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to extract [target_organ] from [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to extract something from [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You can feel your [target_organ.name] being removed from your [target.parse_zone_with_bodypart(target_zone)]!") - else - return SURGERY_STEP_FAIL - -/datum/surgery_step/manipulate_organs/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if (target_zone == BODY_ZONE_PRECISE_EYES) - target_zone = check_zone(target_zone) - if(current_type == "insert") - var/obj/item/apparatus - if(istype(tool, /obj/item/borg/apparatus/organ_storage)) - apparatus = tool - tool = tool.contents[1] - target_organ = tool - user.temporarilyRemoveItemFromInventory(target_organ, TRUE) - target_organ.Insert(target) - if(apparatus) - apparatus.icon_state = initial(apparatus.icon_state) - apparatus.desc = initial(apparatus.desc) - apparatus.cut_overlays() - display_results( - user, - target, - span_notice("You insert [tool] into [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] inserts [tool] into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - span_notice("[user] inserts something into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - display_pain(target, "Your [target.parse_zone_with_bodypart(target_zone)] throbs with pain as your new [tool.name] comes to life!") - target_organ.on_surgical_insertion(user, target, target_zone, tool) - - else if(current_type == "extract") - if(target_organ && target_organ.owner == target) - display_results( - user, - target, - span_notice("You successfully extract [target_organ] from [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] successfully extracts [target_organ] from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - span_notice("[user] successfully extracts something from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - display_pain(target, "Your [target.parse_zone_with_bodypart(target_zone)] throbs with pain, you can't feel your [target_organ.name] anymore!") - log_combat(user, target, "surgically removed [target_organ.name] from", addition="COMBAT MODE: [uppertext(user.combat_mode)]") - target_organ.Remove(target) - target_organ.forceMove(get_turf(target)) - target_organ.on_surgical_removal(user, target, target_zone, tool) - else - display_results( - user, - target, - span_warning("You can't extract anything from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - span_notice("[user] can't seem to extract anything from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - span_notice("[user] can't seem to extract anything from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - if(HAS_MIND_TRAIT(user, TRAIT_MORBID) && ishuman(user)) - var/mob/living/carbon/human/morbid_weirdo = user - morbid_weirdo.add_mood_event("morbid_abominable_surgery_success", /datum/mood_event/morbid_abominable_surgery_success) - return ..() - -///You can never use this MUHAHAHAHAHAHAH (because its the byond version of abstract) -/datum/surgery_step/manipulate_organs/proc/can_use_organ(obj/item/organ/organ) - return FALSE - -/datum/surgery_step/manipulate_organs/any - time = 6.4 SECONDS - name = "manipulate organs (hemostat/organ)" - -/datum/surgery_step/manipulate_organs/any/can_use_organ(obj/item/organ/organ) - return TRUE - -///Surgery step for internal organs, like hearts and brains -/datum/surgery_step/manipulate_organs/internal - time = 6.4 SECONDS - name = "manipulate organs (hemostat/organ)" - -///only operate on internal organs -/datum/surgery_step/manipulate_organs/internal/can_use_organ(obj/item/organ/organ) - return !(organ.organ_flags & ORGAN_EXTERNAL) - -///prosthetic surgery gives full effectiveness to crowbars (and hemostats) -/datum/surgery_step/manipulate_organs/internal/mechanic - implements_extract = list(TOOL_HEMOSTAT = 100, TOOL_CROWBAR = 100, /obj/item/kitchen/fork = 35) - name = "manipulate prosthetic organs (hemostat or crowbar/organ)" - -///Surgery step for external organs/features, like tails, frills, wings etc -/datum/surgery_step/manipulate_organs/external - time = 3.2 SECONDS - name = "manipulate features (hemostat/feature)" - -///Only operate on external organs -/datum/surgery_step/manipulate_organs/external/can_use_organ(obj/item/organ/organ) - return (organ.organ_flags & ORGAN_EXTERNAL) - -///prosthetic surgery gives full effectiveness to crowbars (and hemostats) -/datum/surgery_step/manipulate_organs/external/mechanic - implements_extract = list(TOOL_HEMOSTAT = 100, TOOL_CROWBAR = 100, /obj/item/kitchen/fork = 35) - name = "manipulate prosthetic features (hemostat or crowbar/feature)" diff --git a/code/modules/surgery/organic_steps.dm b/code/modules/surgery/organic_steps.dm deleted file mode 100644 index 30a06d70ff7..00000000000 --- a/code/modules/surgery/organic_steps.dm +++ /dev/null @@ -1,231 +0,0 @@ - -//make incision -/datum/surgery_step/incise - name = "make incision (scalpel)" - implements = list( - TOOL_SCALPEL = 100, - /obj/item/melee/energy/sword = 75, - /obj/item/knife = 65, - /obj/item/shard = 45, - /obj/item = 30) // 30% success with any sharp item. - time = 1.6 SECONDS - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/scalpel2.ogg' - surgery_effects_mood = TRUE - -/datum/surgery_step/incise/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to make an incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to make an incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to make an incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You feel a stabbing in your [target.parse_zone_with_bodypart(target_zone)].") - -/datum/surgery_step/incise/tool_check(mob/user, obj/item/tool) - if(implement_type == /obj/item && !tool.get_sharpness()) - return FALSE - - return TRUE - -/datum/surgery_step/incise/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if ishuman(target) - var/mob/living/carbon/human/human_target = target - if (human_target.can_bleed()) - var/blood_name = human_target.get_bloodtype()?.get_blood_name() || "Blood" - display_results( - user, - target, - span_notice("[blood_name] pools around the incision in [human_target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[blood_name] pools around the incision in [human_target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[blood_name] pools around the incision in [human_target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - var/obj/item/bodypart/target_bodypart = target.get_bodypart(target_zone) - if(target_bodypart) - target_bodypart.adjustBleedStacks(10) - return ..() - -/datum/surgery_step/incise/nobleed/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to carefully make an incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to carefully make an incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to carefully make an incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You feel a careful stabbing in your [target.parse_zone_with_bodypart(target_zone)].") - -//clamp bleeders -/datum/surgery_step/clamp_bleeders - name = "clamp bleeders (hemostat)" - implements = list( - TOOL_HEMOSTAT = 100, - TOOL_WIRECUTTER = 60, - /obj/item/stack/package_wrap = 35, - /obj/item/stack/cable_coil = 15) - time = 2.4 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - -/datum/surgery_step/clamp_bleeders/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to clamp bleeders in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to clamp bleeders in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to clamp bleeders in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You feel a pinch as the bleeding in your [target.parse_zone_with_bodypart(target_zone)] is slowed.") - -/datum/surgery_step/clamp_bleeders/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) - if(locate(/datum/surgery_step/saw) in surgery.steps) - target.heal_bodypart_damage(20, 0, target_zone = target_zone) - if (ishuman(target)) - var/mob/living/carbon/human/human_target = target - var/obj/item/bodypart/target_bodypart = human_target.get_bodypart(target_zone) - if(target_bodypart) - target_bodypart.adjustBleedStacks(-3) - return ..() - -//retract skin -/datum/surgery_step/retract_skin - name = "retract skin (retractor)" - implements = list( - TOOL_RETRACTOR = 100, - TOOL_SCREWDRIVER = 45, - TOOL_WIRECUTTER = 35, - /obj/item/stack/rods = 35) - time = 2.4 SECONDS - preop_sound = 'sound/items/handling/surgery/retractor1.ogg' - success_sound = 'sound/items/handling/surgery/retractor2.ogg' - -/datum/surgery_step/retract_skin/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to retract the skin in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to retract the skin in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to retract the skin in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You feel a severe stinging pain spreading across your [target.parse_zone_with_bodypart(target_zone)] as the skin is pulled back!") - -//close incision -/datum/surgery_step/close - name = "mend incision (cautery)" - implements = list( - TOOL_CAUTERY = 100, - /obj/item/gun/energy/laser = 90, - TOOL_WELDER = 70, - /obj/item = 30) // 30% success with any hot item. - time = 2.4 SECONDS - preop_sound = 'sound/items/handling/surgery/cautery1.ogg' - success_sound = 'sound/items/handling/surgery/cautery2.ogg' - -/datum/surgery_step/close/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to mend the incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to mend the incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to mend the incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "Your [target.parse_zone_with_bodypart(target_zone)] is being burned!") - -/datum/surgery_step/close/tool_check(mob/user, obj/item/tool) - if(implement_type == TOOL_WELDER || implement_type == /obj/item) - return tool.get_temperature() - - return TRUE - -/datum/surgery_step/close/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) - if(locate(/datum/surgery_step/saw) in surgery.steps) - target.heal_bodypart_damage(45, 0, target_zone = target_zone) - if (ishuman(target)) - var/mob/living/carbon/human/human_target = target - var/obj/item/bodypart/target_bodypart = human_target.get_bodypart(target_zone) - if(target_bodypart) - target_bodypart.adjustBleedStacks(-3) - return ..() - - - -//saw bone -/datum/surgery_step/saw - name = "saw bone (circular saw)" - implements = list( - TOOL_SAW = 100, - /obj/item/shovel/serrated = 75, - /obj/item/melee/arm_blade = 75, - /obj/item/fireaxe = 50, - /obj/item/hatchet = 35, - /obj/item/knife/butcher = 35, - /obj/item = 25) //20% success (sort of) with any sharp item with a force >= 10 - time = 5.4 SECONDS - preop_sound = list( - /obj/item/circular_saw = 'sound/items/handling/surgery/saw.ogg', - /obj/item/melee/arm_blade = 'sound/items/handling/surgery/scalpel1.ogg', - /obj/item/fireaxe = 'sound/items/handling/surgery/scalpel1.ogg', - /obj/item/hatchet = 'sound/items/handling/surgery/scalpel1.ogg', - /obj/item/knife/butcher = 'sound/items/handling/surgery/scalpel1.ogg', - /obj/item = 'sound/items/handling/surgery/scalpel1.ogg', - ) - success_sound = 'sound/items/handling/surgery/organ2.ogg' - surgery_effects_mood = TRUE - -/datum/surgery_step/saw/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to saw through the bone in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to saw through the bone in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to saw through the bone in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You feel a horrid ache spread through the inside of your [target.parse_zone_with_bodypart(target_zone)]!") - -/datum/surgery_step/saw/tool_check(mob/user, obj/item/tool) - if(implement_type == /obj/item && !(tool.get_sharpness() && (tool.force >= 10))) - return FALSE - return TRUE - -/datum/surgery_step/saw/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) - target.apply_damage(50, BRUTE, "[target_zone]", wound_bonus=CANT_WOUND) - display_results( - user, - target, - span_notice("You saw [target]'s [target.parse_zone_with_bodypart(target_zone)] open."), - span_notice("[user] saws [target]'s [target.parse_zone_with_bodypart(target_zone)] open!"), - span_notice("[user] saws [target]'s [target.parse_zone_with_bodypart(target_zone)] open!"), - ) - display_pain(target, "It feels like something just broke in your [target.parse_zone_with_bodypart(target_zone)]!") - return ..() - -//drill bone -/datum/surgery_step/drill - name = "drill bone (surgical drill)" - implements = list( - TOOL_DRILL = 100, - /obj/item/screwdriver/power = 80, - /obj/item/pickaxe/drill = 60, - TOOL_SCREWDRIVER = 25, - /obj/item/kitchen/spoon = 20) - time = 3 SECONDS - -/datum/surgery_step/drill/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to drill into the bone in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to drill into the bone in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to drill into the bone in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You feel a horrible piercing pain in your [target.parse_zone_with_bodypart(target_zone)]!") - -/datum/surgery_step/drill/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - display_results( - user, - target, - span_notice("You drill into [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] drills into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - span_notice("[user] drills into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - return ..() diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 814d407d309..176c11e6853 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -88,12 +88,14 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) if(organ_flags & ORGAN_EDIBLE) AddComponentFrom( SOURCE_EDIBLE_INNATE, \ - /datum/component/edible,\ - initial_reagents = food_reagents,\ - foodtypes = foodtype_flags,\ - volume = reagent_vol,\ - tastes = food_tastes,\ - after_eat = CALLBACK(src, PROC_REF(OnEatFrom))) + /datum/component/edible, \ + initial_reagents = food_reagents, \ + foodtypes = foodtype_flags, \ + volume = reagent_vol, \ + tastes = food_tastes, \ + after_eat = CALLBACK(src, PROC_REF(OnEatFrom)), \ + ) + RegisterSignal(src, COMSIG_FOOD_ATTEMPT_EAT, PROC_REF(block_nom)) if(bodypart_overlay) setup_bodypart_overlay() @@ -234,7 +236,8 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) on_death(seconds_per_tick, times_fired) //Kinda hate doing it like this, but I really don't want to call process directly. /obj/item/organ/proc/OnEatFrom(eater, feeder) - useable = FALSE //You can't use it anymore after eating it you spaztic + // You can't use it anymore after eating it + organ_flags |= ORGAN_UNUSABLE /obj/item/organ/item_action_slot_check(slot,mob/user) return //so we don't grant the organ's action to mobs who pick up the organ. @@ -497,6 +500,16 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /obj/item/organ/proc/replace_into(mob/living/carbon/new_owner) Insert(new_owner, special = TRUE, movement_flags = DELETE_IF_REPLACED) +/// Signal proc for [COMSIG_FOOD_ATTEMPT_EAT], block feeding an organ to a mob if they are marked as ready to operate - to prevent mistakenly feeding your patient +/obj/item/organ/proc/block_nom(datum/source, mob/living/carbon/eater, mob/living/carbon/feeder) + SIGNAL_HANDLER + if(!HAS_TRAIT(eater, TRAIT_READY_TO_OPERATE)) + return NONE + if(eater == feeder) + to_chat(feeder, span_warning("You feel it unwise to eat [source] while you're undergoing surgery.")) + else + to_chat(feeder, span_warning("The only thing you could think of doing with [source] right now is feeding it to [eater], but that doesn't seem right.")) + return BLOCK_EAT_ATTEMPT /// Get all possible organ slots by checking every organ, and then store it and give it whenever needed /proc/get_all_slots() diff --git a/code/modules/surgery/organs/internal/stomach/stomach_golem.dm b/code/modules/surgery/organs/internal/stomach/stomach_golem.dm index 74a7f8f6cd1..38e69ba2bcd 100644 --- a/code/modules/surgery/organs/internal/stomach/stomach_golem.dm +++ b/code/modules/surgery/organs/internal/stomach/stomach_golem.dm @@ -27,7 +27,7 @@ if(istype(eating, /obj/item/food/golem_food)) return source.balloon_alert(source, "minerals only!") - return COMSIG_CARBON_BLOCK_EAT + return BLOCK_EAT_ATTEMPT /// Golem stomach cannot process nutriment except from minerals /obj/item/organ/stomach/golem/on_life(delta_time, times_fired) diff --git a/code/modules/surgery/organs/organ_movement.dm b/code/modules/surgery/organs/organ_movement.dm index df99cc52f6c..34d033b70e1 100644 --- a/code/modules/surgery/organs/organ_movement.dm +++ b/code/modules/surgery/organs/organ_movement.dm @@ -17,6 +17,13 @@ if(!mob_insert(receiver, special, movement_flags)) return FALSE + if(bodypart_owner && loc == bodypart_owner && receiver == bodypart_owner.owner) + // ok this is a bit confusing but essentially, thanks to some EXTREME shenanigans + // (tl;dr mob_insert -> set_species -> replace_limb -> bodypart_insert) + // mob_insert can result in bodypart_insert being handled already + // to avoid double insertion, and potential bugs, we'll stop here + return TRUE + bodypart_insert(limb_owner = receiver, movement_flags = movement_flags) if(!special && !(receiver.living_flags & STOP_OVERLAY_UPDATE_BODY_PARTS)) @@ -116,20 +123,26 @@ if(limb_owner) bodypart = limb_owner.get_bodypart(deprecise_zone(zone)) - // The true movement - forceMove(bodypart) - bodypart.contents |= src - bodypart_owner = bodypart + if(bodypart_owner == bodypart) + stack_trace("Organ bodypart_insert called when organ is already owned by that bodypart") + else if(!isnull(bodypart_owner)) + stack_trace("Organ bodypart_insert called when organ is already owned by a different bodypart") - RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(forced_removal)) + // In the event that we're already in the bodypart, DO NOT MOVE IT! otherwise it triggers forced_removal + if(loc != bodypart) + forceMove(bodypart) // The true movement - // Apply unique side-effects. Return value does not matter. - on_bodypart_insert(bodypart) + // Don't re-register if we are already owned + if(bodypart_owner != bodypart) + bodypart_owner = bodypart + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(forced_removal)) + // Apply unique side-effects. Return value does not matter. + on_bodypart_insert(bodypart) return TRUE /// Add any limb specific effects you might want here -/obj/item/organ/proc/on_bodypart_insert(obj/item/bodypart/limb, movement_flags) +/obj/item/organ/proc/on_bodypart_insert(obj/item/bodypart/limb) SHOULD_CALL_PARENT(TRUE) item_flags |= ABSTRACT @@ -144,7 +157,7 @@ if(bodypart_overlay) limb.add_bodypart_overlay(bodypart_overlay) - SEND_SIGNAL(src, COMSIG_ORGAN_BODYPART_INSERTED, limb, movement_flags) + SEND_SIGNAL(src, COMSIG_ORGAN_BODYPART_INSERTED, limb) /* * Remove the organ from the select mob. @@ -270,41 +283,41 @@ color = bodypart_overlay.draw_color //Defaults to the legacy behaviour of applying the color to the item. /// In space station videogame, nothing is sacred. If somehow an organ is removed unexpectedly, handle it properly -/obj/item/organ/proc/forced_removal() +/obj/item/organ/proc/forced_removal(datum/source, atom/old_loc, ...) SIGNAL_HANDLER if(owner) - Remove(owner) + if(loc?.loc == owner) // loc = some bodypart, loc.loc = some bodypart's owner + stack_trace("Forced removal triggered on [src] ([type]) moving into the same mob [owner] ([owner.type])!") + else + Remove(owner) else if(bodypart_owner) - bodypart_remove(bodypart_owner) + if(loc == bodypart_owner) + stack_trace("Forced removal triggered on [src] ([type]) moving into the same bodypart [bodypart_owner] ([bodypart_owner.type])!") + else + bodypart_remove(bodypart_owner) else stack_trace("Force removed an already removed organ!") /** * Proc that gets called when the organ is surgically removed by someone, can be used for special effects */ -/obj/item/organ/proc/on_surgical_removal(mob/living/user, mob/living/carbon/old_owner, target_zone, obj/item/tool) +/obj/item/organ/proc/on_surgical_removal(mob/living/user, obj/item/bodypart/limb, obj/item/tool) SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_ORGAN_SURGICALLY_REMOVED, user, old_owner, target_zone, tool) - RemoveElement(/datum/element/decal/blood, _color = old_owner.get_bloodtype()?.get_color() || BLOOD_COLOR_RED) + SEND_SIGNAL(src, COMSIG_ORGAN_SURGICALLY_REMOVED, user, limb.owner, limb.body_zone, tool) + RemoveElement(/datum/element/decal/blood, _color = limb.owner.get_bloodtype()?.get_color() || BLOOD_COLOR_RED) + /** * Proc that gets called when the organ is surgically inserted by someone. Seem familiar? */ -/obj/item/organ/proc/on_surgical_insertion(mob/living/user, mob/living/carbon/new_owner, target_zone, obj/item/tool) +/obj/item/organ/proc/on_surgical_insertion(mob/living/user, obj/item/bodypart/limb) SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_ORGAN_SURGICALLY_INSERTED, user, new_owner, target_zone, tool) + SEND_SIGNAL(src, COMSIG_ORGAN_SURGICALLY_INSERTED, user, limb.owner, limb.body_zone) /// Proc that gets called when someone starts surgically inserting the organ /obj/item/organ/proc/pre_surgical_insertion(mob/living/user, mob/living/carbon/new_owner, target_zone) - if (!valid_zones) - return TRUE - - // Ensure that in case we're somehow placed elsewhere (HARS-esque bs) we don't break our zone - if (!valid_zones[target_zone]) - return FALSE - - swap_zone(target_zone) - return TRUE + if (valid_zones) + swap_zone(target_zone) /// Readjusts the organ to fit into a different body zone/slot /obj/item/organ/proc/swap_zone(target_zone) diff --git a/code/modules/surgery/plastic_surgery.dm b/code/modules/surgery/plastic_surgery.dm deleted file mode 100644 index 2c518619fbe..00000000000 --- a/code/modules/surgery/plastic_surgery.dm +++ /dev/null @@ -1,135 +0,0 @@ -/// Disk containing info for doing advanced plastic surgery. Spawns in maint and available as a role-restricted item in traitor uplinks. -/obj/item/disk/surgery/advanced_plastic_surgery - name = "Advanced Plastic Surgery Disk" - desc = "The disk provides instructions on how to do an Advanced Plastic Surgery, this surgery allows one-self to completely remake someone's face with that of another. Provided they have a picture of them in their offhand when reshaping the face. With the surgery long becoming obsolete with the rise of genetics technology. This item became an antique to many collectors, With only the cheaper and easier basic form of plastic surgery remaining in use in most places." - surgeries = list(/datum/surgery/plastic_surgery/advanced) - -/datum/surgery/plastic_surgery - name = "Plastic surgery" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB | SURGERY_MORBID_CURIOSITY - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/reshape_face, - /datum/surgery_step/close, - ) - -/datum/surgery/plastic_surgery/advanced - name = "Advanced plastic surgery" - desc = "Surgery allows one-self to completely remake someone's face with that of another. Provided they have a picture of them in their offhand when reshaping the face." - requires_tech = TRUE - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/insert_plastic, - /datum/surgery_step/reshape_face, - /datum/surgery_step/close, - ) - -//Insert plastic step, It ain't called plastic surgery for nothing! :) -/datum/surgery_step/insert_plastic - name = "insert plastic (plastic)" - implements = list( - /obj/item/stack/sheet/plastic = 100, - /obj/item/stack/sheet/meat = 100) - time = 3.2 SECONDS - preop_sound = 'sound/effects/blob/blobattack.ogg' - success_sound = 'sound/effects/blob/attackblob.ogg' - failure_sound = 'sound/effects/blob/blobattack.ogg' - -/datum/surgery_step/insert_plastic/preop(mob/user, mob/living/target, target_zone, obj/item/stack/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to insert [tool] into the incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]..."), - span_notice("[user] begins to insert [tool] into the incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to insert [tool] into the incision in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - display_pain(target, "You feel something inserting just below the skin in your [target.parse_zone_with_bodypart(target_zone)].") - -/datum/surgery_step/insert_plastic/success(mob/user, mob/living/target, target_zone, obj/item/stack/tool, datum/surgery/surgery, default_display_results) - . = ..() - tool.use(1) - -//reshape_face -/datum/surgery_step/reshape_face - name = "reshape face (scalpel)" - implements = list( - TOOL_SCALPEL = 100, - /obj/item/knife = 50, - TOOL_WIRECUTTER = 35) - time = 6.4 SECONDS - surgery_effects_mood = TRUE - -/datum/surgery_step/reshape_face/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - user.visible_message(span_notice("[user] begins to alter [target]'s appearance."), span_notice("You begin to alter [target]'s appearance...")) - display_results( - user, - target, - span_notice("You begin to alter [target]'s appearance..."), - span_notice("[user] begins to alter [target]'s appearance."), - span_notice("[user] begins to make an incision in [target]'s face."), - ) - display_pain(target, "You feel slicing pain across your face!") - -/datum/surgery_step/reshape_face/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(HAS_TRAIT_FROM(target, TRAIT_DISFIGURED, TRAIT_GENERIC)) - REMOVE_TRAIT(target, TRAIT_DISFIGURED, TRAIT_GENERIC) - display_results( - user, - target, - span_notice("You successfully restore [target]'s appearance."), - span_notice("[user] successfully restores [target]'s appearance!"), - span_notice("[user] finishes the operation on [target]'s face."), - ) - display_pain(target, "The pain fades, your face feels normal again!") - else - var/list/names = list() - if(!isabductor(user)) - var/obj/item/offhand = user.get_inactive_held_item() - if(istype(offhand, /obj/item/photo) && istype(surgery, /datum/surgery/plastic_surgery/advanced)) - var/obj/item/photo/disguises = offhand - for(var/namelist in disguises.picture?.names_seen) - names += namelist - else - to_chat(user, span_warning("You have no picture to base the appearance on, reverting to random appearances.")) - for(var/i in 1 to 10) - names += target.generate_random_mob_name(TRUE) - else - for(var/j in 1 to 9) - names += "Subject [target.gender == MALE ? "i" : "o"]-[pick("a", "b", "c", "d", "e")]-[rand(10000, 99999)]" - names += target.generate_random_mob_name(TRUE) //give one normal name in case they want to do regular plastic surgery - var/chosen_name = tgui_input_list(user, "New name to assign", "Plastic Surgery", names) - if(isnull(chosen_name)) - return - var/oldname = target.real_name - target.real_name = chosen_name - var/newname = target.real_name //something about how the code handles names required that I use this instead of target.real_name - display_results( - user, - target, - span_notice("You alter [oldname]'s appearance completely, [target.p_they()] is now [newname]."), - span_notice("[user] alters [oldname]'s appearance completely, [target.p_they()] is now [newname]!"), - span_notice("[user] finishes the operation on [target]'s face."), - ) - display_pain(target, "The pain fades, your face feels new and unfamiliar!") - if(ishuman(target)) - var/mob/living/carbon/human/human_target = target - human_target.update_ID_card() - if(HAS_MIND_TRAIT(user, TRAIT_MORBID) && ishuman(user)) - var/mob/living/carbon/human/morbid_weirdo = user - morbid_weirdo.add_mood_event("morbid_abominable_surgery_success", /datum/mood_event/morbid_abominable_surgery_success) - return ..() - -/datum/surgery_step/reshape_face/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_warning("You screw up, leaving [target]'s appearance disfigured!"), - span_notice("[user] screws up, disfiguring [target]'s appearance!"), - span_notice("[user] finishes the operation on [target]'s face."), - ) - display_pain(target, "Your face feels horribly scarred and deformed!") - ADD_TRAIT(target, TRAIT_DISFIGURED, TRAIT_GENERIC) - return FALSE diff --git a/code/modules/surgery/prosthetic_replacement.dm b/code/modules/surgery/prosthetic_replacement.dm deleted file mode 100644 index dabe94251c0..00000000000 --- a/code/modules/surgery/prosthetic_replacement.dm +++ /dev/null @@ -1,189 +0,0 @@ -/datum/surgery/prosthetic_replacement - name = "Prosthetic replacement" - surgery_flags = NONE - requires_bodypart_type = NONE - possible_locs = list( - BODY_ZONE_R_ARM, - BODY_ZONE_L_ARM, - BODY_ZONE_L_LEG, - BODY_ZONE_R_LEG, - BODY_ZONE_HEAD, - ) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/add_prosthetic, - ) - -/datum/surgery/prosthetic_replacement/can_start(mob/user, mob/living/carbon/target) - if(!..()) - return FALSE - if(!iscarbon(target)) - return FALSE - var/mob/living/carbon/carbon_target = target - if(!carbon_target.get_bodypart(user.zone_selected) && carbon_target.should_have_limb(user.zone_selected)) //can only start if limb is missing - return TRUE - return FALSE - - - -/datum/surgery_step/add_prosthetic - name = "add prosthetic" - implements = list( - /obj/item/bodypart = 100, - /obj/item/borg/apparatus/organ_storage = 100, - /obj/item = 100, - ) - time = 3.2 SECONDS - /// Toxin damage incurred by the target if an organic limb is attached - VAR_FINAL/organ_rejection_dam = 0 - /// List of items that are always allowed to be an arm replacement, even if they fail another requirement. - var/list/always_accepted_prosthetics = list( - /obj/item/chainsaw, // the OG, too large otherwise - /obj/item/melee/synthetic_arm_blade, // also too large otherwise - /obj/item/food/pizzaslice, // he's turning her into a papa john's - ) - -/datum/surgery_step/add_prosthetic/tool_check(mob/user, obj/item/tool) - if(istype(tool, /obj/item/borg/apparatus/organ_storage)) - if(!length(tool.contents)) - return FALSE - tool = tool.contents[1] - if(tool.item_flags & (ABSTRACT|HAND_ITEM|DROPDEL)) - return FALSE - if(isbodypart(tool)) - return TRUE // auto pass - "intended" use case - if(is_type_in_list(tool, always_accepted_prosthetics)) - return TRUE // auto pass - soulful prosthetics - if(tool.w_class < WEIGHT_CLASS_NORMAL || tool.w_class > WEIGHT_CLASS_BULKY) - return FALSE // too large or too small items don't make sense as a limb replacement - if(HAS_TRAIT(tool, TRAIT_WIELDED)) - return FALSE // prevents exploits from weird edge cases - either unwield or get out - return TRUE - -/datum/surgery_step/add_prosthetic/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(istype(tool, /obj/item/borg/apparatus/organ_storage)) - if(!tool.contents.len) - to_chat(user, span_warning("There is nothing inside [tool]!")) - return SURGERY_STEP_FAIL - var/obj/item/organ_storage_contents = tool.contents[1] - if(!isbodypart(organ_storage_contents)) - to_chat(user, span_warning("[organ_storage_contents] cannot be attached!")) - return SURGERY_STEP_FAIL - tool = organ_storage_contents - if(isbodypart(tool)) - var/obj/item/bodypart/bodypart_to_attach = tool - if(IS_ORGANIC_LIMB(bodypart_to_attach)) - organ_rejection_dam = 10 - if(ishuman(target)) - var/mob/living/carbon/human/human_target = target - var/obj/item/bodypart/chest/target_chest = human_target.get_bodypart(BODY_ZONE_CHEST) - if((!(bodypart_to_attach.bodyshape & target_chest.acceptable_bodyshape)) && (!(bodypart_to_attach.bodytype & target_chest.acceptable_bodytype))) - to_chat(user, span_warning("[bodypart_to_attach] doesn't match the patient's morphology.")) - return SURGERY_STEP_FAIL - if(bodypart_to_attach.check_for_frankenstein(target)) - organ_rejection_dam = 30 - - if(!bodypart_to_attach.can_attach_limb(target)) - target.balloon_alert(user, "that doesn't go on the [target.parse_zone_with_bodypart(target_zone)]!") - return SURGERY_STEP_FAIL - - if(target_zone == bodypart_to_attach.body_zone) //so we can't replace a leg with an arm, or a human arm with a monkey arm. - display_results( - user, - target, - span_notice("You begin to replace [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]..."), - span_notice("[user] begins to replace [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]."), - span_notice("[user] begins to replace [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - else - to_chat(user, span_warning("[tool] isn't the right type for [target.parse_zone_with_bodypart(target_zone)].")) - return SURGERY_STEP_FAIL - else if(target_zone == BODY_ZONE_L_ARM || target_zone == BODY_ZONE_R_ARM) - display_results( - user, - target, - span_notice("You begin to attach [tool] onto [target]..."), - span_notice("[user] begins to attach [tool] onto [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] begins to attach something onto [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - ) - else - to_chat(user, span_warning("[tool] must be attached to an arm socket.")) - return SURGERY_STEP_FAIL - -/datum/surgery_step/add_prosthetic/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(istype(tool, /obj/item/borg/apparatus/organ_storage)) - tool.icon_state = initial(tool.icon_state) - tool.desc = initial(tool.desc) - tool.cut_overlays() - tool = tool.contents[1] - else if(!user.temporarilyRemoveItemFromInventory(tool)) - to_chat(user, span_warning("You can't seem to part with [tool]!")) - return FALSE - - . = ..() - if(isbodypart(tool)) - handle_bodypart(user, target, tool, target_zone) - return - handle_arbitrary_prosthetic(user, target, tool, target_zone) - surgery.steps += /datum/surgery_step/secure_arbitrary_prosthetic - -/datum/surgery_step/add_prosthetic/proc/handle_bodypart(mob/user, mob/living/carbon/target, obj/item/bodypart/bodypart_to_attach, target_zone) - bodypart_to_attach.try_attach_limb(target) - if(bodypart_to_attach.check_for_frankenstein(target)) - bodypart_to_attach.bodypart_flags |= BODYPART_IMPLANTED - if(organ_rejection_dam) - target.adjust_tox_loss(organ_rejection_dam) - display_results( - user, target, - span_notice("You succeed in replacing [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] successfully replaces [target]'s [target.parse_zone_with_bodypart(target_zone)] with [bodypart_to_attach]!"), - span_notice("[user] successfully replaces [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - display_pain(target, "You feel synthetic sensation wash from your [target.parse_zone_with_bodypart(target_zone)], which you can feel again!", TRUE) - -/datum/surgery_step/add_prosthetic/proc/handle_arbitrary_prosthetic(mob/user, mob/living/carbon/target, obj/item/thing_to_attach, target_zone) - SSblackbox.record_feedback("tally", "arbitrary_prosthetic", 1, initial(thing_to_attach.name)) - target.make_item_prosthetic(thing_to_attach, target_zone, 80) - display_results( - user, target, - span_notice("You attach [thing_to_attach]."), - span_notice("[user] finishes attaching [thing_to_attach]!"), - span_notice("[user] finishes the attachment procedure!"), - ) - display_pain(target, "You feel a strange sensation as [thing_to_attach] takes place of an arm!", TRUE) - -/datum/surgery_step/secure_arbitrary_prosthetic - name = "secure prosthetic (suture/tape)" - implements = list( - /obj/item/stack/medical/suture = 100, - /obj/item/stack/sticky_tape/surgical = 80, - /obj/item/stack/sticky_tape = 50, - ) - time = 4.8 SECONDS - -/datum/surgery_step/secure_arbitrary_prosthetic/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/obj/limb = target.get_bodypart(target_zone) - var/obj/item/stack/thing = tool - display_results( - user, target, - span_notice("You begin to [thing.singular_name] [limb] to [target]'s body."), - span_notice("[user] begins to [thing.singular_name] [limb] to [target]'s body."), - span_notice("[user] begins to [thing.singular_name] something to [target]'s body."), - ) - display_pain(target, "[user] begins to [thing.singular_name] [limb] to your body!", TRUE) - -/datum/surgery_step/secure_arbitrary_prosthetic/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/obj/limb = target.get_bodypart(target_zone) - var/obj/item/stack/thing = tool - thing.use(1) - limb.AddComponent(/datum/component/item_as_prosthetic_limb, null, 0) // updates drop probability to zero - display_results( - user, target, - span_notice("You [thing.singular_name] [limb] to [target]'s body."), - span_notice("[user] [thing.singular_name] [limb] to [target]'s body!"), - span_notice("[user] [thing.singular_name][plural_s(thing.singular_name)] something to [target]'s body!"), - ) - display_pain(target, "[user] [thing.singular_name][plural_s(thing.singular_name)] [limb] to your body!", TRUE) - return TRUE diff --git a/code/modules/surgery/repair_puncture.dm b/code/modules/surgery/repair_puncture.dm deleted file mode 100644 index 77b36d264cd..00000000000 --- a/code/modules/surgery/repair_puncture.dm +++ /dev/null @@ -1,155 +0,0 @@ - -/////BURN FIXING SURGERIES////// - -//the step numbers of each of these two, we only currently use the first to switch back and forth due to advancing after finishing steps anyway -#define REALIGN_INNARDS 1 -#define WELD_VEINS 2 - -///// Repair puncture wounds -/datum/surgery/repair_puncture - name = "Repair puncture" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - targetable_wound = /datum/wound/pierce/bleed - target_mobtypes = list(/mob/living/carbon) - possible_locs = list( - BODY_ZONE_R_ARM, - BODY_ZONE_L_ARM, - BODY_ZONE_R_LEG, - BODY_ZONE_L_LEG, - BODY_ZONE_CHEST, - BODY_ZONE_HEAD, - ) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/repair_innards, - /datum/surgery_step/seal_veins, - /datum/surgery_step/close, - ) - -/datum/surgery/repair_puncture/can_start(mob/living/user, mob/living/carbon/target) - . = ..() - if(!.) - return . - - var/datum/wound/pierce/bleed/pierce_wound = target.get_bodypart(user.zone_selected).get_wound_type(targetable_wound) - ASSERT(pierce_wound, "[type] on [target] has no pierce wound when it should have been guaranteed to have one by can_start") - return pierce_wound.blood_flow > 0 - -//SURGERY STEPS - -///// realign the blood vessels so we can reweld them -/datum/surgery_step/repair_innards - name = "realign blood vessels (hemostat)" - implements = list( - TOOL_HEMOSTAT = 100, - TOOL_SCALPEL = 85, - TOOL_WIRECUTTER = 40) - time = 3 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - surgery_effects_mood = TRUE - -/datum/surgery_step/repair_innards/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound - if(!pierce_wound) - user.visible_message(span_notice("[user] looks for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), span_notice("You look for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]...")) - return - - if(pierce_wound.blood_flow <= 0) - to_chat(user, span_notice("[target]'s [target.parse_zone_with_bodypart(user.zone_selected)] has no puncture to repair!")) - surgery.status++ - return - - display_results( - user, - target, - span_notice("You begin to realign the torn blood vessels in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]..."), - span_notice("[user] begins to realign the torn blood vessels in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)] with [tool]."), - span_notice("[user] begins to realign the torn blood vessels in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), - ) - display_pain(target, "You feel a horrible stabbing pain in your [target.parse_zone_with_bodypart(user.zone_selected)]!") - -/datum/surgery_step/repair_innards/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound - if(!pierce_wound) - to_chat(user, span_warning("[target] has no puncture wound there!")) - return ..() - - display_results( - user, - target, - span_notice("You successfully realign some of the blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] successfully realigns some of the blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), - span_notice("[user] successfully realigns some of the blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - log_combat(user, target, "realigned blood vessels in", addition="COMBAT MODE: [uppertext(user.combat_mode)]") - target.apply_damage(3, BRUTE, surgery.operated_bodypart, wound_bonus = CANT_WOUND, sharpness = SHARP_EDGED, attacking_item = tool) - pierce_wound.adjust_blood_flow(-0.25) - return ..() - -/datum/surgery_step/repair_innards/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob = 0) - . = ..() - display_results( - user, - target, - span_notice("You jerk apart some of the blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), - span_notice("[user] jerks apart some of the blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), - span_notice("[user] jerk apart some of the blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - target.apply_damage(rand(4, 8), BRUTE, surgery.operated_bodypart, wound_bonus = 10, sharpness = SHARP_EDGED, attacking_item = tool) - -///// Sealing the vessels back together -/datum/surgery_step/seal_veins - name = "weld veins (cautery)" // if your doctor says they're going to weld your blood vessels back together, you're either A) on SS13, or B) in grave mortal peril - implements = list( - TOOL_CAUTERY = 100, - /obj/item/gun/energy/laser = 90, - TOOL_WELDER = 70, - /obj/item = 30) - time = 4 SECONDS - preop_sound = 'sound/items/handling/surgery/cautery1.ogg' - success_sound = 'sound/items/handling/surgery/cautery2.ogg' - -/datum/surgery_step/seal_veins/tool_check(mob/user, obj/item/tool) - if(implement_type == TOOL_WELDER || implement_type == /obj/item) - return tool.get_temperature() - - return TRUE - -/datum/surgery_step/seal_veins/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound - if(!pierce_wound) - user.visible_message(span_notice("[user] looks for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), span_notice("You look for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]...")) - return - display_results( - user, - target, - span_notice("You begin to meld some of the split blood vessels in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]..."), - span_notice("[user] begins to meld some of the split blood vessels in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)] with [tool]."), - span_notice("[user] begins to meld some of the split blood vessels in [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), - ) - display_pain(target, "You're being burned inside your [target.parse_zone_with_bodypart(user.zone_selected)]!") - -/datum/surgery_step/seal_veins/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound - if(!pierce_wound) - to_chat(user, span_warning("[target] has no puncture there!")) - return ..() - - display_results( - user, - target, - span_notice("You successfully meld some of the split blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]."), - span_notice("[user] successfully melds some of the split blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), - span_notice("[user] successfully melds some of the split blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), - ) - log_combat(user, target, "dressed burns in", addition="COMBAT MODE: [uppertext(user.combat_mode)]") - pierce_wound.adjust_blood_flow(-0.5) - if(!QDELETED(pierce_wound) && pierce_wound.blood_flow > 0) - surgery.status = REALIGN_INNARDS - to_chat(user, span_notice("There still seems to be misaligned blood vessels to finish...")) - else - to_chat(user, span_green("You've repaired all the internal damage in [target]'s [target.parse_zone_with_bodypart(target_zone)]!")) - return ..() - -#undef REALIGN_INNARDS -#undef WELD_VEINS diff --git a/code/modules/surgery/revival.dm b/code/modules/surgery/revival.dm deleted file mode 100644 index 8f8120e09b8..00000000000 --- a/code/modules/surgery/revival.dm +++ /dev/null @@ -1,161 +0,0 @@ -/datum/surgery/revival - name = "Revival" - desc = "An experimental surgical procedure which involves reconstruction and reactivation of the patient's brain even long after death. \ - The body must still be able to sustain life." - possible_locs = list(BODY_ZONE_CHEST) - target_mobtypes = list(/mob/living) - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_MORBID_CURIOSITY - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/incise, - /datum/surgery_step/revive, - /datum/surgery_step/close, - ) - -/datum/surgery/revival/mechanic - name = "Full System Reboot" - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/prepare_electronics, - /datum/surgery_step/revive, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/revival/can_start(mob/user, mob/living/target) - if(!..()) - return FALSE - if(target.stat != DEAD) - return FALSE - if(HAS_TRAIT(target, TRAIT_SUICIDED) || HAS_TRAIT(target, TRAIT_HUSK) || HAS_TRAIT(target, TRAIT_DEFIB_BLACKLISTED)) - return FALSE - if(!is_valid_target(target)) - return FALSE - return TRUE - -/// Extra checks which can be overridden -/datum/surgery/revival/proc/is_valid_target(mob/living/patient) - if (iscarbon(patient)) - return FALSE - if (!(patient.mob_biotypes & (MOB_ORGANIC|MOB_HUMANOID))) - return FALSE - return TRUE - -/datum/surgery/revival/mechanic/is_valid_target(mob/living/patient) - if (iscarbon(patient)) - return FALSE - if (!(patient.mob_biotypes & (MOB_ROBOTIC|MOB_HUMANOID))) - return FALSE - return TRUE - -/datum/surgery_step/revive - name = "shock brain (defibrillator)" - implements = list( - /obj/item/shockpaddles = 100, - /obj/item/melee/touch_attack/shock = 100, - /obj/item/melee/baton/security = 75, - /obj/item/gun/energy = 60) - repeatable = TRUE - time = 5 SECONDS - success_sound = 'sound/effects/magic/lightningbolt.ogg' - failure_sound = 'sound/effects/magic/lightningbolt.ogg' - -/datum/surgery_step/revive/tool_check(mob/user, obj/item/tool) - . = TRUE - if(istype(tool, /obj/item/shockpaddles)) - var/obj/item/shockpaddles/paddles = tool - if((paddles.req_defib && !paddles.defib.powered) || !HAS_TRAIT(paddles, TRAIT_WIELDED) || paddles.cooldown || paddles.busy) - to_chat(user, span_warning("You need to wield both paddles, and [paddles.defib] must be powered!")) - return FALSE - if(istype(tool, /obj/item/melee/baton/security)) - var/obj/item/melee/baton/security/baton = tool - if(!baton.active) - to_chat(user, span_warning("[baton] needs to be active!")) - return FALSE - if(istype(tool, /obj/item/gun/energy)) - var/obj/item/gun/energy/egun = tool - if(egun.chambered && istype(egun.chambered, /obj/item/ammo_casing/energy/electrode)) - return TRUE - else - to_chat(user, span_warning("You need an electrode for this!")) - return FALSE - -/datum/surgery_step/revive/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You prepare to give [target]'s brain the spark of life with [tool]."), - span_notice("[user] prepares to shock [target]'s brain with [tool]."), - span_notice("[user] prepares to shock [target]'s brain with [tool]."), - ) - target.notify_revival("Someone is trying to zap your brain.", source = target) - -/datum/surgery_step/revive/play_preop_sound(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(istype(tool, /obj/item/shockpaddles)) - playsound(tool, 'sound/machines/defib/defib_charge.ogg', 75, 0) - else - ..() - -/datum/surgery_step/revive/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) - display_results( - user, - target, - span_notice("You successfully shock [target]'s brain with [tool]..."), - span_notice("[user] send a powerful shock to [target]'s brain with [tool]..."), - span_notice("[user] send a powerful shock to [target]'s brain with [tool]..."), - ) - target.grab_ghost() - target.adjust_oxy_loss(-50) - if(iscarbon(target)) - var/mob/living/carbon/carbon_target = target - carbon_target.set_heartattack(FALSE) - if(target.revive()) - on_revived(user, target) - return TRUE - - target.visible_message(span_warning("...[target.p_they()] convulse[target.p_s()], then lie[target.p_s()] still.")) - return FALSE - -/// Called when you have been successfully raised from the dead -/datum/surgery_step/revive/proc/on_revived(mob/surgeon, mob/living/patient) - patient.visible_message(span_notice("...[patient] wakes up, alive and aware!")) - patient.emote("gasp") - if(HAS_MIND_TRAIT(surgeon, TRAIT_MORBID) && ishuman(surgeon)) // Contrary to their typical hatred of resurrection, it wouldn't be very thematic if morbid people didn't love playing god - var/mob/living/carbon/human/morbid_weirdo = surgeon - morbid_weirdo.add_mood_event("morbid_revival_success", /datum/mood_event/morbid_revival_success) - -/datum/surgery_step/revive/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You shock [target]'s brain with [tool], but [target.p_they()] doesn't react."), - span_notice("[user] send a powerful shock to [target]'s brain with [tool], but [target.p_they()] doesn't react."), - span_notice("[user] send a powerful shock to [target]'s brain with [tool], but [target.p_they()] doesn't react."), - ) - return FALSE - -/// Additional revival effects if the target has a brain -/datum/surgery/revival/carbon - possible_locs = list(BODY_ZONE_HEAD) - target_mobtypes = list(/mob/living/carbon) - surgery_flags = parent_type::surgery_flags | SURGERY_REQUIRE_LIMB - -/datum/surgery/revival/carbon/is_valid_target(mob/living/carbon/patient) - var/obj/item/organ/brain/target_brain = patient.get_organ_slot(ORGAN_SLOT_BRAIN) - return !isnull(target_brain) - -/datum/surgery_step/revive/carbon - -/datum/surgery_step/revive/carbon/on_revived(mob/surgeon, mob/living/patient) - . = ..() - patient.adjust_organ_loss(ORGAN_SLOT_BRAIN, 50, 199) // MAD SCIENCE - -/datum/surgery_step/revive/carbon/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - . = ..() - target.adjust_organ_loss(ORGAN_SLOT_BRAIN, 15, 180) diff --git a/code/modules/surgery/sleeper_protocol.dm b/code/modules/surgery/sleeper_protocol.dm deleted file mode 100644 index fd697240eed..00000000000 --- a/code/modules/surgery/sleeper_protocol.dm +++ /dev/null @@ -1,91 +0,0 @@ -/obj/item/disk/surgery/sleeper_protocol - name = "Suspicious Surgery Disk" - desc = "The disk provides instructions on how to turn someone into a sleeper agent for the Syndicate." - surgeries = list( - /datum/surgery/advanced/brainwashing_sleeper, - /datum/surgery/advanced/brainwashing_sleeper/mechanic, - ) - -/datum/surgery/advanced/brainwashing_sleeper - name = "Sleeper Agent Surgery" - desc = "A surgical procedure which implants the sleeper protocol into the patient's brain, making it their absolute priority. It can be cleared using a mindshield implant." - requires_bodypart_type = NONE - possible_locs = list(BODY_ZONE_HEAD) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/brainwash/sleeper_agent, - /datum/surgery_step/close, - ) - -/datum/surgery/advanced/brainwashing_sleeper/mechanic - name = "Sleeper Agent Reprogramming" - desc = "Malware which directly implants the sleeper protocol directive into the robotic patient's operating system, making it their absolute priority. It can be cleared using a mindshield implant." - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/brainwash/sleeper_agent/mechanic, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/advanced/brainwashing_sleeper/can_start(mob/user, mob/living/carbon/target) - . = ..() - if(!.) - return FALSE - var/obj/item/organ/brain/target_brain = target.get_organ_slot(ORGAN_SLOT_BRAIN) - if(!target_brain) - return FALSE - return TRUE - -/datum/surgery_step/brainwash/sleeper_agent - time = 25 SECONDS - var/static/list/possible_objectives = list( - "You love the Syndicate.", - "Do not trust Nanotrasen.", - "The Captain is a lizardperson.", - "Nanotrasen isn't real.", - "They put something in the food to make you forget.", - "You are the only real person on the station.", - "Things would be a lot better on the station if more people were screaming, someone should do something about that.", - "The people in charge around here have only ill intentions for the crew.", - "Help the crew? What have they ever done for you anyways?", - "Does your bag feel lighter? I bet those guys in Security stole something from it. Go get it back.", - "Command is incompetent, someone with some REAL authority should take over around here.", - "The cyborgs and the AI are stalking you. What are they planning?", - ) - -/datum/surgery_step/brainwash/sleeper_agent/mechanic - name = "reprogram (multitool)" - implements = list( - TOOL_MULTITOOL = 85, - TOOL_HEMOSTAT = 50, - TOOL_WIRECUTTER = 50, - /obj/item/stack/package_wrap = 35, - /obj/item/stack/cable_coil = 15) - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - success_sound = 'sound/items/handling/surgery/hemostat1.ogg' - -/datum/surgery_step/brainwash/sleeper_agent/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - objective = pick(possible_objectives) - display_results( - user, - target, - span_notice("You begin to brainwash [target]..."), - span_notice("[user] begins to fix [target]'s brain."), - span_notice("[user] begins to perform surgery on [target]'s brain."), - ) - display_pain(target, "Your head pounds with unimaginable pain!") // Same message as other brain surgeries - -/datum/surgery_step/brainwash/sleeper_agent/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(target.stat == DEAD) - to_chat(user, span_warning("They need to be alive to perform this surgery!")) - return FALSE - . = ..() - if(!.) - return - target.gain_trauma(new /datum/brain_trauma/mild/phobia/conspiracies(), TRAUMA_RESILIENCE_LOBOTOMY) diff --git a/code/modules/surgery/stomachpump.dm b/code/modules/surgery/stomachpump.dm deleted file mode 100644 index ea9a9314464..00000000000 --- a/code/modules/surgery/stomachpump.dm +++ /dev/null @@ -1,75 +0,0 @@ -/datum/surgery/stomach_pump - name = "Stomach Pump" - possible_locs = list(BODY_ZONE_CHEST) - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/incise, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/stomach_pump, - /datum/surgery_step/close, - ) - -/datum/surgery/stomach_pump/mechanic - name = "Nutrient Processing Purge" - requires_bodypart_type = BODYTYPE_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/stomach_pump, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close, - ) - -/datum/surgery/stomach_pump/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/stomach/target_stomach = target.get_organ_slot(ORGAN_SLOT_STOMACH) - if(HAS_TRAIT(target, TRAIT_HUSK)) - return FALSE - if(!target_stomach) - return FALSE - return ..() - -//Working the stomach by hand in such a way that you induce vomiting. -/datum/surgery_step/stomach_pump - name = "pump stomach (hand)" - accept_hand = TRUE - repeatable = TRUE - time = 2 SECONDS - success_sound = 'sound/items/handling/surgery/organ2.ogg' - -/datum/surgery_step/stomach_pump/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin pumping [target]'s stomach..."), - span_notice("[user] begins to pump [target]'s stomach."), - span_notice("[user] begins to press on [target]'s chest."), - ) - display_pain(target, "You feel a horrible sloshing feeling in your gut! You're going to be sick!") - -/datum/surgery_step/stomach_pump/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - if(ishuman(target)) - var/mob/living/carbon/human/target_human = target - display_results( - user, - target, - span_notice("[user] forces [target_human] to vomit, cleansing their stomach of some chemicals!"), - span_notice("[user] forces [target_human] to vomit, cleansing their stomach of some chemicals!"), - span_notice("[user] forces [target_human] to vomit!"), - ) - target_human.vomit((MOB_VOMIT_MESSAGE | MOB_VOMIT_STUN), lost_nutrition = 20, purge_ratio = 0.67) //higher purge ratio than regular vomiting - return ..() - -/datum/surgery_step/stomach_pump/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(ishuman(target)) - var/mob/living/carbon/human/target_human = target - display_results( - user, - target, - span_warning("You screw up, bruising [target_human]'s chest!"), - span_warning("[user] screws up, brusing [target_human]'s chest!"), - span_warning("[user] screws up!"), - ) - target_human.adjust_organ_loss(ORGAN_SLOT_STOMACH, 5) - target_human.adjust_brute_loss(5) diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm deleted file mode 100644 index 445cabe8682..00000000000 --- a/code/modules/surgery/surgery.dm +++ /dev/null @@ -1,212 +0,0 @@ -/datum/surgery - ///The name of the surgery operation - var/name = "surgery" - ///The description of the surgery, what it does. - var/desc - - ///Bitfield for flags that determine different behaviors and requirement for the surgery. See __DEFINES/surgery.dm - var/surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB - ///The surgery step we're currently on, increases each time we do a step. - var/status = 1 - ///All steps the surgery has to do to complete. - var/list/steps = list() - ///Boolean on whether a surgery step is currently being done, to prevent multi-surgery. - var/step_in_progress = FALSE - - ///The bodypart this specific surgery is being performed on. - var/location = BODY_ZONE_CHEST - ///The possible bodyparts that the surgery can be started on. - var/list/possible_locs = list() - ///Mobs that are valid to have surgery performed on them. - var/list/target_mobtypes = list(/mob/living/carbon/human) - - ///The person the surgery is being performed on. Funnily enough, it isn't always a carbon. - VAR_FINAL/mob/living/carbon/target - ///The specific bodypart being operated on. - VAR_FINAL/obj/item/bodypart/operated_bodypart - ///The wound datum that is being operated on. - VAR_FINAL/datum/wound/operated_wound - - ///Types of wounds this surgery can target. - var/targetable_wound - ///The types of bodyparts that this surgery can have performed on it. Used for augmented surgeries. - var/requires_bodypart_type = BODYTYPE_ORGANIC - - ///The speed modifier given to the surgery through external means. - var/speed_modifier = 1 - ///Whether the surgery requires research to do. You need to add a design if using this! - var/requires_tech = FALSE - ///typepath of a surgery that will, once researched, replace this surgery in the operating menu. - var/replaced_by - /// Organ being directly manipulated, used for checking if the organ is still in the body after surgery has begun - var/organ_to_manipulate - -/datum/surgery/New(atom/surgery_target, surgery_location, surgery_bodypart) - . = ..() - if(!surgery_target) - return - target = surgery_target - target.surgeries += src - if(surgery_location) - location = surgery_location - if(!surgery_bodypart) - return - operated_bodypart = surgery_bodypart - if(targetable_wound) - operated_wound = operated_bodypart.get_wound_type(targetable_wound) - operated_wound.attached_surgery = src - - SEND_SIGNAL(surgery_target, COMSIG_MOB_SURGERY_STARTED, src, surgery_location, surgery_bodypart) - -/datum/surgery/Destroy() - if(operated_wound) - operated_wound.attached_surgery = null - operated_wound = null - if(target) - target.surgeries -= src - if(!QDELING(target)) - SEND_SIGNAL(target, COMSIG_MOB_SURGERY_FINISHED, type, location, operated_bodypart) - target = null - operated_bodypart = null - return ..() - - -/datum/surgery/proc/can_start(mob/user, mob/living/patient) //FALSE to not show in list - SHOULD_CALL_PARENT(TRUE) - - . = TRUE - if(replaced_by == /datum/surgery) - return FALSE - - // True surgeons (like abductor scientists) need no instructions - if(HAS_MIND_TRAIT(user, TRAIT_SURGEON)) - if(replaced_by) // only show top-level surgeries - return FALSE - else - return TRUE - - if(!requires_tech && !replaced_by) - return TRUE - - if(requires_tech) - . = FALSE - - var/surgery_signal = SEND_SIGNAL(user, COMSIG_SURGERY_STARTING, src, patient) - if(surgery_signal & COMPONENT_FORCE_SURGERY) - return TRUE - if(surgery_signal & COMPONENT_CANCEL_SURGERY) - return FALSE - - var/turf/patient_turf = get_turf(patient) - - //Get the relevant operating computer - var/obj/machinery/computer/operating/opcomputer = locate_operating_computer(patient_turf) - if (isnull(opcomputer)) - return . - if(replaced_by in opcomputer.advanced_surgeries) - return FALSE - if(type in opcomputer.advanced_surgeries) - return TRUE - return . - -/datum/surgery/proc/next_step(mob/living/user, modifiers) - if(location != user.zone_selected) - return FALSE - if(user.combat_mode) - return FALSE - if(step_in_progress) - return TRUE - - var/try_to_fail = FALSE - if(LAZYACCESS(modifiers, RIGHT_CLICK)) - try_to_fail = TRUE - - var/datum/surgery_step/surgery_step = GLOB.surgery_steps[steps[status]] - if(isnull(surgery_step)) - return FALSE - var/obj/item/tool = user.get_active_held_item() - if(tool) - tool = tool.get_proxy_attacker_for(target, user) - if(surgery_step.try_op(user, target, user.zone_selected, tool, src, try_to_fail)) - return TRUE - if(!tool) - return FALSE - //Just because you used the wrong tool it doesn't mean you meant to whack the patient with it - if((surgery_flags & SURGERY_CHECK_TOOL_BEHAVIOUR) ? tool.tool_behaviour : (tool.item_flags & SURGICAL_TOOL)) - to_chat(user, span_warning("This step requires a different tool!")) - return TRUE - - return FALSE - -/datum/surgery/proc/get_surgery_next_step() - if(status < steps.len) - var/step_type = steps[status + 1] - return new step_type - return null - -/datum/surgery/proc/complete(mob/surgeon) - SSblackbox.record_feedback("tally", "surgeries_completed", 1, type) - surgeon.add_mob_memory(/datum/memory/surgery, deuteragonist = surgeon, surgery_type = name) - qdel(src) - -/// Returns a nearby operating computer linked to an operating table -/datum/surgery/proc/locate_operating_computer(turf/patient_turf) - if (isnull(patient_turf)) - return null - - var/obj/structure/table/optable/operating_table = locate(/obj/structure/table/optable, patient_turf) - var/obj/machinery/computer/operating/operating_computer = operating_table?.computer - - if (isnull(operating_computer)) - return null - - if(operating_computer.machine_stat & (NOPOWER|BROKEN)) - return null - - return operating_computer - -/datum/surgery/advanced - name = "advanced surgery" - requires_tech = TRUE - -/obj/item/disk/surgery - name = "Surgery Procedure Disk" - desc = "A disk that contains advanced surgery procedures, must be loaded into an Operating Console." - icon_state = "datadisk1" - custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT * 3, /datum/material/glass=SMALL_MATERIAL_AMOUNT) - var/list/surgeries - -/obj/item/disk/surgery/debug - name = "Debug Surgery Disk" - desc = "A disk that contains all existing surgery procedures." - icon_state = "datadisk1" - custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT * 3, /datum/material/glass=SMALL_MATERIAL_AMOUNT) - -/obj/item/disk/surgery/debug/Initialize(mapload) - . = ..() - surgeries = list() - var/list/req_tech_surgeries = subtypesof(/datum/surgery) - for(var/datum/surgery/beep as anything in req_tech_surgeries) - if(initial(beep.requires_tech)) - surgeries += beep - -//INFO -//Check /mob/living/carbon/attackby for how surgery progresses, and also /mob/living/carbon/attack_hand. -//As of Feb 21 2013 they are in code/modules/mob/living/carbon/carbon.dm, lines 459 and 51 respectively. -//Other important variables are var/list/surgeries (/mob/living) and var/list/organs (/mob/living/carbon) -// var/list/bodyparts (/mob/living/carbon/human) is the LIMBS of a Mob. -//Surgical procedures are initiated by attempt_initiate_surgery(), which is called by surgical drapes and bedsheets. - - -//TODO -//specific steps for some surgeries (fluff text) -//more interesting failure options -//randomised complications -//more surgeries! -//add a probability modifier for the state of the surgeon- health, twitching, etc. blindness, god forbid. -//helper for converting a zone_sel.selecting to body part (for damage) - - -//RESOLVED ISSUES //"Todo" jobs that have been completed -//combine hands/feet into the arms - Hands/feet were removed - RR -//surgeries (not steps) that can be initiated on any body part (corresponding with damage locations) - Call this one done, see possible_locs var - c0 diff --git a/code/modules/surgery/surgery_disks.dm b/code/modules/surgery/surgery_disks.dm new file mode 100644 index 00000000000..6f8c1440f0b --- /dev/null +++ b/code/modules/surgery/surgery_disks.dm @@ -0,0 +1,59 @@ +/obj/item/disk/surgery + name = "surgery procedure disk" + desc = "A disk that contains advanced surgery procedures, must be loaded into an Operating Console." + icon_state = "datadisk1" + custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT * 3, /datum/material/glass=SMALL_MATERIAL_AMOUNT) + /// List of surgical operations contained on this disk + var/list/surgeries + +/obj/item/disk/surgery/debug + name = "debug surgery disk" + desc = "A disk that contains all existing surgery procedures." + icon_state = "datadisk1" + custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT * 3, /datum/material/glass=SMALL_MATERIAL_AMOUNT) + +/obj/item/disk/surgery/debug/Initialize(mapload) + . = ..() + surgeries = list() + for(var/datum/surgery_operation/operation as anything in GLOB.operations.get_instances_from(subtypesof(/datum/surgery_operation))) + surgeries += operation.type + +/obj/item/disk/surgery/advanced_plastic_surgery + name = "advanced plastic surgery disk" + desc = "Provides instructions on how to perform more intricate plastic surgeries." + + surgeries = list( + /datum/surgery_operation/limb/add_plastic, + ) + +/obj/item/disk/surgery/advanced_plastic_surgery/examine(mob/user) + . = ..() + . += span_info("Unlocks the [/datum/surgery_operation/limb/add_plastic::name] surgical operation.") + . += span_info("Performing this before a [/datum/surgery_operation/limb/plastic_surgery::name] upgrades the operation, \ + allowing you to copy the appearance of any individual - \ + provided you have a photo of them in your offhand during the surgery.") + +/obj/item/disk/surgery/advanced_plastic_surgery/Initialize(mapload) + . = ..() + AddElement(/datum/element/examine_lore, \ + lore = "Most forms of plastic surgery became obsolete due in no small part to advances in genetics technology. \ + Very basic methods still remain in use, but scarcely, and primarily to reverse a patient's disfigurements. \ + As a consequence, this item became an antique to many collectors - \ + though some back alley surgeons still seek one out for its now uncommon knowledge." \ + ) + +/obj/item/disk/surgery/brainwashing + name = "brainwashing surgery disk" + desc = "Provides instructions on how to impress an order on a brain, making it the primary objective of the patient." + surgeries = list( + /datum/surgery_operation/organ/brainwash, + /datum/surgery_operation/organ/brainwash/mechanic, + ) + +/obj/item/disk/surgery/sleeper_protocol + name = "suspicious surgery disk" + desc = "Provides instructions on how to convert a patient into a sleeper agent for the Syndicate." + surgeries = list( + /datum/surgery_operation/organ/brainwash/sleeper, + /datum/surgery_operation/organ/brainwash/sleeper/mechanic, + ) diff --git a/code/modules/surgery/surgery_helpers.dm b/code/modules/surgery/surgery_helpers.dm deleted file mode 100644 index 00beb0de449..00000000000 --- a/code/modules/surgery/surgery_helpers.dm +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Gets the surgery speed modifier for a given mob, based off what sort of table/bed/whatever is on their turf. - */ -/proc/get_location_modifier(mob/located_mob) - // Technically this IS a typecache, just not the usual kind :3 - var/static/list/modifiers = zebra_typecacheof(list( - /obj/structure/table = 0.8, - /obj/structure/table/optable = 1, - /obj/structure/table/optable/abductor = 1.2, - /obj/machinery/stasis = 0.9, - /obj/structure/bed = 0.7, - )) - . = 0.5 - for(var/obj/thingy in get_turf(located_mob)) - . = max(., modifiers[thingy.type]) - - -/proc/get_location_accessible(mob/located_mob, location) - var/covered_locations = 0 //based on body_parts_covered - var/face_covered = 0 //based on flags_inv - var/eyesmouth_covered = 0 //based on flags_cover - if(iscarbon(located_mob)) - var/mob/living/carbon/clothed_carbon = located_mob - for(var/obj/item/clothing/clothes in list(clothed_carbon.back, clothed_carbon.wear_mask, clothed_carbon.head)) - covered_locations |= clothes.body_parts_covered - face_covered |= clothes.flags_inv - eyesmouth_covered |= clothes.flags_cover - if(ishuman(clothed_carbon)) - var/mob/living/carbon/human/clothed_human = clothed_carbon - for(var/obj/item/clothes in list(clothed_human.wear_suit, clothed_human.w_uniform, clothed_human.shoes, clothed_human.belt, clothed_human.gloves, clothed_human.glasses, clothed_human.ears)) - covered_locations |= clothes.body_parts_covered - face_covered |= clothes.flags_inv - eyesmouth_covered |= clothes.flags_cover - - switch(location) - if(BODY_ZONE_HEAD) - if(covered_locations & HEAD) - return FALSE - if(BODY_ZONE_PRECISE_EYES) - if((face_covered & HIDEEYES) || (eyesmouth_covered & (MASKCOVERSEYES|HEADCOVERSEYES|GLASSESCOVERSEYES))) - return FALSE - if(BODY_ZONE_PRECISE_MOUTH) - if((face_covered & HIDEFACE) || (eyesmouth_covered & (MASKCOVERSMOUTH|HEADCOVERSMOUTH))) - return FALSE - if(BODY_ZONE_CHEST) - if(covered_locations & CHEST) - return FALSE - if(BODY_ZONE_PRECISE_GROIN) - if(covered_locations & GROIN) - return FALSE - if(BODY_ZONE_L_ARM) - if(covered_locations & ARM_LEFT) - return FALSE - if(BODY_ZONE_R_ARM) - if(covered_locations & ARM_RIGHT) - return FALSE - if(BODY_ZONE_L_LEG) - if(covered_locations & LEG_LEFT) - return FALSE - if(BODY_ZONE_R_LEG) - if(covered_locations & LEG_RIGHT) - return FALSE - if(BODY_ZONE_PRECISE_L_HAND) - if(covered_locations & HAND_LEFT) - return FALSE - if(BODY_ZONE_PRECISE_R_HAND) - if(covered_locations & HAND_RIGHT) - return FALSE - if(BODY_ZONE_PRECISE_L_FOOT) - if(covered_locations & FOOT_LEFT) - return FALSE - if(BODY_ZONE_PRECISE_R_FOOT) - if(covered_locations & FOOT_RIGHT) - return FALSE - - return TRUE diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm deleted file mode 100644 index 11ce9320b64..00000000000 --- a/code/modules/surgery/surgery_step.dm +++ /dev/null @@ -1,375 +0,0 @@ -#define SURGERY_STATE_STARTED "surgery_started" -#define SURGERY_STATE_FAILURE "surgery_failed" -#define SURGERY_STATE_SUCCESS "surgery_success" -#define SURGERY_MOOD_CATEGORY "surgery" - -/datum/surgery_step - var/name - var/list/implements = list() //format is path = probability of success. alternatively - var/implement_type = null //the current type of implement used. This has to be stored, as the actual typepath of the tool may not match the list type. - var/accept_hand = FALSE //does the surgery step require an open hand? If true, ignores implements. Compatible with accept_any_item. - var/accept_any_item = FALSE //does the surgery step accept any item? If true, ignores implements. Compatible with require_hand. - var/time = 1 SECONDS //how long does the step take? - var/repeatable = FALSE //can this step be repeated? Make shure it isn't last step, or else the surgeon will be stuck in the loop - var/list/chems_needed = list() //list of chems needed to complete the step. Even on success, the step will have no effect if there aren't the chems required in the mob. - var/require_all_chems = TRUE //any on the list or all on the list? - var/silicons_obey_prob = FALSE - var/preop_sound //Sound played when the step is started - var/success_sound //Sound played if the step succeeded - var/failure_sound //Sound played if the step fails - ///If the surgery causes mood changes if the patient is conscious. - var/surgery_effects_mood = FALSE - ///Which mood event to give the patient when surgery is starting while they're conscious. This should be permanent/not have a timer until the surgery either succeeds or fails, as those states will immediately replace it. Mostly just flavor text. - var/datum/mood_event/surgery/surgery_started_mood_event = /datum/mood_event/surgery - ///Which mood event to give the conscious patient when surgery succeeds. Lasts far shorter than if it failed. - var/datum/mood_event/surgery/surgery_success_mood_event = /datum/mood_event/surgery/success - ///Which mood event to give the consious patient when surgery fails. Lasts muuuuuch longer. - var/datum/mood_event/surgery/surgery_failure_mood_event = /datum/mood_event/surgery/failure - - -/datum/surgery_step/proc/try_op(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) - var/success = FALSE - if(surgery.organ_to_manipulate && !target.get_organ_slot(surgery.organ_to_manipulate)) - to_chat(user, span_warning("[target] seems to be missing the organ necessary to complete this surgery!")) - return FALSE - - if(accept_hand) - if(!tool) - success = TRUE - if(iscyborg(user)) - var/mob/living/silicon/robot/borg = user - if(istype(borg.module_active, /obj/item/borg/cyborghug)) - success = TRUE - - if(accept_any_item) - if(tool && tool_check(user, tool)) - success = TRUE - - else if(tool) - for(var/key in implements) - var/match = FALSE - - if(ispath(key) && istype(tool, key)) - match = TRUE - else if(tool.tool_behaviour == key) - match = TRUE - - if(match) - implement_type = key - if(tool_check(user, tool)) - success = TRUE - break - - if(success) - if(target_zone == surgery.location) - if(get_location_accessible(target, target_zone) || (surgery.surgery_flags & SURGERY_IGNORE_CLOTHES)) - initiate(user, target, target_zone, tool, surgery, try_to_fail) - else - to_chat(user, span_warning("You need to expose [target]'s [target.parse_zone_with_bodypart(target_zone)] to perform surgery on it!")) - return TRUE //returns TRUE so we don't stab the guy in the dick or wherever. - - if(repeatable) - var/datum/surgery_step/next_step = surgery.get_surgery_next_step() - if(next_step) - surgery.status++ - if(next_step.try_op(user, target, user.zone_selected, user.get_active_held_item(), surgery)) - return TRUE - else - surgery.status-- - - return FALSE - -#define SURGERY_SLOWDOWN_CAP_MULTIPLIER 2.5 //increase to make surgery slower but fail less, and decrease to make surgery faster but fail more -///Modifier given to surgery speed for dissected bodies. -#define SURGERY_SPEED_DISSECTION_MODIFIER 0.8 -///Modifier given to users with TRAIT_MORBID on certain surgeries -#define SURGERY_SPEED_MORBID_CURIOSITY 0.7 -///Modifier given to patients with TRAIT_ANALGESIA -#define SURGERY_SPEED_TRAIT_ANALGESIA 0.8 - -/datum/surgery_step/proc/initiate(mob/living/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) - // Only followers of Asclepius have the ability to use Healing Touch and perform miracle feats of surgery. - // Prevents people from performing multiple simultaneous surgeries unless they're holding a Rod of Asclepius. - - var/interaction_key = HAS_TRAIT(user, TRAIT_HIPPOCRATIC_OATH) ? target : DOAFTER_SOURCE_SURGERY - if(DOING_INTERACTION(user, interaction_key)) - user.balloon_alert(user, "already doing surgery!") - return FALSE - - if(!chem_check(target)) - user.balloon_alert(user, "missing [LOWER_TEXT(get_chem_list())]!") - to_chat(user, span_warning("[target] is missing the [LOWER_TEXT(get_chem_list())] required to perform this surgery step!")) - return FALSE - - if(preop(user, target, target_zone, tool, surgery) == SURGERY_STEP_FAIL) - update_surgery_mood(target, SURGERY_STATE_FAILURE) - return FALSE - - surgery.step_in_progress = TRUE - var/speed_mod = 1 - var/fail_prob = 0//100 - fail_prob = success_prob - var/advance = FALSE - - update_surgery_mood(target, SURGERY_STATE_STARTED) - play_preop_sound(user, target, target_zone, tool, surgery) // Here because most steps overwrite preop - - if(tool) - speed_mod = tool.toolspeed - - if(HAS_TRAIT(target, TRAIT_SURGICALLY_ANALYZED)) - speed_mod *= SURGERY_SPEED_DISSECTION_MODIFIER - - if(check_morbid_curiosity(user, tool, surgery)) - speed_mod *= SURGERY_SPEED_MORBID_CURIOSITY - - if(HAS_TRAIT(target, TRAIT_ANALGESIA)) - speed_mod *= SURGERY_SPEED_TRAIT_ANALGESIA - - var/implement_speed_mod = 1 - if(implement_type) //this means it isn't a require hand or any item step. - implement_speed_mod = implements[implement_type] / 100.0 - - //multiply speed_mod by sterilizer modifier - speed_mod *= surgery.speed_modifier - - speed_mod /= (get_location_modifier(target) * implement_speed_mod) * target.mob_surgery_speed_mod - var/modded_time = time * speed_mod - - - fail_prob = max(0, modded_time - (time * SURGERY_SLOWDOWN_CAP_MULTIPLIER)) //if modded_time > time * modifier, then fail_prob = modded_time - time*modifier - - var/list/user_modifiers = list(0, 1) - SEND_SIGNAL(user, COMSIG_LIVING_INITIATE_SURGERY_STEP, user, target, target_zone, tool, surgery, src, user_modifiers) - fail_prob += user_modifiers[FAIL_PROB_INDEX] - modded_time *= user_modifiers[SPEED_MOD_INDEX] - - var/list/target_modifiers = list(0, 1) - SEND_SIGNAL(target, COMSIG_LIVING_SURGERY_STEP_INITIATED_ON, user, target, target_zone, tool, surgery, src, target_modifiers) - fail_prob += target_modifiers[FAIL_PROB_INDEX] - modded_time *= target_modifiers[SPEED_MOD_INDEX] - - fail_prob = min(max(0, fail_prob),99) // clamp fail_prob between 0 and 99 - modded_time = min(modded_time, time * SURGERY_SLOWDOWN_CAP_MULTIPLIER)// cap modded_time at time*modifier - - if(iscyborg(user))//any immunities to surgery slowdown should go in this check. - modded_time = time * tool.toolspeed - - var/was_sleeping = (target.stat != DEAD && target.IsSleeping()) - - if(do_after(user, modded_time, target = target, interaction_key = interaction_key)) //If we have the hippocratic oath, we can perform one surgery on each target, otherwise we can only do one surgery in total. - - if((prob(100-fail_prob) || (iscyborg(user) && !silicons_obey_prob)) && !try_to_fail) - if(success(user, target, target_zone, tool, surgery)) - if((tool && tool.item_flags & CRUEL_IMPLEMENT) || (accept_hand && surgery.surgery_flags & SURGERY_MORBID_CURIOSITY && HAS_MIND_TRAIT(user, TRAIT_MORBID))) - update_surgery_mood(target, SURGERY_STATE_FAILURE) - else - update_surgery_mood(target, SURGERY_STATE_SUCCESS) - play_success_sound(user, target, target_zone, tool, surgery) - advance = TRUE - else - if(failure(user, target, target_zone, tool, surgery, fail_prob)) - play_failure_sound(user, target, target_zone, tool, surgery) - update_surgery_mood(target, SURGERY_STATE_FAILURE) - advance = TRUE - if(advance && !repeatable) - surgery.status++ - if(surgery.status > surgery.steps.len) - surgery.complete(user) - - else if(!QDELETED(target)) - update_surgery_mood(target, SURGERY_STATE_FAILURE) - - if(target.stat == DEAD && was_sleeping && user.client) - user.client.give_award(/datum/award/achievement/jobs/sandman, user) - - surgery.step_in_progress = FALSE - return advance - -/** - * Handles updating the mob's mood depending on the surgery states. - * * surgery_state = SURGERY_STATE_STARTED, SURGERY_STATE_FAILURE, SURGERY_STATE_SUCCESS - * * To prevent typos, the event category is defined as SURGERY_MOOD_CATEGORY ("surgery") -*/ -/datum/surgery_step/proc/update_surgery_mood(mob/living/target, surgery_state) - if(!target) - CRASH("Not passed a target, how did we get here?") - if(!surgery_effects_mood) - return - // Determine how drunk our patient is - var/drunken_patient = target.get_drunk_amount() - // Create a probability to ignore the pain based on drunkenness level - var/drunken_ignorance_probability = clamp(drunken_patient, 0, 90) - - if(HAS_TRAIT(target, TRAIT_ANALGESIA) || drunken_patient && prob(drunken_ignorance_probability)) - target.clear_mood_event(SURGERY_MOOD_CATEGORY) //incase they gained the trait mid-surgery (or became drunk). has the added side effect that if someone has a bad surgical memory/mood and gets drunk & goes back to surgery, they'll forget they hated it, which is kinda funny imo. - return - if(target.stat >= UNCONSCIOUS) - var/datum/mood_event/surgery/target_mood_event = target.mob_mood?.mood_events[SURGERY_MOOD_CATEGORY] - if(!target_mood_event || target_mood_event.surgery_completed) //don't give sleeping mobs trauma. that said, if they fell asleep mid-surgery after already getting the bad mood, lets make sure they wake up to a (hopefully) happy memory. - return - switch(surgery_state) - if(SURGERY_STATE_STARTED) - target.add_mood_event(SURGERY_MOOD_CATEGORY, surgery_started_mood_event) - if(SURGERY_STATE_SUCCESS) - target.add_mood_event(SURGERY_MOOD_CATEGORY, surgery_success_mood_event) - if(SURGERY_STATE_FAILURE) - target.add_mood_event(SURGERY_MOOD_CATEGORY, surgery_failure_mood_event) - else - CRASH("passed invalid surgery_state, \"[surgery_state]\".") - - -/datum/surgery_step/proc/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results( - user, - target, - span_notice("You begin to perform surgery on [target]..."), - span_notice("[user] begins to perform surgery on [target]."), - span_notice("[user] begins to perform surgery on [target]."), - ) - -/datum/surgery_step/proc/play_preop_sound(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/sound_file_use - if(islist(preop_sound)) - for(var/typepath in preop_sound)//iterate and assign subtype to a list, works best if list is arranged from subtype first and parent last - if(istype(tool, typepath)) - sound_file_use = preop_sound[typepath] - break - else - sound_file_use = preop_sound - if(!sound_file_use) - return - playsound(target, sound_file_use, 75, TRUE, falloff_exponent = 12, falloff_distance = 1) - -/datum/surgery_step/proc/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = TRUE) - SEND_SIGNAL(user, COMSIG_MOB_SURGERY_STEP_SUCCESS, src, target, target_zone, tool, surgery, default_display_results) - if(default_display_results) - display_results( - user, - target, - span_notice("You succeed."), - span_notice("[user] succeeds!"), - span_notice("[user] finishes."), - ) - if(ishuman(user)) - var/mob/living/carbon/human/surgeon = user - surgeon.add_blood_DNA_to_items(target.get_blood_dna_list(), ITEM_SLOT_GLOVES) - else - user.add_mob_blood(target) - return TRUE - -/datum/surgery_step/proc/play_success_sound(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(!success_sound) - return - playsound(get_turf(target), success_sound, 75, TRUE, falloff_exponent = 12, falloff_distance = 1) - -/datum/surgery_step/proc/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob = 0) - var/screwedmessage = "" - switch(fail_prob) - if(0 to 24) - screwedmessage = " You almost had it, though." - if(50 to 74)//25 to 49 = no extra text - screwedmessage = " This is hard to get right in these conditions..." - if(75 to 99) - screwedmessage = " This is practically impossible in these conditions..." - - display_results( - user, - target, - span_warning("You screw up![screwedmessage]"), - span_warning("[user] screws up!"), - span_notice("[user] finishes."), TRUE) //By default the patient will notice if the wrong thing has been cut - return FALSE - -/datum/surgery_step/proc/play_failure_sound(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(!failure_sound) - return - playsound(get_turf(target), failure_sound, 75, TRUE, falloff_exponent = 12, falloff_distance = 1) - -/datum/surgery_step/proc/tool_check(mob/user, obj/item/tool) - return TRUE - -/datum/surgery_step/proc/chem_check(mob/living/target) - if(!LAZYLEN(chems_needed)) - return TRUE - - if(require_all_chems) - . = TRUE - for(var/reagent in chems_needed) - if(!target.reagents.has_reagent(reagent)) - return FALSE - else - . = FALSE - for(var/reagent in chems_needed) - if(target.reagents.has_reagent(reagent)) - return TRUE - -/datum/surgery_step/proc/get_chem_list() - if(!LAZYLEN(chems_needed)) - return - var/list/chems = list() - for(var/reagent in chems_needed) - var/datum/reagent/temp = GLOB.chemical_reagents_list[reagent] - if(temp) - var/chemname = temp.name - chems += chemname - return english_list(chems, and_text = require_all_chems ? " and " : " or ") - -// Check if we are entitled to morbid bonuses -/datum/surgery_step/proc/check_morbid_curiosity(mob/user, obj/item/tool, datum/surgery/surgery) - if(!(surgery.surgery_flags & SURGERY_MORBID_CURIOSITY)) - return FALSE - if(!HAS_MIND_TRAIT(user, TRAIT_MORBID)) - return FALSE - if(!tool && accept_hand) - return TRUE - if(tool && !(tool.item_flags & CRUEL_IMPLEMENT)) - return FALSE - return TRUE - -//Replaces visible_message during operations so only people looking over the surgeon can see them. -/datum/surgery_step/proc/display_results(mob/user, mob/living/target, self_message, detailed_message, vague_message, target_detailed = FALSE) - user.visible_message(detailed_message, self_message, vision_distance = 1, ignored_mobs = target_detailed ? null : target) - if(!target_detailed) - var/you_feel = pick("a brief pain", "your body tense up", "an unnerving sensation") - if(!vague_message) - if(detailed_message) - stack_trace("DIDN'T GET PASSED A VAGUE MESSAGE.") - vague_message = detailed_message - else - stack_trace("NO MESSAGES TO SEND TO TARGET!") - vague_message = span_notice("You feel [you_feel] as you are operated on.") - target.show_message(vague_message, MSG_VISUAL, span_notice("You feel [you_feel] as you are operated on.")) -/** - * Sends a pain message to the target, including a chance of screaming. - * - * Arguments: - * * target - Who the message will be sent to - * * pain_message - The message to be displayed - * * mechanical_surgery - Boolean flag that represents if a surgery step is done on a mechanical limb (therefore does not force scream) - */ -/datum/surgery_step/proc/display_pain(mob/living/target, pain_message, mechanical_surgery = FALSE) - // Determine how drunk our patient is - var/drunken_patient = target.get_drunk_amount() - // Create a probability to ignore the pain based on drunkenness level - var/drunken_ignorance_probability = clamp(drunken_patient, 0, 90) - - if(target.stat < UNCONSCIOUS) - if(HAS_TRAIT(target, TRAIT_ANALGESIA) || drunken_patient && prob(drunken_ignorance_probability)) - if(!pain_message) - return - to_chat(target, span_notice("You feel a dull, numb sensation as your body is surgically operated on.")) - else - if(!pain_message) - return - to_chat(target, span_userdanger(pain_message)) - if(prob(30) && !mechanical_surgery) - target.emote("scream") - -#undef SURGERY_SPEED_TRAIT_ANALGESIA -#undef SURGERY_SPEED_DISSECTION_MODIFIER -#undef SURGERY_SPEED_MORBID_CURIOSITY -#undef SURGERY_SLOWDOWN_CAP_MULTIPLIER -#undef SURGERY_STATE_STARTED -#undef SURGERY_STATE_FAILURE -#undef SURGERY_STATE_SUCCESS -#undef SURGERY_MOOD_CATEGORY diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/surgery_tools.dm similarity index 97% rename from code/modules/surgery/tools.dm rename to code/modules/surgery/surgery_tools.dm index ee863fb54cf..b271d70a81b 100644 --- a/code/modules/surgery/tools.dm +++ b/code/modules/surgery/surgery_tools.dm @@ -332,10 +332,11 @@ attack_verb_simple = list("slap") drop_sound = SFX_CLOTH_DROP pickup_sound = SFX_CLOTH_PICKUP + gender = PLURAL /obj/item/surgical_drapes/Initialize(mapload) . = ..() - AddComponent(/datum/component/surgery_initiator) + AddElement(/datum/element/surgery_aid, name) /obj/item/surgical_drapes/cyborg icon = 'icons/mob/silicon/robot_items.dmi' @@ -354,7 +355,7 @@ /obj/item/surgical_processor/Initialize(mapload) . = ..() - AddComponent(/datum/component/surgery_initiator) + AddElement(/datum/element/surgery_aid, /obj/item/surgical_drapes::name) // i guess it's a drape dispenser /obj/item/surgical_processor/examine(mob/user) . = ..() @@ -362,22 +363,20 @@ . += span_boldnotice("Advanced surgeries available:") //list of downloaded surgeries' names var/list/surgeries_names = list() - for(var/datum/surgery/downloaded_surgery as anything in loaded_surgeries) - if(initial(downloaded_surgery.replaced_by) in loaded_surgeries) //if a surgery has a better version replacing it, we don't include it in the list - continue - surgeries_names += "[initial(downloaded_surgery.name)]" + for(var/datum/surgery_operation/downloaded_surgery as anything in GLOB.operations.get_instances_from(loaded_surgeries)) + surgeries_names += "[capitalize(downloaded_surgery.name)]" . += span_notice("[english_list(surgeries_names)]") /obj/item/surgical_processor/equipped(mob/user, slot, initial) . = ..() if(!(slot & ITEM_SLOT_HANDS)) - UnregisterSignal(user, COMSIG_SURGERY_STARTING) + UnregisterSignal(user, COMSIG_LIVING_OPERATING_ON) return - RegisterSignal(user, COMSIG_SURGERY_STARTING, PROC_REF(check_surgery)) + RegisterSignal(user, COMSIG_LIVING_OPERATING_ON, PROC_REF(check_surgery), override = TRUE) /obj/item/surgical_processor/dropped(mob/user, silent) . = ..() - UnregisterSignal(user, COMSIG_SURGERY_STARTING) + UnregisterSignal(user, COMSIG_LIVING_OPERATING_ON) /obj/item/surgical_processor/interact_with_atom(atom/design_holder, mob/living/user, list/modifiers) if(!istype(design_holder, /obj/item/disk/surgery) && !istype(design_holder, /obj/machinery/computer/operating)) @@ -402,13 +401,10 @@ if(downloaded) . += mutable_appearance(src.icon, "+downloaded") -/obj/item/surgical_processor/proc/check_surgery(mob/user, datum/surgery/surgery, mob/patient) +/obj/item/surgical_processor/proc/check_surgery(datum/source, mob/living/patient, list/operations) SIGNAL_HANDLER - if(surgery.replaced_by in loaded_surgeries) - return COMPONENT_CANCEL_SURGERY - if(surgery.type in loaded_surgeries) - return COMPONENT_FORCE_SURGERY + operations |= loaded_surgeries /obj/item/scalpel/advanced name = "laser scalpel" @@ -428,6 +424,7 @@ light_power = 0.4 light_color = LIGHT_COLOR_BLUE sharpness = SHARP_EDGED + item_flags = parent_type::item_flags | NO_BLOOD_ON_ITEM /obj/item/scalpel/advanced/get_all_tool_behaviours() return list(TOOL_SAW, TOOL_SCALPEL) diff --git a/code/modules/unit_tests/designs.dm b/code/modules/unit_tests/designs.dm index 134c9ed726f..a9b293ed24a 100644 --- a/code/modules/unit_tests/designs.dm +++ b/code/modules/unit_tests/designs.dm @@ -3,11 +3,8 @@ /datum/unit_test/designs/Run() //Can't use allocate because of bug with certain datums var/datum/design/default_design = new /datum/design() - var/datum/design/surgery/default_design_surgery = new /datum/design/surgery() - for(var/path in subtypesof(/datum/design)) - if (ispath(path, /datum/design/surgery)) //We are checking surgery design separatly later since they work differently - continue + for(var/path in subtypesof(/datum/design) - typesof(/datum/design/surgery)) //We are checking surgery design separatly later since they work differently var/datum/design/current_design = new path //Create an instance of each design if (current_design.id == DESIGN_ID_IGNORE) //Don't check designs with ignore ID continue @@ -21,28 +18,25 @@ if (length(current_design.reagents_list) && !(current_design.build_type & LIMBGROWER)) TEST_FAIL("Design [current_design.type] requires reagents but isn't a limb grower design. Reagent costs are only supported by limb grower designs") - for(var/path in subtypesof(/datum/design/surgery)) - var/datum/design/surgery/current_design = new path //Create an instance of each design - if (isnull(current_design.id) || current_design.id == default_design_surgery.id) //Check if ID was not set - TEST_FAIL("Surgery Design [current_design.type] has no ID set") - if (isnull(current_design.id) || current_design.name == default_design_surgery.name) //Check if name was not set - TEST_FAIL("Surgery Design [current_design.type] has default or null name var") - if (isnull(current_design.desc) || current_design.desc == default_design_surgery.desc) //Check if desc was not set - TEST_FAIL("Surgery Design [current_design.type] has default or null desc var") - if (isnull(current_design.surgery) || current_design.surgery == default_design_surgery.surgery) //Check if surgery was not set - TEST_FAIL("Surgery Design [current_design.type] has default or null surgery var") + for(var/datum/design/surgery/path as anything in subtypesof(/datum/design/surgery)) + if (path::id == DESIGN_ID_IGNORE) + TEST_FAIL("Surgery Design [path] has no ID set") + if (isnull(path::surgery)) + TEST_FAIL("Surgery Design [path] has null surgery var") + continue + if (isnull(path::name) && isnull(path::surgery::rnd_name) && isnull(path::surgery::name)) + TEST_FAIL("Surgery Design [path] has no name set or inferable from surgery type") + if (isnull(path::desc) && isnull(path::surgery::rnd_desc) && isnull(path::surgery::desc)) + TEST_FAIL("Surgery Design [path] has no desc set or inferable from surgery type") /datum/unit_test/design_source /datum/unit_test/design_source/Run() var/list/all_designs = list() - var/list/exceptions = list( - /datum/design/surgery/healing, // Ignored due to the above test - ) for (var/datum/design/design as anything in subtypesof(/datum/design)) design = new design() - if (design.id == DESIGN_ID_IGNORE || (design.type in exceptions)) + if (design.id == DESIGN_ID_IGNORE) continue if (design.id in all_designs) TEST_FAIL("Design [design.type] shares an ID \"[design.id]\" with another design") diff --git a/code/modules/unit_tests/omnitools.dm b/code/modules/unit_tests/omnitools.dm index 3a04ade9b76..5a4e66cd3c8 100644 --- a/code/modules/unit_tests/omnitools.dm +++ b/code/modules/unit_tests/omnitools.dm @@ -30,3 +30,18 @@ //unequip borg.drop_all_held_items() + +/datum/unit_test/omnitool_icons + +/datum/unit_test/omnitool_icons/Run() + var/list/all_tools = GLOB.all_tool_behaviours.Copy() + for(var/tool, tool_image in GLOB.tool_to_image) + if(!(tool in GLOB.all_tool_behaviours)) + TEST_FAIL("Tool behaviour [tool] has an image defined in global tool_to_image but is not present in all_tool_behaviours list.") + var/image/tool_image_real = tool_image + if(!icon_exists(tool_image_real.icon, tool_image_real.icon_state)) + TEST_FAIL("Tool image for [tool] not found ([tool_image_real.icon], [tool_image_real.icon_state])") + all_tools -= tool + + for(var/missing_tool in all_tools) + TEST_FAIL("No tool image defined for tool behaviour [missing_tool] in global tool_to_image") diff --git a/code/modules/unit_tests/organ_bodypart_shuffle.dm b/code/modules/unit_tests/organ_bodypart_shuffle.dm index 11c0bcd71be..e05c6bd0bd9 100644 --- a/code/modules/unit_tests/organ_bodypart_shuffle.dm +++ b/code/modules/unit_tests/organ_bodypart_shuffle.dm @@ -7,26 +7,31 @@ // Test if organs are all properly updating when forcefully removed var/list/removed_organs = list() + // 1. remove all the organs from the mob for(var/obj/item/organ/organ as anything in hollow_boy.organs) organ.moveToNullspace() removed_organs += organ + // 2. ensure removed organs proper disassociate from the mob and the bodypart for(var/obj/item/organ/organ as anything in removed_organs) TEST_ASSERT(!(organ in hollow_boy.organs), "Organ '[organ.name] remained inside human after forceMove into nullspace.") - TEST_ASSERT(organ.loc == null, "Organ '[organ.name] did not move to nullspace after being forced to.") - TEST_ASSERT(!(organ.owner), "Organ '[organ.name] kept reference to human after forceMove into nullspace.") - TEST_ASSERT(!(organ.bodypart_owner), "Organ '[organ.name] kept reference to bodypart after forceMove into nullspace.") + TEST_ASSERT_NULL(organ.loc, "Organ '[organ.name] did not move to nullspace after being forced to.") + TEST_ASSERT_NULL(organ.owner, "Organ '[organ.name] kept reference to human after forceMove into nullspace.") + TEST_ASSERT_NULL(organ.bodypart_owner, "Organ '[organ.name] kept reference to bodypart after forceMove into nullspace.") + // 3. replace all bodyparts with new ones and place the previously removed organs into the new bodyparts for(var/obj/item/bodypart/bodypart as anything in hollow_boy.bodyparts) - bodypart = new bodypart.type() //fresh, duplice bodypart with no insides + var/obj/item/bodypart/replacement = allocate(bodypart.type) for(var/obj/item/organ/organ as anything in removed_organs) - if(bodypart.body_zone != deprecise_zone(organ.zone)) + if(replacement.body_zone != deprecise_zone(organ.zone)) continue - organ.bodypart_insert(bodypart) // Put all the old organs back in - bodypart.replace_limb(hollow_boy) //so stick new bodyparts on them with their old organs - // Check if, after we put the old organs in a new limb, and after we put that new limb on the mob, if the organs came with - for(var/obj/item/organ/organ as anything in removed_organs) //technically readded organ now - if(bodypart.body_zone != deprecise_zone(organ.zone)) - continue - TEST_ASSERT(organ in hollow_boy.organs, "Organ '[organ.name] was put in an empty bodypart that replaced a humans, but the organ did not come with.") + organ.bodypart_insert(replacement) + if(!replacement.replace_limb(hollow_boy)) + TEST_FAIL("Failed to replace [replacement] with a new one of the same type.") + qdel(bodypart) // it's been replaced, clean up + // 4. ensure organs are properly associated with the new bodyparts and the mob + for(var/obj/item/organ/organ as anything in removed_organs) + TEST_ASSERT(organ in hollow_boy.organs, "Organ '[organ.name] was put in an empty bodypart that replaced a humans, but the organ did not come with.") + TEST_ASSERT(organ.owner == hollow_boy, "Organ '[organ.name]'s owner was not properly updated to the new human after being placed in a replacement bodypart.") + TEST_ASSERT(organ.bodypart_owner in hollow_boy.bodyparts, "Organ '[organ.name]'s bodypart_owner was not properly updated to the new bodypart after being placed in a replacement bodypart.") diff --git a/code/modules/unit_tests/surgeries.dm b/code/modules/unit_tests/surgeries.dm index dbec8532ecb..deb154742f8 100644 --- a/code/modules/unit_tests/surgeries.dm +++ b/code/modules/unit_tests/surgeries.dm @@ -1,13 +1,13 @@ /datum/unit_test/amputation/Run() var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent) var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent) + var/obj/item/circular_saw/saw = allocate(/obj/item/circular_saw) TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 0, "Patient is somehow missing limbs before surgery") - var/datum/surgery/amputation/surgery = new(patient, BODY_ZONE_R_ARM, patient.get_bodypart(BODY_ZONE_R_ARM)) + var/datum/surgery_operation/limb/amputate/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] - var/datum/surgery_step/sever_limb/sever_limb = new - sever_limb.success(user, patient, BODY_ZONE_R_ARM, null, surgery) + UNLINT(surgery.success(patient.get_bodypart(BODY_ZONE_R_ARM), user, saw, list())) TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 1, "Patient did not lose any limbs") TEST_ASSERT_EQUAL(patient.get_missing_limbs()[1], BODY_ZONE_R_ARM, "Patient is missing a limb that isn't the one we operated on") @@ -20,9 +20,10 @@ TEST_ASSERT(patient.has_trauma_type(), "Patient does not have any traumas, despite being given one") var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent) + var/obj/item/hemostat/hemostat = allocate(/obj/item/hemostat) - var/datum/surgery_step/fix_brain/fix_brain = new - fix_brain.success(user, patient) + var/datum/surgery_operation/organ/repair/brain/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + UNLINT(surgery.success(patient.get_organ_slot(ORGAN_SLOT_BRAIN), user, hemostat, list())) TEST_ASSERT(!patient.has_trauma_type(), "Patient kept their brain trauma after brain surgery") TEST_ASSERT(patient.get_organ_loss(ORGAN_SLOT_BRAIN) < 20, "Patient did not heal their brain damage after brain surgery") @@ -56,9 +57,9 @@ TEST_ASSERT_EQUAL(bobs_head.real_name, "Bob", "Bob's head does not remember that it is from Bob") // Put Bob's head onto Alice's body - var/datum/surgery_step/add_prosthetic/add_prosthetic = new + var/datum/surgery_operation/prosthetic_replacement/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] user.put_in_active_hand(bobs_head) - add_prosthetic.success(user, alice, BODY_ZONE_HEAD, bobs_head) + UNLINT(surgery.success(alice.get_bodypart(BODY_ZONE_CHEST), user, bobs_head, list())) TEST_ASSERT(!isnull(alice.get_bodypart(BODY_ZONE_HEAD)), "Alice has no head after prosthetic replacement") TEST_ASSERT_EQUAL(alice.get_visible_name(), "Bob", "Bob's head was transplanted onto Alice's body, but their name is not Bob") @@ -69,56 +70,71 @@ /datum/unit_test/multiple_surgeries/Run() var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent/slow) + ADD_TRAIT(user, TRAIT_HIPPOCRATIC_OATH, TRAIT_SOURCE_UNIT_TESTS) + var/mob/living/carbon/human/patient_zero = allocate(/mob/living/carbon/human/consistent) var/mob/living/carbon/human/patient_one = allocate(/mob/living/carbon/human/consistent) + patient_zero.set_body_position(LYING_DOWN) + patient_one.set_body_position(LYING_DOWN) + + ADD_TRAIT(patient_zero, TRAIT_READY_TO_OPERATE, TRAIT_SOURCE_UNIT_TESTS) + ADD_TRAIT(patient_one, TRAIT_READY_TO_OPERATE, TRAIT_SOURCE_UNIT_TESTS) + + var/obj/item/bodypart/chest/patient_zero_chest = patient_zero.get_bodypart(BODY_ZONE_CHEST) + var/obj/item/bodypart/chest/patient_one_chest = patient_one.get_bodypart(BODY_ZONE_CHEST) + + ADD_TRAIT(patient_zero_chest, TRAIT_READY_TO_OPERATE, TRAIT_SOURCE_UNIT_TESTS) + ADD_TRAIT(patient_one_chest, TRAIT_READY_TO_OPERATE, TRAIT_SOURCE_UNIT_TESTS) + var/obj/item/scalpel/scalpel = allocate(/obj/item/scalpel) + user.put_in_active_hand(scalpel) - var/datum/surgery_step/incise/surgery_step = new - var/datum/surgery/organ_manipulation/surgery_for_zero = new + ASYNC + user.perform_surgery(patient_zero, scalpel) - INVOKE_ASYNC(surgery_step, TYPE_PROC_REF(/datum/surgery_step, initiate), user, patient_zero, BODY_ZONE_CHEST, scalpel, surgery_for_zero) - TEST_ASSERT(surgery_for_zero.step_in_progress, "Surgery on patient zero was not initiated") + TEST_ASSERT(DOING_INTERACTION(user, patient_zero), "User is not performing surgery on patient zero as expected") - var/datum/surgery/organ_manipulation/surgery_for_one = new + ASYNC + user.perform_surgery(patient_one, scalpel) - // Without waiting for the incision to complete, try to start a new surgery - TEST_ASSERT(!surgery_step.initiate(user, patient_one, BODY_ZONE_CHEST, scalpel, surgery_for_one), "Was allowed to start a second surgery without the rod of asclepius") - TEST_ASSERT(!surgery_for_one.step_in_progress, "Surgery for patient one is somehow in progress, despite not initiating") + TEST_ASSERT(DOING_INTERACTION(user, patient_one), "User is not able to perform surgery on two patients at once despite having the Hippocratic Oath trait") - user.apply_status_effect(/datum/status_effect/hippocratic_oath) - INVOKE_ASYNC(surgery_step, TYPE_PROC_REF(/datum/surgery_step, initiate), user, patient_one, BODY_ZONE_CHEST, scalpel, surgery_for_one) - TEST_ASSERT(surgery_for_one.step_in_progress, "Surgery on patient one was not initiated, despite having rod of asclepius") - -/// Ensures that the tend wounds surgery can be started +// Ensures that the tend wounds surgery can be started /datum/unit_test/start_tend_wounds /datum/unit_test/start_tend_wounds/Run() var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent) var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent) + var/obj/item/hemostat/hemostat = allocate(/obj/item/hemostat) - var/datum/surgery/surgery = new /datum/surgery/healing/brute/basic + patient.set_body_position(LYING_DOWN) - if (!surgery.can_start(user, patient)) - TEST_FAIL("Can't start basic tend wounds!") + ADD_TRAIT(patient, TRAIT_READY_TO_OPERATE, TRAIT_SOURCE_UNIT_TESTS) + var/obj/item/bodypart/chest/patient_chest = patient.get_bodypart(BODY_ZONE_CHEST) + ADD_TRAIT(patient_chest, TRAIT_READY_TO_OPERATE, TRAIT_SOURCE_UNIT_TESTS) - qdel(surgery) + var/datum/surgery_operation/basic/tend_wounds/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + TEST_ASSERT(!surgery.check_availability(patient, patient, user, hemostat, BODY_ZONE_CHEST), "Tend wounds surgery was available on an undamaged, unoperated patient") + + patient.take_overall_damage(10, 10) + TEST_ASSERT(!surgery.check_availability(patient, patient, user, hemostat, BODY_ZONE_CHEST), "Tend wounds surgery was available on a damaged but unoperated patient") + + patient_chest.add_surgical_state(SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED) + TEST_ASSERT(surgery.check_availability(patient, patient, user, hemostat, BODY_ZONE_CHEST), "Tend wounds surgery was not available on a damaged, operated patient") /datum/unit_test/tend_wounds/Run() var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent) patient.take_overall_damage(100, 100) var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent) + var/obj/item/hemostat/hemostat = allocate(/obj/item/hemostat) // Test that tending wounds actually lowers damage - var/datum/surgery_step/heal/brute/basic/basic_brute_heal = new - basic_brute_heal.success(user, patient, BODY_ZONE_CHEST) + var/datum/surgery_operation/basic/tend_wounds/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + UNLINT(surgery.success(patient, user, hemostat, list("[OPERATION_BRUTE_HEAL]" = 10, "[OPERATION_BRUTE_MULTIPLIER]" = 0.1))) TEST_ASSERT(patient.get_brute_loss() < 100, "Tending brute wounds didn't lower brute damage ([patient.get_brute_loss()])") - var/datum/surgery_step/heal/burn/basic/basic_burn_heal = new - basic_burn_heal.success(user, patient, BODY_ZONE_CHEST) - TEST_ASSERT(patient.get_fire_loss() < 100, "Tending burn wounds didn't lower burn damage ([patient.get_fire_loss()])") - // Test that wearing clothing lowers heal amount var/mob/living/carbon/human/naked_patient = allocate(/mob/living/carbon/human/consistent) naked_patient.take_overall_damage(100) @@ -127,8 +143,8 @@ clothed_patient.equipOutfit(/datum/outfit/job/doctor, TRUE) clothed_patient.take_overall_damage(100) - basic_brute_heal.success(user, naked_patient, BODY_ZONE_CHEST) - basic_brute_heal.success(user, clothed_patient, BODY_ZONE_CHEST) + UNLINT(surgery.success(naked_patient, user, hemostat, list("[OPERATION_BRUTE_HEAL]" = 10, "[OPERATION_BRUTE_MULTIPLIER]" = 0.1))) + UNLINT(surgery.success(clothed_patient, user, hemostat, list("[OPERATION_BRUTE_HEAL]" = 10, "[OPERATION_BRUTE_MULTIPLIER]" = 0.1))) TEST_ASSERT(naked_patient.get_brute_loss() < clothed_patient.get_brute_loss(), "Naked patient did not heal more from wounds tending than a clothed patient") @@ -153,3 +169,78 @@ nullrod.on_selected(null, null, picker) TEST_ASSERT(HAS_TRAIT_FROM(nullrod, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT), "Chainsaw nullrod item attachment failed! Item does not have the nodrop trait") + +/// Checks all operations have a name and description +/datum/unit_test/verify_surgery_setup + +/datum/unit_test/verify_surgery_setup/Run() + for(var/datum/surgery_operation/operation as anything in GLOB.operations.get_instances_from(subtypesof(/datum/surgery_operation), filter_replaced = FALSE)) + if (isnull(operation.name)) + TEST_FAIL("Surgery operation [operation.type] has no name set") + if (isnull(operation.desc)) + TEST_FAIL("Surgery operation [operation.type] has no description set") + +/// Checks replaced surgeries are filtered out correctly +/datum/unit_test/verify_surgery_replacements + +/datum/unit_test/verify_surgery_replacements/Run() + for(var/datum/surgery_operation/operation as anything in GLOB.operations.get_instances_from(subtypesof(/datum/surgery_operation), filter_replaced = TRUE)) + if(!operation.replaced_by || operation.replaced_by.type == operation.type) + continue + TEST_FAIL("Surgery operation [operation.type] is marked as replaced by [operation.replaced_by.type], \ + but the operation was not correctly filtered by get_instances.") + +/// Tests that make incision shows up when expected +/datum/unit_test/incision_check + +/datum/unit_test/incision_check/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/surgeon = allocate(/mob/living/carbon/human/consistent) + var/obj/item/scalpel/scalpel = allocate(/obj/item/scalpel) + var/obj/item/bodypart/chest/chest = patient.get_bodypart(BODY_ZONE_CHEST) + var/list/operations + + ADD_TRAIT(patient, TRAIT_READY_TO_OPERATE, TRAIT_SOURCE_UNIT_TESTS) + ADD_TRAIT(chest, TRAIT_READY_TO_OPERATE, TRAIT_SOURCE_UNIT_TESTS) + + surgeon.put_in_active_hand(scalpel) + operations = surgeon.get_available_operations(patient, scalpel, BODY_ZONE_CHEST) + TEST_ASSERT_EQUAL(length(operations), 0, "Surgery operations were available on a standing patient") + + patient.set_body_position(LYING_DOWN) + operations = surgeon.get_available_operations(patient, scalpel, BODY_ZONE_CHEST) + if(length(operations) > 1) + TEST_FAIL("More operations than expected were available on the patient") + return + + if(length(operations) == 1) + var/list/found_operation_data = operations[operations[1]] + var/datum/surgery_operation/operation = found_operation_data[1] + var/atom/movable/operating_on = found_operation_data[2] + TEST_ASSERT_EQUAL(operation.type, /datum/surgery_operation/limb/incise_skin, "The available surgery operation was not \"make incision\"") + TEST_ASSERT_EQUAL(operating_on, patient.get_bodypart(BODY_ZONE_CHEST), "The available surgery operation was not on the chest bodypart") + return + + TEST_ASSERT_EQUAL(patient.body_position, LYING_DOWN, "Patient is not lying down as expected") + + var/datum/surgery_operation/incise_operation = GLOB.operations.operations_by_typepath[/datum/surgery_operation/limb/incise_skin] + var/atom/movable/operate_on = incise_operation.get_operation_target(patient, BODY_ZONE_CHEST) + TEST_ASSERT_EQUAL(operate_on, patient.get_bodypart(BODY_ZONE_CHEST), "Incise skin operation did not return the chest bodypart as a valid operation target") + + if(incise_operation.check_availability(patient, operate_on, surgeon, scalpel, BODY_ZONE_CHEST)) + TEST_FAIL("Make incision operation was not found among available operations despite being available") + else + TEST_FAIL("Make incision operation was not available when it should have been") + +/// Checks is_location_accessible works as intended +/datum/unit_test/location_accessibility + +/datum/unit_test/location_accessibility/Run() + var/mob/living/carbon/human/test_mob = allocate(/mob/living/carbon/human/consistent) + + test_mob.equipOutfit(/datum/outfit/job/assistant/consistent) + TEST_ASSERT(!test_mob.is_location_accessible(BODY_ZONE_CHEST), "Chest should be inaccessible when wearing a jumpsuit") + + var/obj/item/clothing/under/jumpsuit = test_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING) + jumpsuit.adjust_to_alt() + TEST_ASSERT(test_mob.is_location_accessible(BODY_ZONE_CHEST), "Chest should be accessible after rolling jumpsuit down") diff --git a/strings/tips.txt b/strings/tips.txt index 0ae2546f7e8..37fd842d1de 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -67,14 +67,17 @@ As a Janitor, you can command cleanbots by pointing at a tile and saying mop/cle As a Lawyer, try to negotiate with the Warden if sentences seem too high for the crime. As a Lawyer, you can try to convince the Captain and Head of Security to hold trials for prisoners in the courtroom. As a Medical Cyborg, you can fully perform surgery and even augment people. +As a Medical Doctor, Saline-Glucose not only acts as a temporary boost to a patient's blood level, it also speeds blood regeneration! Perfect for drained patients! As a Medical Doctor, a shower can be used to help recover from sleep, unconsciousness, confusion, drowsiness, jitters, dizziness, and drunkness. As a Medical Doctor, almost every type of wound can be treated at least temporarily with gauze. When in doubt, wrap it up! +As a Medical Doctor, better environments make for faster surgeries. Perform surgery on an operating computer and using Sterilizine on the patient will speed up the process. As a Medical Doctor, corpses placed inside a freezer or morgue tray will have their organs frozen preventing decay. If you don't have time to revive multiple dead bodies, transfer them to the morgue temporarily! -As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and can't be revived. -As a Medical Doctor, Critical Slash wounds are one of the most dangerous conditions someone can have. Apply gauze, epipens, sutures, cauteries, whatever you can, as soon as possible! -As a Medical Doctor, Saline-Glucose not only acts as a temporary boost to a patient's blood level, it also speeds blood regeneration! Perfect for drained patients! -As a Medical Doctor, treating plasmamen is not impossible! Salbutamol stops them from suffocating and showers stop them from burning alive. You can even perform surgery on them by doing the procedure on a roller bed under a shower. Salbutamol and convermol do NOT heal their oxyloss damage. -As a Medical Doctor, try messing with the virology lab sometime! Viruses can range from healing powers so great that you can heal out of critical status, or diseases so dangerous they can kill the entire crew with airborne spontaneous combustion. Experiment! +As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them - you can revive them, but no player will take control, making it largely pointless. +As a Medical Doctor, critical slash wounds are one of the most dangerous conditions someone can have. Apply gauze, epipens, sutures, cauteries, whatever you can, as soon as possible! +As a Medical Doctor, operating computers contain instructions on how to complete every surgery you could dream of. If you are ever confused, use one to guide you through the process. +As a Medical Doctor, treating Plasmamen is not impossible! Stasis machines will keep them from burning and suffocating while you perform necessary surgeries. If you don't have the luxury of one, you can use Salbutamol to prevent suffocation and keep them under a running shower to prevent fire. +As a Medical Doctor, try messing with the Virology lab sometime! Viruses can range from healing powers so great that you can heal out of critical status, or diseases so dangerous they can kill the entire crew with airborne spontaneous combustion. Experiment! +As a Medical Doctor, unless your patient is in stasis, they will lose blood as you perform surgery on them - especially before you clamp bleeders. Keep an eye on their blood levels, work quickly, and most importantly don't forget to cauterize or suture them after you're finished! As a Medical Doctor, when messing with viruses, remember that robotic organs can give immunity to disease effects and transmissibility. Make use of the inorganic biology symptom to bypass the protection. As a Medical Doctor, while there's an pandemic, you only require small amounts of vaccine to heal a sick patient. Work with the Chemist to distribute your cures more efficiently. As a Medical Doctor, you can attempt to drain blood from a husk with a syringe to determine the cause. If you can extract blood, it was caused by extreme temperatures or lasers, if there is no blood to extract, you have confirmed the presence of changelings. @@ -84,6 +87,7 @@ As a Medical Doctor, you can extract implants by holding an empty implant case i As a Medical Doctor, you can point your penlight at people to create a medical hologram. This lets them know that you're coming to treat them. As a Medical Doctor, you can surgically implant or extract things from people's chests. This can range from putting in a bomb to pulling out an alien larva. As a Medical Doctor, you must target the correct limb and not be in combat mode when trying to perform surgery on someone. Right clicking your patient will intentionally fail the surgery step. +As a Medical doctor, note that some species - such as Plasmamen or Jellypeople - lack flesh or bones. This will change how you perform surgery on them, allowing you to skip steps such as making incisions or sawing bones. As a Monkey, you can crawl through air or scrubber vents by alt+left clicking them. You must drop everything you are wearing and holding to do this, however. As a Monkey, you can still wear a few human items, such as backpacks, gas masks and hats, and still have two free hands. As a Nuclear Operative, communication is key! Use ; to speak to your fellow operatives and coordinate an attack plan. @@ -205,9 +209,11 @@ As the Clown, if you're a Traitor and get an emag on sale (or convince another t As the Clown, spice your gimmicks up! Nobody likes a one-trick pony. As the Clown, you can use your stamp on a sheet of cardboard as the first step of making a honkbot. Fun for the whole crew! As the Clown, your Grail is the mineral bananium, which can be given to the Roboticist to build you a fun and robust mech beloved by everyone. +As the Coroner, remember completing an autopsy on a body will speed up any further surgeries. As multiple people can operate on a body at once, consider teaming up with your Medical Doctors to get bodies revived faster! As the Coroner, remember that your autopsy scanner also works as an advanced health analyzer on right-click, but only for corpses. As the Coroner, you are more comfortable working on cadavers. You can perform autopsies or harvest organs from corpses a lot faster than your Medical Doctor counterparts. Work in tandem with them by helping prepare bodies for revival. As the Coroner, you can perform autopsies on corpses recovered from strange circumstances with your handheld autopsy scanner to discover how they died. By teaming up with a Detective, you can solve several cases together! +As the Coroner, your unique surgical tools are considered 'cruel implements', which speeds up surgery on corpses - slows it on not-yet-corpses. A few other items also have this classification. As the Curator, be sure to keep the shelves stocked and the library clean for crew. As the Curator, you are not completely defenseless. Your whip easily disarms people, your laser pointer can blind humans and cyborgs, and you can hide items in wirecut books. As the Detective, people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department. @@ -253,6 +259,7 @@ If you knock into somebody while doing a wicked grind on a skateboard, they will If you need to drag multiple people either to safety or to space, bring a locker or crate over and stuff them all in before hauling them off. If you're bleeding, you can apply pressure to the limb by grabbing yourself while targeting the bleeding limb. This will slow you down and take up a hand, but it'll slow down how fast you lose blood. Note this won't help the bleeding clot any faster. If you're using hotkey mode, you can stop pulling things using H. +If you've come out of a surgery and your doctor forgot to cauterize or suture you, you can do it yourself - even with a lighter. In a pinch, stripping yourself naked will give you a sizeable resistance to being tackled. What do you value more, your freedom or your dignity? In a pinch, you can reduce bleeding or burn infection with several commonplace reagents - flour, salt, saltwater can all be splashed onto the wound to stall for time, and tea can be drank for a boost to your body's defences. Laying down will help slow down bloodloss. Death will halt it entirely. @@ -271,9 +278,9 @@ Some weapons are better at taking down robots and structures than others. Don't Spears are capable of breaking into secure lockers by striking them in melee! Standard epipens contain a potent coagulant that not only slow bloodloss, but also help clot whichever of your wounds is bleeding the most! If you're suffering multiple bad bleeding wounds, make sure to seek out additional treatment ASAP! Suit storage units not only remove blood and dirt from clothing, but also radiation! +Surgery can be performed with a wide variety of tools not found in Medbay, but it'll drastically increase the time it takes and the risk of failure. However, you can offset the penalty by using sterilizing agents like alcohol or by performing it on a table or bed. Syndicate Duffelbags are a lot quicker to zip and unzip, have significantly less slowdown, and can carry up to two of various bulky, objective-related items - such as fire axes, guns, or gibtonite. Examine them closely to see all the possibilities. The Chaplain can bless any container with water by hitting it with their bible. Holy water has a myriad of uses against both cults and large amounts of it are a great contributor to success against them. -The Coroner's surgical tools are considered 'cruel implements', which speeds up surgery on corpses but slows it on not-yet-corpses. A few other items also have it. The P2P chat function found on tablet computers allows for a stealthy way to communicate with people. The resist button will allow you to resist out of handcuffs, being buckled to a chair or bed, out of locked lockers and more. Whenever you're stuck, try resisting! The station's self-destruct terminal is indestructible. Go find the disk instead of trying to destroy it. diff --git a/tgstation.dme b/tgstation.dme index d67abf61816..a6f43255a08 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1322,7 +1322,6 @@ #include "code\datums\components\subtype_picker.dm" #include "code\datums\components\summoning.dm" #include "code\datums\components\supermatter_crystal.dm" -#include "code\datums\components\surgery_initiator.dm" #include "code\datums\components\swabbing.dm" #include "code\datums\components\swarming.dm" #include "code\datums\components\table_smash.dm" @@ -1662,6 +1661,7 @@ #include "code\datums\elements\squish_sound.dm" #include "code\datums\elements\strippable.dm" #include "code\datums\elements\structure_repair.dm" +#include "code\datums\elements\surgery_aid.dm" #include "code\datums\elements\swabbable.dm" #include "code\datums\elements\swimming_tile.dm" #include "code\datums\elements\tenacious.dm" @@ -3240,7 +3240,6 @@ #include "code\modules\antagonists\abductor\abductee\abductee.dm" #include "code\modules\antagonists\abductor\abductee\abductee_objectives.dm" #include "code\modules\antagonists\abductor\equipment\abduction_outfits.dm" -#include "code\modules\antagonists\abductor\equipment\abduction_surgery.dm" #include "code\modules\antagonists\abductor\equipment\gland.dm" #include "code\modules\antagonists\abductor\equipment\orderable_gear.dm" #include "code\modules\antagonists\abductor\equipment\gear\abductor_clothing.dm" @@ -3728,6 +3727,7 @@ #include "code\modules\autowiki\pages\fishing.dm" #include "code\modules\autowiki\pages\soup.dm" #include "code\modules\autowiki\pages\stockparts.dm" +#include "code\modules\autowiki\pages\surgery.dm" #include "code\modules\autowiki\pages\techweb.dm" #include "code\modules\autowiki\pages\vending.dm" #include "code\modules\awaymissions\away_props.dm" @@ -6338,55 +6338,8 @@ #include "code\modules\station_goals\meteor_shield.dm" #include "code\modules\station_goals\station_goal.dm" #include "code\modules\station_goals\vault_mutation.dm" -#include "code\modules\surgery\amputation.dm" -#include "code\modules\surgery\asthmatic_bypass.dm" -#include "code\modules\surgery\autopsy.dm" -#include "code\modules\surgery\blood_filter.dm" -#include "code\modules\surgery\bone_mending.dm" -#include "code\modules\surgery\brain_surgery.dm" -#include "code\modules\surgery\burn_dressing.dm" -#include "code\modules\surgery\cavity_implant.dm" -#include "code\modules\surgery\core_removal.dm" -#include "code\modules\surgery\coronary_bypass.dm" -#include "code\modules\surgery\dental_implant.dm" -#include "code\modules\surgery\ear_surgery.dm" -#include "code\modules\surgery\experimental_dissection.dm" -#include "code\modules\surgery\eye_surgery.dm" -#include "code\modules\surgery\gastrectomy.dm" -#include "code\modules\surgery\healing.dm" -#include "code\modules\surgery\hepatectomy.dm" -#include "code\modules\surgery\implant_removal.dm" -#include "code\modules\surgery\limb_augmentation.dm" -#include "code\modules\surgery\lipoplasty.dm" -#include "code\modules\surgery\lobectomy.dm" -#include "code\modules\surgery\mechanic_steps.dm" -#include "code\modules\surgery\organ_manipulation.dm" -#include "code\modules\surgery\organic_steps.dm" -#include "code\modules\surgery\plastic_surgery.dm" -#include "code\modules\surgery\prosthetic_replacement.dm" -#include "code\modules\surgery\repair_puncture.dm" -#include "code\modules\surgery\revival.dm" -#include "code\modules\surgery\sleeper_protocol.dm" -#include "code\modules\surgery\stomachpump.dm" -#include "code\modules\surgery\surgery.dm" -#include "code\modules\surgery\surgery_helpers.dm" -#include "code\modules\surgery\surgery_step.dm" -#include "code\modules\surgery\tools.dm" -#include "code\modules\surgery\advanced\brainwashing.dm" -#include "code\modules\surgery\advanced\lobotomy.dm" -#include "code\modules\surgery\advanced\necrotic_revival.dm" -#include "code\modules\surgery\advanced\pacification.dm" -#include "code\modules\surgery\advanced\viral_bonding.dm" -#include "code\modules\surgery\advanced\wingreconstruction.dm" -#include "code\modules\surgery\advanced\bioware\bioware_surgery.dm" -#include "code\modules\surgery\advanced\bioware\cortex_folding.dm" -#include "code\modules\surgery\advanced\bioware\cortex_imprint.dm" -#include "code\modules\surgery\advanced\bioware\ligament_hook.dm" -#include "code\modules\surgery\advanced\bioware\ligament_reinforcement.dm" -#include "code\modules\surgery\advanced\bioware\muscled_veins.dm" -#include "code\modules\surgery\advanced\bioware\nerve_grounding.dm" -#include "code\modules\surgery\advanced\bioware\nerve_splicing.dm" -#include "code\modules\surgery\advanced\bioware\vein_threading.dm" +#include "code\modules\surgery\surgery_disks.dm" +#include "code\modules\surgery\surgery_tools.dm" #include "code\modules\surgery\bodyparts\_bodyparts.dm" #include "code\modules\surgery\bodyparts\bodypart_effects.dm" #include "code\modules\surgery\bodyparts\dismemberment.dm" @@ -6405,6 +6358,39 @@ #include "code\modules\surgery\bodyparts\species_parts\misc_bodyparts.dm" #include "code\modules\surgery\bodyparts\species_parts\moth_bodyparts.dm" #include "code\modules\surgery\bodyparts\species_parts\plasmaman_bodyparts.dm" +#include "code\modules\surgery\operations\_basic_surgery_state.dm" +#include "code\modules\surgery\operations\_operation.dm" +#include "code\modules\surgery\operations\operation_add_limb.dm" +#include "code\modules\surgery\operations\operation_amputation.dm" +#include "code\modules\surgery\operations\operation_asthma.dm" +#include "code\modules\surgery\operations\operation_autopsy.dm" +#include "code\modules\surgery\operations\operation_bioware.dm" +#include "code\modules\surgery\operations\operation_bone_repair.dm" +#include "code\modules\surgery\operations\operation_brainwash.dm" +#include "code\modules\surgery\operations\operation_cavity_implant.dm" +#include "code\modules\surgery\operations\operation_core.dm" +#include "code\modules\surgery\operations\operation_debride.dm" +#include "code\modules\surgery\operations\operation_dental.dm" +#include "code\modules\surgery\operations\operation_dissection.dm" +#include "code\modules\surgery\operations\operation_filter.dm" +#include "code\modules\surgery\operations\operation_generic.dm" +#include "code\modules\surgery\operations\operation_generic_basic.dm" +#include "code\modules\surgery\operations\operation_generic_mechanic.dm" +#include "code\modules\surgery\operations\operation_healing.dm" +#include "code\modules\surgery\operations\operation_implant_removal.dm" +#include "code\modules\surgery\operations\operation_lipo.dm" +#include "code\modules\surgery\operations\operation_lobotomy.dm" +#include "code\modules\surgery\operations\operation_organ_manip.dm" +#include "code\modules\surgery\operations\operation_organ_repair.dm" +#include "code\modules\surgery\operations\operation_pacify.dm" +#include "code\modules\surgery\operations\operation_plastic_surgery.dm" +#include "code\modules\surgery\operations\operation_pump.dm" +#include "code\modules\surgery\operations\operation_puncture.dm" +#include "code\modules\surgery\operations\operation_replace_limb.dm" +#include "code\modules\surgery\operations\operation_revival.dm" +#include "code\modules\surgery\operations\operation_virus.dm" +#include "code\modules\surgery\operations\operation_wing_repair.dm" +#include "code\modules\surgery\operations\operation_zombie.dm" #include "code\modules\surgery\organs\_organ.dm" #include "code\modules\surgery\organs\autosurgeon.dm" #include "code\modules\surgery\organs\helpers.dm" diff --git a/tgui/packages/tgui/interfaces/ExperimentConfigure.tsx b/tgui/packages/tgui/interfaces/ExperimentConfigure.tsx index 81602211c41..c6b8f52d654 100644 --- a/tgui/packages/tgui/interfaces/ExperimentConfigure.tsx +++ b/tgui/packages/tgui/interfaces/ExperimentConfigure.tsx @@ -8,11 +8,11 @@ import { Table, Tooltip, } from 'tgui-core/components'; - +import type { BooleanLike } from 'tgui-core/react'; import { useBackend } from '../backend'; import { Window } from '../layouts'; -type Techweb = { +export type Techweb = { all_servers: string[]; ref: string; selected: number; @@ -20,13 +20,14 @@ type Techweb = { web_org: string; }; -type ExperimentData = { +export type ExperimentData = { description: string; name: string; performance_hint: string; progress: Stage[]; ref: string; - selected: number; + selected?: BooleanLike; + completed?: BooleanLike; tag: string; }; @@ -84,9 +85,14 @@ function ExperimentStageRow(props: ExperimentStageRowProps) { ); } -function TechwebServer(props) { +type TechwebServerProps = { + techwebs: Techweb[]; + can_select?: boolean; +}; + +export function TechwebServer(props: TechwebServerProps) { const { act } = useBackend(); - const { techwebs } = props; + const { techwebs, can_select = true } = props; return techwebs.map((server, index) => ( @@ -98,19 +104,21 @@ function TechwebServer(props) { {server.web_id} / {server.web_org} - - - + {can_select && ( + + + + )} @@ -126,23 +134,64 @@ function TechwebServer(props) { )); } -export function Experiment(props) { +type ExperimentTitleElementProps = { + selected: boolean; + ref: string; + can_select?: boolean; + children?: React.ReactNode; +}; + +function ExperimentTitleElement(props: ExperimentTitleElementProps) { const { act } = useBackend(); - const { exp } = props; + const { selected, ref, can_select = true, children } = props; + + if (!can_select) { + return ( + + {children} + + ); + } + + return ( + + ); +} + +type ExperimentProps = { + exp: ExperimentData; + children?: React.ReactNode; + can_select?: boolean; +}; + +export function Experiment(props: ExperimentProps) { + const { exp, children, can_select } = props; const { name, description, tag, selected, progress, performance_hint, ref } = exp; return ( - -
    - +
    {description} - {props.children} + {children} {progress.map((stage, idx) => ( diff --git a/tgui/packages/tgui/interfaces/OperatingComputer.jsx b/tgui/packages/tgui/interfaces/OperatingComputer.jsx deleted file mode 100644 index 57481774973..00000000000 --- a/tgui/packages/tgui/interfaces/OperatingComputer.jsx +++ /dev/null @@ -1,151 +0,0 @@ -import { - AnimatedNumber, - Button, - LabeledList, - NoticeBox, - ProgressBar, - Section, - Tabs, -} from 'tgui-core/components'; - -import { useBackend, useSharedState } from '../backend'; -import { Window } from '../layouts'; - -const damageTypes = [ - { - label: 'Brute', - type: 'bruteLoss', - }, - { - label: 'Burn', - type: 'fireLoss', - }, - { - label: 'Toxin', - type: 'toxLoss', - }, - { - label: 'Respiratory', - type: 'oxyLoss', - }, -]; - -export const OperatingComputer = (props) => { - const { act } = useBackend(); - const [tab, setTab] = useSharedState('tab', 1); - - return ( - - - - setTab(1)}> - Patient State - - setTab(2)}> - Surgery Procedures - - act('open_experiments')}> - Experiments - - - {tab === 1 && } - {tab === 2 && } - - - ); -}; - -const PatientStateView = (props) => { - const { act, data } = useBackend(); - const { table, procedures = [], patient = {} } = data; - if (!table) { - return No Table Detected; - } - - return ( - <> -
    - {Object.keys(patient).length ? ( - - - {patient.stat} - - - {patient.blood_type || 'Unable to determine blood type'} - - - = 0 ? 'good' : 'average'} - > - - - - {damageTypes.map((type) => ( - - - - - - ))} - - ) : ( - 'No Patient Detected' - )} -
    - {procedures.length === 0 &&
    No Active Procedures
    } - {procedures.map((procedure) => ( -
    - - - {procedure.next_step} - - {procedure.chems_needed && ( - - - {procedure.chems_needed} - - - )} - {procedure.alternative_step && ( - - {procedure.alternative_step} - - )} - {procedure.alt_chems_needed && ( - - - {procedure.alt_chems_needed} - - - )} - -
    - ))} - - ); -}; - -const SurgeryProceduresView = (props) => { - const { act, data } = useBackend(); - const { surgeries = [] } = data; - return ( -
    -
    - ); -}; diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/ExperimentView.tsx b/tgui/packages/tgui/interfaces/OperatingComputer/ExperimentView.tsx new file mode 100644 index 00000000000..7a6d0e26af9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/OperatingComputer/ExperimentView.tsx @@ -0,0 +1,44 @@ +import { Button, NoticeBox, Section, Stack } from 'tgui-core/components'; +import { useBackend } from '../../backend'; +import { Experiment, TechwebServer } from '../ExperimentConfigure'; +import type { OperatingComputerData } from './types'; + +export const ExperimentView = () => { + const { act, data } = useBackend(); + const { techwebs, experiments } = data; + + return ( + + +
    act('open_experiments')}>Open Config + } + > + +
    +
    + + + {techwebs.some((e) => e.selected) && ( + +
    + {experiments.length > 0 ? ( + experiments + .sort((a, b) => (a.name > b.name ? 1 : -1)) + .map((exp, i) => ( + + )) + ) : ( + No experiments found! + )} +
    +
    + )} +
    +
    +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx b/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx new file mode 100644 index 00000000000..64d2031639b --- /dev/null +++ b/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx @@ -0,0 +1,354 @@ +import { + AnimatedNumber, + Blink, + Button, + Icon, + LabeledList, + NoticeBox, + ProgressBar, + Section, + Stack, +} from 'tgui-core/components'; +import { capitalizeAll, capitalizeFirst } from 'tgui-core/string'; +import { useBackend, useSharedState } from '../../backend'; +import { type BodyZone, BodyZoneSelector } from '../common/BodyZoneSelector'; +import { extractSurgeryName } from './helpers'; +import { + ComputerTabs, + damageTypes, + type OperatingComputerData, + type PatientData, +} from './types'; + +type PatientStateViewProps = { + setTab: (tab: number) => void; + setSearchText: (text: string) => void; + pinnedOperations: string[]; + setPinnedOperations: (text: string[]) => void; +}; +export const PatientStateView = (props: PatientStateViewProps) => { + const { data } = useBackend(); + const { setTab, setSearchText, pinnedOperations, setPinnedOperations } = + props; + + const { has_table, patient } = data; + if (!has_table) { + return ( +
    + + No table detected + +
    + ); + } + if (!patient) { + return ( +
    + + No patient detected + +
    + ); + } + return ( +
    + + + + + + + + + + + +
    + ); +}; + +type PatientStateMainStateViewProps = { + patient: PatientData; +}; + +const PatientStateMainStateView = (props: PatientStateMainStateViewProps) => { + const { patient } = props; + + return ( + + + {patient.stat} + + + {patient.blood_type || 'Unable to determine blood type'} + + + = 0 ? 'good' : 'average'} + > + `${Math.round(value)}%`} + /> + + + + = patient.standard_blood_level * 0.7 + ? 'good' + : patient.blood_level >= patient.standard_blood_level * 0.45 + ? 'average' + : 'bad' + } + > + `${Math.round(value * 100)}%`} + /> + + + {damageTypes.map((type) => ( + + + + + + ))} + + ); +}; + +type PatientStateSurgeryStateViewProps = { + patient: PatientData; + target_zone: BodyZone; +}; + +const PatientStateSurgeryStateView = ( + props: PatientStateSurgeryStateViewProps, +) => { + const { act, data } = useBackend(); + const { patient, target_zone } = props; + + return ( + + + + zone !== target_zone && act('change_zone', { new_zone: zone }) + } + selectedZone={target_zone} + /> + + + + {patient.surgery_state.map((state) => ( + - {state} + ))} + + + + ); +}; + +type PatientStateNextOperationsViewProps = { + pinnedOperations: string[]; + setPinnedOperations: (text: string[]) => void; + setTab: (tab: number) => void; + setSearchText: (text: string) => void; +}; + +const PatientStateNextOperationsView = ( + props: PatientStateNextOperationsViewProps, +) => { + const { data } = useBackend(); + const { surgeries } = data; + const { pinnedOperations, setPinnedOperations, setTab, setSearchText } = + props; + + const possible_next_operations = surgeries.filter( + (operation) => operation.show_as_next, + ); + + const allTools = ['all tools'].concat( + possible_next_operations + .map((operation) => operation.tool_rec) + .flatMap((tool) => tool.split(' / ')) + .filter((tools, index, self) => self.indexOf(tools) === index), + ); + + const [filterByTool, setFilterByTool] = useSharedState( + 'filter_by_tool', + allTools[0], + ); + + if (pinnedOperations.length > 0) { + possible_next_operations.sort((a, b) => { + if ( + pinnedOperations.includes(a.name) && + !pinnedOperations.includes(b.name) + ) { + return -1; + } + if ( + !pinnedOperations.includes(a.name) && + pinnedOperations.includes(b.name) + ) { + return 1; + } + return 0; + }); + } + + possible_next_operations.sort((a, b) => (a.priority && !b.priority ? -1 : 1)); + + return ( +
    + setFilterByTool( + allTools[(allTools.indexOf(filterByTool) + 1) % allTools.length], + ) + } + onContextMenu={() => setFilterByTool(allTools[0])} + > + {capitalizeFirst(filterByTool)} + + } + > + + {possible_next_operations.length === 0 ? ( + + + No operations available + + + ) : ( + possible_next_operations + .filter( + (operation) => + filterByTool === allTools[0] || + operation.tool_rec.includes(filterByTool), + ) + .map((operation) => { + const { name, tool } = extractSurgeryName(operation, false); + return ( + + + + ); + }) + )} + +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/SurgeryProceduresView.tsx b/tgui/packages/tgui/interfaces/OperatingComputer/SurgeryProceduresView.tsx new file mode 100644 index 00000000000..20fc350c71f --- /dev/null +++ b/tgui/packages/tgui/interfaces/OperatingComputer/SurgeryProceduresView.tsx @@ -0,0 +1,238 @@ +import { + Box, + Button, + Collapsible, + Input, + Section, + Stack, +} from 'tgui-core/components'; +import { capitalizeAll, capitalizeFirst } from 'tgui-core/string'; +import { useBackend, useSharedState } from '../../backend'; +import { extractRequirementMap, extractSurgeryName } from './helpers'; +import type { OperatingComputerData, OperationData } from './types'; + +type SurgeryRequirementsInnerProps = { + cat_text: string; + cat_contents: string[]; +}; + +export const SurgeryRequirementsInner = ( + props: SurgeryRequirementsInnerProps, +) => { + const { cat_text, cat_contents } = props; + + return ( + + + {cat_text} + + + + {cat_contents.map((req, i) => ( + - {capitalizeFirst(req)} + ))} + + + + ); +}; + +type SurgeryProceduresViewProps = { + searchedSurgeries: OperationData[]; + searchText: string; + setSearchText: (text: string) => void; + pinnedOperations: string[]; + setPinnedOperations: (text: string[]) => void; +}; + +export const SurgeryProceduresView = (props: SurgeryProceduresViewProps) => { + const { data } = useBackend(); + const { surgeries } = data; + const { + searchedSurgeries, + searchText, + setSearchText, + pinnedOperations, + setPinnedOperations, + } = props; + const [sortType, setSortType] = useSharedState<'default' | 'name' | 'tool'>( + 'catalog_sort_type', + 'default', + ); + const [filterRobotic, setFilterRobotic] = useSharedState( + 'catalog_filter', + false, + ); + const rawSurgeryList = + searchedSurgeries.length > 0 ? searchedSurgeries : surgeries; + + const surgeryList = rawSurgeryList + .filter((surgery) => surgery.show_in_list) + .filter((surgery) => !filterRobotic || !surgery.mechanic) + .filter( + (surgery, index, self) => + index === self.findIndex((s) => s.name === surgery.name), + ); + + if (sortType === 'name') { + surgeryList.sort((a, b) => (a.name > b.name ? 1 : -1)); + } else if (sortType === 'tool') { + surgeryList.sort((a, b) => (a.tool_rec > b.tool_rec ? 1 : -1)); + } + + if (pinnedOperations.length > 0) { + surgeryList.sort((a, b) => { + if ( + pinnedOperations.includes(a.name) && + !pinnedOperations.includes(b.name) + ) { + return -1; + } + if ( + !pinnedOperations.includes(a.name) && + pinnedOperations.includes(b.name) + ) { + return 1; + } + return 0; + }); + } + + return ( +
    + + + + + } + > + {surgeryList.map((surgery) => { + const { name, tool, true_name } = extractSurgeryName(surgery, true); + + return ( + + + + + + +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/helpers.ts b/tgui/packages/tgui/interfaces/OperatingComputer/helpers.ts new file mode 100644 index 00000000000..a4e285741d3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/OperatingComputer/helpers.ts @@ -0,0 +1,77 @@ +import { capitalizeFirst } from 'tgui-core/string'; +import type { OperationData } from './types'; + +export function extractSurgeryName( + operation: OperationData, + catalog: boolean, +): { name: string; tool: string; true_name?: string } { + // operation names may be "make incision", "lobotomy", or "disarticulation (amputation)" + const { name, tool_rec } = operation; + if (!name) { + return { name: 'Error Surgery', tool: 'Error' }; + } + const parenthesis = name.indexOf('('); + if (parenthesis === -1) { + return { name: capitalizeFirst(name), tool: tool_rec }; + } + const in_parenthesis = name.slice(parenthesis + 1, name.indexOf(')')).trim(); + if (!catalog) { + return { + name: capitalizeFirst(in_parenthesis), + tool: tool_rec, + }; + } + const out_parenthesis = capitalizeFirst(name.slice(0, parenthesis).trim()); + return { + name: capitalizeFirst(out_parenthesis), + tool: tool_rec, + true_name: capitalizeFirst(in_parenthesis), + }; +} + +export function extractRequirementMap( + surgery: OperationData, +): Record { + if (surgery.requirements?.length !== 4) { + return {}; // we can assert this never happens, but just in case... + } + const hard_requirements = surgery.requirements[0]; + const soft_requirements = surgery.requirements[1]; + const optional_requirements = surgery.requirements[2]; + const blocked_requirements = surgery.requirements[3]; + + // you *have* to have one of the soft requirements, so if there's only one... + if (soft_requirements.length === 1) { + hard_requirements.push(soft_requirements[0]); + soft_requirements.length = 0; + } + + const optional_title = () => { + if (optional_requirements.length === 1) { + if (hard_requirements.length === 0) return 'The following is optional:'; + return 'Additionally, the following is optional:'; + } + if (hard_requirements.length === 0) + return 'All of the following are optional:'; + return 'Additionally, all of the following are optional:'; + }; + + const blocked_title = () => { + if (blocked_requirements.length === 1) { + if (hard_requirements.length === 0) + return 'The following would block the procedure:'; + return 'However, the following would block the procedure:'; + } + if (hard_requirements.length === 0) + return 'Any of the following would block the procedure:'; + return 'However, any of the following would block the procedure:'; + }; + + return { + [`${hard_requirements.length === 1 ? 'The' : 'All of the'} following are required:`]: + hard_requirements, + 'Additionally, one of the following is required:': soft_requirements, + [optional_title()]: optional_requirements, + [blocked_title()]: blocked_requirements, + }; +} diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/index.tsx b/tgui/packages/tgui/interfaces/OperatingComputer/index.tsx new file mode 100644 index 00000000000..ca4040fd984 --- /dev/null +++ b/tgui/packages/tgui/interfaces/OperatingComputer/index.tsx @@ -0,0 +1,92 @@ +import '../../styles/interfaces/OperatingComputer.scss'; + +import { Section, Stack, Tabs } from 'tgui-core/components'; +import { useFuzzySearch } from 'tgui-core/fuzzysearch'; +import { useBackend, useSharedState } from '../../backend'; +import { Window } from '../../layouts'; +import { ExperimentView } from './ExperimentView'; +import { PatientStateView } from './PatientStateView'; +import { SurgeryProceduresView } from './SurgeryProceduresView'; +import { + ComputerTabs, + type OperatingComputerData, + type OperationData, +} from './types'; + +export const OperatingComputer = () => { + const [tab, setTab] = useSharedState('tab', 1); + const { data } = useBackend(); + const { surgeries } = data; + + const { query, setQuery, results } = useFuzzySearch({ + searchArray: surgeries, + matchStrategy: 'aggressive', + getSearchString: (item: OperationData) => `${item.name} ${item.tool_rec}`, + }); + + const [pinnedOperations, setPinnedOperations] = useSharedState( + 'pinned_operation', + [], + ); + + return ( + + + + + + setTab(1)} + > + Patient State + + setTab(2)} + > + Operation Catalog + + setTab(3)} + > + Experiments + + + + + {tab === ComputerTabs.PatientState && ( + + )} + {tab === ComputerTabs.OperationCatalog && ( + + )} + {tab === ComputerTabs.Experiments && } + + +
    + DefOS 1.0 © Nanotrasen-Deforest Corporation. All rights + reserved. +
    +
    +
    +
    +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/types.ts b/tgui/packages/tgui/interfaces/OperatingComputer/types.ts new file mode 100644 index 00000000000..5cc1e8ee1e2 --- /dev/null +++ b/tgui/packages/tgui/interfaces/OperatingComputer/types.ts @@ -0,0 +1,73 @@ +import type { BooleanLike } from 'tgui-core/react'; +import type { BodyZone } from '../common/BodyZoneSelector'; +import type { ExperimentData, Techweb } from '../ExperimentConfigure'; + +export type OperatingComputerData = { + has_table: BooleanLike; + patient?: PatientData; + target_zone: BodyZone; + // static data + surgeries: OperationData[]; + techwebs: Techweb[]; + experiments: ExperimentData[]; +}; + +export type PatientData = { + health: number; + blood_type: string; + stat: 'Conscious' | 'Unconscious' | 'Dead'; + statstate: 'good' | 'average' | 'bad'; + minHealth: number; + maxHealth: number; + bruteLoss: number; + fireLoss: number; + toxLoss: number; + oxyLoss: number; + blood_level: number; + standard_blood_level: number; + target_zone: BodyZone; + surgery_state: string[]; +}; + +export type OperationData = { + name: string; + desc: string; + tool_rec: string; + priority?: BooleanLike; + mechanic?: BooleanLike; + requirements?: string[][]; + // show operation as a recommended next step + show_as_next: BooleanLike; + // show operation in the full list + show_in_list: BooleanLike; +}; + +export type damageType = { + label: string; + type: 'bruteLoss' | 'fireLoss' | 'toxLoss' | 'oxyLoss'; +}; + +export const damageTypes: damageType[] = [ + { + label: 'Brute', + type: 'bruteLoss', + }, + { + label: 'Burn', + type: 'fireLoss', + }, + { + label: 'Toxin', + type: 'toxLoss', + }, + { + label: 'Respiratory', + type: 'oxyLoss', + }, +]; + +export enum ComputerTabs { + PatientState = 1, + OperationCatalog = 2, + Experiments = 3, +} diff --git a/tgui/packages/tgui/interfaces/SimpleBot.tsx b/tgui/packages/tgui/interfaces/SimpleBot.tsx index 1c1f7b9c100..889a4e2126d 100644 --- a/tgui/packages/tgui/interfaces/SimpleBot.tsx +++ b/tgui/packages/tgui/interfaces/SimpleBot.tsx @@ -262,7 +262,6 @@ function SettingsDisplay(props) { } enum ControlType { - MedbotSync = 'sync_tech', MedbotThreshold = 'heal_threshold', FloorbotTiles = 'tile_stack', FloorbotLine = 'line_mode', @@ -280,8 +279,6 @@ function ControlHelper(props: ControlProps) { const { control } = props; switch (control[0]) { - case ControlType.MedbotSync: - return ; case ControlType.MedbotThreshold: return ; case ControlType.FloorbotTiles: @@ -300,25 +297,6 @@ function ControlHelper(props: ControlProps) { } } -/** Small button to sync medbots with research. */ -function MedbotSync(props) { - const { act } = useBackend(); - - return ( - - act('sync_tech')} - /> - - ); -} - /** Slider button for medbot healing thresholds */ function MedbotThreshold(props: ControlProps) { const { act } = useBackend(); diff --git a/tgui/packages/tgui/interfaces/SurgeryInitiator.tsx b/tgui/packages/tgui/interfaces/SurgeryInitiator.tsx deleted file mode 100644 index 73acf3e3798..00000000000 --- a/tgui/packages/tgui/interfaces/SurgeryInitiator.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { sortBy } from 'es-toolkit'; -import { Component } from 'react'; -import { Button, KeyListener, Stack } from 'tgui-core/components'; -import { KEY_DOWN, KEY_ENTER, KEY_UP } from 'tgui-core/keycodes'; -import type { BooleanLike } from 'tgui-core/react'; - -import { useBackend } from '../backend'; -import { Window } from '../layouts'; -import { type BodyZone, BodyZoneSelector } from './common/BodyZoneSelector'; - -type Surgery = { - name: string; - blocked?: BooleanLike; -}; - -type SurgeryInitiatorData = { - selected_zone: BodyZone; - surgeries: Surgery[]; - target_name: string; -}; - -const sortSurgeries = (array: Surgery[]) => - sortBy(array, [(surgery) => surgery.name]); - -type SurgeryInitiatorInnerState = { - selectedSurgeryIndex: number; -}; - -class SurgeryInitiatorInner extends Component< - SurgeryInitiatorData, - SurgeryInitiatorInnerState -> { - state = { - selectedSurgeryIndex: 0, - }; - - componentDidMount() { - this.updateSelectedSurgeryIndexState(); - } - - componentDidUpdate(prevProps: SurgeryInitiatorData) { - if (prevProps.selected_zone !== this.props.selected_zone) { - this.updateSelectedSurgeryIndexState(); - } - } - - updateSelectedSurgeryIndexState() { - this.setState({ - selectedSurgeryIndex: this.findSelectedSurgeryAfter(-1) || 0, - }); - } - - findSelectedSurgeryAfter(after: number): number | undefined { - const foundIndex = this.props.surgeries.findIndex( - (surgery, index) => index > after && !surgery.blocked, - ); - - return foundIndex === -1 ? undefined : foundIndex; - } - - findSelectedSurgeryBefore(before: number): number | undefined { - for (let index = before; index >= 0; index--) { - const surgery = this.props.surgeries[index]; - if (!surgery.blocked) { - return index; - } - } - - return undefined; - } - - render() { - const { act } = useBackend(); - const { selected_zone, surgeries, target_name } = this.props; - - return ( - - - - - act('change_zone', { new_zone: zone })} - selectedZone={selected_zone} - /> - - - - - {surgeries.map((surgery, index) => ( - - ))} - - - - - { - const keyCode = event.code; - const surgery = surgeries[this.state.selectedSurgeryIndex]; - - switch (keyCode) { - case KEY_DOWN: - this.setState((state) => { - return { - selectedSurgeryIndex: - this.findSelectedSurgeryAfter( - state.selectedSurgeryIndex, - ) || - this.findSelectedSurgeryAfter(-1) || - 0, - }; - }); - - break; - case KEY_UP: - this.setState((state) => { - return { - selectedSurgeryIndex: - this.findSelectedSurgeryBefore( - state.selectedSurgeryIndex - 1, - ) ?? - this.findSelectedSurgeryBefore( - this.props.surgeries.length - 1, - ) ?? - 0, - }; - }); - - break; - case KEY_ENTER: - if (surgery) { - act('start_surgery', { - surgery_name: surgery.name, - }); - } - - break; - } - }} - /> - - - ); - } -} - -export const SurgeryInitiator = (props) => { - const { data } = useBackend(); - - return ( - - ); -}; diff --git a/tgui/packages/tgui/interfaces/Techweb/nodes/TechNode.tsx b/tgui/packages/tgui/interfaces/Techweb/nodes/TechNode.tsx index 6788bf2f3f4..6e4d7aadb40 100644 --- a/tgui/packages/tgui/interfaces/Techweb/nodes/TechNode.tsx +++ b/tgui/packages/tgui/interfaces/Techweb/nodes/TechNode.tsx @@ -206,7 +206,7 @@ export function TechNode(props: Props) { if (thisExp === null || thisExp === undefined) { return ; } - return ; + return ; })} )} @@ -221,7 +221,7 @@ export function TechNode(props: Props) { return ; } return ( - + Provides a discount of {discount_experiments[k]} points to all required point pools. diff --git a/tgui/packages/tgui/interfaces/Techweb/types.ts b/tgui/packages/tgui/interfaces/Techweb/types.ts index 8b5b3180156..0c40c46f47d 100644 --- a/tgui/packages/tgui/interfaces/Techweb/types.ts +++ b/tgui/packages/tgui/interfaces/Techweb/types.ts @@ -1,4 +1,5 @@ import type { BooleanLike } from 'tgui-core/react'; +import type { ExperimentData } from '../ExperimentConfigure'; type StoredDesigns = Record; @@ -10,17 +11,6 @@ type TechDisk = { stored_research: StoredDesigns; }; -type ProgressTuple = [string, string, number, number]; - -type Experiment = { - name: string; - description: string; - tag: string; - progress: ProgressTuple[]; - completed: BooleanLike; - performance_hint: string; -}; - // Base node type export type NodeCache = { description: string; @@ -56,7 +46,7 @@ type StaticData = { export type TechWebData = { d_disk: DesignDisk | null; - experiments: Record; + experiments: Record; locked: BooleanLike; nodes: TechwebNode[]; point_types_abbreviations: Record; diff --git a/tgui/packages/tgui/interfaces/common/BodyZoneSelector.tsx b/tgui/packages/tgui/interfaces/common/BodyZoneSelector.tsx index 84549f87198..5a67252411d 100644 --- a/tgui/packages/tgui/interfaces/common/BodyZoneSelector.tsx +++ b/tgui/packages/tgui/interfaces/common/BodyZoneSelector.tsx @@ -15,7 +15,15 @@ export enum BodyZone { Groin = 'groin', } -function bodyZonePixelToZone(x: number, y: number): BodyZone | null { +const renderTogetherIfImprecise = { + [BodyZone.Chest]: [BodyZone.Groin], +}; + +function bodyZonePixelToZone( + x: number, + y: number, + precise: boolean, +): BodyZone | null { // TypeScript translation of /atom/movable/screen/zone_sel/proc/get_zone_at if (y < 1) { return null; @@ -29,7 +37,7 @@ function bodyZonePixelToZone(x: number, y: number): BodyZone | null { if (x > 8 && x < 11) { return BodyZone.RightArm; } else if (x > 12 && x < 20) { - return BodyZone.Groin; + return precise ? BodyZone.Groin : BodyZone.Chest; } else if (x > 21 && x < 24) { return BodyZone.LeftArm; } @@ -43,9 +51,9 @@ function bodyZonePixelToZone(x: number, y: number): BodyZone | null { } } else if (y < 30 && x > 12 && x < 20) { if (y > 23 && y < 24 && x > 15 && x < 17) { - return BodyZone.Mouth; + return precise ? BodyZone.Mouth : BodyZone.Head; } else if (y > 25 && y < 27 && x > 14 && x < 18) { - return BodyZone.Eyes; + return precise ? BodyZone.Eyes : BodyZone.Head; } else { return BodyZone.Head; } @@ -54,11 +62,63 @@ function bodyZonePixelToZone(x: number, y: number): BodyZone | null { return null; } +type BodyImageProps = { + zone: BodyZone; + scale?: number; + theme?: string; + opacity?: number; + precise?: boolean; +}; + +function BodyImage(props: BodyImageProps) { + const { + zone, + scale = 3, + theme = 'midnight', + opacity = 1, + precise = true, + } = props; + + return ( + <> + + {!precise && + renderTogetherIfImprecise[zone]?.map((otherZone) => ( + + ))} + + ); +} + +function HoverImage(props: BodyImageProps) { + return ; +} + type BodyZoneSelectorProps = { onClick?: (zone: BodyZone) => void; scale?: number; selectedZone: BodyZone | null; theme?: string; + precise?: boolean; }; type BodyZoneSelectorState = { @@ -76,7 +136,12 @@ export class BodyZoneSelector extends Component< render() { const { hoverZone } = this.state; - const { scale = 3, selectedZone, theme = 'midnight' } = this.props; + const { + scale = 3, + selectedZone, + theme = 'midnight', + precise = true, + } = this.props; return (
    - - {selectedZone && ( - - )} - + {selectedZone && } {hoverZone && hoverZone !== selectedZone && ( - + )}
    ); diff --git a/tgui/packages/tgui/styles/assets/bg-deforest.svg b/tgui/packages/tgui/styles/assets/bg-deforest.svg new file mode 100644 index 00000000000..018c2e984bd --- /dev/null +++ b/tgui/packages/tgui/styles/assets/bg-deforest.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tgui/packages/tgui/styles/interfaces/ExperimentConfigure.scss b/tgui/packages/tgui/styles/interfaces/ExperimentConfigure.scss index 1eff7049392..c2135bc9b0e 100644 --- a/tgui/packages/tgui/styles/interfaces/ExperimentConfigure.scss +++ b/tgui/packages/tgui/styles/interfaces/ExperimentConfigure.scss @@ -3,6 +3,10 @@ background: var(--color-black); border: var(--border-thickness-tiny) solid var(--color-primary); margin: var(--space-s) 0; + + &--selected { + border: var(--border-thickness-tiny) solid var(--button-background-selected); + } } .ExperimentTechwebServer__WebHeader { @@ -27,6 +31,26 @@ border-radius: 0; } +.ExperimentConfigure__ExperimentNameFakeButton { + background-color: var(--color-primary); + font-weight: bold; + border-radius: 0; + position: relative; + display: block; + margin-left: 0; + margin-right: 0; + white-space: nowrap; + line-height: var(--button-height); + padding: 0 var(--space-m); + margin-bottom: var(--space-xs); + border-radius: var(--button-border-radius); + outline: none; + &__content { + display: block; + align-self: stretch; + } +} + .ExperimentConfigure__ExperimentContent { /* prettier-ignore */ padding: var(--space-s) calc(var(--space-xl) * 1.5) var(--space-s) var(--space-s); @@ -34,7 +58,6 @@ .ExperimentStage__Indicator { font-weight: bold; - margin-right: var(--space-xl); text-align: center; } diff --git a/tgui/packages/tgui/styles/interfaces/OperatingComputer.scss b/tgui/packages/tgui/styles/interfaces/OperatingComputer.scss new file mode 100644 index 00000000000..b7b3cf03fbf --- /dev/null +++ b/tgui/packages/tgui/styles/interfaces/OperatingComputer.scss @@ -0,0 +1,25 @@ +.theme-operating_computer { + .Layout__content { + background-image: url('../assets/bg-deforest.svg'); + background-size: 90%; + } + // Base + --color-base: hsl(150, 100%, 18%); + --color-primary: hsl(from var(--color-base) h s calc(l + 5)); + --color-border-primary: hsla(0, 0%, 100%, 0.75); + --secondary-lightness-adjustment: -10; + --base-gradient-spread: 0; + + // Components + --button-background-selected: hsl(0, 90.3%, 32.35%); + --button-background-caution: hsl(29.5, 90.95%, 39.02%); + --button-background-danger: hsl(61.15, 100%, 30.78%); + --button-border-radius: 0; + + --collapsible-background-selected: hsl(0, 90.3%, 32.35%); + --collapsible-background-caution: hsl(29.5, 90.95%, 39.02%); + --collapsible-background-danger: hsl(61.15, 100%, 30.78%); + + --progress-bar-background: hsla(0, 0%, 0%, 0.5); + --tab-background-selected: var(--color-base); +}