From bdf0d143ac37d611b8225e02eaa99d0ebc09836e 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 01/35] Spooky Scary Supreme Surgery (Rework) (#93697) --- code/__DEFINES/bodyparts.dm | 130 ++ code/__DEFINES/combat.dm | 8 +- 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 | 3 + code/__DEFINES/dcs/signals/signals_techweb.dm | 3 + code/__DEFINES/surgery.dm | 104 +- code/__DEFINES/traits/declarations.dm | 12 +- code/__DEFINES/wounds.dm | 5 + code/__HELPERS/global_lists.dm | 8 - code/__HELPERS/type2type.dm | 73 +- code/_globalvars/bitfields.dm | 22 +- code/_globalvars/lists/engineering.dm | 66 + code/_globalvars/lists/objects.dm | 2 - code/_globalvars/traits/_traits.dm | 6 +- code/_globalvars/traits/admin_tooling.dm | 1 - code/_onclick/hud/radial.dm | 2 +- code/_onclick/item_attack.dm | 22 +- code/datums/components/fishing_spot.dm | 2 +- code/datums/components/food/edible.dm | 6 +- code/datums/components/surgery_initiator.dm | 344 ----- 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 | 76 +- code/datums/wounds/bones.dm | 29 +- code/datums/wounds/burns.dm | 16 +- code/datums/wounds/cranial_fissure.dm | 3 + code/datums/wounds/pierce.dm | 23 +- code/datums/wounds/slash.dm | 26 +- .../machinery/computer/operating_computer.dm | 340 ++++- .../game/machinery/dna_infuser/dna_infuser.dm | 1 + .../dna_infuser/organ_sets/fly_organs.dm | 15 +- 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 +- .../game/objects/items/robot/items/storage.dm | 36 +- code/game/objects/items/stacks/medical.dm | 21 +- code/game/objects/items/stacks/stack.dm | 2 + code/game/objects/structures/bedsheet_bin.dm | 2 +- code/game/objects/structures/tables_racks.dm | 75 +- 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 | 15 +- .../traitor/objectives/sleeper_protocol.dm | 68 - 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 | 2 +- .../experiment/handlers/experiment_handler.dm | 11 +- .../experisci/experiment/types/experiment.dm | 11 + .../ruins/spaceruin_code/forgottenship.dm | 7 +- .../oldstation/oldstation_rnd.dm | 13 +- .../mob/living/basic/bots/medbot/medbot.dm | 44 +- .../mob/living/basic/ruin_defender/flesh.dm | 2 +- .../carbon/alien/special/alien_embryo.dm | 10 +- code/modules/mob/living/carbon/carbon.dm | 18 +- .../mob/living/carbon/carbon_defense.dm | 20 +- 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 | 55 +- code/modules/mob/living/living_defines.dm | 4 +- .../modules/mob/living/silicon/robot/robot.dm | 2 +- .../mob/living/simple_animal/slime/defense.dm | 11 - .../mob/living/simple_animal/slime/slime.dm | 4 +- code/modules/mod/modules/modules_medical.dm | 81 +- .../file_system/programs/techweb.dm | 12 +- code/modules/projectiles/gun.dm | 6 - .../projectiles/guns/ballistic/pistol.dm | 13 + .../projectiles/guns/energy/recharge.dm | 22 +- .../reagents/drinks/alcohol_reagents.dm | 2 +- .../chemistry/reagents/food_reagents.dm | 2 +- .../chemistry/reagents/medicine_reagents.dm | 5 +- .../chemistry/reagents/other_reagents.dm | 4 +- .../reagents/pyrotechnic_reagents.dm | 3 +- code/modules/religion/burdened/psyker.dm | 2 +- .../research/designs/medical_designs.dm | 214 +-- 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 | 13 - .../advanced/bioware/cortex_folding.dm | 94 -- .../advanced/bioware/cortex_imprint.dm | 90 -- .../surgery/advanced/bioware/ligament_hook.dm | 69 - .../bioware/ligament_reinforcement.dm | 69 - .../surgery/advanced/bioware/muscled_veins.dm | 69 - .../advanced/bioware/nerve_grounding.dm | 70 - .../advanced/bioware/nerve_splicing.dm | 73 - .../advanced/bioware/vein_threading.dm | 69 - code/modules/surgery/advanced/brainwashing.dm | 98 -- code/modules/surgery/advanced/lobotomy.dm | 121 -- .../surgery/advanced/necrotic_revival.dm | 63 - code/modules/surgery/advanced/pacification.dm | 83 - .../modules/surgery/advanced/viral_bonding.dm | 71 - .../surgery/advanced/wingreconstruction.dm | 69 - code/modules/surgery/amputation.dm | 84 - code/modules/surgery/autopsy.dm | 57 - code/modules/surgery/blood_filter.dm | 96 -- code/modules/surgery/bodyparts/_bodyparts.dm | 225 ++- .../surgery/bodyparts/dismemberment.dm | 34 +- code/modules/surgery/bodyparts/head.dm | 1 + code/modules/surgery/bodyparts/parts.dm | 7 +- code/modules/surgery/bodyparts/wounds.dm | 4 +- code/modules/surgery/bone_mending.dm | 315 ---- code/modules/surgery/brain_surgery.dm | 87 -- code/modules/surgery/burn_dressing.dm | 183 --- code/modules/surgery/cavity_implant.dm | 91 -- code/modules/surgery/core_removal.dm | 59 - code/modules/surgery/coronary_bypass.dm | 149 -- code/modules/surgery/dental_implant.dm | 60 - code/modules/surgery/ear_surgery.dm | 91 -- .../surgery/experimental_dissection.dm | 153 -- code/modules/surgery/eye_surgery.dm | 92 -- code/modules/surgery/gastrectomy.dm | 88 -- code/modules/surgery/healing.dm | 382 ----- code/modules/surgery/hepatectomy.dm | 87 -- code/modules/surgery/implant_removal.dm | 110 -- code/modules/surgery/internal_bleeding.dm | 1 + code/modules/surgery/limb_augmentation.dm | 114 -- code/modules/surgery/lipoplasty.dm | 117 -- code/modules/surgery/lobectomy.dm | 90 -- code/modules/surgery/mechanic_steps.dm | 183 --- .../operations/_basic_surgery_state.dm | 34 + code/modules/surgery/operations/_operation.dm | 1360 +++++++++++++++++ .../surgery/operations/operation_add_limb.dm | 223 +++ .../operations/operation_amputation.dm | 131 ++ .../surgery/operations/operation_asthma.dm | 72 + .../surgery/operations/operation_autopsy.dm | 56 + .../surgery/operations/operation_bioware.dm | 472 ++++++ .../operations/operation_bone_repair.dm | 250 +++ .../surgery/operations/operation_brainwash.dm | 162 ++ .../operations/operation_cavity_implant.dm | 221 +++ .../surgery/operations/operation_core.dm | 59 + .../surgery/operations/operation_debride.dm | 92 ++ .../surgery/operations/operation_dental.dm | 152 ++ .../operations/operation_dissection.dm | 151 ++ .../surgery/operations/operation_filter.dm | 84 + .../surgery/operations/operation_generic.dm | 561 +++++++ .../operations/operation_generic_basic.dm | 185 +++ .../operations/operation_generic_mechanic.dm | 257 ++++ .../surgery/operations/operation_healing.dm | 276 ++++ .../operations/operation_implant_removal.dm | 93 ++ .../surgery/operations/operation_lipo.dm | 110 ++ .../surgery/operations/operation_lobotomy.dm | 118 ++ .../operations/operation_organ_manip.dm | 302 ++++ .../operations/operation_organ_repair.dm | 664 ++++++++ .../surgery/operations/operation_pacify.dm | 90 ++ .../operations/operation_plastic_surgery.dm | 168 ++ .../surgery/operations/operation_pump.dm | 64 + .../surgery/operations/operation_puncture.dm | 151 ++ .../operations/operation_replace_limb.dm | 101 ++ .../surgery/operations/operation_revival.dm | 126 ++ .../surgery/operations/operation_virus.dm | 83 + .../operations/operation_wing_repair.dm | 73 + .../surgery/operations/operation_zombie.dm | 88 ++ code/modules/surgery/organ_manipulation.dm | 351 ----- code/modules/surgery/organic_steps.dm | 272 ---- code/modules/surgery/organs/_organ.dm | 28 +- .../organs/internal/stomach/stomach_golem.dm | 2 +- code/modules/surgery/organs/organ_movement.dm | 65 +- code/modules/surgery/plastic_surgery.dm | 156 -- .../modules/surgery/prosthetic_replacement.dm | 140 -- code/modules/surgery/repair_puncture.dm | 159 -- code/modules/surgery/revival.dm | 147 -- code/modules/surgery/stomachpump.dm | 68 - code/modules/surgery/surgery.dm | 220 --- code/modules/surgery/surgery_disks.dm | 59 + code/modules/surgery/surgery_helpers.dm | 73 - code/modules/surgery/surgery_step.dm | 315 ---- .../surgery/{tools.dm => surgery_tools.dm} | 26 +- code/modules/unit_tests/designs.dm | 30 +- code/modules/unit_tests/omnitools.dm | 109 +- .../unit_tests/organ_bodypart_shuffle.dm | 29 +- code/modules/unit_tests/surgeries.dm | 181 ++- maplestation.dme | 2 - .../objects/structures/operating_computer.dm | 74 - .../human/species_types/animid/animid_fish.dm | 20 +- .../chemistry/reagents/rim_reagents.dm | 5 +- strings/tips.txt | 23 +- 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 | 173 --- .../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 + 206 files changed, 10350 insertions(+), 7658 deletions(-) create mode 100644 code/_globalvars/lists/engineering.dm 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/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/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 maplestation_modules/code/game/objects/structures/operating_computer.dm 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 5c6cfa28c73b..c36eeda76dee 100644 --- a/code/__DEFINES/bodyparts.dm +++ b/code/__DEFINES/bodyparts.dm @@ -32,3 +32,133 @@ #define AUGGED_CHEST_EMP_SHAKE_TIME 5 SECONDS /// When hit by an EMP, the time an augged head will make vision fucky for. #define AUGGED_HEAD_EMP_GLITCH_DURATION 6 SECONDS + +// // Color priorities for bodyparts +// /// Abductor team recoloring priority +// #define LIMB_COLOR_AYYLMAO 5 +// /// Hulk effect color priority +// #define LIMB_COLOR_HULK 10 +// /// Fish infusion color priority +// #define LIMB_COLOR_FISH_INFUSION 15 +// /// Carp infusion color priority +// #define LIMB_COLOR_CARP_INFUSION 20 +// /// Untextured fish suicide color priority +// #define LIMB_COLOR_CS_SOURCE_SUICIDE 30 +// /// Base priority for atom colors, gets atom priorities added to it +// #define LIMB_COLOR_ATOM_COLOR 40 +// /// Voidwalker effect color priority +// #define LIMB_COLOR_VOIDWALKER_CURSE 50 + +// // Overlay priorities +// #define BODYPART_OVERLAY_FISH_INFUSION 1 +// #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 19a59b8795e8..0431cf735b40 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -245,8 +245,6 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define BODY_ZONE_L_LEG "l_leg" #define BODY_ZONE_R_LEG "r_leg" -GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) - #define BODY_ZONE_PRECISE_EYES "eyes" #define BODY_ZONE_PRECISE_MOUTH "mouth" #define BODY_ZONE_PRECISE_GROIN "groin" @@ -255,6 +253,12 @@ GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) #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 113826a44863..365684397d7e 100644 --- a/code/__DEFINES/dcs/signals/signals_food.dm +++ b/code/__DEFINES/dcs/signals/signals_food.dm @@ -5,6 +5,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 2dca6487f702..de3814adb4a0 100644 --- a/code/__DEFINES/dcs/signals/signals_medical.dm +++ b/code/__DEFINES/dcs/signals/signals_medical.dm @@ -1,8 +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_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" @@ -13,7 +16,10 @@ #define COMSIG_DEFIBRILLATOR_SUCCESS "defib_success" // #define COMPONENT_DEFIB_STOP (1<<0) // Same return, to stop default defib handling -/// 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) +/// 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" + +/// 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 01b8bcd576e8..ffded4af1dca 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -107,8 +107,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 80d72ab4e39b..8154768dee2b 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -312,3 +312,6 @@ #define COMPONENT_NO_LOOT_DROP (1<<0) /// From /datum/element/death_drops/on_death(mob/living/target, gibbed) : (list/loot, gibbed) #define COMSIG_LIVING_DROPPED_LOOT "living_dropped_loot" + +/// 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 7765bfe5a3a6..ce0b5bc3f45f 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 2f98f69b10e8..4f9b4b192471 100644 --- a/code/__DEFINES/surgery.dm +++ b/code/__DEFINES/surgery.dm @@ -34,6 +34,15 @@ #define ORGAN_EXTERNAL (1<<13) /// Organ is currently irradiated, causing damage over time to the owner unless removed or fixed #define ORGAN_IRRADIATED (1<<14) +/// This is a ghost organ, which can be used for wall phasing. +#define ORGAN_GHOST (1<<15) +/// This is a mutant organ, having this makes you a -derived mutant to health analyzers. +#define ORGAN_MUTANT (1<<16) +/// The organ has been chomped or otherwise rendered unusable. +#define ORGAN_UNUSABLE (1<<17) + +/// Organ flags that correspond to bodytypes +#define ORGAN_TYPE_FLAGS (ORGAN_ORGANIC | ORGAN_ROBOTIC | ORGAN_MINERAL | ORGAN_GHOST) /// Helper to figure out if a limb is organic #define IS_ORGANIC_LIMB(limb) (limb.bodytype & BODYTYPE_ORGANIC) @@ -76,22 +85,83 @@ /// Default for most heads #define HEAD_DEFAULT_FEATURES (HEAD_HAIR|HEAD_FACIAL_HAIR|HEAD_LIPS|HEAD_EYESPRITES|HEAD_EYECOLOR|HEAD_EYEHOLES|HEAD_DEBRAIN|HEAD_SHOW_ORGANS_ON_EXAMINE) -/// 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) + +/// 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) + +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" -// 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) +/// 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))) -///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)) +// 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 9d2895aecb11..c2bf531e175e 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -318,7 +318,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" @@ -1232,6 +1231,17 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Mob gets far less severe negative moodlets from seeing death / blood #define TRAIT_DESENSITIZED "desensitized" +/// 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" + +/// 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 434150b4ce13..fa7905e0d743 100644 --- a/code/__DEFINES/wounds.dm +++ b/code/__DEFINES/wounds.dm @@ -77,6 +77,8 @@ GLOBAL_LIST_INIT(wound_severities_chronological, list( #define BIO_BLOODED (1<<4) /// Is connected by a joint - can suffer T1 bone blunt wounds (dislocation) #define BIO_JOINTED (1<<5) +/// Skin is covered in thick chitin and is resistant to cutting +#define BIO_CHITIN (1<<6) /// 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 @@ -85,6 +87,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 @@ -106,6 +110,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 6bfdb2d1176c..3f2cacaf1045 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 aae96c386014..c6ffbbbeebfb 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -182,6 +182,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) @@ -199,50 +201,33 @@ GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH, var/list/covered_parts = list() if(!bpc) - return 0 - - 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) - - 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 & 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 & 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) + return covered_parts + + if(bpc & HEAD) + covered_parts |= list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH) + + 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 & 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 & 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 2043a077d3aa..9bc2e8b27586 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -281,12 +281,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( @@ -440,6 +442,18 @@ DEFINE_BITFIELD(bodypart_flags, list( "BODYPART_IMPLANTED" = BODYPART_IMPLANTED, )) +DEFINE_BITFIELD(biological_state, list( + "BIO_INORGANIC" = BIO_INORGANIC, + "BIO_BONE" = BIO_BONE, + "BIO_FLESH" = BIO_FLESH, + "BIO_METAL" = BIO_METAL, + "BIO_WOOD" = BIO_WOOD, + "BIO_WIRED" = BIO_WIRED, + "BIO_BLOODED" = BIO_BLOODED, + "BIO_JOINTED" = BIO_JOINTED, + "BIO_CHITIN" = BIO_CHITIN, +)) + DEFINE_BITFIELD(change_exempt_flags, list( "BP_BLOCK_CHANGE_SPECIES" = BP_BLOCK_CHANGE_SPECIES, )) diff --git a/code/_globalvars/lists/engineering.dm b/code/_globalvars/lists/engineering.dm new file mode 100644 index 000000000000..602452d4789e --- /dev/null +++ b/code/_globalvars/lists/engineering.dm @@ -0,0 +1,66 @@ +// List of all tool behaviours. +GLOBAL_LIST_INIT(all_tool_behaviours, list( + TOOL_ANALYZER, + TOOL_BLOODFILTER, + TOOL_BONESET, + TOOL_CAUTERY, + TOOL_CROWBAR, + TOOL_DRILL, + TOOL_HEMOSTAT, + TOOL_KNIFE, + TOOL_MINING, + TOOL_MULTITOOL, + TOOL_RETRACTOR, + TOOL_ROLLINGPIN, + TOOL_RUSTSCRAPER, + TOOL_SAW, + TOOL_SCALPEL, + TOOL_SCREWDRIVER, + TOOL_SHOVEL, + TOOL_WELDER, + 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 c9eeecde977b..8d6656c87ff4 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -32,8 +32,6 @@ GLOBAL_LIST_EMPTY(deliverybeacontags) 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()) /// Global list of all non-cooking related crafting recipes. GLOBAL_LIST_EMPTY(crafting_recipes) diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 01e13fb5df4a..09ac1d941ca3 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -274,8 +274,9 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_HULK" = TRAIT_HULK, "TRAIT_HUSK" = TRAIT_HUSK, "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, @@ -404,6 +405,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_RELAYING_ATTACKER" = TRAIT_RELAYING_ATTACKER, @@ -466,7 +468,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_TACKLING_FRAIL_ATTACKER" = TRAIT_TACKLING_FRAIL_ATTACKER, "TRAIT_TACKLING_TAILED_DEFENDER" = TRAIT_TACKLING_TAILED_DEFENDER, @@ -622,6 +623,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( ), /obj/item/organ = list( "TRAIT_LIVING_HEART" = TRAIT_LIVING_HEART, + "TRAIT_ORGAN_OPERATED_ON" = TRAIT_ORGAN_OPERATED_ON, ), /obj/item/organ/liver = list( "TRAIT_BALLMER_SCIENTIST" = TRAIT_BALLMER_SCIENTIST, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 8c878d4c0481..235087e50dd6 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -228,7 +228,6 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_STUBBY_BODY" = TRAIT_STUBBY_BODY, "TRAIT_STUNIMMUNE" = TRAIT_STUNIMMUNE, "TRAIT_STURDY_FRAME" = TRAIT_STURDY_FRAME, - "TRAIT_SURGEON" = TRAIT_SURGEON, "TRAIT_SURGICALLY_ANALYZED" = TRAIT_SURGICALLY_ANALYZED, "TRAIT_TAGGER" = TRAIT_TAGGER, "TRAIT_TENTACLE_IMMUNE" = TRAIT_TENTACLE_IMMUNE, diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index 428f6509ebaf..b2564c2fc605 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -389,7 +389,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 26ebf23fb290..a9216b6f2383 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -163,13 +163,18 @@ return attacking_item.attack_atom(src, user, params) /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)) - 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 @@ -312,7 +317,7 @@ damage *= attacking_item.demolition_mod 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) @@ -494,4 +499,3 @@ return " in the [input_area]" return "" - diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm index 3dc99cd8067d..ff161d7b16db 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 522e131c041e..c21eb5aaf4aa 100644 --- a/code/datums/components/food/edible.dm +++ b/code/datums/components/food/edible.dm @@ -523,8 +523,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 18999a33f292..000000000000 --- 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 - -/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 && !(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 - REMOVE_TRAIT(patient, TRAIT_ALLOWED_HONORBOUND_ATTACK, type) - user.visible_message( - span_notice("[user] removes [parent] from [patient]'s [parse_zone(selected_zone)]."), - span_notice("You remove [parent] from [patient]'s [parse_zone(selected_zone)]."), - ) - - patient.balloon_alert(user, "stopped work on [parse_zone(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 - - the_surgery.operated_bodypart?.adjustBleedStacks(-5) - - patient.surgeries -= the_surgery - REMOVE_TRAIT(patient, TRAIT_ALLOWED_HONORBOUND_ATTACK, ELEMENT_TRAIT(type)) - - user.visible_message( - span_notice("[user] closes [patient]'s [parse_zone(selected_zone)] with [close_tool] and removes [parent]."), - span_notice("You close [patient]'s [parse_zone(selected_zone)] with [close_tool] and remove [parent]."), - ) - - patient.balloon_alert(user, "closed up [parse_zone(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()] [parse_zone(selected_zone)]!") - return - - ui_close() - - var/datum/surgery/procedure = new surgery.type(target, selected_zone, affecting_limb) - ADD_TRAIT(target, TRAIT_ALLOWED_HONORBOUND_ATTACK, type) - - target.balloon_alert(user, "starting \"[LOWER_TEXT(procedure.name)]\"") - - user.visible_message( - span_notice("[user] drapes [parent] over [target]'s [parse_zone(selected_zone)] to prepare for surgery."), - span_notice("You drape [parent] over [target]'s [parse_zone(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/noticable_organ.dm b/code/datums/elements/noticable_organ.dm index 1a6a895e5354..ab15bafb0fa0 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/on_implanted(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 000000000000..ca351cc6a56c --- /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 15ae9c0a74dd..ad5842c43428 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 b32fbd8dd139..130b8fddce29 100644 --- a/code/datums/wounds/_wounds.dm +++ b/code/datums/wounds/_wounds.dm @@ -63,8 +63,6 @@ /// Specific items such as bandages or sutures that can try directly treating this wound var/list/treatable_by - /// Specific items such as bandages or sutures that can try directly treating this wound only if the user has the victim in an aggressive grab or higher - var/list/treatable_by_grabbed /// Any tools with any of the flags in this list will be usable to try directly treating this wound var/list/treatable_tools /// How long it will take to treat this wound with a standard effective tool, assuming it doesn't need surgery @@ -93,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 @@ -123,7 +119,6 @@ update_actionspeed_modifier() /datum/wound/Destroy() - QDEL_NULL(attached_surgery) if (limb) remove_wound() @@ -267,6 +262,7 @@ UnregisterSignal(victim, COMSIG_QDELETING) UnregisterSignal(victim, COMSIG_MOB_SWAP_HANDS) UnregisterSignal(victim, COMSIG_CARBON_POST_REMOVE_LIMB) + UnregisterSignal(victim, COMSIG_ATOM_ITEM_INTERACTION) if (actionspeed_mod) victim.remove_actionspeed_modifier(actionspeed_mod) // no need to qdelete it, just remove it from our victim @@ -275,7 +271,7 @@ if(victim) RegisterSignal(victim, COMSIG_QDELETING, PROC_REF(null_victim)) RegisterSignals(victim, list(COMSIG_MOB_SWAP_HANDS, COMSIG_CARBON_POST_REMOVE_LIMB, COMSIG_CARBON_POST_ATTACH_LIMB), PROC_REF(add_or_remove_actionspeed_mod)) - + RegisterSignal(victim, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(interact_try_treating)) if (limb) start_limping_if_we_should() // the status effect already handles removing itself add_or_remove_actionspeed_mod() @@ -451,71 +447,79 @@ victim.apply_status_effect(/datum/status_effect/determined, WOUND_DETERMINATION_CRITICAL) if(WOUND_SEVERITY_LOSS) victim.apply_status_effect(/datum/status_effect/determined, WOUND_DETERMINATION_LOSS) - // victim.add_reagent(/datum/reagent/medicine/epinephrine, severity * 2) + +/datum/wound/proc/interact_try_treating(datum/source, mob/living/user, obj/item/tool, ...) + SIGNAL_HANDLER + + return try_treating(tool, user) /** - * try_treating() is an intercept run from [/mob/living/carbon/proc/attackby] right after surgeries but before anything else. Return TRUE here if the item is something that is relevant to treatment to take over the interaction. + * Attempt to treat the wound with the given item/tool by the given user + * This proc leads into [/datum/wound/proc/treat] * - * This proc leads into [/datum/wound/proc/treat] and probably shouldn't be added onto in children types. You can specify what items or tools you want to be intercepted - * with var/list/treatable_by and var/treatable_tool, then if an item fulfills one of those requirements and our wound claims it first, it goes over to treat() and treat_self(). + * You can specify what items or tools you want to be intercepted + * with var/list/treatable_by and var/treatable_tool, + * then if an item fulfills one of those requirements and our wound claims it first, + * it goes over to treat() and treat_self(). * * Arguments: * * I: The item we're trying to use * * user: The mob trying to use it on us */ -/datum/wound/proc/try_treating(obj/item/I, mob/user) - // first we weed out if we're not dealing with our wound's bodypart, or if it might be an attack - if(!I || limb.body_zone != user.zone_selected) - return FALSE +/datum/wound/proc/try_treating(obj/item/tool, mob/living/user) + SHOULD_NOT_OVERRIDE(TRUE) - if(isliving(user)) - var/mob/living/tendee = user - if(I.force && tendee.combat_mode) - return FALSE + if(limb.body_zone != user.zone_selected) + return NONE - if(!item_can_treat(I, user)) - return FALSE + if(user.combat_mode && tool.force) + return NONE - // now that we've determined we have a valid attempt at treating, we can stomp on their dreams if we're already interacting with the patient or if their part is obscured + if(!item_can_treat(tool, user)) + return NONE + + // now that we've determined we have a valid attempt at treating, + // we can stomp on their dreams if we're already interacting with the patient or if their part is obscured if(DOING_INTERACTION_WITH_TARGET(user, victim)) to_chat(user, span_warning("You're already interacting with [victim]!")) - return TRUE + return ITEM_INTERACT_BLOCKING // next we check if the bodypart in actually accessible (not under thick clothing). We skip the species trait check since skellies // & such may need to use bone gel but may be wearing a space suit for..... whatever reason a skeleton would wear a space suit for if(ishuman(victim)) var/mob/living/carbon/human/victim_human = victim if(!victim_human.try_inject(user, injection_flags = INJECT_CHECK_IGNORE_SPECIES | INJECT_TRY_SHOW_ERROR_MESSAGE)) - return TRUE + return ITEM_INTERACT_BLOCKING - // lastly, treat them - return treat(I, user) // we allow treat to return a value so it can control if the item does its normal interaction or not + INVOKE_ASYNC(src, PROC_REF(treat), tool, user) + return ITEM_INTERACT_SUCCESS /// 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) // check if we have a valid treatable tool if(potential_treater.tool_behaviour in treatable_tools) return TRUE - if((TOOL_CAUTERY in treatable_tools) && potential_treater.get_temperature() && (user == victim)) // allow improvised cauterization on yourself without an aggro grab - return TRUE // failing that, see if we're aggro grabbing them and if we have an item that works for aggro grabs only - if(user.pulling == victim && user.grab_state >= GRAB_AGGRESSIVE && check_grab_treatments(potential_treater, user)) + if((user == victim || (user.pulling == victim && user.grab_state >= GRAB_AGGRESSIVE)) && check_grab_treatments(potential_treater, user)) return TRUE // failing THAT, we check if we have a generally allowed item - for(var/allowed_type in treatable_by) - if(istype(potential_treater, allowed_type)) - return TRUE + if(is_type_in_list(potential_treater, treatable_by)) + return TRUE + return FALSE -/// Return TRUE if we have an item that can only be used while aggro grabbed (unhanded aggro grab treatments go in [/datum/wound/proc/try_handling]). Treatment is still is handled in [/datum/wound/proc/treat] -/datum/wound/proc/check_grab_treatments(obj/item/I, mob/user) +/// Return TRUE if we have an item that can only be used while aggro grabbed +/// (unhanded aggro grab treatments go in [/datum/wound/proc/try_handling]). +/// Treatment is still is handled in [/datum/wound/proc/treat] +/datum/wound/proc/check_grab_treatments(obj/item/tool, mob/user) return FALSE -/// Like try_treating() but for unhanded interactions from humans, used by joint dislocations for manual bodypart chiropractice for example. Ignores thick material checks since you can pop an arm into place through a thick suit unlike using sutures -/datum/wound/proc/try_handling(mob/living/carbon/human/user) +/// Like try_treating() but for unhanded interactions, used by joint dislocations for manual bodypart chiropractice for example. +/// Ignores thick material checks since you can pop an arm into place through a thick suit unlike using sutures +/datum/wound/proc/try_handling(mob/living/user) return FALSE /// Someone is using something that might be used for treating the wound on this limb -/datum/wound/proc/treat(obj/item/I, mob/user) +/datum/wound/proc/treat(obj/item/tool, mob/user) return /// If var/processing is TRUE, this is run on each life tick diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index e0665d2cadb3..bbcc59a2995c 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -99,7 +99,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 @@ -433,16 +433,16 @@ malpractice(user) -/datum/wound/blunt/bone/moderate/treat(obj/item/I, mob/user) +/datum/wound/blunt/bone/moderate/treat(obj/item/tool, mob/user) var/scanned = HAS_TRAIT(src, TRAIT_WOUND_SCANNED) var/self_penalty_mult = user == victim ? 1.5 : 1 var/scanned_mult = scanned ? 0.5 : 1 var/treatment_delay = base_treat_time * self_penalty_mult * scanned_mult if(victim == user) - victim.visible_message(span_danger("[user] begins [scanned ? "expertly " : ""]resetting [victim.p_their()] [limb.plaintext_zone] with [I]."), span_warning("You begin resetting your [limb.plaintext_zone] with [I][scanned ? ", keeping the holo-image's indications in mind" : ""]...")) + victim.visible_message(span_danger("[user] begins [scanned ? "expertly " : ""]resetting [victim.p_their()] [limb.plaintext_zone] with [tool]."), span_warning("You begin resetting your [limb.plaintext_zone] with [tool][scanned ? ", keeping the holo-image's indications in mind" : ""]...")) else - user.visible_message(span_danger("[user] begins [scanned ? "expertly " : ""]resetting [victim]'s [limb.plaintext_zone] with [I]."), span_notice("You begin resetting [victim]'s [limb.plaintext_zone] with [I][scanned ? ", keeping the holo-image's indications in mind" : ""]...")) + user.visible_message(span_danger("[user] begins [scanned ? "expertly " : ""]resetting [victim]'s [limb.plaintext_zone] with [tool]."), span_notice("You begin resetting [victim]'s [limb.plaintext_zone] with [tool][scanned ? ", keeping the holo-image's indications in mind" : ""]...")) if(!do_after(user, treatment_delay, target = victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)))) return @@ -535,6 +535,9 @@ though this is nigh-impossible for most people to do so individually \ unless they've dosed themselves with one or more painkillers." + /// 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 @@ -552,7 +555,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) @@ -632,11 +635,15 @@ processes = TRUE return TRUE -/datum/wound/blunt/bone/treat(obj/item/I, mob/user) - if(istype(I, /obj/item/stack/medical/bone_gel)) - return gel(I, user) - else if(istype(I, /obj/item/stack/sticky_tape/surgical)) - return tape(I, user) +/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) + if(istype(tool, /obj/item/stack/sticky_tape/surgical)) + tape(tool, user) /datum/wound/blunt/bone/get_scanner_description(mob/user) . = ..() @@ -644,7 +651,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: \ Operate where possible. In the event of emergency, apply bone gel directly to injured limb. \ diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm index 2903da191079..12b0e45754ba 100644 --- a/code/datums/wounds/burns.dm +++ b/code/datums/wounds/burns.dm @@ -9,7 +9,6 @@ default_scar_file = FLESH_SCAR_FILE processes = TRUE - treatable_by = list(/obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) // sterilizer and alcohol will require reagent treatments, coming soon /// Shorthand for the name of the wound for the analyzer var/scanner_name = "" @@ -236,10 +235,10 @@ /datum/wound/flesh/proc/uv(obj/item/flashlight/pen/paramedic/I, mob/user) if(!COOLDOWN_FINISHED(I, uv_cooldown)) to_chat(user, span_notice("[I] is still recharging!")) - return TRUE + return if(infection <= 0 || infection < sanitization) to_chat(user, span_notice("There's no infection to treat on [victim]'s [limb.plaintext_zone]!")) - return TRUE + return user.visible_message( span_notice("[user] flashes the burns on [victim]'s [limb] with [I]."), @@ -249,11 +248,6 @@ ) sanitization += I.uv_power COOLDOWN_START(I, uv_cooldown, I.uv_cooldown_length) - return TRUE - -/datum/wound/flesh/treat(obj/item/I, mob/user) - if(istype(I, /obj/item/flashlight/pen/paramedic)) - return uv(I, user) // people complained about burns not healing on stasis beds, so in addition to checking if it's cured, they also get the special ability to very slowly heal on stasis beds if they have the healing effects stored /datum/wound/flesh/on_stasis(seconds_per_tick, times_fired) @@ -357,7 +351,7 @@ damage_multiplier_penalty = 1.2 threshold_penalty = 40 status_effect_type = /datum/status_effect/wound/burn/flesh/severe - treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) + treatable_by = list(/obj/item/flashlight/pen/paramedic) infection_rate = 0.07 // appx 9 minutes to reach sepsis without any treatment flesh_damage = 12.5 scar_keyword = "burnsevere" @@ -388,7 +382,7 @@ sound_effect = 'sound/effects/wounds/sizzle2.ogg' threshold_penalty = 80 status_effect_type = /datum/status_effect/wound/burn/flesh/critical - treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) + treatable_by = list(/obj/item/flashlight/pen/paramedic) infection_rate = 0.075 // appx 4.33 minutes to reach sepsis without any treatment flesh_damage = 20 scar_keyword = "burncritical" @@ -452,7 +446,7 @@ threshold_penalty = 25 infection_rate = 0.05 flesh_damage = 10 - treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) + treatable_by = list(/obj/item/flashlight/pen/paramedic) simple_desc = "Patient's skin is frozen and dying, with a risk of infection and reduced limb integrity." simple_treat_text = "Bandage and monitor for worsening condition while rewarming the limb. Regenerative mesh will not directly help, but will sanitize the wound." diff --git a/code/datums/wounds/cranial_fissure.dm b/code/datums/wounds/cranial_fissure.dm index 8c743e22103e..4ecfc03e38bc 100644 --- a/code/datums/wounds/cranial_fissure.dm +++ b/code/datums/wounds/cranial_fissure.dm @@ -32,6 +32,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 0634b79f9cfb..5604bb6feb7a 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -29,7 +29,6 @@ name = "Piercing Wound" sound_effect = 'sound/weapons/slice.ogg' processes = TRUE - treatable_by = list(/obj/item/stack/medical/suture) treatable_tools = list(TOOL_CAUTERY) base_treat_time = 3 SECONDS wound_flags = parent_type::wound_flags | CAN_BE_GRASPED @@ -45,6 +44,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) @@ -130,13 +131,14 @@ to_chat(victim, span_green("The holes on your [limb.plaintext_zone] have [!limb.can_bleed() ? "healed up" : "stopped bleeding"]!")) qdel(src) -/datum/wound/pierce/bleed/check_grab_treatments(obj/item/I, mob/user) - if(I.get_temperature()) // if we're using something hot but not a cautery, we need to be aggro grabbing them first, so we don't try treating someone we're eswording - return TRUE +/datum/wound/pierce/bleed/check_grab_treatments(obj/item/tool, mob/user) + // if we're using something hot but not a cautery, we need to be aggro grabbing them first, + // so we don't try treating someone we're eswording + return tool.get_temperature() -/datum/wound/pierce/bleed/treat(obj/item/I, mob/user) - if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature()) - return tool_cauterize(I, user) +/datum/wound/pierce/bleed/treat(obj/item/tool, mob/user) + if(tool.tool_behaviour == TOOL_CAUTERY || tool.get_temperature()) + tool_cauterize(tool, user) /datum/wound/pierce/bleed/on_xadone(power) . = ..() @@ -176,14 +178,13 @@ adjust_blood_flow(-blood_cauterized) if(blood_flow > 0) - return try_treating(I, user) - return TRUE + try_treating(I, user) /datum/wound_pregen_data/flesh_pierce abstract = TRUE - required_limb_biostate = (BIO_FLESH) - required_wounding_types = list(WOUND_PIERCE) + required_limb_biostate = BIO_FLESH + required_wounding_type = WOUND_PIERCE wound_series = WOUND_SERIES_FLESH_PUNCTURE_BLEED diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm index 78537522ea7c..3d55eb82a07b 100644 --- a/code/datums/wounds/slash.dm +++ b/code/datums/wounds/slash.dm @@ -39,8 +39,6 @@ /datum/wound/slash/flesh name = "Slashing (Cut) Flesh Wound" processes = TRUE - treatable_by = list(/obj/item/stack/medical/suture) - treatable_by_grabbed = list(/obj/item/gun/energy/laser) treatable_tools = list(TOOL_CAUTERY) base_treat_time = 3 SECONDS wound_flags = parent_type::wound_flags | CAN_BE_GRASPED @@ -171,17 +169,18 @@ /* BEWARE, THE BELOW NONSENSE IS MADNESS. bones.dm looks more like what I have in mind and is sufficiently clean, don't pay attention to this messiness */ -/datum/wound/slash/flesh/check_grab_treatments(obj/item/I, mob/user) - if(istype(I, /obj/item/gun/energy/laser)) +/datum/wound/slash/flesh/check_grab_treatments(obj/item/tool, mob/user) + if(istype(tool, /obj/item/gun/energy/laser)) return TRUE - if(I.get_temperature()) // if we're using something hot but not a cautery, we need to be aggro grabbing them first, so we don't try treating someone we're eswording + if(tool.get_temperature()) // if we're using something hot but not a cautery, we need to be aggro grabbing them first, so we don't try treating someone we're eswording return TRUE + return FALSE -/datum/wound/slash/flesh/treat(obj/item/I, mob/user) - if(istype(I, /obj/item/gun/energy/laser)) - return las_cauterize(I, user) - else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature()) - return tool_cauterize(I, user) +/datum/wound/slash/flesh/treat(obj/item/tool, mob/user) + if(istype(tool, /obj/item/gun/energy/laser)) + las_cauterize(tool, user) + else if(tool.tool_behaviour == TOOL_CAUTERY || tool.get_temperature()) + tool_cauterize(tool, user) /datum/wound/slash/flesh/try_handling(mob/living/carbon/human/user) if(user.pulling != victim || user.zone_selected != limb.body_zone || !isanimid(user) || !victim.try_inject(user, injection_flags = INJECT_TRY_SHOW_ERROR_MESSAGE)) @@ -253,9 +252,8 @@ if(!lasgun.process_fire(victim, victim, TRUE, null, limb.body_zone)) return victim.pain_emote("scream") - adjust_blood_flow(-1 * (damage / (5 * self_penalty_mult))) // 20 / 5 = 4 bloodflow removed, p good victim.visible_message(span_warning("The cuts on [victim]'s [limb.plaintext_zone] scar over!")) - return TRUE + adjust_blood_flow(-1 * (damage / (5 * self_penalty_mult))) // 20 / 5 = 4 bloodflow removed, p good /// If someone is using either a cautery tool or something with heat to cauterize this cut /datum/wound/slash/flesh/proc/tool_cauterize(obj/item/I, mob/user) @@ -285,11 +283,9 @@ adjust_blood_flow(-blood_cauterized) if(blood_flow > minimum_flow) - return try_treating(I, user) + try_treating(I, user) else if(demotes_to) to_chat(user, span_green("You successfully lower the severity of [user == victim_stored ? "your" : "[victim_stored]'s"] cuts.")) - return TRUE - return FALSE /datum/wound/slash/get_limb_examine_description() return span_warning("The flesh on this limb appears badly lacerated.") diff --git a/code/game/machinery/computer/operating_computer.dm b/code/game/machinery/computer/operating_computer.dm index a4f4cbb1b74e..08e5f76bcd0d 100644 --- a/code/game/machinery/computer/operating_computer.dm +++ b/code/game/machinery/computer/operating_computer.dm @@ -8,12 +8,21 @@ icon_keyboard = "med_key" circuit = /obj/item/circuitboard/computer/operating + 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 - light_color = LIGHT_COLOR_BLUE - + /// 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) . = ..() @@ -25,6 +34,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), ) @@ -49,25 +66,48 @@ linked_techweb = tool.buffer return TRUE -/obj/machinery/computer/operating/attackby(obj/item/O, mob/user, params) - 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 + + if((tool.item_flags & SURGICAL_TOOL) && !user.combat_mode) + ui_interact(user) + return ITEM_INTERACT_SUCCESS + + return NONE + +// NON-MODULE CHANGE +/obj/machinery/computer/operating/emag_act(mob/user, obj/item/card/emag/emag_card) + . = ..() + if(obj_flags & EMAGGED) + return + if(!is_operational) + return + + obj_flags |= EMAGGED + balloon_alert(user, "safeties overridden") + playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE, SHORT_RANGE_SOUND_EXTRARANGE) + playsound(src, SFX_SPARKS, 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) -/obj/machinery/computer/operating/proc/sync_surgeries() - if(!linked_techweb) +// NON-MODULE CHANGE +/obj/machinery/computer/operating/on_set_is_operational(old_value) + update_static_data_for_all_viewers() + if(is_operational) 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 + // Losing power / getting broken will auto disable anesthesia + table.safety_disable() /obj/machinery/computer/operating/proc/find_table() for(var/direction in GLOB.alldirs) @@ -76,35 +116,74 @@ table.computer = src break -/obj/machinery/computer/operating/ui_state(mob/user) - return GLOB.not_incapacitated_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) // NON-MODULE CHANGE - 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 // NON-MODULE CHANGE data["patient"] = list() - if(!table.patient) - return data var/mob/living/carbon/patient = table.patient switch(patient.stat) @@ -112,7 +191,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" @@ -125,36 +204,102 @@ // check here to see if the patient has standard blood reagent, or special blood (like how ethereals bleed liquid electricity) to show the proper name in the computer data["patient"]["blood_type"] = "[patient.get_blood_type() || "None"]" // NON-MODULE CHANGE data["patient"]["maxHealth"] = patient.maxHealth - data["patient"]["minHealth"] = HEALTH_THRESHOLD_LIKELY_DEAD - data["patient"]["bruteLoss"] = patient.getBruteLoss() - data["patient"]["fireLoss"] = patient.getFireLoss() - data["patient"]["toxLoss"] = patient.getToxLoss() - data["patient"]["oxyLoss"] = patient.getOxyLoss() - data["procedures"] = list() - if(patient.surgeries.len) - for(var/datum/surgery/procedure in patient.surgeries) - var/datum/surgery_step/surgery_step = procedure.get_surgery_step() - 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("[parse_zone(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"]["minHealth"] = HEALTH_THRESHOLD_DEAD + data["patient"]["bruteLoss"] = patient.get_brute_loss() + data["patient"]["fireLoss"] = patient.get_fire_loss() + data["patient"]["toxLoss"] = patient.get_tox_loss() + data["patient"]["oxyLoss"] = patient.get_oxy_loss() + 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)) + + // NON-MODULE CHANGE + var/obj/item/organ/patient_brain = table.patient.get_organ_slot(ORGAN_SLOT_BRAIN) + data["patient"]["brain"] = isnull(patient_brain) ? 100 : ((patient_brain.damage / patient_brain.maxHealth) * 100) + data["patient"]["bloodVolumePercent"] = round((table.patient.blood_volume / BLOOD_VOLUME_NORMAL) * 100) + data["patient"]["heartRate"] = table.patient.get_bpm() + // We can also show pain and stuff here if we want. + + var/tank_exists = !isnull(table.attached_tank) + var/patient_exists = !isnull(table.patient) + data["anesthesia"] = list( + "has_tank" = tank_exists, + "open" = tank_exists && patient_exists && table.patient.external == table.attached_tank, + "can_open_tank" = tank_exists && patient_exists && table.can_have_tank_opened(table.patient), + "failsafe" = table.failsafe_time == INFINITY ? -1 : (table.failsafe_time / 10), + ) + // NON-MODULE CHANGE END + 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 @@ -164,12 +309,63 @@ if(.) return switch(action) - if("sync") - sync_surgeries() - return TRUE // NON-MODULE CHANGE if("open_experiments") experiment_handler.ui_interact(usr) - return TRUE // NON-MODULE CHANGE + return TRUE + + 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 + + // NON-MODULE CHANGE START + if("toggle_anesthesia") + if(iscarbon(usr)) + var/mob/living/carbon/toggler = usr + if(toggler == table.patient && table.patient_set_at == -1 && table.failsafe_time >= 5 MINUTES) + to_chat(toggler, span_warning("You feel as if you know better than to do that.")) + return FALSE + + table.toggle_anesthesia() + return TRUE + + if("set_failsafe") + table.failsafe_time = clamp(text2num(params["new_failsafe_time"]) * 10, 5 SECONDS, 10 MINUTES) + return TRUE + + if("disable_failsafe") + table.failsafe_time = INFINITY + return TRUE + // NON-MODULE CHANGE END + + 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/dna_infuser.dm b/code/game/machinery/dna_infuser/dna_infuser.dm index ff530a229c08..9fac4aac9ede 100644 --- a/code/game/machinery/dna_infuser/dna_infuser.dm +++ b/code/game/machinery/dna_infuser/dna_infuser.dm @@ -137,6 +137,7 @@ // Valid organ successfully picked. new_organ = new new_organ() new_organ.replace_into(target) + new_organ.organ_flags |= ORGAN_MUTANT check_tier_progression(target) return TRUE 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 4dff8261bbe1..0d74debf080c 100644 --- a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm @@ -15,7 +15,20 @@ var/mob/living/carbon/human/new_fly = owner if(isflyperson(new_fly)) return - //okay you NEED to be a fly + // This is ugly as sin, but we're called before the organ finishes inserting into the bodypart + // so if we swap species directly the bodypart will be replaced and we'll be gone + // 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) + SIGNAL_HANDLER + var/mob/living/carbon/human/new_fly = owner + // just in case? + if(isflyperson(new_fly)) + return + // needs to be done before the species is set + UnregisterSignal(source, COMSIG_ORGAN_BODYPART_INSERTED) + // okay you NEED to be a fly to_chat(new_fly, span_danger("Too much fly DNA! Your skin begins to discolor into a horrible black as you become more fly than person!")) new_fly.set_species(/datum/species/fly) diff --git a/code/game/objects/effects/info.dm b/code/game/objects/effects/info.dm index adf609d50c23..3356b4dd9f5e 100644 --- a/code/game/objects/effects/info.dm +++ b/code/game/objects/effects/info.dm @@ -15,7 +15,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 decbbe35f165..cc98f5f52b0d 100644 --- a/code/game/objects/items/cosmetics.dm +++ b/code/game/objects/items/cosmetics.dm @@ -222,8 +222,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!")) @@ -239,8 +240,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!")) @@ -275,7 +277,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)) @@ -290,7 +292,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 071561d57a09..829202ed8425 100644 --- a/code/game/objects/items/debug_items.dm +++ b/code/game/objects/items/debug_items.dm @@ -76,72 +76,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/obj/tools.dmi', icon_state = "screwdriver_map"), - "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 4498f648d902..9486e042e434 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -128,7 +128,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/robot/items/storage.dm b/code/game/objects/items/robot/items/storage.dm index 967c3f7a2de7..08c777cab00b 100644 --- a/code/game/objects/items/robot/items/storage.dm +++ b/code/game/objects/items/robot/items/storage.dm @@ -210,6 +210,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() . = ..() @@ -221,22 +238,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.")) @@ -244,8 +245,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 bc82fedff047..95e9bcd833f4 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -17,6 +17,7 @@ source = /datum/robot_energy_storage/medical merge_type = /obj/item/stack/medical pickup_sound = 'maplestation_modules/sound/items/pickup/surgery_cloth.ogg' + apply_verb = "treating" /// Sound played when heal doafter begins var/heal_sound /// How long it takes to apply it to yourself @@ -35,8 +36,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 /// Optional flags to supply to can_inject @@ -321,7 +320,7 @@ break // one at a time affecting.adjustBleedStacks(-1 * stop_bleeding) if(flesh_regeneration || sanitization) - for(var/datum/wound/flesh/burn/wound as anything in affecting.wounds) + for(var/datum/wound/flesh/wound in affecting.wounds) if(wound.can_be_ointmented_or_meshed()) wound.flesh_healing += flesh_regeneration wound.sanitization += sanitization @@ -375,6 +374,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 @@ -501,6 +502,18 @@ span_green("You bandage the wounds on [user == patient ? "your" : "[patient]'s"] [limb.plaintext_zone]."), visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, ) + if(heal_end_sound) + playsound(patient, heal_end_sound, 75, TRUE, MEDIUM_RANGE_SOUND_EXTRARANGE) + + 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 @@ -553,6 +566,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 64e33ab59cb5..21adeac18aa6 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -59,6 +59,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 314176a55512..e79193a2c6ed 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) AddElement(/datum/element/pat_out_fire) if(bedsheet_type == BEDSHEET_DOUBLE) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 72756bad2116..6b5427ce918f 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -802,8 +802,15 @@ computer.table = src break - RegisterSignal(loc, COMSIG_ATOM_ENTERED, PROC_REF(mark_patient)) - RegisterSignal(loc, COMSIG_ATOM_EXITED, PROC_REF(unmark_patient)) + 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) + + for (var/mob/living/carbon/potential_patient in loc) + mark_patient(potential_patient) /obj/structure/table/optable/Destroy() if(computer && computer.table == src) @@ -864,15 +871,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 @@ -888,16 +895,60 @@ if(patient && patient != potential_patient) return - if(potential_patient.body_position == LYING_DOWN && potential_patient.loc == loc) - patient = potential_patient + 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) - patient = replacement_patient - return - patient = null + 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(found_replacement) + +/obj/structure/table/optable/proc/set_patient(mob/living/carbon/new_patient) + if (patient) + 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 + 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 /* * Racks diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm index be2ea4426cab..df0a93b4f07c 100644 --- a/code/modules/antagonists/abductor/abductor.dm +++ b/code/modules/antagonists/abductor/abductor.dm @@ -106,13 +106,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 296eef07e814..000000000000 --- 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 = 32 - 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 = 32 - -/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, 2) - return 1 diff --git a/code/modules/antagonists/abductor/equipment/glands/heal.dm b/code/modules/antagonists/abductor/equipment/glands/heal.dm index 2af2abbc06d9..9b4c56a29989 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 03957af1763e..def08a576c88 100644 --- a/code/modules/antagonists/heretic/knowledge/starting_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/starting_lore.dm @@ -184,9 +184,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(!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 d6eb89f452fe..29e754421f15 100644 --- a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm +++ b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm @@ -157,12 +157,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/bioware/vein_threading, - /datum/surgery/advanced/bioware/nerve_splicing, - /datum/surgery_step/heal/combo/upgraded, - /datum/surgery_step/pacify, - /datum/surgery_step/revive, + /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/antagonists/traitor/objectives/sleeper_protocol.dm b/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm index baced8f03e88..d6c35f5ca942 100644 --- a/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm +++ b/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm @@ -66,74 +66,6 @@ /datum/traitor_objective/sleeper_protocol/ungenerate_objective() disk = null -/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 - 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." - 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/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/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 = target, - target_zone = BODY_ZONE_HEAD, - pain_message = "Your head pounds with unimaginable pain!", // Same message as other brain surgeries - pain_amount = SURGERY_PAIN_SEVERE, - ) - -/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) /datum/traitor_objective/sleeper_protocol/everybody //Much harder for non-med and non-robo progression_minimum = 30 MINUTES diff --git a/code/modules/asset_cache/assets/body_zones.dm b/code/modules/asset_cache/assets/body_zones.dm index cbdf268f6c1a..388b57b57e24 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 000000000000..029267788f94 --- /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 7dce8c4e6a2e..278c6d71e63a 100644 --- a/code/modules/client/preferences/species_features/lizard.dm +++ b/code/modules/client/preferences/species_features/lizard.dm @@ -125,7 +125,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 036a9029e1b9..c93b6cc55143 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/disco name = "horrific necktie" diff --git a/code/modules/clothing/suits/cloaks.dm b/code/modules/clothing/suits/cloaks.dm index 60652edcdee9..5ea661eb9709 100644 --- a/code/modules/clothing/suits/cloaks.dm +++ b/code/modules/clothing/suits/cloaks.dm @@ -12,7 +12,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!")) diff --git a/code/modules/experisci/experiment/handlers/experiment_handler.dm b/code/modules/experisci/experiment/handlers/experiment_handler.dm index 622d84551a28..ddecb9440ceb 100644 --- a/code/modules/experisci/experiment/handlers/experiment_handler.dm +++ b/code/modules/experisci/experiment/handlers/experiment_handler.dm @@ -344,16 +344,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, params) . = ..() diff --git a/code/modules/experisci/experiment/types/experiment.dm b/code/modules/experisci/experiment/types/experiment.dm index eea7525f30f4..4f37d99c5a91 100644 --- a/code/modules/experisci/experiment/types/experiment.dm +++ b/code/modules/experisci/experiment/types/experiment.dm @@ -106,3 +106,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/mapfluff/ruins/spaceruin_code/forgottenship.dm b/code/modules/mapfluff/ruins/spaceruin_code/forgottenship.dm index fa39b7063ae1..6522fad5b29a 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 5f0935bb8bde..8a002f4a8186 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, params) - 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 caa960882734..10a596ee67df 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 /// 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/update_icon_state() . = ..() // NON-MODULE CHANGE @@ -205,7 +214,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 @@ -228,21 +236,6 @@ medical_mode_flags ^= MEDBOT_DECLARE_CRIT if("stationary_mode") medical_mode_flags ^= MEDBOT_STATIONARY_MODE - if("sync_tech") - if(!linked_techweb) - to_chat(our_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() @@ -334,7 +327,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 @@ -363,6 +356,23 @@ if(CanReach(patient)) 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)]%!") /mob/living/basic/bot/medbot/autopatrol bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION diff --git a/code/modules/mob/living/basic/ruin_defender/flesh.dm b/code/modules/mob/living/basic/ruin_defender/flesh.dm index 2a1948172cf2..a938c70bbf7b 100644 --- a/code/modules/mob/living/basic/ruin_defender/flesh.dm +++ b/code/modules/mob/living/basic/ruin_defender/flesh.dm @@ -120,7 +120,7 @@ target.visible_message(span_danger("[src] [target_part ? "tears off and attaches itself" : "attaches itself"] to where [target][target.p_s()] limb used to be!")) current_bodypart = new part_type(TRUE) //dont_spawn_flesh, we cant use named arguments here - current_bodypart.replace_limb(target, TRUE) + current_bodypart.replace_limb(target) forceMove(current_bodypart) register_to_limb(current_bodypart) 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 d166e7d60fb6..9c80df1faebd 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -75,14 +75,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(!istype(operations.get_surgery_step(), /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 dae770051ecb..5235b696102f 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -25,16 +25,6 @@ QDEL_NULL(breathing_loop) GLOB.carbon_list -= src -/mob/living/carbon/item_interaction(mob/living/user, obj/item/tool, list/modifiers) - . = ..() - if(. & ITEM_INTERACT_ANY_BLOCKER) - return . - // Needs to happen after parent call otherwise wounds are prioritized over surgery - for(var/datum/wound/wound as anything in shuffle(all_wounds)) - if(wound.try_treating(tool, user)) - return ITEM_INTERACT_SUCCESS - return . - /mob/living/carbon/click_ctrl_shift(mob/user) if(iscarbon(user)) var/mob/living/carbon/carbon_user = user @@ -923,6 +913,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(regenerate_existing = (heal_flags & HEAL_REFRESH_ORGANS)) @@ -1147,9 +1139,9 @@ 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)) - admin_ticket_log("key_name_admin(usr)] has replaced [src]'s [BP.type] with [new_bp.type]") - qdel(BP) + 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 to_chat(usr, "Failed to replace bodypart! They might be incompatible.") admin_ticket_log("[key_name_admin(usr)] has attempted to modify the bodyparts of [src]") diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 25ac715ee08d..62c3d01092de 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -665,15 +665,17 @@ changed_something = TRUE new_organ = new new_organ() new_organ.replace_into(src) - - var/obj/item/bodypart/new_part = pick(GLOB.bioscrambler_valid_parts) - var/obj/item/bodypart/picked_user_part = get_bodypart(initial(new_part.body_zone)) - 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) - if (picked_user_part) - qdel(picked_user_part) + new_organ.organ_flags |= ORGAN_MUTANT + + if (!HAS_TRAIT(src, TRAIT_NODISMEMBER)) + var/obj/item/bodypart/new_part = pick(GLOB.bioscrambler_valid_parts) + var/obj/item/bodypart/picked_user_part = get_bodypart(initial(new_part.body_zone)) + 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) + if (picked_user_part) + qdel(picked_user_part) if (!changed_something) to_chat(src, span_notice("Your augmented body protects you from [scramble_source]!")) diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index 2796f58aa4de..ae6164b81456 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -83,6 +83,11 @@ if(wound_msg) . += span_danger("[wound_msg]") + 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 980de7d296fa..3277df806e13 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -1576,7 +1576,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 672ff8c1f85d..d0d17fa4ef08 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -506,24 +506,46 @@ RETURN_TYPE(/list) SHOULD_NOT_OVERRIDE(TRUE) - var/covered_flags = NONE - var/list/all_worn_items = get_equipped_items() - 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() - 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 1c0c56661ff5..49a0ab0c2991 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -172,11 +172,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 5a5a54f91239..bf11c7d9cf4b 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -65,7 +65,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 125cef69cc80..424d8c1eff8c 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -98,6 +98,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/hitting_projectile, def_zone, piercing_hit = FALSE) . = ..() if(. != BULLET_ACT_HIT) @@ -382,13 +396,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 @@ -754,35 +763,3 @@ return TRUE return FALSE - -/// Adds a modifier to the mob's surgery and updates any ongoing surgeries. -/// Multiplicative, so two 0.8 modifiers would result in a 0.64 speed modifier, meaning surgery is 0.64x faster. -/mob/living/proc/add_surgery_speed_mod(id, speed_mod) - if(LAZYACCESS(mob_surgery_speed_mods, id) == speed_mod) - return FALSE - if(LAZYACCESS(mob_surgery_speed_mods, id)) - remove_surgery_speed_mod(id) - - LAZYSET(mob_surgery_speed_mods, id, speed_mod) - for(var/datum/surgery/ongoing as anything in surgeries) - ongoing.speed_modifier *= speed_mod - return TRUE - -/// Removes a modifier from the mob's surgery and updates any ongoing surgeries. -/mob/living/proc/remove_surgery_speed_mod(id) - var/removing_mod = LAZYACCESS(mob_surgery_speed_mods, id) - if(!removing_mod) - return FALSE - - LAZYREMOVE(mob_surgery_speed_mods, id) - for(var/datum/surgery/ongoing as anything in surgeries) - ongoing.speed_modifier /= removing_mod - return TRUE - -/// Helper to add a surgery speed modifier which is removed after a set duration. -/mob/living/proc/add_timed_surgery_speed_mod(id, speed_mod, duration) - if(QDELING(src)) - return - if(!add_surgery_speed_mod(id, speed_mod)) - return - addtimer(CALLBACK(src, PROC_REF(remove_surgery_speed_mod), id), duration) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 29c3f91dc38d..776b22320414 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -107,9 +107,7 @@ /// 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() - /// Lazylist of surgery speed modifiers + /// 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. diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index d4dada446278..8fa7fd9354f9 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), 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/mob/living/simple_animal/slime/defense.dm b/code/modules/mob/living/simple_animal/slime/defense.dm index dd94e4e2bab6..a46307b541a4 100644 --- a/code/modules/mob/living/simple_animal/slime/defense.dm +++ b/code/modules/mob/living/simple_animal/slime/defense.dm @@ -58,11 +58,6 @@ discipline_slime(user) else - if(stat == DEAD && surgeries.len) - if(!user.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK)) - for(var/datum/surgery/operations as anything in surgeries) - if(operations.next_step(user, modifiers)) - return TRUE if(..()) //successful attack attacked_stacks += 10 @@ -72,12 +67,6 @@ discipline_slime(user) /mob/living/simple_animal/slime/attackby(obj/item/attacking_item, mob/living/user, params) - if(stat == DEAD && surgeries.len) - var/list/modifiers = params2list(params) - if(!user.combat_mode || (LAZYACCESS(modifiers, RIGHT_CLICK))) - for(var/datum/surgery/operations as anything in surgeries) - if(operations.next_step(user, modifiers)) - return TRUE if(istype(attacking_item, /obj/item/stack/sheet/mineral/plasma) && !stat) //Lets you feed slimes plasma. add_friendship(user, 1) to_chat(user, span_notice("You feed the slime the plasma. It chirps happily.")) diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index 8267401fa2d1..f2d9b9c88a0c 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -169,7 +169,9 @@ cut_overlays() var/icon_text = "[slime_type.colour]-[life_stage]" icon_dead = "[icon_text]-dead" - if(stat != DEAD) + if(cores <= 0) + icon_state = "[slime_type.colour]-cut" + else if(stat != DEAD) icon_state = icon_text if(current_mood && !stat) add_overlay("aslime-[current_mood]") diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm index c240d55cbdbc..9b05bd50a7e6 100644 --- a/code/modules/mod/modules/modules_medical.dm +++ b/code/modules/mod/modules/modules_medical.dm @@ -193,40 +193,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(!ishuman(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/procedure as anything in organ_receiver.surgeries) - if(procedure.location != organ.zone) - continue - if(!istype(procedure, /datum/surgery/organ_manipulation)) - continue - var/datum/surgery_step/surgery_step = procedure.get_surgery_step() - if(!istype(surgery_step, /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 @@ -407,15 +393,36 @@ /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/bioware/nerve_splicing, - /datum/surgery/advanced/bioware/nerve_grounding, - /datum/surgery/advanced/bioware/vein_threading, - /datum/surgery/advanced/bioware/muscled_veins, - /datum/surgery/advanced/bioware/ligament_hook, - /datum/surgery/advanced/bioware/ligament_reinforcement, - /datum/surgery/advanced/bioware/cortex_imprint, - /datum/surgery/advanced/bioware/cortex_folding, + /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 + desc = "A module using an onboard surgical computer which can be connected to other computers to download and \ + perform advanced surgeries on the go. This one came pre-loaded with some emergency surgeries." + device = /obj/item/surgical_processor/mod/emergency + +/obj/item/surgical_processor/mod/emergency + loaded_surgeries = list( + /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 3dc404118ee1..7fc0dcd736f1 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/gun.dm b/code/modules/projectiles/gun.dm index 3a5402d4a517..3a62b5946726 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -405,12 +405,6 @@ return if(target == user && (user.zone_selected != BODY_ZONE_PRECISE_MOUTH && doafter_self_shoot)) //so we can't shoot ourselves (unless mouth selected) return - if(iscarbon(target)) - var/mob/living/carbon/C = target - for(var/i in C.all_wounds) - var/datum/wound/W = i - if(W.try_treating(src, user)) - return // another coward cured! if(istype(user))//Check if the user can use the gun, if the user isn't alive(turrets) assume it can. var/mob/living/L = user diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm index d187d5996794..a457c214464d 100644 --- a/code/modules/projectiles/guns/ballistic/pistol.dm +++ b/code/modules/projectiles/guns/ballistic/pistol.dm @@ -67,6 +67,19 @@ /obj/item/gun/ballistic/automatic/pistol/clandestine/fisher/Initialize(mapload) . = ..() underbarrel = new /obj/item/gun/energy/recharge/fisher(src) + AddElement(/datum/element/examine_lore, \ + 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.
    \ +
    \ + The slide is chopped down, with the front half of the handgun featuring a monolithic integral suppressor built around the barrel, \ + and a compact kinetic light disruptor mounted underneath the barrel assembly. The integral suppressor is engineered to not affect \ + ballistic performance nor affect the concealability of the handgun, leading to a surprisingly robust firearm.
    \ +
    \ + Scarborough Arms has never actually addressed allegations of their involvement with the modification and/or manufacture \ + of the SC/FISHER or similar disruptor weapons. Prospective operators are reminded that kinetic light disruptors do not actually physically harm targets.
    \ +
    \ + Caveat emptor." \ + ) /obj/item/gun/ballistic/automatic/pistol/clandestine/fisher/Destroy() QDEL_NULL(underbarrel) diff --git a/code/modules/projectiles/guns/energy/recharge.dm b/code/modules/projectiles/guns/energy/recharge.dm index 22f494a62f61..b97397f1c206 100644 --- a/code/modules/projectiles/guns/energy/recharge.dm +++ b/code/modules/projectiles/guns/energy/recharge.dm @@ -150,13 +150,23 @@ recharge_time = 1.2 SECONDS ammo_type = list(/obj/item/ammo_casing/energy/fisher) -/obj/item/gun/energy/recharge/fisher/examine_more(mob/user) +/obj/item/gun/energy/recharge/fisher/Initialize(mapload) . = ..() - . += span_notice("The SC/FISHER is an illegally-modified kinetic accelerator cut down and refit into a disassembled miniature energy gun chassis, \ - with its pressure chamber attenuated to launch kinetic bolts that temporarily disrupt flashlights, cameras, and certain other electronics. \ - This effect also works on cyborg headlamps, and works longer in melee.

    \ - While some would argue that this is a really terrible design choice, others argue that it is very funny to be able to shoot at light sources.
    \ - Caveat emptor.") + AddElement(/datum/element/examine_lore, \ + 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.
    \ +
    \ + The reengineered kinetic accelerator central to the SC/FISHER's functionality has been modified for its kinetic bolts to \ + temporarily disrupt flashlights, cameras, APCs, and pAI speech modules, in return for dealing no damage. \ + This effect works longer against targets struck with the SC/FISHER either in melee or by having it thrown at them, but \ + you probably shouldn't be throwing it at people.
    \ +
    \ + While some would argue that sacrificing damage for a light-disrupting, fixture-breaking gimmick \ + makes the SC/FISHER a dead-end in equipment development, others argue that it is both amusing and tactically sound \ + to be able to shoot at light sources and pesky pAIs to disrupt their function.
    \ +
    \ + Caveat emptor." \ + ) /obj/item/gun/energy/recharge/fisher/attack(mob/living/target_mob, mob/living/user, params) . = ..() diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm index fc871a182177..f5eafe127c39 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm @@ -105,7 +105,7 @@ return exposed_mob.adjust_fire_stacks(reac_volume / 15) - exposed_mob.add_timed_surgery_speed_mod(type, clamp(round(20 / (15 + sqrt(max(1, boozepwr))), 0.01), 0.25, 1.25), min(reac_volume * 1 MINUTES, 5 MINUTES)) + 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 26a27e9cbad3..fc52a584ecc2 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -826,7 +826,7 @@ if(!(methods & (TOUCH|VAPOR|PATCH))) return - exposed_mob.add_timed_surgery_speed_mod(type, 0.4, min(reac_volume * 1 MINUTES, 5 MINUTES)) + 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/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 90c634bf508b..03bf31476660 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -388,7 +388,7 @@ to_chat(exposed_mob, span_warning("Your stomach feels empty and cramps!")) if(methods & (PATCH|TOUCH)) - exposed_mob.add_timed_surgery_speed_mod(type, 0.9, min(reac_volume * 1 MINUTES, 5 MINUTES)) + exposed_mob.add_surgery_speed_mod(type, 0.9, min(reac_volume * 1 MINUTES, 5 MINUTES)) if(show_message) to_chat(exposed_mob, span_danger("You feel your injuries fade away to nothing!") ) @@ -1204,7 +1204,8 @@ . = ..() if(!(methods & (TOUCH|VAPOR|PATCH))) return - exposed_mob.add_timed_surgery_speed_mod(type, 1.1, min(reac_volume * 1 MINUTES, 5 MINUTES)) + + 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 92c0bfb3beb1..af85066889f6 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1178,9 +1178,9 @@ . = ..() if(!(methods & (TOUCH|VAPOR|PATCH))) return - exposed_mob.add_timed_surgery_speed_mod(type, 0.8, min(reac_volume * 1 MINUTES, 5 MINUTES)) + 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/flesh/burn_wound) +/datum/reagent/space_cleaner/sterilizine/on_burn_wound_processing(datum/wound/burn/flesh/burn_wound) burn_wound.sanitization += 0.9 /datum/reagent/iron diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index 78b44317b6f9..702d31269450 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -248,7 +248,8 @@ /datum/reagent/cryostylane/on_mob_add(mob/living/affected_mob, amount) . = ..() - affected_mob.add_surgery_speed_mod(type, 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.add_atom_colour(COLOR_CYAN, TEMPORARY_COLOUR_PRIORITY) ADD_TRAIT(affected_mob, TRAIT_NO_ORGAN_DECAY, type) diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm index 5d7ad422e581..64049ba6dafd 100644 --- a/code/modules/religion/burdened/psyker.dm +++ b/code/modules/religion/burdened/psyker.dm @@ -76,7 +76,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() diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index 4193f763c57f..691c42121ebd 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -1013,171 +1013,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 + id = "surgery_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 + id = "surgery_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/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_upgrade/femto + name = "Tend Wounds Upgrade" + surgery = /datum/surgery_operation/basic/tend_wounds/upgraded/master + id = "surgery_heal_upgrade_femto" + +/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 + id = "surgery_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 + 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_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 + 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_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 + 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_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 them to pump blood without a heart." + 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 + 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_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 + 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." + id = "surgery_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 + 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_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 + 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_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 + 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_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 ccd4b83ca470..b0e3dbe2a328 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -231,16 +231,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 fd6ca7949b44..14d1edadd01e 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 @@ -565,3 +566,10 @@ handler.announce_message_to_all(announcetext) 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 c5d21c255cf8..a389f14bf2c3 100644 --- a/code/modules/research/techweb/nodes/alien_nodes.dm +++ b/code/modules/research/techweb/nodes/alien_nodes.dm @@ -72,6 +72,7 @@ "alien_retractor", "alien_saw", "alien_scalpel", + "medibot_upgrade_four", "surgery_brainwashing", "surgery_heal_combo_upgrade_femto", "surgery_zombie", diff --git a/code/modules/research/techweb/nodes/surgery_nodes.dm b/code/modules/research/techweb/nodes/surgery_nodes.dm index 0b8812191e2a..73ddd1377139 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) @@ -28,9 +28,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_wing_reconstruction", ) @@ -43,6 +43,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_imprint", "surgery_heal_combo_upgrade", diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm index 5b3bbd063b95..788782c453f7 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 4db161e52493..000000000000 --- a/code/modules/surgery/advanced/bioware/bioware_surgery.dm +++ /dev/null @@ -1,13 +0,0 @@ -/datum/surgery/advanced/bioware - name = "Enhancement surgery" - var/bioware_target = BIOWARE_GENERIC - -/datum/surgery/advanced/bioware/can_start(mob/user, mob/living/carbon/human/target) - if(!..()) - return FALSE - if(!istype(target)) - return FALSE - for(var/datum/bioware/bioware as anything in target.biowares) - if(bioware.mod_type == bioware_target) - return FALSE - return TRUE 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 43050f9ddd3f..000000000000 --- a/code/modules/surgery/advanced/bioware/cortex_folding.dm +++ /dev/null @@ -1,94 +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." - 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/fold_cortex, - /datum/surgery_step/close, - ) - - bioware_target = BIOWARE_CORTEX - -/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/fold_cortex - name = "fold cortex (hand)" - accept_hand = TRUE - time = 125 - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONE_HEAD, - pain_message = "Your head throbs with gruesome pain, it's nearly too much to handle!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/datum/surgery_step/fold_cortex/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 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 = target, - target_zone = BODY_ZONE_HEAD, - pain_message = "Your brain feels stronger... more flexible!", - surgery_moodlet = /datum/mood_event/surgery/major, - ) - new /datum/bioware/cortex_fold(target) - return ..() - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONE_HEAD, - pain_message = "Your brain throbs with intense pain; thinking hurts!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - target.adjustOrganLoss(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 - -/datum/bioware/cortex_fold - name = "Cortex Fold" - desc = "The cerebral cortex has been folded into a complex fractal pattern, and can support non-standard neural patterns." - mod_type = BIOWARE_CORTEX - -/datum/bioware/cortex_fold/on_gain() - . = ..() - ADD_TRAIT(owner, TRAIT_SPECIAL_TRAUMA_BOOST, EXPERIMENTAL_SURGERY_TRAIT) - -/datum/bioware/cortex_fold/on_lose() - REMOVE_TRAIT(owner, TRAIT_SPECIAL_TRAUMA_BOOST, EXPERIMENTAL_SURGERY_TRAIT) - return ..() 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 448678a5c9a2..000000000000 --- a/code/modules/surgery/advanced/bioware/cortex_imprint.dm +++ /dev/null @@ -1,90 +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." - 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/imprint_cortex, - /datum/surgery_step/close, - ) - - bioware_target = BIOWARE_CORTEX - -/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/imprint_cortex - name = "imprint cortex (hand)" - accept_hand = TRUE - time = 125 - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONE_HEAD, - pain_message = "Your head throbs with gruesome pain, it's nearly too much to handle!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/datum/surgery_step/imprint_cortex/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 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 = target, - target_zone = BODY_ZONE_HEAD, - pain_message = "Your brain feels stronger... more resillient!", - surgery_moodlet = /datum/mood_event/surgery/major, - ) - new /datum/bioware/cortex_imprint(target) - return ..() - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONE_HEAD, - pain_message = "Your brain throbs with intense pain; thinking hurts!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - target.adjustOrganLoss(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 - -/datum/bioware/cortex_imprint - name = "Cortex Imprint" - desc = "The cerebral cortex has been reshaped into a redundant neural pattern, making the brain able to bypass impediments caused by minor brain traumas." - mod_type = BIOWARE_CORTEX - can_process = TRUE - -/datum/bioware/cortex_imprint/process() - owner.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC) 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 80f5cf41779c..000000000000 --- a/code/modules/surgery/advanced/bioware/ligament_hook.dm +++ /dev/null @@ -1,69 +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." - 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/reshape_ligaments, - /datum/surgery_step/close, - ) - bioware_target = BIOWARE_LIGAMENTS - -/datum/surgery_step/reshape_ligaments - name = "reshape ligaments (hand)" - accept_hand = TRUE - time = 125 - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONES_LIMBS, - pain_message = "Your limbs burn with severe pain!", - pain_amount = SURGERY_PAIN_LOW, - pain_type = BURN, - pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - target.cause_pain(BODY_ZONES_LIMBS, 20, BURN) // NON-MODULE CHANGE - -/datum/surgery_step/reshape_ligaments/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 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 = target, - target_zone = BODY_ZONES_LIMBS, - pain_message = "Your limbs feel... strangely loose.", - surgery_moodlet = /datum/mood_event/surgery/major, - ) - new /datum/bioware/hooked_ligaments(target) - return ..() - -/datum/bioware/hooked_ligaments - name = "Hooked Ligaments" - desc = "The ligaments and nerve endings that connect the torso to the limbs are formed into a hook-like shape, so limbs can be attached without requiring surgery, but are easier to sever." - mod_type = BIOWARE_LIGAMENTS - -/datum/bioware/hooked_ligaments/on_gain() - ..() - owner.add_traits(list(TRAIT_LIMBATTACHMENT, TRAIT_EASYDISMEMBER), EXPERIMENTAL_SURGERY_TRAIT) - -/datum/bioware/hooked_ligaments/on_lose() - ..() - owner.remove_traits(list(TRAIT_LIMBATTACHMENT, TRAIT_EASYDISMEMBER), EXPERIMENTAL_SURGERY_TRAIT) 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 45e3864c8555..000000000000 --- a/code/modules/surgery/advanced/bioware/ligament_reinforcement.dm +++ /dev/null @@ -1,69 +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." - 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/reinforce_ligaments, - /datum/surgery_step/close, - ) - - bioware_target = BIOWARE_LIGAMENTS - -/datum/surgery_step/reinforce_ligaments - name = "reinforce ligaments (hand)" - accept_hand = TRUE - time = 125 - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONES_LIMBS, - pain_message = "Your limbs burn with severe pain!", - pain_amount = SURGERY_PAIN_LOW, - pain_type = BURN, - pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/datum/surgery_step/reinforce_ligaments/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 reinforce [target]'s ligaments!"), - span_notice("[user] reinforces [target]'s ligaments!"), - span_notice("[user] finishes manipulating [target]'s ligaments."), - ) - display_pain( - target = target, - target_zone = BODY_ZONES_LIMBS, - pain_message = "Your limbs feel more secure, but also more frail.", - surgery_moodlet = /datum/mood_event/surgery/major, - ) - new /datum/bioware/reinforced_ligaments(target) - return ..() - -/datum/bioware/reinforced_ligaments - name = "Reinforced Ligaments" - desc = "The ligaments and nerve endings that connect the torso to the limbs are protected by a mix of bone and tissues, and are much harder to separate from the body, but are also easier to wound." - mod_type = BIOWARE_LIGAMENTS - -/datum/bioware/reinforced_ligaments/on_gain() - ..() - owner.add_traits(list(TRAIT_NODISMEMBER, TRAIT_EASILY_WOUNDED), EXPERIMENTAL_SURGERY_TRAIT) - -/datum/bioware/reinforced_ligaments/on_lose() - ..() - owner.remove_traits(list(TRAIT_NODISMEMBER, TRAIT_EASILY_WOUNDED), EXPERIMENTAL_SURGERY_TRAIT) 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 ea72f9101ed3..000000000000 --- a/code/modules/surgery/advanced/bioware/muscled_veins.dm +++ /dev/null @@ -1,69 +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." - 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/muscled_veins, - /datum/surgery_step/close, - ) - - bioware_target = BIOWARE_CIRCULATION - -/datum/surgery_step/muscled_veins - name = "shape vein muscles (hand)" - accept_hand = TRUE - time = 125 - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONES_ALL, - pain_message = "Your entire body burns in agony!", - pain_amount = SURGERY_PAIN_MEDIUM, - pain_type = BURN, - pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - target.cause_pain(BODY_ZONES_ALL, 25, BURN) // NON-MODULE CHANGE - -/datum/surgery_step/muscled_veins/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 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 = target, - target_zone = BODY_ZONES_ALL, - pain_message = "You can feel your heartbeat's powerful pulses ripple through your body!", - surgery_moodlet = /datum/mood_event/surgery/major, - ) - new /datum/bioware/muscled_veins(target) - return ..() - -/datum/bioware/muscled_veins - name = "Muscled Veins" - desc = "The circulatory system is affixed with a muscled membrane, allowing the veins to pump blood without the need for a heart." - mod_type = BIOWARE_CIRCULATION - -/datum/bioware/muscled_veins/on_gain() - ..() - ADD_TRAIT(owner, TRAIT_STABLEHEART, EXPERIMENTAL_SURGERY_TRAIT) - -/datum/bioware/muscled_veins/on_lose() - ..() - REMOVE_TRAIT(owner, TRAIT_STABLEHEART, EXPERIMENTAL_SURGERY_TRAIT) 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 3369016c3dec..000000000000 --- a/code/modules/surgery/advanced/bioware/nerve_grounding.dm +++ /dev/null @@ -1,70 +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." - 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/ground_nerves, - /datum/surgery_step/close, - ) - - bioware_target = BIOWARE_NERVES - -/datum/surgery_step/ground_nerves - name = "ground nerves (hand)" - accept_hand = TRUE - time = 155 - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONES_ALL, - pain_message = "Your entire body goes numb!", - pain_amount = SURGERY_PAIN_HIGH, - pain_type = BURN, - pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - target.cause_pain(BODY_ZONES_ALL, 15, BURN) // NON-MODULE CHANGE - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONES_ALL, - pain_message = "You regain feeling in your body! You feel energzed!", - pain_amount = -0.5 * SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - new /datum/bioware/grounded_nerves(target) - return ..() - -/datum/bioware/grounded_nerves - name = "Grounded Nerves" - desc = "Nerves form a safe path for electricity to traverse, protecting the body from electric shocks." - mod_type = BIOWARE_NERVES - -/datum/bioware/grounded_nerves/on_gain() - ..() - ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, EXPERIMENTAL_SURGERY_TRAIT) - -/datum/bioware/grounded_nerves/on_lose() - ..() - REMOVE_TRAIT(owner, TRAIT_SHOCKIMMUNE, EXPERIMENTAL_SURGERY_TRAIT) 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 1cddee7d8d76..000000000000 --- a/code/modules/surgery/advanced/bioware/nerve_splicing.dm +++ /dev/null @@ -1,73 +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." - 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/splice_nerves, - /datum/surgery_step/close, - ) - - bioware_target = BIOWARE_NERVES - -/datum/surgery_step/splice_nerves - name = "splice nerves (hand)" - accept_hand = TRUE - time = 155 - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONES_ALL, - pain_message = "Your entire body goes numb!", - pain_amount = SURGERY_PAIN_HIGH, - pain_type = BURN, - pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/datum/surgery_step/splice_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 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 = target, - target_zone = BODY_ZONES_ALL, - pain_message = "You regain feeling in your body; It feels like everything's happening around you in slow motion!", - pain_amount = -0.5 * SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - new /datum/bioware/spliced_nerves(target) - if(target.ckey) - SSblackbox.record_feedback("nested tally", "nerve_splicing", 1, list("[target.ckey]", "got")) - return ..() - -/datum/bioware/spliced_nerves - name = "Spliced Nerves" - desc = "Nerves are connected to each other multiple times, greatly reducing the impact of stunning effects." - mod_type = BIOWARE_NERVES - -/datum/bioware/spliced_nerves/on_gain() - ..() - owner.physiology.stun_mod *= 0.5 - owner.physiology.stamina_mod *= 0.8 - -/datum/bioware/spliced_nerves/on_lose() - ..() - owner.physiology.stun_mod *= 2 - owner.physiology.stamina_mod *= 1.25 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 b3b22f524221..000000000000 --- a/code/modules/surgery/advanced/bioware/vein_threading.dm +++ /dev/null @@ -1,69 +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." - 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/thread_veins, - /datum/surgery_step/close, - ) - - bioware_target = BIOWARE_CIRCULATION - -/datum/surgery_step/thread_veins - name = "thread veins (hand)" - accept_hand = TRUE - time = 125 - -/datum/surgery_step/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 = target, - target_zone = BODY_ZONES_ALL, - pain_message = "Your entire body burns in agony!", - pain_amount = SURGERY_PAIN_MEDIUM, - pain_type = BURN, - pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - target.cause_pain(BODY_ZONES_ALL, 25, BURN) // NON-MODULE CHANGE - -/datum/surgery_step/thread_veins/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 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 = target, - target_zone = BODY_ZONES_ALL, - pain_message = "You can feel your blood pumping through reinforced veins!", - surgery_moodlet = /datum/mood_event/surgery/major, - ) - new /datum/bioware/threaded_veins(target) - return ..() - -/datum/bioware/threaded_veins - name = "Threaded Veins" - desc = "The circulatory system is woven into a mesh, severely reducing the amount of blood lost from wounds." - mod_type = BIOWARE_CIRCULATION - -/datum/bioware/threaded_veins/on_gain() - ..() - owner.physiology.bleed_mod *= 0.25 - -/datum/bioware/threaded_veins/on_lose() - ..() - owner.physiology.bleed_mod *= 4 diff --git a/code/modules/surgery/advanced/brainwashing.dm b/code/modules/surgery/advanced/brainwashing.dm deleted file mode 100644 index 4b2879c6a135..000000000000 --- a/code/modules/surgery/advanced/brainwashing.dm +++ /dev/null @@ -1,98 +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 - 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." - 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/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 = 200 - preop_sound = 'sound/surgery/hemostat1.ogg' - success_sound = 'sound/surgery/hemostat1.ogg' - failure_sound = 'sound/surgery/organ2.ogg' - var/objective - -/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") - 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 = target, - target_zone = BODY_ZONE_HEAD, - pain_message = "Your head pounds with unimaginable pain!", - pain_amount = SURGERY_PAIN_SEVERE, - ) - -/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_TRAIT(target, TRAIT_MINDSHIELD)) - 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 = target, - target_zone = BODY_ZONE_HEAD, - pain_message = "Your head throbs with horrible pain!", - pain_amount = SURGERY_PAIN_SEVERE, - ) - target.adjustOrganLoss(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 6c1a0039a827..000000000000 --- a/code/modules/surgery/advanced/lobotomy.dm +++ /dev/null @@ -1,121 +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." - possible_locs = list(BODY_ZONE_HEAD) - requires_bodypart_type = NONE - 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/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 = 100 - preop_sound = 'sound/surgery/scalpel1.ogg' - success_sound = 'sound/surgery/scalpel2.ogg' - failure_sound = 'sound/surgery/organ2.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 = target, - target_zone = target_zone, - pain_message = "Your head pounds with unimaginable pain!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/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 = target, - target_zone = target_zone, - pain_message = "Your head goes totally numb for a moment, the pain is overwhelming!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - - 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 = target, - target_zone = target_zone, - pain_message = "The pain in your head only seems to get worse!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - 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 14d8329f9236..000000000000 --- a/code/modules/surgery/advanced/necrotic_revival.dm +++ /dev/null @@ -1,63 +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." - 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 = 50 - 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 = target, - target_zone = target_zone, - pain_message = "Your head pounds with unimaginable pain!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/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 = target, - target_zone = target_zone, - pain_message = "Your head goes totally numb for a moment, the pain is overwhelming!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - 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 e7f351984066..000000000000 --- a/code/modules/surgery/advanced/pacification.dm +++ /dev/null @@ -1,83 +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." - possible_locs = list(BODY_ZONE_HEAD) - requires_bodypart_type = NONE - 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/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 = 40 - preop_sound = 'sound/surgery/hemostat1.ogg' - success_sound = 'sound/surgery/hemostat1.ogg' - failure_sound = 'sound/surgery/organ2.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 = target, - target_zone = target_zone, - pain_message = "Your head pounds with unimaginable pain!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/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 = target, - target_zone = target_zone, - pain_message = "Your head pounds... the concept of violence flashes in your head, and nearly makes you hurl!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - target.adjust_disgust(DISGUST_LEVEL_DISGUSTED) - 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 = target, - target_zone = target_zone, - pain_message = "Your head pounds, and it feels like it's getting worse!", - pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - 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 fd16c2e17142..000000000000 --- a/code/modules/surgery/advanced/viral_bonding.dm +++ /dev/null @@ -1,71 +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." - 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 = 100 - 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 = target, - target_zone = target_zone, - pain_message = "You feel a searing heat spread through your chest!", - pain_amount = SURGERY_PAIN_HIGH, - pain_type = BURN, - pain_overlay_severity = 1, - ) - -/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 = target, - target_zone = target_zone, - pain_message = "You feel a faint throbbing in your chest.", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - 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 e4b83ed9e9ab..000000000000 --- a/code/modules/surgery/advanced/wingreconstruction.dm +++ /dev/null @@ -1,69 +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 = 200 - 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 = target, - target_zone = target_zone, - pain_message = "Your wings sting like hell!", - pain_amount = SURGERY_PAIN_TRIVIAL, - pain_type = BURN, - ) - -/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 = target, - target_zone = target_zone, - pain_message = "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() - - 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 d5b76c971f23..000000000000 --- a/code/modules/surgery/amputation.dm +++ /dev/null @@ -1,84 +0,0 @@ - -/datum/surgery/amputation - name = "Amputation" - requires_bodypart_type = NONE - 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/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 = 64 - preop_sound = 'sound/surgery/scalpel1.ogg' - success_sound = 'sound/surgery/organ2.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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to sever [target]'s [parse_zone(target_zone)]!"), - span_notice("[user] begins to sever [target]'s [parse_zone(target_zone)]!"), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a gruesome pain in your [parse_zone(target_zone)]'s joint!", - pain_amount = SURGERY_PAIN_MEDIUM, // loss of the limb also applies pain to the chest, so we can afford to make this a bit lower - pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - - -/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 [parse_zone(target_zone)]."), - span_notice("[user] severs [target]'s [parse_zone(target_zone)]!"), - span_notice("[user] severs [target]'s [parse_zone(target_zone)]!"), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You can no longer feel your severed [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_MEDIUM, - pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - - 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/autopsy.dm b/code/modules/surgery/autopsy.dm deleted file mode 100644 index 42ed52bb6cac..000000000000 --- a/code/modules/surgery/autopsy.dm +++ /dev/null @@ -1,57 +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/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 begins 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."), - ) - -/datum/surgery_step/autopsy/success(mob/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) - if(!HAS_TRAIT(src, TRAIT_SURGICALLY_ANALYZED)) - 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) && ishuman(user)) - var/mob/living/carbon/human/morbid_weirdo = user - morbid_weirdo.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.adjustBruteLoss(5) diff --git a/code/modules/surgery/blood_filter.dm b/code/modules/surgery/blood_filter.dm deleted file mode 100644 index 72ce9a90176c..000000000000 --- a/code/modules/surgery/blood_filter.dm +++ /dev/null @@ -1,96 +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/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) - 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)) - 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/ping.ogg' - -/datum/surgery_step/filter_blood/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - 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 [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a throbbing pain in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_LOW, - ) - -/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, min(chem.volume * 0.22, 10)) - display_results( - user, - target, - span_notice("\The [tool] pings as it finishes filtering [target]'s blood."), - span_notice("\The [tool] pings as it stops pumping [target]'s blood."), - span_notice("\The [tool] pings as it stops pumping."), - ) - - 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.adjustBruteLoss(5) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index fd01d4da1e15..eb9486b89f80 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -93,9 +93,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 @@ -202,6 +199,9 @@ /// A potential texturing overlay to put on the limb var/datum/bodypart_overlay/texture/texture_bodypart_overlay + /// 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) @@ -233,6 +233,16 @@ if(!IS_ORGANIC_LIMB(src)) grind_results = null + 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() @@ -365,6 +375,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 @@ -376,18 +390,121 @@ if(current_gauze) check_list += span_notice("\t\tThere is some [current_gauze.name] wrapped around it.") else if(can_bleed()) - switch(get_modified_bleed_rate()) + 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) @@ -640,7 +757,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. @@ -764,6 +880,9 @@ UnregisterSignal(old_owner, COMSIG_ATOM_RESTYLE) + 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) @@ -795,6 +914,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) @@ -1214,7 +1336,29 @@ if(generic_bleedstacks > 0) cached_bleed_rate += 1 - for(var/obj/item/embeddies in embedded_objects) + // 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.is_embed_harmless()) continue cached_bleed_rate += embeddies.embed_data.blood_loss @@ -1368,6 +1512,8 @@ return "flesh" if (biological_state & BIO_WIRED) return "wiring" + if (biological_state & BIO_CHITIN) + return "chitin" return "error" @@ -1377,6 +1523,12 @@ return "bone" if (biological_state & BIO_METAL) return "metal" + if (biological_state & BIO_FLESH) + 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" @@ -1403,3 +1555,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 6a11c94b3c4a..5cebc2754820 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -101,14 +101,9 @@ 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 - for(var/obj/item/embedded in embedded_objects) - embedded.forceMove(src) // It'll self remove via signal reaction, just need to move it + embedded.forceMove(drop_loc) // It'll self remove via signal reaction, just need to move it + if(!phantom_owner.has_embedded_objects()) phantom_owner.clear_alert(ALERT_EMBEDDED_OBJECT) phantom_owner.clear_mood_event("embedded") @@ -241,17 +236,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) @@ -291,16 +286,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 6e9bd4b990f0..2bd2f6cfdc06 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -242,6 +242,7 @@ max_damage = LIMB_MAX_HP_ALIEN_CORE 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 acb49c32047c..40f392ea6abe 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -104,7 +104,8 @@ bodypart_flags = BODYPART_UNREMOVABLE max_damage = LIMB_MAX_HP_ALIEN_CORE acceptable_bodyshape = BODYSHAPE_HUMANOID - wing_types = NONE + wing_types = null + biological_state = BIO_STANDARD_ALIEN burn_modifier = 2 /obj/item/bodypart/chest/larva @@ -282,6 +283,7 @@ should_draw_greyscale = FALSE appendage_noun = "scythe-like hand" burn_modifier = 2 + biological_state = BIO_STANDARD_ALIEN /obj/item/bodypart/arm/right name = "right arm" @@ -381,6 +383,7 @@ should_draw_greyscale = FALSE appendage_noun = "scythe-like hand" burn_modifier = 2 + biological_state = BIO_STANDARD_ALIEN /// Parent Type for legs, should not appear in game. /obj/item/bodypart/leg @@ -493,6 +496,7 @@ max_damage = LIMB_MAX_HP_ALIEN_LIMBS should_draw_greyscale = FALSE burn_modifier = 2 + biological_state = BIO_STANDARD_ALIEN /obj/item/bodypart/leg/right name = "right leg" @@ -583,3 +587,4 @@ max_damage = LIMB_MAX_HP_ALIEN_LIMBS should_draw_greyscale = FALSE burn_modifier = 2 + biological_state = BIO_STANDARD_ALIEN diff --git a/code/modules/surgery/bodyparts/wounds.dm b/code/modules/surgery/bodyparts/wounds.dm index 49396f4e06ea..f27f773faf8c 100644 --- a/code/modules/surgery/bodyparts/wounds.dm +++ b/code/modules/surgery/bodyparts/wounds.dm @@ -328,10 +328,8 @@ current_gauze.absorption_capacity = new_gauze.absorption_capacity current_gauze.worn_icon_state = "[body_zone][rand(1, 3)]" current_gauze.update_appearance() - if(can_bleed() && get_modified_bleed_rate()) + if(can_bleed() && cached_bleed_rate) current_gauze.add_mob_blood(owner) - if(!QDELETED(new_gauze)) - new_gauze.add_mob_blood(owner) SEND_SIGNAL(src, COMSIG_BODYPART_GAUZED, current_gauze, new_gauze) owner.update_damage_overlays() diff --git a/code/modules/surgery/bone_mending.dm b/code/modules/surgery/bone_mending.dm deleted file mode 100644 index d6c3c1f8e114..000000000000 --- a/code/modules/surgery/bone_mending.dm +++ /dev/null @@ -1,315 +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, - ) - -/datum/surgery/repair_broken_rib - name = "Repair fractured rib (hairline)" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - targetable_wound = /datum/wound/blunt/bone/rib_break - 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/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 = 40 - -/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to repair the fracture in [target]'s [parse_zone(target_zone)] with [tool]."), - span_notice("[user] begins to repair the fracture in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "Your [parse_zone(target_zone)] aches with pain!", - pain_amount = SURGERY_PAIN_MEDIUM, - ) - else - user.visible_message( - span_notice("[user] looks for [target]'s [parse_zone(target_zone)]."), - span_notice("You look for [target]'s [parse_zone(target_zone)]..."), - ) - -/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 [parse_zone(target_zone)]."), - span_notice("[user] successfully repairs the fracture in [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] successfully repairs the fracture in [target]'s [parse_zone(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 = 40 - -/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to reset the bone in [target]'s [parse_zone(target_zone)] with [tool]."), - span_notice("[user] begins to reset the bone in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "The aching pain in your [parse_zone(target_zone)] is overwhelming!", - pain_amount = SURGERY_PAIN_HIGH, - ) - else - user.visible_message( - span_notice("[user] looks for [target]'s [parse_zone(target_zone)]."), - span_notice("You look for [target]'s [parse_zone(target_zone)]..."), - ) - -/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 [parse_zone(target_zone)]."), - span_notice("[user] successfully resets the bone in [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] successfully resets the bone in [target]'s [parse_zone(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 = 40 - -/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to repair the fracture in [target]'s [parse_zone(target_zone)] with [tool]."), - span_notice("[user] begins to repair the fracture in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "The aching pain in your [parse_zone(target_zone)] is overwhelming!", - pain_amount = SURGERY_PAIN_HIGH, - ) - else - user.visible_message( - span_notice("[user] looks for [target]'s [parse_zone(target_zone)]."), - span_notice("You look for [target]'s [parse_zone(target_zone)]..."), - ) - -/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 [parse_zone(target_zone)]."), - span_notice("[user] successfully repairs the fracture in [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] successfully repairs the fracture in [target]'s [parse_zone(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/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to discard the smaller skull debris in [target]'s [parse_zone(target_zone)]..."), - span_notice("[user] begins to poke around in [target]'s [parse_zone(target_zone)]..."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "Your brain feels like it's getting stabbed by little shards of glass!", - pain_amount = SURGERY_PAIN_HIGH, - ) - -/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 = target, - target_zone = target_zone, - pain_message = "You can feel pieces of your skull rubbing against your brain!", - pain_amount = SURGERY_PAIN_LOW, - ) - -/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 d4c089d8bb71..000000000000 --- a/code/modules/surgery/brain_surgery.dm +++ /dev/null @@ -1,87 +0,0 @@ -/datum/surgery/brain_surgery - name = "Brain surgery" - possible_locs = list(BODY_ZONE_HEAD) - requires_bodypart_type = NONE - 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_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/surgery/hemostat1.ogg' - success_sound = 'sound/surgery/hemostat1.ogg' - failure_sound = 'sound/surgery/organ2.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 = target, - target_zone = target_zone, - pain_message = "Your head pounds with unimaginable pain!", - pain_amount = SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/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 = target, - target_zone = target_zone, - pain_message = "The pain in your head receeds, thinking becomes a bit easier!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - if(target.mind?.has_antag_datum(/datum/antagonist/brainwashed)) - target.mind.remove_antag_datum(/datum/antagonist/brainwashed) - target.setOrganLoss(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 = target, - target_zone = target_zone, - pain_message = "Your head throbs with horrible pain; thinking hurts!", - pain_amount = SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - target.adjustOrganLoss(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 17b83e9ac860..000000000000 --- a/code/modules/surgery/burn_dressing.dm +++ /dev/null @@ -1,183 +0,0 @@ - -/////BURN FIXING SURGERIES////// - -///// Debride burnt flesh -/datum/surgery/debride - name = "Debride infected flesh" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB - targetable_wound = /datum/wound/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/is_valid_wound(datum/wound/flesh/wound) - return ..() && 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 = 30 - repeatable = TRUE - preop_sound = 'sound/surgery/scalpel1.ogg' - success_sound = 'sound/surgery/retractor2.ogg' - failure_sound = 'sound/surgery/organ1.ogg' - /// How much sanitization is added per step - var/sanitization_added = 0.5 - /// How much infection is removed per step (positive number) - var/infection_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/flesh/burn_wound) - if(!burn_wound?.infection || !infection_removed) - return - var/estimated_remaining_steps = burn_wound.infection / infection_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/flesh/burn_wound = surgery.operated_wound - if(burn_wound.infection <= 0) - to_chat(user, span_notice("[target]'s [parse_zone(target_zone)] 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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to excise infected flesh from [target]'s [parse_zone(target_zone)] with [tool]."), - span_notice("[user] begins to excise infected flesh from [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "The infection in your [parse_zone(target_zone)] stings like hell! It feels like you're being stabbed!", - pain_amount = SURGERY_PAIN_LOW, - pain_type = BURN, - ) - else - user.visible_message(span_notice("[user] looks for [target]'s [parse_zone(target_zone)]."), span_notice("You look for [target]'s [parse_zone(target_zone)]...")) - -/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/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 [parse_zone(target_zone)][progress_text]."), - span_notice("[user] successfully excises some of the infected flesh from [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] successfully excises some of the infected flesh from [target]'s [parse_zone(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, attacking_item = tool) - burn_wound.infection -= infection_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 [parse_zone(target_zone)]."), - span_notice("[user] carves away some of the healthy flesh from [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] carves away some of the healthy flesh from [target]'s [parse_zone(target_zone)]!"), - ) - target.apply_damage(rand(4, 8), BRUTE, surgery.operated_bodypart, wound_bonus = 10, 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/flesh/burn/burn_wound = surgery.operated_wound - while(burn_wound && burn_wound.infection > 0.25) - if(!..()) - break - -///// Dressing burns -/datum/surgery_step/dress - name = "bandage flesh (gauze/tape)" - implements = list( - /obj/item/stack/medical/gauze = 100, - /obj/item/stack/sticky_tape/surgical = 100) - time = 40 - /// 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/flesh/burn_wound = surgery.operated_wound - if(burn_wound) - display_results( - user, - target, - span_notice("You begin to dress the flesh on [target]'s [parse_zone(target_zone)]..."), - span_notice("[user] begins to dress the flesh on [target]'s [parse_zone(target_zone)] with [tool]."), - span_notice("[user] begins to dress the flesh on [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "The infection in your [parse_zone(target_zone)] stings like hell!", - ) - else - user.visible_message(span_notice("[user] looks for [target]'s [parse_zone(target_zone)]."), span_notice("You look for [target]'s [parse_zone(target_zone)]...")) - -/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/flesh/burn_wound = surgery.operated_wound - if(burn_wound) - display_results( - user, - target, - span_notice("You successfully wrap [target]'s [parse_zone(target_zone)] with [tool]."), - span_notice("[user] successfully wraps [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] successfully wraps [target]'s [parse_zone(target_zone)]!"), - ) - log_combat(user, target, "dressed flesh 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 flesh wounds 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 f74cf714f990..000000000000 --- a/code/modules/surgery/cavity_implant.dm +++ /dev/null @@ -1,91 +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) - -//handle cavity -/datum/surgery_step/handle_cavity - name = "implant item" - accept_hand = 1 - implements = list(/obj/item = 100) - repeatable = TRUE - time = 32 - preop_sound = 'sound/surgery/organ1.ogg' - success_sound = 'sound/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to insert [tool] into [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to insert [tool.w_class > WEIGHT_CLASS_SMALL ? tool : "something"] into [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You can feel something being inserted into your [parse_zone(target_zone)], it hurts like hell!", - pain_amount = SURGERY_PAIN_MEDIUM, - ) - else - display_results( - user, - target, - span_notice("You check for items in [target]'s [parse_zone(target_zone)]..."), - span_notice("[user] checks for items in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] looks for something in [target]'s [parse_zone(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 || HAS_TRAIT(tool, TRAIT_NODROP) || isorgan(tool)) - to_chat(user, span_warning("You can't seem to fit [tool] in [target]'s [parse_zone(target_zone)]!")) - return FALSE - else - display_results( - user, - target, - span_notice("You stuff [tool] into [target]'s [parse_zone(target_zone)]."), - span_notice("[user] stuffs [tool] into [target]'s [parse_zone(target_zone)]!"), - span_notice("[user] stuffs [tool.w_class > WEIGHT_CLASS_SMALL ? tool : "something"] into [target]'s [parse_zone(target_zone)]."), - ) - user.transferItemToLoc(tool, target, TRUE) - target_chest.cavity_item = tool - return ..() - else - if(item_for_cavity) - display_results( - user, - target, - span_notice("You pull [item_for_cavity] out of [target]'s [parse_zone(target_zone)]."), - span_notice("[user] pulls [item_for_cavity] out of [target]'s [parse_zone(target_zone)]!"), - span_notice("[user] pulls [item_for_cavity.w_class > WEIGHT_CLASS_SMALL ? item_for_cavity : "something"] out of [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "Something is pulled out of your [parse_zone(target_zone)]! It hurts like hell!", - pain_amount = SURGERY_PAIN_MEDIUM, - ) - user.put_in_hands(item_for_cavity) - target_chest.cavity_item = null - return ..() - else - to_chat(user, span_warning("You don't find anything in [target]'s [parse_zone(target_zone)].")) - return FALSE diff --git a/code/modules/surgery/core_removal.dm b/code/modules/surgery/core_removal.dm deleted file mode 100644 index aa4028997e76..000000000000 --- a/code/modules/surgery/core_removal.dm +++ /dev/null @@ -1,59 +0,0 @@ -/datum/surgery/core_removal - name = "Core removal" - target_mobtypes = list(/mob/living/simple_animal/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 = 16 - -/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/simple_animal/slime/target_slime = target - if(target_slime.cores > 0) - target_slime.cores-- - display_results( - user, - target, - span_notice("You successfully extract a core from [target]. [target_slime.cores] core\s remaining."), - span_notice("[user] successfully extracts a core from [target]!"), - span_notice("[user] successfully extracts a core from [target]!"), - ) - - new target_slime.slime_type.core_type(target_slime.loc) - - if(target_slime.cores <= 0) - target_slime.icon_state = "[target_slime.slime_type.colour] baby slime dead-nocore" - return ..() - else - return FALSE - else - 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 a8fab60a710b..000000000000 --- a/code/modules/surgery/coronary_bypass.dm +++ /dev/null @@ -1,149 +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/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 = 16 - preop_sound = 'sound/surgery/scalpel1.ogg' - success_sound = 'sound/surgery/scalpel2.ogg' - failure_sound = 'sound/surgery/organ2.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 = target, - target_zone = target_zone, - pain_message = "You feel a horrendous pain in your heart, it's almost enough to make you pass out!", - pain_amount = SURGERY_PAIN_CRITICAL, // It is extremely unlikely this surgery is done on alive people to feel (most) of this - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/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 (!HAS_TRAIT(target_human, TRAIT_NOBLOOD)) - display_results( - user, - target, - span_notice("Blood pools around the incision in [target_human]'s heart."), - span_notice("Blood pools around the incision in [target_human]'s heart."), - span_notice("Blood 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.adjustBruteLoss(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 - display_results( - user, - target, - span_warning("You screw up, cutting too deeply into the heart!"), - span_warning("[user] screws up, causing blood to spurt out of [target_human]'s chest!"), - span_warning("[user] screws up, causing blood 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.adjustOrganLoss(ORGAN_SLOT_HEART, 10) - target_human.adjustBruteLoss(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 = 90 - preop_sound = 'sound/surgery/hemostat1.ogg' - success_sound = 'sound/surgery/hemostat1.ogg' - failure_sound = 'sound/surgery/organ2.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 = target, - target_zone = target_zone, - pain_message = "The pain in your chest is unbearable! You can barely take it anymore!", - pain_amount = SURGERY_PAIN_SEVERE, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/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.setOrganLoss(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 - 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 = target, - target_zone = target_zone, - pain_message = "The pain in your chest throbs, but your heart feels better than ever!", - pain_amount = -0.5 * SURGERY_PAIN_SEVERE, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - return ..() - -/datum/surgery_step/coronary_bypass/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - 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]'s chest profusely!"), - span_warning("[user] screws up, causing blood to spurt out of [target]'s chest profusely!"), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "Your chest burns; you feel like you're going insane!", - pain_amount = SURGERY_PAIN_SEVERE, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - target.adjustOrganLoss(ORGAN_SLOT_HEART, 20) - target.cause_wound_of_type_and_severity(WOUND_PIERCE, target.get_bodypart(target_zone), WOUND_SEVERITY_SEVERE, wound_source = "failed coronary bypass") - return FALSE diff --git a/code/modules/surgery/dental_implant.dm b/code/modules/surgery/dental_implant.dm deleted file mode 100644 index afd57ca9e347..000000000000 --- a/code/modules/surgery/dental_implant.dm +++ /dev/null @@ -1,60 +0,0 @@ -/datum/surgery/dental_implant - name = "Dental implant" - possible_locs = list(BODY_ZONE_PRECISE_MOUTH) - steps = list( - /datum/surgery_step/drill, - /datum/surgery_step/insert_pill, - ) - -/datum/surgery_step/insert_pill - name = "insert pill" - implements = list(/obj/item/reagent_containers/pill = 100) - time = 16 - -/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to wedge \the [tool] in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to wedge something in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "Something's being jammed into your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - -/datum/surgery_step/insert_pill/success(mob/user, mob/living/carbon/target, target_zone, obj/item/reagent_containers/pill/tool, datum/surgery/surgery, default_display_results = FALSE) - if(!istype(tool)) - return FALSE - - user.transferItemToLoc(tool, target, TRUE) - - var/datum/action/item_action/hands_free/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 [parse_zone(target_zone)]."), - span_notice("[user] wedges \the [tool] into [target]'s [parse_zone(target_zone)]!"), - span_notice("[user] wedges something into [target]'s [parse_zone(target_zone)]!"), - ) - return ..() - -/datum/action/item_action/hands_free/activate_pill - name = "Activate Pill" - -/datum/action/item_action/hands_free/activate_pill/do_effect(trigger_flags) - 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 diff --git a/code/modules/surgery/ear_surgery.dm b/code/modules/surgery/ear_surgery.dm deleted file mode 100644 index 048018698d2d..000000000000 --- a/code/modules/surgery/ear_surgery.dm +++ /dev/null @@ -1,91 +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 = 64 - -/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 = target, - target_zone = target_zone, - pain_message = "You feel a dizzying pain in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - -/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 = target, - target_zone = target_zone, - pain_message = "Your [parse_zone(target_zone)] swims, but it seems like you can feel your hearing coming back!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - target_ears.adjustEarDamage(-INFINITY, rand(16, 24)) - 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 = target, - target_zone = target_zone, - pain_message = "You feel a visceral stabbing pain right through your head, [parse_zone(target_zone)] into your brain!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - target.adjustOrganLoss(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 = target, - target_zone = target_zone, - pain_message = "You feel a visceral stabbing pain right through your head [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - return FALSE diff --git a/code/modules/surgery/experimental_dissection.dm b/code/modules/surgery/experimental_dissection.dm deleted file mode 100644 index 95c952e7724d..000000000000 --- 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("[user] starts dissecting [target].", "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("[user] dissects [target], discovering [points_earned] point\s of data!", "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( - "[user] dissects [target]!", - "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, params) - 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 142553eb9b46..000000000000 --- a/code/modules/surgery/eye_surgery.dm +++ /dev/null @@ -1,92 +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 = 64 - -/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 = target, - target_zone = target_zone, - pain_message = "You feel a stabbing pain in your eyes!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - -/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 = target, - target_zone = target_zone, - pain_message = "Your vision blurs, but it seems like you can see a little better now!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - 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 = target, - target_zone = target_zone, - pain_message = "You feel a visceral stabbing pain right through your [parse_zone(target_zone)], into your brain!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - target.adjustOrganLoss(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 = target, - target_zone = target_zone, - pain_message = "You feel a visceral stabbing pain right through your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - return FALSE diff --git a/code/modules/surgery/gastrectomy.dm b/code/modules/surgery/gastrectomy.dm deleted file mode 100644 index 4b74b1015cad..000000000000 --- a/code/modules/surgery/gastrectomy.dm +++ /dev/null @@ -1,88 +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/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 = 52 - preop_sound = 'sound/surgery/scalpel1.ogg' - success_sound = 'sound/surgery/organ1.ogg' - failure_sound = 'sound/surgery/organ2.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 = target, - target_zone = target_zone, - pain_message = "You feel a horrible stab in your gut!", - pain_amount = SURGERY_PAIN_MEDIUM, - ) - -/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.setOrganLoss(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 - 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 = target, - target_zone = target_zone, - pain_message = "The pain in your gut ebbs and fades somewhat.", - pain_amount = -0.5 * SURGERY_PAIN_MEDIUM, - ) - 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.adjustOrganLoss(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 = target, - target_zone = target_zone, - pain_message = "Your stomach throbs with pain; it's not getting any better!", - pain_amount = SURGERY_PAIN_HIGH, - ) diff --git a/code/modules/surgery/healing.dm b/code/modules/surgery/healing.dm deleted file mode 100644 index 6a24280bad78..000000000000 --- a/code/modules/surgery/healing.dm +++ /dev/null @@ -1,382 +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 = 65, - /obj/item/pen = 55) - repeatable = TRUE - time = 25 - success_sound = 'sound/surgery/retractor2.ogg' - failure_sound = 'sound/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/proc/get_perfect_information(mob/user, mob/target) - if(issilicon(user)) - return TRUE - if(user.is_holding_item_of_type(/obj/item/healthanalyzer)) - return TRUE - for(var/obj/machinery/computer/puter in range(2, target)) - if(istype(puter, /obj/machinery/computer/operating)) - var/obj/machinery/computer/operating/op_comp = puter - if(op_comp.table?.patient == target) - return TRUE - if(istype(puter, /obj/machinery/computer/vitals_reader)) - var/obj/machinery/computer/vitals_reader/vr_comp = puter - if(vr_comp.patient == target) - return TRUE - // melbert todo : add modsuit health analyzer to this - return FALSE - -/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 = target, - target_zone = target_zone, - pain_message = "Your [woundtype] sting like hell!", - pain_amount = SURGERY_PAIN_TRIVIAL, - pain_type = burnhealing ? BURN : BRUTE, - ) - -/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.getBruteLoss()) || (burnhealing && target.getFireLoss())) - 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.getBruteLoss() * (brute_multiplier * 0.2)),0.1) - burn_healed += round((target.getFireLoss() * (burn_multiplier * 0.2)),0.1) - dead_patient = TRUE - else - brute_healed += round((target.getBruteLoss() * brute_multiplier),0.1) - burn_healed += round((target.getFireLoss() * 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.getBruteLoss() * (brute_multiplier * 0.5)),0.1) - burn_dealt += round((target.getFireLoss() * (burn_multiplier * 0.5)),0.1) - target.damage_random_bodypart(brute_dealt, BRUTE, wound_bonus = CANT_WOUND) - target.damage_random_bodypart(burn_dealt, BURN, 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.getBruteLoss() / brute_healed - var/progress_text - - if(get_perfect_information(user, target)) - progress_text = ". Remaining brute: [target.getBruteLoss()]" - else - switch(estimated_remaining_steps) - if(-INFINITY to 1) - return - if(1 to 3) - progress_text = ", stitching up the last few scrapes" - if(3 to 6) - progress_text = ", counting down the last few bruises left to treat" - if(6 to 9) - progress_text = ", continuing to plug away at [target.p_their()] extensive rupturing" - if(9 to 12) - progress_text = ", steadying yourself for the long surgery ahead" - if(12 to 15) - progress_text = ", though [target.p_they()] still look[target.p_s()] more like ground beef than a person" - if(15 to INFINITY) - progress_text = ", though you feel like you're barely making a dent in treating [target.p_their()] pulped body" - - return progress_text - -/datum/surgery_step/heal/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.getFireLoss() / burn_healed - var/progress_text - - if(get_perfect_information(user, target)) - progress_text = ". Remaining burn: [target.getFireLoss()]" - else - switch(estimated_remaining_steps) - if(-INFINITY to 1) - return - if(1 to 3) - progress_text = ", finishing up the last few singe marks" - if(3 to 6) - progress_text = ", counting down the last few blisters left to treat" - if(6 to 9) - progress_text = ", continuing to plug away at [target.p_their()] thorough roasting" - if(9 to 12) - progress_text = ", steadying yourself for the long surgery ahead" - if(12 to 15) - progress_text = ", though [target.p_they()] still look[target.p_s()] more like burnt steak than a 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.getBruteLoss() / brute_healed)) - if(burn_healed > 0) - estimated_remaining_steps = max(estimated_remaining_steps, (target.getFireLoss() / burn_healed)) // whichever is higher between brute or burn steps - - var/progress_text - - if(get_perfect_information(user, target)) - if(target.getBruteLoss()) - progress_text = ". Remaining brute: [target.getBruteLoss()]" - if(target.getFireLoss()) - progress_text += ". Remaining burn: [target.getFireLoss()]" - else - switch(estimated_remaining_steps) - if(-INFINITY to 1) - return - if(1 to 3) - progress_text = ", finishing up the last few 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 = 10 - -/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.damage_random_bodypart(5, BRUTE) - target.damage_random_bodypart(5, BURN) diff --git a/code/modules/surgery/hepatectomy.dm b/code/modules/surgery/hepatectomy.dm deleted file mode 100644 index 887f7db9f37f..000000000000 --- a/code/modules/surgery/hepatectomy.dm +++ /dev/null @@ -1,87 +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/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 = 52 - preop_sound = 'sound/surgery/scalpel1.ogg' - success_sound = 'sound/surgery/organ1.ogg' - failure_sound = 'sound/surgery/organ2.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 = target, - target_zone = target_zone, - pain_message = "Your abdomen burns in horrific stabbing pain!", - pain_amount = SURGERY_PAIN_MEDIUM, - ) - -/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.setOrganLoss(ORGAN_SLOT_LIVER, 10) //not bad, not great - if(target_liver) - target_liver.operated = TRUE - 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 = target, - target_zone = target_zone, - pain_message = "The pain in your abdomen receeds slightly.", - pain_amount = -1 * SURGERY_PAIN_LOW, - ) - 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.adjustOrganLoss(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 = target, - target_zone = target_zone, - pain_message = "You feel a sharp stab inside your abdomen!", - pain_amount = SURGERY_PAIN_HIGH, - ) diff --git a/code/modules/surgery/implant_removal.dm b/code/modules/surgery/implant_removal.dm deleted file mode 100644 index 0dc6f6ee74a2..000000000000 --- a/code/modules/surgery/implant_removal.dm +++ /dev/null @@ -1,110 +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 = 64 - success_sound = 'sound/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) - for(var/obj/item/object in target.implants) - implant = object - break - if(implant) - display_results( - user, - target, - span_notice("You begin to extract [implant] from [target]'s [parse_zone(target_zone)]..."), - span_notice("[user] begins to extract [implant] from [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to extract something from [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a serious pain in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_HIGH, - ) - else - display_results( - user, - target, - span_notice("You look for an implant in [target]'s [parse_zone(target_zone)]..."), - span_notice("[user] looks for an implant in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] looks for something in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a pain in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_LOW, - ) - -/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 [parse_zone(target_zone)]."), - span_notice("[user] successfully removes [implant] from [target]'s [parse_zone(target_zone)]!"), - span_notice("[user] successfully removes something from [target]'s [parse_zone(target_zone)]!"), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You can feel your [implant.name] pulled out of you!", - pain_amount = SURGERY_PAIN_LOW, - ) - implant.removed(target) - - 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) - - else - to_chat(user, span_warning("You can't find anything in [target]'s [parse_zone(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/internal_bleeding.dm b/code/modules/surgery/internal_bleeding.dm index da2d4302a989..3c5c7a878b8f 100644 --- a/code/modules/surgery/internal_bleeding.dm +++ b/code/modules/surgery/internal_bleeding.dm @@ -1,4 +1,5 @@ /// Repair internal bleeding + /datum/surgery/internal_bleeding name = "Repair Internal Bleeding" surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB | SURGERY_IGNORE_CLOTHES diff --git a/code/modules/surgery/limb_augmentation.dm b/code/modules/surgery/limb_augmentation.dm deleted file mode 100644 index 91191b533b34..000000000000 --- a/code/modules/surgery/limb_augmentation.dm +++ /dev/null @@ -1,114 +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 = 32 - 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 [parse_zone(target_zone)].")) - return SURGERY_STEP_FAIL - if(!aug.can_attach_limb(target)) - to_chat(user, span_warning("[aug] doesn't match the patient's morphology.")) - return SURGERY_STEP_FAIL - - target_limb = surgery.operated_bodypart - if(target_limb) - display_results( - user, - target, - span_notice("You begin to augment [target]'s [parse_zone(target_zone)]..."), - span_notice("[user] begins to augment [target]'s [parse_zone(target_zone)] with [aug]."), - span_notice("[user] begins to augment [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a horrible pain in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_LOW, // augmentation comes with pain so we can undersell - ) - else - user.visible_message( - span_notice("[user] looks for [target]'s [parse_zone(target_zone)]."), - span_notice("You look for [target]'s [parse_zone(target_zone)]..."), - ) - - -//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 [parse_zone(target_zone)]! Their body has rejected [tool]!"), - span_warning("[user] fails to replace [target]'s [parse_zone(target_zone)]!"), - span_warning("[user] fails to replaces [target]'s [parse_zone(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 [parse_zone(target_zone)]."), - span_notice("[user] successfully augments [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] successfully augments [target]'s [parse_zone(target_zone)]!"), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "Your [parse_zone(target_zone)] comes awash with synthetic sensation!", - mechanical_surgery = TRUE, - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - log_combat(user, target, "augmented", addition="by giving him new [parse_zone(target_zone)] COMBAT MODE: [uppertext(user.combat_mode)]") - else - to_chat(user, span_warning("[target] has no organic [parse_zone(target_zone)] there!")) - return ..() diff --git a/code/modules/surgery/lipoplasty.dm b/code/modules/surgery/lipoplasty.dm deleted file mode 100644 index 3745a029a907..000000000000 --- a/code/modules/surgery/lipoplasty.dm +++ /dev/null @@ -1,117 +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/can_start(mob/user, mob/living/carbon/target) - if(!HAS_TRAIT(target, TRAIT_FAT) || 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 = 64 - preop_sound = list( - /obj/item/circular_saw = 'sound/surgery/saw.ogg', - /obj/item = 'sound/surgery/scalpel1.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 [parse_zone(target_zone)] with [tool]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a stabbing in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - -/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 [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "The fat in your [parse_zone(target_zone)] comes loose, dangling and hurting like hell!", - pain_amount = SURGERY_PAIN_MEDIUM, - ) - 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 = 32 - preop_sound = 'sound/surgery/retractor1.ogg' - success_sound = 'sound/surgery/retractor2.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 [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "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 553012bc9d60..000000000000 --- a/code/modules/surgery/lobectomy.dm +++ /dev/null @@ -1,90 +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/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 = 42 - preop_sound = 'sound/surgery/scalpel1.ogg' - success_sound = 'sound/surgery/organ1.ogg' - failure_sound = 'sound/surgery/organ2.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 = target, - target_zone = target_zone, - pain_message = "You feel a stabbing pain in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/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) - target_lungs.operated = TRUE - human_target.setOrganLoss(ORGAN_SLOT_LUNGS, 60) - 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 = target, - target_zone = target_zone, - pain_message = "Your [parse_zone(target_zone)] hurts like hell, but breathing becomes slightly easier.", - surgery_moodlet = /datum/mood_event/surgery/major, - ) - 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 = target, - target_zone = target_zone, - pain_message = "You feel a sharp stab in your [parse_zone(target_zone)]; the wind is knocked out of you and it hurts to catch your breath!", - pain_amount = SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - human_target.losebreath += 4 - human_target.adjustOrganLoss(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 43cd83216ba2..000000000000 --- a/code/modules/surgery/mechanic_steps.dm +++ /dev/null @@ -1,183 +0,0 @@ -//open shell -/datum/surgery_step/mechanic_open - name = "unscrew shell (screwdriver)" - 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 = 24 - preop_sound = 'sound/items/screwdriver.ogg' - success_sound = 'sound/items/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to unscrew the shell of [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to unscrew the shell of [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You can feel your [parse_zone(target_zone)] grow numb as the sensory panel is unscrewed.", - mechanical_surgery = 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)" - implements = list( - TOOL_SCREWDRIVER = 100, - TOOL_SCALPEL = 75, - /obj/item/knife = 50, - /obj/item = 10) // 10% success with any sharp item. - time = 24 - preop_sound = 'sound/items/screwdriver.ogg' - success_sound = 'sound/items/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to screw the shell of [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to screw the shell of [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel the faint pricks of sensation return as your [parse_zone(target_zone)]'s panel is screwed in.", - mechanical_surgery = 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)" - implements = list( - TOOL_MULTITOOL = 100, - TOOL_HEMOSTAT = 10) // try to reboot internal controllers via short circuit with some conductor - time = 24 - 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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to prepare electronics in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to prepare electronics in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You can feel a faint buzz in your [parse_zone(target_zone)] as the electronics reboot.", - mechanical_surgery = TRUE, - ) - -//unwrench -/datum/surgery_step/mechanic_unwrench - name = "unwrench bolts (wrench)" - implements = list( - TOOL_WRENCH = 100, - TOOL_RETRACTOR = 10) - time = 24 - preop_sound = 'sound/items/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to unwrench some bolts in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to unwrench some bolts in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a jostle in your [parse_zone(target_zone)] as the bolts begin to loosen.", - mechanical_surgery = 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)" - implements = list( - TOOL_WRENCH = 100, - TOOL_RETRACTOR = 10) - time = 24 - preop_sound = 'sound/items/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to wrench some bolts in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to wrench some bolts in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a jostle in your [parse_zone(target_zone)] as the bolts begin to tighten.", - mechanical_surgery = 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 = 10 - preop_sound = 'sound/items/ratchet.ogg' - preop_sound = 'sound/machines/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to open the hatch holders in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to open the hatch holders in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "The last faint pricks of tactile sensation fade from your [parse_zone(target_zone)] as the hatch is opened.", - mechanical_surgery = 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 000000000000..196062c381a1 --- /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 000000000000..12fe44087233 --- /dev/null +++ b/code/modules/surgery/operations/_operation.dm @@ -0,0 +1,1360 @@ +/** + * 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") + +// /datum/surgery_step/proc/display_pain( +// mob/living/carbon/target, +// pain_message, +// mechanical_surgery = FALSE, +// target_zone = BODY_ZONE_CHEST, // can be a list of zones +// pain_amount = 0, +// pain_type = BRUTE, +// surgery_moodlet = /datum/mood_event/surgery, +// pain_overlay_severity = pain_amount >= 20 ? 2 : 1, +// ) +// ASSERT(!isnull(target)) +// ASSERT(istext(pain_message)) +// // Not actually causing pain, just feedback +// if(pain_amount <= 0) +// target.cause_pain(target_zone, pain_amount) +// target.pain_message(span_danger(pain_message)) +// return +// // Only feels pain if we feels pain +// if(!CAN_FEEL_PAIN(target)) +// target.add_mood_event("surgery", /datum/mood_event/anesthetic) +// target.pain_message(span_danger(pain_message)) +// return +// // No pain from mechanics but still show the message (usually) +// if(mechanical_surgery) +// target.pain_message(span_danger(pain_message)) +// return + +// if(implement_type && (implements[implement_type] > 0)) +// pain_amount = round(pain_amount * 1 / (sqrt(implements[implement_type]) * 0.1), 0.1) + +// target.cause_pain(target_zone, pain_amount, pain_type) +// if(target.IsSleeping() || target.stat >= UNCONSCIOUS) +// return +// // Replace this check with localized anesthesia in the future +// var/obj/item/bodypart/checked_bodypart = target.get_bodypart(target_zone) +// if(checked_bodypart && checked_bodypart.bodypart_pain_modifier * target.pain_controller.pain_modifier < 0.5) +// return +// target.add_mood_event("surgery", surgery_moodlet) +// target.flash_pain_overlay(pain_overlay_severity, 0.5 SECONDS) +// target.adjust_traumatic_shock(pain_amount * 0.33 * target.pain_controller.pain_modifier) +// target.pain_emote() +// target.pain_message(span_userdanger(pain_message)) + +/// 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 000000000000..f1be077011fd --- /dev/null +++ b/code/modules/surgery/operations/operation_add_limb.dm @@ -0,0 +1,223 @@ +#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( + target = chest.owner, + target_zone = BODY_ZONE_CHEST, + pain_message ="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( + target = patient, + target_zone = BODY_ZONE_CHEST, + pain_message = "You feel synthetic sensation wash from your [bodypart_to_attach.plaintext_zone], which you can feel again!", + ) + +/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( + target = patient, + target_zone = BODY_ZONE_CHEST, + pain_message = "You feel a strange sensation as [thing_to_attach] takes the place of your arm!", + ) + +#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( + target = limb.owner, + target_zone = BODY_ZONE_CHEST, + pain_message = "[surgeon] begins to [tool.singular_name] [limb] to your body!", + pain_amount = IS_ROBOTIC_LIMB(chest) ? 0 : SURGERY_PAIN_LOW, + ) + +/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( + target = limb.owner, + target_zone = BODY_ZONE_CHEST, + pain_message = "You feel more secure as your prosthetic is firmly attached to your body!", + pain_amount = IS_ROBOTIC_LIMB(chest) ? 0 : SURGERY_PAIN_LOW, + ) + 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 000000000000..76fb2a9615c0 --- /dev/null +++ b/code/modules/surgery/operations/operation_amputation.dm @@ -0,0 +1,131 @@ +/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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a gruesome pain in your [limb.plaintext_zone]'s joint!", + pain_amount = SURGERY_PAIN_MEDIUM, // loss of the limb also applies pain to the chest, so we can afford to make this a bit lower + pain_overlay_severity = 2, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You can no longer feel your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_MEDIUM, + pain_overlay_severity = 2, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + 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 000000000000..427832b9659e --- /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 000000000000..ac0af85d6cbe --- /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 000000000000..d772c1820345 --- /dev/null +++ b/code/modules/surgery/operations/operation_bioware.dm @@ -0,0 +1,472 @@ +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONES_ALL, + pain_message = "Your entire body burns in agony!", + pain_amount = SURGERY_PAIN_HIGH, + pain_type = BURN, + pain_overlay_severity = 2, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONES_ALL, + pain_message = "You can feel your blood pumping through reinforced veins!", + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONES_ALL, + pain_message = "Your entire body burns in agony!", + pain_amount = SURGERY_PAIN_HIGH, + pain_type = BURN, + pain_overlay_severity = 2, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = target, + target_zone = BODY_ZONES_ALL, + pain_message = "You can feel your heartbeat's powerful pulses ripple through your body!", + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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( + target = limb.owner, + target_zone = BODY_ZONES_ALL, + pain_message = "Your entire body goes numb!", + pain_amount = SURGERY_PAIN_HIGH, + pain_type = BURN, + pain_overlay_severity = 2, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONES_ALL, + pain_message = "You regain feeling in your body; It feels like everything's happening around you in slow motion!", + pain_amount = -0.5 * SURGERY_PAIN_HIGH, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONES_ALL, + pain_message = "Your entire body goes numb!", + pain_amount = SURGERY_PAIN_HIGH, + pain_type = BURN, + pain_overlay_severity = 2, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = target, + target_zone = BODY_ZONES_ALL, + pain_message = "You regain feeling in your body! You feel energzed!", + pain_amount = -0.5 * SURGERY_PAIN_HIGH, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = target, + target_zone = BODY_ZONES_LIMBS, + pain_message = "Your limbs burn with severe pain!", + pain_amount = SURGERY_PAIN_MEDIUM, + pain_type = BURN, + pain_overlay_severity = 2, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONES_LIMBS, + pain_message = "Your limbs feel... strangely loose.", + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONES_LIMBS, + pain_message = "Your limbs burn with severe pain!", + pain_amount = SURGERY_PAIN_MEDIUM, + pain_type = BURN, + pain_overlay_severity = 2, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONES_LIMBS, + pain_message = "Your limbs feel more secure, but also more frail.", + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONE_HEAD, + pain_message = "Your head throbs with gruesome pain, it's nearly too much to handle!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONE_HEAD, + pain_message = "Your brain feels stronger... more flexible!", + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONE_HEAD, + pain_message = "Your head throbs with excruciating pain!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + 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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONE_HEAD, + pain_message = "Your head throbs with gruesome pain, it's nearly too much to handle!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONE_HEAD, + pain_message = "Your brain feels stronger... more resillient!", + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONE_HEAD, + pain_message = "Your brain throbs with intense pain; thinking hurts!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + 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 000000000000..023be2f5153e --- /dev/null +++ b/code/modules/surgery/operations/operation_bone_repair.dm @@ -0,0 +1,250 @@ +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "Your [limb.plaintext_zone] aches with pain!", + pain_amount = SURGERY_PAIN_LOW, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "The aching pain in your [limb.plaintext_zone] is overwhelming!", + pain_amount = SURGERY_PAIN_HIGH, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "The aching pain in your [limb.plaintext_zone] is overwhelming!", + pain_amount = SURGERY_PAIN_HIGH, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "Your brain feels like it's getting stabbed by little shards of glass!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You can feel pieces of your skull rubbing against your brain!", + pain_amount = SURGERY_PAIN_LOW, + ) + +/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 000000000000..a03ad7283006 --- /dev/null +++ b/code/modules/surgery/operations/operation_brainwash.dm @@ -0,0 +1,162 @@ +#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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your head pounds with unimaginable pain!", // Same message as other brain surgeries + pain_amount = SURGERY_PAIN_SEVERE, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your head throbs with horrible pain!", + pain_amount = SURGERY_PAIN_SEVERE, + ) + 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 000000000000..e6d76db7b240 --- /dev/null +++ b/code/modules/surgery/operations/operation_cavity_implant.dm @@ -0,0 +1,221 @@ +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.zone, + pain_message = "You can feel pressure as your [limb.plaintext_zone] is being opened wide!" + pain_amount = SURGERY_PAIN_MEDIUM, + surgery_moodlet = /datum/mood_event/surgery, + ) + +/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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.zone, + pain_message = "You can feel something being inserted into your [limb.plaintext_zone], it hurts like hell!", + pain_amount = SURGERY_PAIN_MEDIUM, + surgery_moodlet = /datum/mood_event/surgery, + ) + +/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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.zone, + pain_message = "You feel a serious pain in your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_MEDIUM, + surgery_moodlet = /datum/mood_event/surgery, + ) + +/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]!"), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.zone, + pain_message = "You can feel [implant.name] being pulled out of you!" + pain_amount = SURGERY_PAIN_TRIVIAL, + ) + 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 000000000000..7eff5dc4099c --- /dev/null +++ b/code/modules/surgery/operations/operation_core.dm @@ -0,0 +1,59 @@ +/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/simple_animal/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/simple_animal/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]!")) + + if(patient.cores > 0) + patient.cores-- + display_results( + user, + target, + span_notice("You successfully extract a core from [target]. [patient.cores] core\s remaining."), + span_notice("[user] successfully extracts a core from [target]!"), + span_notice("[user] successfully extracts a core from [target]!"), + ) + + new patient.slime_type.core_type(patient.loc) + patient.regenerate_icons() + + else + to_chat(user, span_warning("There aren't any cores left in [target]!")) diff --git a/code/modules/surgery/operations/operation_debride.dm b/code/modules/surgery/operations/operation_debride.dm new file mode 100644 index 000000000000..7e8ef284c8a9 --- /dev/null +++ b/code/modules/surgery/operations/operation_debride.dm @@ -0,0 +1,92 @@ +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "The infection in your [limb.plaintext_zone] stings like hell! It feels like you're being stabbed!", + pain_amount = SURGERY_PAIN_LOW, + pain_type = BURN, + ) + +/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 000000000000..4974106d15e3 --- /dev/null +++ b/code/modules/surgery/operations/operation_dental.dm @@ -0,0 +1,152 @@ +/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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.zone, + pain_message = "Something's being jammed into your mouth!", + pain_amount = SURGERY_PAIN_TRIVIAL, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.zone, + pain_message = "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 000000000000..1756d536e023 --- /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 000000000000..cb4f352a4f37 --- /dev/null +++ b/code/modules/surgery/operations/operation_filter.dm @@ -0,0 +1,84 @@ +/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) + . = ..() + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = BODY_ZONE_CHEST, + pain_message = "You feel a throbbing pain in your chest!", + pain_amount = SURGERY_PAIN_LOW, + ) + +/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 000000000000..5d21af6929da --- /dev/null +++ b/code/modules/surgery/operations/operation_generic.dm @@ -0,0 +1,561 @@ +// 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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a stabbing in your [limb.plaintext_zone].", + pain_amount = SURGERY_PAIN_LOW, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a severe stinging pain spreading across your [limb.plaintext_zone] as the skin is pulled back.", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "Your [limb.plaintext_zone] is being [istype(tool, /obj/item/stack/medical/suture) ? "pinched" : "burned"]!", + pain_amount = SURGERY_PAIN_LOW, + pain_type = istype(tool, /obj/item/stack/medical/suture) ? BRUTE : BURN, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a pinch as the bleeding in your [limb.plaintext_zone] is slowed.", + pain_amount = SURGERY_PAIN_LOW, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a pressure release as blood starts flowing in your [limb.plaintext_zone] again.", + pain_amount = SURGERY_PAIN_LOW, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a horrid ache spread through the inside of your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "It feels like something just broke in your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/// 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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a grinding sensation in your [limb.plaintext_zone] as the bones are set back in place.", + pain_amount = SURGERY_PAIN_LOW, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a horrible piercing pain in your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a stabbing in your [limb.plaintext_zone].", + pain_amount = SURGERY_PAIN_LOW, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a sharp pain from inside your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_LOW, + ) + +/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 000000000000..8c8840ece78b --- /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 000000000000..d4eba7622a22 --- /dev/null +++ b/code/modules/surgery/operations/operation_generic_mechanic.dm @@ -0,0 +1,257 @@ +// 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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel your [limb.plaintext_zone] grow numb as the shell is unscrewed.", + mechanical = 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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "The last faint pricks of tactile sensation fade from your [limb.plaintext_zone] as the hatch is opened.", + mechanical = 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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel the faint pricks of sensation return as your [limb.plaintext_zone]'s shell is screwed in." + mechanical = 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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You can feel a faint buzz in your [limb.plaintext_zone] as the electronics reboot.", + mechanical = 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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to loosen.", + mechanical = 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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to tighten.", + mechanical = 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 000000000000..9da7e9d29541 --- /dev/null +++ b/code/modules/surgery/operations/operation_healing.dm @@ -0,0 +1,276 @@ +/// 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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = patient, + target_zone = BODY_ZONE_CHEST, + pain_message = "Your [woundtype] sting like hell!", + pain_amount = SURGERY_PAIN_TRIVIAL, + pain_type = burn_heal ? BURN : BRUTE, + ) + +#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(show_stats(surgeon, patient)) + 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 + +/// Checks whether we show precise damage stats during the surgery +/datum/surgery_operation/basic/tend_wounds/proc/show_stats(mob/living/surgeon, mob/living/patient) + if(surgeon.is_holding_item_of_type(/obj/item/healthanalyzer)) + return TRUE + for(var/obj/machinery/vitals_reader/vitals in view(4, patient)) + if(vitals.patient == patient) + return TRUE + for(var/obj/machinery/computer/operating/op_pc in range(1, patient)) + if(op_pc.table?.patient == patient) + return TRUE + for(var/obj/item/mod/control/modsuit in surgeon.get_equipped_items()) + if(modsuit.active && istype(modsuit.selected_module, /obj/item/mod/module/health_analyzer)) + return TRUE + return FALSE + +#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 000000000000..af8c00eb3fe4 --- /dev/null +++ b/code/modules/surgery/operations/operation_implant_removal.dm @@ -0,0 +1,93 @@ +/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( + target = patient, + target_zone = BODY_ZONE_TORSO, + pain_message = "You feel a serious pain as [surgeon] digs around inside you!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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( + target = patient, + target_zone = BODY_ZONE_CHEST, + pain_message = "You can feel your [implant.name] pulled out of you!", + pain_amount = SURGERY_PAIN_LOW, + ) + 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 000000000000..8eec4656e5a6 --- /dev/null +++ b/code/modules/surgery/operations/operation_lipo.dm @@ -0,0 +1,110 @@ +/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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone + pain_message = "You feel a stabbing in your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_TRIVIAL, + ) + +/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 000000000000..d89f93991951 --- /dev/null +++ b/code/modules/surgery/operations/operation_lobotomy.dm @@ -0,0 +1,118 @@ +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your head pounds with unimaginable pain!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your head goes totally numb for a moment, the pain is overwhelming!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + + 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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "The pain in your head only seems to get worse!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + 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 000000000000..0ba5e6a51e7a --- /dev/null +++ b/code/modules/surgery/operations/operation_organ_manip.dm @@ -0,0 +1,302 @@ +#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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a tugging sensation in your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + 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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You can feel something being placed in your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_TRIVIAL, + ) + +/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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "Your [limb.plaintext_zone] throbs with pain, you can't feel your [organ.name] anymore!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + 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( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "Your [limb.plaintext_zone] throbs with pain as your new [organ.name] comes to life!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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 000000000000..f3427847710c --- /dev/null +++ b/code/modules/surgery/operations/operation_organ_repair.dm @@ -0,0 +1,664 @@ +/// 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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "You feel a stabbing pain in your [parse_zone(organ.zone)]!", + pain_amount = SURGERY_PAIN_HIGH, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your [parse_zone(organ.zone)] hurts like hell, but breathing becomes slightly easier.", + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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!"), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "You feel a sharp stab in your [parse_zone(organ.zone)]; the wind is knocked out of you and it hurts to catch your breath!", + pain_amount = SURGERY_PAIN_HIGH, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your abdomen burns in horrific stabbing pain!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "The pain in your abdomen receeds slightly.", + pain_amount = -1 * SURGERY_PAIN_LOW, + ) + +/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!"), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = The pain in your abdomen intensifies!", + pain_amount = SURGERY_PAIN_HIGH, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "The pain in your [parse_zone(organ.zone)] is unbearable! You can barely take it anymore!", + pain_amount = 1.5 * SURGERY_PAIN_SEVERE, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "The pain in your [parse_zone(organ.zone)] throbs, but your heart feels better than ever!", + pain_amount = -0.5 * SURGERY_PAIN_SEVERE, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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!"), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your [parse_zone(organ.zone)] burns; you feel like you're going insane!", + pain_amount = SURGERY_PAIN_SEVERE, + surgery_moodlet = /datum/mood_event/surgery/major, + pain_type = BURN, + ) + +/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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "You feel a horrible stab in your gut!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "The pain in your gut recedes slightly!", + pain_amount = -0.5 * SURGERY_PAIN_MEDIUM, + ) + +/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!"), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "The pain in your gut intensifies!", + pain_amount = SURGERY_PAIN_HIGH, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "You feel a dizzying pain in your [parse_zone(organ.zone)]!", + pain_amount = SURGERY_PAIN_TRIVIAL, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your [parse_zone(organ.zone)] swims, but it seems like you can feel your hearing coming back!", + pain_amount = SURGERY_PAIN_TRIVIAL, + ) + +/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!"), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "You feel a visceral stabbing pain right through your [parse_zone(organ.zone)], into your brain!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + 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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "You feel a stabbing pain in your eyes!", + pain_amount = SURGERY_PAIN_TRIVIAL, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your vision blurs, but it seems like you can see a little better now!", + pain_amount = SURGERY_PAIN_TRIVIAL, + ) + +/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!"), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "You feel a visceral stabbing pain right through your [parse_zone(organ.zone)], into your brain!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + 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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your [parse_zone(organ.zone)] pounds with unimaginable pain!", + pain_amount = SURGERY_PAIN_HIGH, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "The pain in your head recedes, thinking becomes a bit easier!", + pain_amount = -0.33 * SURGERY_PAIN_HIGH, + ) + 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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your head throbs with horrible pain; thinking hurts!", + pain_amount = SURGERY_PAIN_HIGH, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + 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 000000000000..472a2b74f680 --- /dev/null +++ b/code/modules/surgery/operations/operation_pacify.dm @@ -0,0 +1,90 @@ +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your head pounds with unimaginable pain!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your head pounds... the concept of violence flashes in your head, and nearly makes you hurl!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + 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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your head pounds, and it feels like it's getting worse!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + 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 000000000000..091992628452 --- /dev/null +++ b/code/modules/surgery/operations/operation_plastic_surgery.dm @@ -0,0 +1,168 @@ +#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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel slicing pain across your face!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "The pain fades, your face feels normal again!", + pain_amount = -0.5 * SURGERY_PAIN_MEDIUM, + ) + 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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "The pain fades, your face feels new and unfamiliar!", + pain_amount = -0.5 * SURGERY_PAIN_MEDIUM, + ) + 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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "Your face feels horribly scarred and deformed!", + pain_amount = SURGERY_PAIN_LOW, + ) + 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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "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 000000000000..e765356b5d2e --- /dev/null +++ b/code/modules/surgery/operations/operation_pump.dm @@ -0,0 +1,64 @@ +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = BDOY_ZONES_CHEST, + pain_message = "You feel a horrible sloshing feeling in your gut! You're going to be sick!", + pain_amount = SURGERY_PAIN_LOW, + ) + +/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 000000000000..27ef9591f3d0 --- /dev/null +++ b/code/modules/surgery/operations/operation_puncture.dm @@ -0,0 +1,151 @@ +/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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a horrible stabbing pain in your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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]."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a burning sensation in your [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_TRIVIAL, + pain_type = BURN, + ) + +/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 000000000000..93a6d944a284 --- /dev/null +++ b/code/modules/surgery/operations/operation_replace_limb.dm @@ -0,0 +1,101 @@ +/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( + target = limb.owner, + target_zone = BODY_ZONE_CHEST, + pain_message = "You feel a horrible pain in your chest and [limb.plaintext_zone]!", + pain_amount = SURGERY_PAIN_MEDIUM, + ) + +/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( + target = patient, + target_zone = BODY_ZONE_CHEST, + pain_message = "Your [limb.plaintext_zone] comes awash with synthetic sensation!", + mechanical = 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 000000000000..6f52060ab838 --- /dev/null +++ b/code/modules/surgery/operations/operation_revival.dm @@ -0,0 +1,126 @@ +/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(patient.getOxyLoss() > 50) + patient.setOxyLoss(50) + if(patient.getToxLoss() > 50) + patient.setToxLoss(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 + + patient.grab_ghost() + patient.apply_status_effect(/datum/status_effect/recent_defib) + patient.updatehealth() + 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 000000000000..5c19263ee253 --- /dev/null +++ b/code/modules/surgery/operations/operation_virus.dm @@ -0,0 +1,83 @@ +/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]..."), + ) + // NON-MODULE CHANGE + display_pain( + target = patient, + target_zone = BODY_ZONE_CHEST, + pain_message = "You feel a searing heat spread through your chest!", + pain_amount = SURGERY_PAIN_HIGH, + pain_type = BURN, + pain_overlay_severity = 1, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = patient, + target_zone = BODY_ZONE_CHEST, + pain_message = "You feel a faint throbbing in your chest.", + pain_amount = SURGERY_PAIN_TRIVIAL, + ) + 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 000000000000..046bd3bf95b9 --- /dev/null +++ b/code/modules/surgery/operations/operation_wing_repair.dm @@ -0,0 +1,73 @@ +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "Your wings sting like hell!", + pain_amount = SURGERY_PAIN_TRIVIAL, + pain_type = BURN, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = organ.owner, + target_zone = organ.zone, + pain_message = "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 000000000000..7a5436dab0b1 --- /dev/null +++ b/code/modules/surgery/operations/operation_zombie.dm @@ -0,0 +1,88 @@ +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "Your [limb.plaintext_zone] pounds with unimaginable pain!", // Same message as other brain surgeries + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + +/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."), + ) + // NON-MODULE CHANGE + display_pain( + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "Your [limb.plaintext_zone] goes totally numb for a moment, the pain is overwhelming!", + pain_amount = SURGERY_PAIN_CRITICAL, + surgery_moodlet = /datum/mood_event/surgery/major, + ) + 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 97ef68d6ea85..000000000000 --- a/code/modules/surgery/organ_manipulation.dm +++ /dev/null @@ -1,351 +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, - ) - -/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/internal, - /datum/surgery_step/close, - ) - -/datum/surgery/organ_manipulation/mechanic - name = "Prosthesis organ manipulation" - requires_bodypart_type = BODYTYPE_ROBOTIC - surgery_flags = SURGERY_SELF_OPERABLE | SURGERY_REQUIRE_LIMB - 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/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/step = get_surgery_step() - if(isnull(step)) - return FALSE - var/obj/item/tool = user.get_active_held_item() - if(tool) - tool = tool.get_proxy_attacker_for(target, user) - if(step.try_op(user, target, user.zone_selected, tool, src, try_to_fail)) - return TRUE - if(tool && tool.tool_behaviour) //Mechanic organ manipulation isn't done with just surgery tools - to_chat(user, span_warning("This step requires a different tool!")) - return TRUE - - return FALSE - -/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 = "Prosthetic 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( //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 it's 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/surgery/organ2.ogg' - success_sound = 'sound/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 [parse_zone(target_zone)]!")) - return SURGERY_STEP_FAIL - tool = target_organ - if(isorgan(tool)) - current_type = "insert" - preop_sound = 'sound/surgery/hemostat1.ogg' - success_sound = 'sound/surgery/organ2.ogg' - target_organ = tool - 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 [parse_zone(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(user, 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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to insert [tool] into [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to insert something into [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You can feel something being placed in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_TRIVIAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - - 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(user, 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 [parse_zone(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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to extract [target_organ] from [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to extract something from [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You can feel your [target_organ.name] being removed from your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_MEDIUM, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - 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) - if(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 [parse_zone(target_zone)]."), - span_notice("[user] inserts [tool] into [target]'s [parse_zone(target_zone)]!"), - span_notice("[user] inserts something into [target]'s [parse_zone(target_zone)]!"), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "Your [parse_zone(target_zone)] throbs with pain as your new [tool.name] comes to life!", - pain_amount = SURGERY_PAIN_TRIVIAL, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - else - target_organ.forceMove(target.loc) - - 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 [parse_zone(target_zone)]."), - span_notice("[user] successfully extracts [target_organ] from [target]'s [parse_zone(target_zone)]!"), - span_notice("[user] successfully extracts something from [target]'s [parse_zone(target_zone)]!"), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "Your [parse_zone(target_zone)] throbs with pain, you can't feel your [target_organ.name] anymore!", - pain_amount = SURGERY_PAIN_LOW, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - 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 [parse_zone(target_zone)]!"), - span_notice("[user] can't seem to extract anything from [target]'s [parse_zone(target_zone)]!"), - span_notice("[user] can't seem to extract anything from [target]'s [parse_zone(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(mob/user, obj/item/organ/organ) - return FALSE - -///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(mob/user, 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(mob/user, 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 e10481eb64d4..000000000000 --- a/code/modules/surgery/organic_steps.dm +++ /dev/null @@ -1,272 +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 = 16 - preop_sound = 'sound/surgery/scalpel1.ogg' - success_sound = 'sound/surgery/scalpel2.ogg' - -/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to make an incision in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to make an incision in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a stabbing in your [parse_zone(target_zone)].", - pain_amount = SURGERY_PAIN_LOW, - ) - -/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 (!HAS_TRAIT(human_target, TRAIT_NOBLOOD)) - display_results( - user, - target, - span_notice("Blood pools around the incision in [human_target]'s [parse_zone(target_zone)]."), - span_notice("Blood pools around the incision in [human_target]'s [parse_zone(target_zone)]."), - span_notice("Blood pools around the incision in [human_target]'s [parse_zone(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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to carefully make an incision in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to carefully make an incision in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a careful stabbing in your [parse_zone(target_zone)].", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - -//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 = 24 - preop_sound = 'sound/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to clamp bleeders in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to clamp bleeders in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a pinch as the bleeding in your [parse_zone(target_zone)] is slowed.", - pain_amount = SURGERY_PAIN_TRIVIAL, - ) - -/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 = 24 - preop_sound = 'sound/surgery/retractor1.ogg' - success_sound = 'sound/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to retract the skin in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to retract the skin in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a severe stinging pain spreading across your [parse_zone(target_zone)] as the skin is pulled back!", - pain_amount = SURGERY_PAIN_LOW, - ) - -//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 = 24 - preop_sound = 'sound/surgery/cautery1.ogg' - success_sound = 'sound/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to mend the incision in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to mend the incision in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "Your [parse_zone(target_zone)] is being burned!", - pain_amount = SURGERY_PAIN_LOW, - pain_type = BURN, - ) - -/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 = 25, - /obj/item = 20) //20% success (sort of) with any sharp item with a force >= 10 - time = 54 - preop_sound = list( - /obj/item/circular_saw = 'sound/surgery/saw.ogg', - /obj/item/melee/arm_blade = 'sound/surgery/scalpel1.ogg', - /obj/item/fireaxe = 'sound/surgery/scalpel1.ogg', - /obj/item/hatchet = 'sound/surgery/scalpel1.ogg', - /obj/item/knife/butcher = 'sound/surgery/scalpel1.ogg', - /obj/item = 'sound/surgery/scalpel1.ogg', - ) - success_sound = 'sound/surgery/organ2.ogg' - -/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to saw through the bone in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to saw through the bone in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a horrid ache spread through the inside of your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/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 [parse_zone(target_zone)] open."), - span_notice("[user] saws [target]'s [parse_zone(target_zone)] open!"), - span_notice("[user] saws [target]'s [parse_zone(target_zone)] open!"), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "It feels like something just broke in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_LOW, // apply damage causes pain directly, so we undersell slightly - surgery_moodlet = /datum/mood_event/surgery/major, - ) - 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 = 30 - -/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to drill into the bone in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to drill into the bone in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a horrible piercing pain in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_HIGH, // apply damage causes pain directly, so we undersell slightly - surgery_moodlet = /datum/mood_event/surgery/major, - ) - -/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 [parse_zone(target_zone)]."), - span_notice("[user] drills into [target]'s [parse_zone(target_zone)]!"), - span_notice("[user] drills into [target]'s [parse_zone(target_zone)]!"), - ) - return ..() diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 705be27a147f..b93958c9e1b8 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -46,6 +46,10 @@ /// Food reagents if the organ is edible var/list/food_reagents = list(/datum/reagent/consumable/nutriment = 5) + /// Foodtypes if the organ is edible + var/foodtype_flags = RAW | MEAT | GORE + /// Overrides tastes if the organ is edible + var/food_tastes /// The size of the reagent container if the organ is edible var/reagent_vol = 10 @@ -73,10 +77,13 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) . = ..() if(organ_flags & ORGAN_EDIBLE) AddComponent(/datum/component/edible,\ - initial_reagents = food_reagents,\ - foodtypes = RAW | MEAT | GORE,\ - volume = reagent_vol,\ - after_eat = CALLBACK(src, PROC_REF(OnEatFrom))) + initial_reagents = food_reagents, \ + foodtypes = foodtypes, \ + 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() @@ -171,7 +178,8 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) return /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. @@ -397,6 +405,16 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /obj/item/organ/proc/replace_into(mob/living/carbon/new_owner) return 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 c4fa888f6cb6..41880b91d9e0 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 97f5a95a9332..7126deda9bf7 100644 --- a/code/modules/surgery/organs/organ_movement.dm +++ b/code/modules/surgery/organs/organ_movement.dm @@ -21,6 +21,12 @@ if(!PERFORM_ALL_TESTS(organ_sanity)) stack_trace("Tried to insert organ into non-carbon: [receiver.type]") 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 mob_insert(receiver, special, movement_flags) bodypart_insert(limb_owner = receiver, movement_flags = movement_flags) @@ -118,20 +124,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 @@ -141,6 +153,8 @@ if(bodypart_overlay) limb.add_bodypart_overlay(bodypart_overlay) + SEND_SIGNAL(src, COMSIG_ORGAN_BODYPART_INSERTED, limb) + /* * Remove the organ from the select mob. * @@ -251,13 +265,19 @@ color = bodypart_overlay.draw_color /// 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!") @@ -265,7 +285,26 @@ * Proc that gets called when the organ is surgically removed by someone, can be used for special effects * Currently only used so surplus organs can explode when surgically removed. */ -/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) + SEND_SIGNAL(src, COMSIG_ORGAN_SURGICALLY_REMOVED, user, limb.owner, limb.body_zone, tool) RemoveElement(/datum/element/decal/blood) + +/** + * Proc that gets called when the organ is surgically inserted by someone. Seem familiar? + */ +/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, 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) + swap_zone(target_zone) + +/// Readjusts the organ to fit into a different body zone/slot +/obj/item/organ/proc/swap_zone(target_zone) + if (!valid_zones[target_zone]) + CRASH("[src]'s ([type]) swap_zone was called with invalid zone [target_zone]") + zone = target_zone + slot = valid_zones[zone] diff --git a/code/modules/surgery/plastic_surgery.dm b/code/modules/surgery/plastic_surgery.dm deleted file mode 100644 index 4c1477f8ee89..000000000000 --- a/code/modules/surgery/plastic_surgery.dm +++ /dev/null @@ -1,156 +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/blobattack.ogg' - success_sound = 'sound/effects/attackblob.ogg' - failure_sound = 'sound/effects/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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to insert [tool] into the incision in [target]'s [parse_zone(target_zone)]."), - span_notice("[user] begins to insert [tool] into the incision in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel something inserting just below the skin in your [parse_zone(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 = 64 - -/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 = target, - target_zone = target_zone, - pain_message = "You feel slicing pain across your face!", - pain_amount = SURGERY_PAIN_MEDIUM, - ) - -/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 = target, - target_zone = target_zone, - pain_message = "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 as anything in disguises.picture?.names_seen) - names += namelist - else - user.visible_message(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 = target, - target_zone = target_zone, - pain_message = "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 = target, - target_zone = target_zone, - pain_message = "Your face feels horribly scarred and deformed!", - pain_amount = SURGERY_PAIN_LOW, - ) - 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 da4f2b5155ab..000000000000 --- a/code/modules/surgery/prosthetic_replacement.dm +++ /dev/null @@ -1,140 +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)) //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/chainsaw = 100, - /obj/item/melee/synthetic_arm_blade = 100) - time = 32 - var/organ_rejection_dam = 0 - -/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(!bodypart_to_attach.can_attach_limb(target)) - 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(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 [parse_zone(target_zone)] with [tool]..."), - span_notice("[user] begins to replace [target]'s [parse_zone(target_zone)] with [tool]."), - span_notice("[user] begins to replace [target]'s [parse_zone(target_zone)]."), - ) - else - to_chat(user, span_warning("[tool] isn't the right type for [parse_zone(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 [parse_zone(target_zone)]."), - span_notice("[user] begins to attach something onto [target]'s [parse_zone(target_zone)]."), - ) - else - to_chat(user, span_warning("[tool] must be installed onto an arm.")) - 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] - if(isbodypart(tool) && user.temporarilyRemoveItemFromInventory(tool)) - var/obj/item/bodypart/bodypart_to_attach = tool - 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.adjustToxLoss(organ_rejection_dam) - display_results( - user, - target, - span_notice("You succeed in replacing [target]'s [parse_zone(target_zone)]."), - span_notice("[user] successfully replaces [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] successfully replaces [target]'s [parse_zone(target_zone)]!"), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel synthetic sensation wash from your [parse_zone(target_zone)], which you can feel again!", - mechanical_surgery = TRUE, - ) - return - else - var/obj/item/bodypart/bodypart_to_attach = target.newBodyPart(target_zone) - bodypart_to_attach.try_attach_limb(target) - bodypart_to_attach.bodypart_flags |= BODYPART_PSEUDOPART | BODYPART_IMPLANTED - user.visible_message(span_notice("[user] finishes attaching [tool]!"), span_notice("You attach [tool].")) - display_results( - user, - target, - span_notice("You attach [tool]."), - span_notice("[user] finishes attaching [tool]!"), - span_notice("[user] finishes the attachment procedure!"), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a strange sensation from your new [parse_zone(target_zone)].", - mechanical_surgery = TRUE, - ) - if(istype(tool, /obj/item/chainsaw)) - qdel(tool) - var/obj/item/chainsaw/mounted_chainsaw/new_arm = new(target) - target_zone == BODY_ZONE_R_ARM ? target.put_in_r_hand(new_arm) : target.put_in_l_hand(new_arm) - return - else if(istype(tool, /obj/item/melee/synthetic_arm_blade)) - qdel(tool) - var/obj/item/melee/arm_blade/new_arm = new(target,TRUE,TRUE) - target_zone == BODY_ZONE_R_ARM ? target.put_in_r_hand(new_arm) : target.put_in_l_hand(new_arm) - return - return ..() //if for some reason we fail everything we'll print out some text okay? diff --git a/code/modules/surgery/repair_puncture.dm b/code/modules/surgery/repair_puncture.dm deleted file mode 100644 index ba61b31e2fb7..000000000000 --- a/code/modules/surgery/repair_puncture.dm +++ /dev/null @@ -1,159 +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/is_valid_wound(datum/wound/wound) - return ..() && 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/surgery/hemostat1.ogg' - -/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 [parse_zone(target_zone)]."), span_notice("You look for [target]'s [parse_zone(target_zone)]...")) - return - - if(pierce_wound.blood_flow <= 0) - to_chat(user, span_notice("[target]'s [parse_zone(target_zone)] 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 [parse_zone(target_zone)]..."), - span_notice("[user] begins to realign the torn blood vessels in [target]'s [parse_zone(target_zone)] with [tool]."), - span_notice("[user] begins to realign the torn blood vessels in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a horrible stabbing pain in your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_MEDIUM, - ) - -/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 [parse_zone(target_zone)]."), - span_notice("[user] successfully realigns some of the blood vessels in [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] successfully realigns some of the blood vessels in [target]'s [parse_zone(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, 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 [parse_zone(target_zone)]."), - span_notice("[user] jerks apart some of the blood vessels in [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] jerk apart some of the blood vessels in [target]'s [parse_zone(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/surgery/cautery1.ogg' - success_sound = 'sound/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 [parse_zone(target_zone)]."), span_notice("You look for [target]'s [parse_zone(target_zone)]...")) - return - display_results( - user, - target, - span_notice("You begin to meld some of the split blood vessels in [target]'s [parse_zone(target_zone)]..."), - span_notice("[user] begins to meld some of the split blood vessels in [target]'s [parse_zone(target_zone)] with [tool]."), - span_notice("[user] begins to meld some of the split blood vessels in [target]'s [parse_zone(target_zone)]."), - ) - display_pain( - target = target, - target_zone = target_zone, - pain_message = "You're being burned inside your [parse_zone(target_zone)]!", - pain_amount = SURGERY_PAIN_MEDIUM, - pain_type = BURN, - ) - -/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 [parse_zone(target_zone)] with [tool]."), - span_notice("[user] successfully melds some of the split blood vessels in [target]'s [parse_zone(target_zone)] with [tool]!"), - span_notice("[user] successfully melds some of the split blood vessels in [target]'s [parse_zone(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 [parse_zone(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 6c21d19ed740..000000000000 --- a/code/modules/surgery/revival.dm +++ /dev/null @@ -1,147 +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." - requires_bodypart_type = NONE - 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/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_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/magic/lightningbolt.ogg' - failure_sound = 'sound/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_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]..."), - ) - if(target.getOxyLoss() > 50) - target.setOxyLoss(50) - if(target.getToxLoss() > 50) - target.setToxLoss(50) - target.grab_ghost() - target.apply_status_effect(/datum/status_effect/recent_defib) - target.updatehealth() - 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.adjustOrganLoss(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.adjustOrganLoss(ORGAN_SLOT_BRAIN, 15, 180) diff --git a/code/modules/surgery/stomachpump.dm b/code/modules/surgery/stomachpump.dm deleted file mode 100644 index a32c278a7eb0..000000000000 --- a/code/modules/surgery/stomachpump.dm +++ /dev/null @@ -1,68 +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/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 = 20 - success_sound = 'sound/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 = target, - target_zone = target_zone, - pain_message = "You feel a horrible sloshing feeling in your gut! You're going to be sick!", - pain_amount = SURGERY_PAIN_LOW, - ) - -/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(20, FALSE, TRUE, 1, TRUE, FALSE, 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, brusing [target_human]'s chest!"), - span_warning("[user] screws up, brusing [target_human]'s chest!"), - span_warning("[user] screws up!"), - ) - target_human.adjustOrganLoss(ORGAN_SLOT_STOMACH, 5) - target_human.adjustBruteLoss(5) diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm deleted file mode 100644 index f1a331661112..000000000000 --- a/code/modules/surgery/surgery.dm +++ /dev/null @@ -1,220 +0,0 @@ -/datum/surgery - ///The name of the surgery operation - var/name = "surgery" - ///The description of the surgery, what it does. - var/desc - - ///From __DEFINES/surgery.dm - ///Selection: SURGERY_IGNORE_CLOTHES | SURGERY_SELF_OPERABLE | SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB | SURGERY_MORBID_CURIOSITY - 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 - target = null - operated_bodypart = null - return ..() - -/datum/surgery/proc/is_valid_wound(datum/wound/wound) - return istype(wound, targetable_wound) - -/datum/surgery/proc/can_start(mob/user, mob/living/patient) //FALSE to not show in list - SHOULD_CALL_PARENT(TRUE) - - if(replaced_by == /datum/surgery) - return FALSE - - if(targetable_wound) - var/any_wound = FALSE - var/obj/item/bodypart/targeted_bodypart = patient.get_bodypart(user.zone_selected) - for(var/datum/wound/found_wound as anything in targeted_bodypart?.wounds) - if(is_valid_wound(found_wound)) - any_wound = TRUE - break - - if(!any_wound) - return FALSE - - // True surgeons (like abductor scientists) need no instructions - if(HAS_MIND_TRAIT(user, TRAIT_SURGEON)) - return !replaced_by - - if(!requires_tech && !replaced_by) - return TRUE - - . = 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 - - //Get the relevant operating computer - var/obj/machinery/computer/operating/opcomputer = locate_operating_computer(get_turf(patient)) - 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/step = get_surgery_step() - if(isnull(step)) - return FALSE - var/obj/item/tool = user.get_active_held_item() - if(tool) - tool = tool.get_proxy_attacker_for(target, user) - if(step.try_op(user, target, user.zone_selected, tool, src, try_to_fail)) - return TRUE - if(tool && tool.item_flags & SURGICAL_TOOL) //Just because you used the wrong tool it doesn't mean you meant to whack the patient with it - to_chat(user, span_warning("This step requires a different tool!")) - return TRUE - - return FALSE - -/datum/surgery/proc/get_surgery_step() - var/step_type = steps[status] - return new step_type - -/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 000000000000..6f8c1440f0b2 --- /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 922ef836dbb4..000000000000 --- a/code/modules/surgery/surgery_helpers.dm +++ /dev/null @@ -1,73 +0,0 @@ -/proc/get_location_modifier(mob/located_mob) - var/turf/mob_turf = get_turf(located_mob) - if(locate(/obj/structure/table/optable, mob_turf)) - return 1 - else if(locate(/obj/machinery/stasis, mob_turf)) - return 0.9 - else if(locate(/obj/structure/table, mob_turf)) - return 0.8 - else if(locate(/obj/structure/bed, mob_turf)) - return 0.7 - else - return 0.5 - - -/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(covered_locations & HEAD || face_covered & HIDEEYES || eyesmouth_covered & GLASSESCOVERSEYES) - return FALSE - if(BODY_ZONE_PRECISE_MOUTH) - if(covered_locations & HEAD || face_covered & HIDEFACE || eyesmouth_covered & MASKCOVERSMOUTH || eyesmouth_covered & 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 73624fb011a2..000000000000 --- a/code/modules/surgery/surgery_step.dm +++ /dev/null @@ -1,315 +0,0 @@ -/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 = 10 //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 - -/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)) - 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 [parse_zone(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 //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 - -/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. - - surgery.step_in_progress = TRUE - var/speed_mod = 1 - var/fail_prob = 0//100 - fail_prob = success_prob - var/advance = 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!")) - surgery.step_in_progress = FALSE - return FALSE - - if(preop(user, target, target_zone, tool, surgery) == SURGERY_STEP_FAIL) - surgery.step_in_progress = FALSE - return FALSE - - 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(implement_type && (implements[implement_type] > 0)) //this means it isn't a require hand or any item step. - speed_mod *= (1 / (implements[implement_type] / 100.0)) - - speed_mod *= surgery.speed_modifier - - speed_mod *= (1 / get_location_modifier(target)) - - for(var/id in target.mob_surgery_speed_mods) - speed_mod *= target.mob_surgery_speed_mods[id] - - var/modded_time = time * speed_mod - - fail_prob = min(max(0, modded_time - (time * SURGERY_SLOWDOWN_CAP_MULTIPLIER)),99)//if modded_time > time * modifier, then fail_prob = modded_time - time*modifier. starts at 0, caps at 99 - modded_time = min(modded_time, time * SURGERY_SLOWDOWN_CAP_MULTIPLIER)//also if that, then 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 = user.has_status_effect(/datum/status_effect/hippocratic_oath) ? target : DOAFTER_SOURCE_SURGERY)) //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)) - 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) - advance = TRUE - if(advance && !repeatable) - surgery.status++ - if(surgery.status > surgery.steps.len) - surgery.complete(user) - - 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 - -/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) - if(!preop_sound) - return - 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 - 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(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(tool && !(tool.item_flags & CRUEL_IMPLEMENT)) - return FALSE - if(!HAS_MIND_TRAIT(user, TRAIT_MORBID)) - 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/carbon/target, - pain_message, - mechanical_surgery = FALSE, - target_zone = BODY_ZONE_CHEST, // can be a list of zones - pain_amount = 0, - pain_type = BRUTE, - surgery_moodlet = /datum/mood_event/surgery, - pain_overlay_severity = pain_amount >= 20 ? 2 : 1, -) - ASSERT(!isnull(target)) - ASSERT(istext(pain_message)) - // Not actually causing pain, just feedback - if(pain_amount <= 0) - target.cause_pain(target_zone, pain_amount) - target.pain_message(span_danger(pain_message)) - return - // Only feels pain if we feels pain - if(!CAN_FEEL_PAIN(target)) - target.add_mood_event("surgery", /datum/mood_event/anesthetic) - target.pain_message(span_danger(pain_message)) - return - // No pain from mechanics but still show the message (usually) - if(mechanical_surgery) - target.pain_message(span_danger(pain_message)) - return - - if(implement_type && (implements[implement_type] > 0)) - pain_amount = round(pain_amount * 1 / (sqrt(implements[implement_type]) * 0.1), 0.1) - - target.cause_pain(target_zone, pain_amount, pain_type) - if(target.IsSleeping() || target.stat >= UNCONSCIOUS) - return - // Replace this check with localized anesthesia in the future - var/obj/item/bodypart/checked_bodypart = target.get_bodypart(target_zone) - if(checked_bodypart && checked_bodypart.bodypart_pain_modifier * target.pain_controller.pain_modifier < 0.5) - return - target.add_mood_event("surgery", surgery_moodlet) - target.flash_pain_overlay(pain_overlay_severity, 0.5 SECONDS) - target.adjust_traumatic_shock(pain_amount * 0.33 * target.pain_controller.pain_modifier) - target.pain_emote() - target.pain_message(span_userdanger(pain_message)) - -#undef SURGERY_SPEED_DISSECTION_MODIFIER -#undef SURGERY_SPEED_MORBID_CURIOSITY -#undef SURGERY_SLOWDOWN_CAP_MULTIPLIER 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 bffd2bb3db8e..f0f0b75e6270 100644 --- a/code/modules/surgery/tools.dm +++ b/code/modules/surgery/surgery_tools.dm @@ -329,11 +329,11 @@ attack_verb_simple = list("slap") drop_sound = 'maplestation_modules/sound/items/drop/generic2.ogg' pickup_sound = 'maplestation_modules/sound/items/pickup/generic3.ogg' - interaction_flags_atom = parent_type::interaction_flags_atom | INTERACT_ATOM_IGNORE_MOBILITY + 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' @@ -352,7 +352,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) . = ..() @@ -360,22 +360,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/cyborg_unequip(mob/user) . = ..() @@ -404,13 +402,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" @@ -430,6 +425,7 @@ light_power = 1.2 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 643262630db4..0d528ab628ee 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)) var/design_id = design::id - if (design_id == DESIGN_ID_IGNORE || (design in exceptions)) + if (design_id == DESIGN_ID_IGNORE) continue if (design_id in all_designs) TEST_FAIL("Design [design] 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 9d974138de61..5a4e66cd3c82 100644 --- a/code/modules/unit_tests/omnitools.dm +++ b/code/modules/unit_tests/omnitools.dm @@ -1,100 +1,47 @@ -/datum/unit_test/omnitools - abstract_type = /datum/unit_test/omnitools - - //The borg model tot ransform to - var/borg_model = /obj/item/robot_model - //Tool type - var/tool_type = /obj/item/borg/cyborg_omnitool - -///Test the current tool in the toolkit -/datum/unit_test/omnitools/proc/TestTool(mob/living/silicon/robot/borg, obj/item/borg/cyborg_omnitool) - PROTECTED_PROC(TRUE) - - return - /datum/unit_test/omnitools/Run() var/mob/living/silicon/robot/borg = allocate(__IMPLIED_TYPE__) + var/obj/structure/frame/machine/test_frame = allocate(__IMPLIED_TYPE__) + test_frame.state = FRAME_STATE_WIRED //transform to engiborg - borg.model.transform_to(borg_model, forced = TRUE, transform = FALSE) + borg.model.transform_to(/obj/item/robot_model/engineering, forced = TRUE, transform = FALSE) var/obj/item/borg/cyborg_omnitool/omnitool = null for(var/obj/item/borg/tool as anything in borg.model.modules) - if(istype(tool, tool_type)) + if(istype(tool, /obj/item/borg/cyborg_omnitool/engineering)) omnitool = tool break - TEST_ASSERT_NOTNULL(omnitool, "Could not find [tool_type] in borg inbuilt modules!") - borg.shown_robot_modules = TRUE //stops hud from updating which would runtime cause our mob does not have one - borg.equip_module_to_slot(omnitool, 1) + TEST_ASSERT_NOTNULL(omnitool, "Could not find /obj/item/borg/cyborg_omnitool/engineering in borg inbuilt modules!") + borg.put_in_hand(omnitool, 1) borg.select_module(1) //these must match TEST_ASSERT_EQUAL(borg.get_active_held_item(), omnitool, "Borg held tool is not the selected omnitool!") - for(var/obj/item/internal_tool as anything in omnitool.omni_toolkit) - //Initialize the tool - omnitool.reference = internal_tool - omnitool.tool_behaviour = initial(internal_tool.tool_behaviour) - - //Test it - TestTool(borg, omnitool) - - borg.unequip_module_from_slot(omnitool, 1) - - -/// Tests for engiborg omnitool -/datum/unit_test/omnitools/engiborg - borg_model = /obj/item/robot_model/engineering - tool_type = /obj/item/borg/cyborg_omnitool/engineering - - /// frame to test wirecutter & screwdriver - var/obj/structure/frame/machine/test_frame - -/datum/unit_test/omnitools/engiborg/TestTool(mob/living/silicon/robot/borg, obj/item/borg/cyborg_omnitool/held_item) - var/tool_behaviour = held_item.tool_behaviour - - switch(tool_behaviour) - //Tests for omnitool wrench - if(TOOL_WRENCH) - var/obj/machinery/cell_charger/charger = allocate(__IMPLIED_TYPE__) - //Test 1: charger must be anchored - held_item.melee_attack_chain(borg, charger) - TEST_ASSERT(!charger.anchored, "Cell charger was not unanchored by borg omnitool wrench!") - //Test 2: charger must be unanchored - held_item.melee_attack_chain(borg, charger) - TEST_ASSERT(charger.anchored, "Cell charger was not anchored by borg omnitool wrench!") + //Initialize the tool + omnitool.set_internal_tool(/obj/item/wirecutters/cyborg) - //Tests for omnitool wirecutter - if(TOOL_WIRECUTTER) - //Test 1: is holding wirecutters for wires - TEST_ASSERT(borg.is_holding_tool_quality(TOOL_WIRECUTTER), "Cannot find borg omnitool wirecutters in borgs hand!") + //Check the proxy attacker is of this type + var/obj/item/proxy = omnitool.get_proxy_attacker_for(test_frame, borg) + TEST_ASSERT_EQUAL(proxy.type, /obj/item/wirecutters/cyborg, "Omnitool proxy attacker [proxy.type] does not match selected type /obj/item/wirecutters/cyborg") - //Test 2: frame wires must be cut - if(isnull(test_frame)) - test_frame = allocate(__IMPLIED_TYPE__) - test_frame.state = FRAME_STATE_WIRED - held_item.melee_attack_chain(borg, test_frame) - TEST_ASSERT_EQUAL(test_frame.state, FRAME_STATE_EMPTY, "Machine frame's wires were not cut by the borg omnitool wirecutters!") + //Test the attack chain to see if the internal tool interacted correctly with the target + omnitool.melee_attack_chain(borg, test_frame) + TEST_ASSERT_EQUAL(test_frame.state, FRAME_STATE_EMPTY, "Machine frame's wires were not cut by the borg omnitool wirecutters!") - //Test for omnitool screwdriver - if(TOOL_SCREWDRIVER) - //Test 1: dissemble frame - held_item.melee_attack_chain(borg, test_frame) - TEST_ASSERT(QDELETED(test_frame), "Machine frame was not deconstructed by borg omnitool screwdriver!") + //unequip + borg.drop_all_held_items() - //Test for borg omnitool crowbar - if(TOOL_CROWBAR) - var/obj/machinery/recharger/recharger = allocate(__IMPLIED_TYPE__) - recharger.panel_open = TRUE - //Test 1: should dissemble the charger - held_item.melee_attack_chain(borg, recharger) - TEST_ASSERT(QDELETED(recharger), "Recharger was not deconstructed by borg omnitool crowbar!") +/datum/unit_test/omnitool_icons - //Test for borg omnitool multitool - if(TOOL_MULTITOOL) - var/obj/machinery/ore_silo/silo = allocate(__IMPLIED_TYPE__) - //Test 1: should store silo in buffer - held_item.melee_attack_chain(borg, silo) - var/obj/item/multitool/tool = held_item.get_proxy_attacker_for(silo, borg) - TEST_ASSERT(istype(tool), "Borg failed to switch internal tool to multitool") - TEST_ASSERT(istype(tool.buffer, /obj/machinery/ore_silo), "Borg omnitool multitool failed to log ore silo!") +/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 11c0bcd71bec..e05c6bd0bd94 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 8d2901bf77ff..d980839d9693 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,55 +70,70 @@ /datum/unit_test/multiple_surgeries/Run() var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent) + 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) - var/obj/item/scalpel/scalpel = allocate(/obj/item/scalpel) + 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/datum/surgery_step/incise/surgery_step = new - var/datum/surgery/organ_manipulation/surgery_for_zero = new + var/obj/item/scalpel/scalpel = allocate(/obj/item/scalpel) + user.put_in_active_hand(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") + ASYNC + user.perform_surgery(patient_zero, scalpel) - var/datum/surgery/organ_manipulation/surgery_for_one = new + TEST_ASSERT(DOING_INTERACTION(user, patient_zero), "User is not performing surgery on patient zero as expected") - // 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") + ASYNC + user.perform_surgery(patient_one, scalpel) - 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") + 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") -/// 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) + + patient.set_body_position(LYING_DOWN) + + 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) - var/datum/surgery/surgery = new /datum/surgery/healing/brute/basic + 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") - if (!surgery.can_start(user, patient)) - TEST_FAIL("Can't start basic tend wounds!") + 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") - qdel(surgery) + 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) - TEST_ASSERT(patient.getBruteLoss() < 100, "Tending brute wounds didn't lower brute damage ([patient.getBruteLoss()])") - - var/datum/surgery_step/heal/burn/basic/basic_burn_heal = new - basic_burn_heal.success(user, patient, BODY_ZONE_CHEST) - TEST_ASSERT(patient.getFireLoss() < 100, "Tending burn wounds didn't lower burn damage ([patient.getFireLoss()])") + 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.getBruteLoss() < 100, "Tending brute wounds didn't lower brute damage ([patient.get_brute_loss()])") // Test that wearing clothing lowers heal amount var/mob/living/carbon/human/naked_patient = allocate(/mob/living/carbon/human/consistent) @@ -127,7 +143,104 @@ 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.getBruteLoss() < clothed_patient.get_brute_loss(), "Naked patient did not heal more from wounds tending than a clothed patient") + +/// Tests items-as-prosthetic-limbs can apply +/datum/unit_test/prosthetic_item + +/datum/unit_test/prosthetic_item/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent) + var/obj/item/claymore/sword = allocate(/obj/item/claymore) + + patient.make_item_prosthetic(sword) + + TEST_ASSERT(HAS_TRAIT_FROM(sword, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT), "Prosthetic item attachment failed! Item does not have the nodrop trait") + +/// Specifically checks the chainsaw nullrod +/datum/unit_test/prosthetic_item/nullrod + +/datum/unit_test/prosthetic_item/nullrod/Run() + var/mob/living/carbon/human/picker = allocate(/mob/living/carbon/human/consistent) + var/obj/item/nullrod/chainsaw/nullrod = allocate(/obj/item/nullrod/chainsaw) + + 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") - TEST_ASSERT(naked_patient.getBruteLoss() < clothed_patient.getBruteLoss(), "Naked patient did not heal more from wounds tending than a clothed patient") + 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/maplestation.dme b/maplestation.dme index beceaf9aa32c..5bd466ae7384 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -5856,7 +5856,6 @@ #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\autopsy.dm" #include "code\modules\surgery\blood_filter.dm" #include "code\modules\surgery\bone_mending.dm" @@ -6417,7 +6416,6 @@ #include "maplestation_modules\code\game\objects\spawners\cycle_helper.dm" #include "maplestation_modules\code\game\objects\spawners\random\contraband.dm" #include "maplestation_modules\code\game\objects\structures\item_dispensers.dm" -#include "maplestation_modules\code\game\objects\structures\operating_computer.dm" #include "maplestation_modules\code\game\objects\structures\pass_id_panel.dm" #include "maplestation_modules\code\game\objects\structures\stasis.dm" #include "maplestation_modules\code\game\objects\structures\static_plaques.dm" diff --git a/maplestation_modules/code/game/objects/structures/operating_computer.dm b/maplestation_modules/code/game/objects/structures/operating_computer.dm deleted file mode 100644 index 6e66c3421883..000000000000 --- a/maplestation_modules/code/game/objects/structures/operating_computer.dm +++ /dev/null @@ -1,74 +0,0 @@ -/obj/machinery/computer/operating - -/obj/machinery/computer/operating/attackby(obj/item/weapon, mob/living/user, params) - if(!istype(user) || user.combat_mode) - return ..() - if(weapon.item_flags & SURGICAL_TOOL) - // You can open it while doing surgery - return interact(user) - return ..() - -/obj/machinery/computer/operating/emag_act(mob/user, obj/item/card/emag/emag_card) - . = ..() - if(obj_flags & EMAGGED) - return - if(!is_operational) - return - - obj_flags |= EMAGGED - balloon_alert(user, "safeties overridden") - playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE, SHORT_RANGE_SOUND_EXTRARANGE) - playsound(src, SFX_SPARKS, 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) - -/obj/machinery/computer/operating/on_set_is_operational(old_value) - if(is_operational) - return - // Losing power / getting broken will auto disable anesthesia - table.safety_disable() - -/obj/machinery/computer/operating/ui_data(mob/user) - var/list/data = ..() - if(isnull(table)) - return data - - var/tank_exists = !isnull(table.attached_tank) - var/patient_exists = !isnull(table.patient) - data["anesthesia"] = list( - "has_tank" = tank_exists, - "open" = tank_exists && patient_exists && table.patient.external == table.attached_tank, - "can_open_tank" = tank_exists && patient_exists && table.can_have_tank_opened(table.patient), - "failsafe" = table.failsafe_time == INFINITY ? -1 : (table.failsafe_time / 10), - ) - - if(patient_exists) - var/obj/item/organ/patient_brain = table.patient.get_organ_slot(ORGAN_SLOT_BRAIN) - data["patient"]["brain"] = isnull(patient_brain) ? 100 : ((patient_brain.damage / patient_brain.maxHealth) * 100) - data["patient"]["bloodVolumePercent"] = round((table.patient.blood_volume / BLOOD_VOLUME_NORMAL) * 100) - data["patient"]["heartRate"] = table.patient.get_bpm() - // We can also show pain and stuff here if we want. - - return data - -/obj/machinery/computer/operating/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) - . = ..() - if(. || isnull(table)) - return - - switch(action) - if("toggle_anesthesia") - if(iscarbon(usr)) - var/mob/living/carbon/toggler = usr - if(toggler == table.patient && table.patient_set_at == -1 && table.failsafe_time >= 5 MINUTES) - to_chat(toggler, span_warning("You feel as if you know better than to do that.")) - return FALSE - - table.toggle_anesthesia() - return TRUE - - if("set_failsafe") - table.failsafe_time = clamp(text2num(params["new_failsafe_time"]) * 10, 5 SECONDS, 10 MINUTES) - return TRUE - - if("disable_failsafe") - table.failsafe_time = INFINITY - return TRUE diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_fish.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_fish.dm index d378886d3a8a..b6aac951227b 100644 --- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_fish.dm +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_fish.dm @@ -72,16 +72,16 @@ // organ_traits = list(TRAIT_FLOPPING, TRAIT_SWIMMER) restyle_flags = EXTERNAL_RESTYLE_FLESH - // // Fishlike reagents, you could serve it raw like fish - // food_reagents = list( - // /datum/reagent/consumable/nutriment/protein = 10, - // /datum/reagent/consumable/nutriment/vitamin = 5, - // /datum/reagent/consumable/nutriment/fat = 10, - // ) - // // Seafood instead of meat, because it's a fish organ - // foodtype_flags = RAW | SEAFOOD | GORE - // // Also just tastes like fish - // food_tastes = list("fatty fish" = 1) + // Fishlike reagents, you could serve it raw like fish + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 10, + /datum/reagent/consumable/nutriment/vitamin = 5, + /datum/reagent/consumable/nutriment/fat = 10, + ) + // Seafood instead of meat, because it's a fish organ + foodtype_flags = RAW | SEAFOOD | GORE + // Also just tastes like fish + food_tastes = list("fatty fish" = 1) /// The fillet type this fish tail is processable into var/fillet_type = /obj/item/food/fishmeat/fish_tail /// The amount of fillets this gets processed into diff --git a/maplestation_modules/code/modules/reagents/chemistry/reagents/rim_reagents.dm b/maplestation_modules/code/modules/reagents/chemistry/reagents/rim_reagents.dm index 77e3aa36ac37..215a9c953534 100644 --- a/maplestation_modules/code/modules/reagents/chemistry/reagents/rim_reagents.dm +++ b/maplestation_modules/code/modules/reagents/chemistry/reagents/rim_reagents.dm @@ -97,9 +97,8 @@ // Can cure wounds, too if(SPT_PROB(6, seconds_per_tick)) - var/list/shuffled_wounds = shuffle(user.all_wounds) - for(var/datum/wound/wound as anything in shuffled_wounds) - wound.remove_wound() + for(var/datum/wound/wound as anything in shuffle(user.all_wounds)) + qdel(wound) break . = ..() diff --git a/strings/tips.txt b/strings/tips.txt index 8012312c2acd..a8a3c8348ab4 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -48,18 +48,26 @@ As a Janitor Cyborg, you are the bane of all slaughter demons and even Bubblegum As a Janitor, if someone steals your janicart, you can instead use your space cleaner spray, grenades, water sprayer, exact bloody revenge or order another from Cargo. As a Janitor, mousetraps can be used to create bombs or booby-trap containers. 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, 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. As a Medical Doctor, you can deal with patients who have absurd amounts of wounds by putting them in cryo. This will slowly treat all of their wounds simultaneously, but is much slower than direct treatment. As a Medical Doctor, you can extract implants by holding an empty implant case in your offhand while performing the extraction step. 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 Morph, you can talk while disguised, but your words have a chance of being slurred, giving you away! @@ -177,6 +185,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. @@ -225,6 +238,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? Laying down will help slow down bloodloss. Death will halt it entirely. Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using. @@ -237,6 +251,7 @@ Some roles cannot be antagonists by default, but antag selection is decided firs Some weapons are better at taking down robots and structures than others. Don't try to break a window with a scalpel, try a toolbox. 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. 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 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! diff --git a/tgstation.dme b/tgstation.dme index e523ceb7a193..ca825ca6448f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1232,7 +1232,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\tackle.dm" @@ -1520,6 +1519,7 @@ #include "code\datums\elements\squish.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\temporary_atom.dm" #include "code\datums\elements\tenacious.dm" @@ -2992,7 +2992,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" @@ -3473,6 +3472,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" @@ -5863,55 +5863,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\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\internal_bleeding.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\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.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\dismemberment.dm" #include "code\modules\surgery\bodyparts\head.dm" @@ -5927,6 +5880,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 86341391d8d2..947121f03e8a 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 574817749732..000000000000 --- 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 000000000000..7a6d0e26af9d --- /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 000000000000..64d2031639b2 --- /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 000000000000..20fc350c71f4 --- /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 000000000000..a4e285741d3f --- /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 000000000000..ca4040fd9843 --- /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 000000000000..5cc1e8ee1e2b --- /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 8e22f45de40c..ce38b11ca51d 100644 --- a/tgui/packages/tgui/interfaces/SimpleBot.tsx +++ b/tgui/packages/tgui/interfaces/SimpleBot.tsx @@ -246,7 +246,6 @@ function SettingsDisplay(props) { } enum ControlType { - MedbotSync = 'sync_tech', MedbotThreshold = 'heal_threshold', FloorbotTiles = 'tile_stack', FloorbotLine = 'line_mode', @@ -264,8 +263,6 @@ function ControlHelper(props: ControlProps) { const { control } = props; switch (control[0]) { - case ControlType.MedbotSync: - return ; case ControlType.MedbotThreshold: return ; case ControlType.FloorbotTiles: @@ -284,25 +281,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 2bc02f699439..000000000000 --- a/tgui/packages/tgui/interfaces/SurgeryInitiator.tsx +++ /dev/null @@ -1,173 +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 6788bf2f3f4b..6e4d7aadb403 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 8b5b31801569..0c40c46f47d4 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 84549f871983..5a67252411dc 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 000000000000..018c2e984bd2 --- /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 9c89bd960dfd..357b729333ca 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 000000000000..b7b3cf03fbfc --- /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); +} From 67ea6b1f9ae62f822a4ca71a622a458c1f6d5820 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 18:28:47 -0600 Subject: [PATCH 02/35] Fixes --- .../machinery/computer/operating_computer.dm | 14 +- code/game/objects/effects/info.dm | 2 +- code/game/objects/items/devices/flashlight.dm | 2 +- code/game/objects/items/stacks/medical.dm | 4 +- .../traitor/objectives/sleeper_protocol.dm | 75 -------- code/modules/clothing/head/jobs.dm | 2 +- code/modules/mob/living/blood.dm | 4 +- code/modules/mob/living/carbon/carbon.dm | 6 +- .../mob/living/carbon/carbon_context.dm | 2 +- .../mob/living/carbon/carbon_defense.dm | 6 +- code/modules/mob/living/carbon/examine.dm | 2 +- code/modules/mob/living/carbon/inventory.dm | 4 +- .../chemistry/reagents/other_reagents.dm | 2 +- code/modules/surgery/bodyparts/_bodyparts.dm | 65 +++---- .../surgery/bodyparts/dismemberment.dm | 4 +- code/modules/surgery/bodyparts/wounds.dm | 2 +- code/modules/surgery/internal_bleeding.dm | 122 ++++++------- code/modules/surgery/operations/_operation.dm | 18 +- .../surgery/operations/operation_bioware.dm | 6 +- .../surgery/operations/operation_brainwash.dm | 6 +- .../operations/operation_cavity_implant.dm | 12 +- .../surgery/operations/operation_core.dm | 14 +- .../surgery/operations/operation_debride.dm | 14 +- .../surgery/operations/operation_dental.dm | 4 +- .../surgery/operations/operation_generic.dm | 2 +- .../operations/operation_generic_mechanic.dm | 14 +- .../surgery/operations/operation_healing.dm | 32 ++-- .../operations/operation_implant_removal.dm | 2 +- .../surgery/operations/operation_lipo.dm | 2 +- .../operations/operation_organ_repair.dm | 12 +- .../surgery/operations/operation_pacify.dm | 2 +- .../surgery/operations/operation_pump.dm | 4 +- .../operations/operation_replace_limb.dm | 6 +- .../surgery/operations/operation_revival.dm | 6 +- code/modules/surgery/organs/_organ.dm | 2 +- code/modules/unit_tests/surgeries.dm | 4 +- maplestation.dme | 87 ++++----- .../advanced_ling/changeling_rebalancing.dm | 4 +- .../advanced_ling/neutered_ling.dm | 171 ++++++++---------- 39 files changed, 303 insertions(+), 439 deletions(-) delete mode 100644 code/modules/antagonists/traitor/objectives/sleeper_protocol.dm diff --git a/code/game/machinery/computer/operating_computer.dm b/code/game/machinery/computer/operating_computer.dm index 08e5f76bcd0d..183383ee323f 100644 --- a/code/game/machinery/computer/operating_computer.dm +++ b/code/game/machinery/computer/operating_computer.dm @@ -204,11 +204,11 @@ // check here to see if the patient has standard blood reagent, or special blood (like how ethereals bleed liquid electricity) to show the proper name in the computer data["patient"]["blood_type"] = "[patient.get_blood_type() || "None"]" // NON-MODULE CHANGE data["patient"]["maxHealth"] = patient.maxHealth - data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD - data["patient"]["bruteLoss"] = patient.get_brute_loss() - data["patient"]["fireLoss"] = patient.get_fire_loss() - data["patient"]["toxLoss"] = patient.get_tox_loss() - data["patient"]["oxyLoss"] = patient.get_oxy_loss() + data["patient"]["minHealth"] = -1 * patient.maxHealth + data["patient"]["bruteLoss"] = patient.getBruteLoss() + data["patient"]["fireLoss"] = patient.getFireLoss() + data["patient"]["toxLoss"] = patient.getToxLoss() + data["patient"]["oxyLoss"] = patient.getOxyLoss() 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)) @@ -302,9 +302,7 @@ return data - - -/obj/machinery/computer/operating/ui_act(action, params) +/obj/machinery/computer/operating/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return diff --git a/code/game/objects/effects/info.dm b/code/game/objects/effects/info.dm index 3356b4dd9f5e..8a925305d29b 100644 --- a/code/game/objects/effects/info.dm +++ b/code/game/objects/effects/info.dm @@ -15,7 +15,7 @@ /obj/effect/abstract/info/Click() . = ..() - to_chat(usr, boxed_message("[span_boldnotice(name)]
    [span_info(info_text)]")) + to_chat(usr, examine_block("[span_boldnotice(name)]
    [span_info(info_text)]")) /obj/effect/abstract/info/MouseEntered(location, control, params) . = ..() diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index 72f4b4bb34f7..f28e7b46076d 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -175,7 +175,7 @@ organ_list += (O.gender == "plural" ? O.name : "\an [O.name]") var/pill_count = 0 - for(var/datum/action/item_action/hands_free/activate_pill/AP in M.actions) + for(var/datum/action/item_action/activate_pill/AP in M.actions) pill_count++ if(M == user)//if we're looking on our own mouth diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 95e9bcd833f4..3889c82dd163 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -266,7 +266,7 @@ var/datum/wound/flesh/any_burn_wound = locate() in affecting.wounds var/can_heal_burn_wounds = (flesh_regeneration || sanitization) && any_burn_wound?.can_be_ointmented_or_meshed() - var/can_suture_bleeding = stop_bleeding && affecting.get_modified_bleed_rate() > 0 + var/can_suture_bleeding = stop_bleeding && affecting.cached_bleed_rate > 0 var/brute_to_heal = heal_brute && affecting.brute_dam > 0 var/burn_to_heal = heal_burn && affecting.burn_dam > 0 @@ -510,7 +510,7 @@ // 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) + for(var/datum/wound/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) diff --git a/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm b/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm deleted file mode 100644 index d6c35f5ca942..000000000000 --- a/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm +++ /dev/null @@ -1,75 +0,0 @@ -/datum/traitor_objective_category/sleeper_protocol - name = "Sleeper Protocol" - objectives = list( - /datum/traitor_objective/sleeper_protocol = 1, - /datum/traitor_objective/sleeper_protocol/everybody = 1, - ) - -/datum/traitor_objective/sleeper_protocol - name = "Perform the sleeper protocol on a crewmember" - description = "Use the button below to materialize a surgery disk in your hand, where you'll then be able to perform the sleeper protocol on a crewmember. If the disk gets destroyed, the objective will fail. This will only work on living and sentient crewmembers." - - progression_minimum = 0 MINUTES - - progression_reward = list(8 MINUTES, 15 MINUTES) - telecrystal_reward = 1 - - var/list/limited_to = list( - JOB_CHIEF_MEDICAL_OFFICER, - JOB_MEDICAL_DOCTOR, - JOB_PARAMEDIC, - JOB_VIROLOGIST, - JOB_ROBOTICIST, - ) - - var/obj/item/disk/surgery/sleeper_protocol/disk - - var/mob/living/current_registered_mob - - var/inverted_limitation = FALSE - -/datum/traitor_objective/sleeper_protocol/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!disk) - buttons += add_ui_button("", "Clicking this will materialize the sleeper protocol surgery in your hand", "save", "summon_disk") - return buttons - -/datum/traitor_objective/sleeper_protocol/ui_perform_action(mob/living/user, action) - switch(action) - if("summon_disk") - if(disk) - return - disk = new(user.drop_location()) - user.put_in_hands(disk) - AddComponent(/datum/component/traitor_objective_register, disk, \ - fail_signals = list(COMSIG_QDELETING)) - -/datum/traitor_objective/sleeper_protocol/proc/on_surgery_success(datum/source, datum/surgery_step/step, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) - SIGNAL_HANDLER - if(istype(step, /datum/surgery_step/brainwash/sleeper_agent)) - succeed_objective() - -/datum/traitor_objective/sleeper_protocol/can_generate_objective(datum/mind/generating_for, list/possible_duplicates) - var/datum/job/job = generating_for.assigned_role - if(!(job.title in limited_to) && !inverted_limitation) - return FALSE - if((job.title in limited_to) && inverted_limitation) - return FALSE - if(length(possible_duplicates) > 0) - return FALSE - return TRUE - -/datum/traitor_objective/sleeper_protocol/generate_objective(datum/mind/generating_for, list/possible_duplicates) - AddComponent(/datum/component/traitor_objective_mind_tracker, generating_for, \ - signals = list(COMSIG_MOB_SURGERY_STEP_SUCCESS = PROC_REF(on_surgery_success))) - return TRUE - -/datum/traitor_objective/sleeper_protocol/ungenerate_objective() - disk = null - -/datum/traitor_objective/sleeper_protocol/everybody //Much harder for non-med and non-robo - progression_minimum = 30 MINUTES - progression_reward = list(8 MINUTES, 15 MINUTES) - telecrystal_reward = 1 - - inverted_limitation = TRUE diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm index 46a715977843..59a1a04ac418 100644 --- a/code/modules/clothing/head/jobs.dm +++ b/code/modules/clothing/head/jobs.dm @@ -702,7 +702,7 @@ else var/obj/item/organ/tongue/has_tongue = human_examined.get_organ_slot(ORGAN_SLOT_TONGUE) var/pill_count = 0 - for(var/datum/action/item_action/hands_free/activate_pill/pill in human_examined.actions) + for(var/datum/action/item_action/activate_pill/pill in human_examined.actions) pill_count++ if(pill_count >= 1 && has_tongue) diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 6fb968293489..913857bb6082 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -103,7 +103,7 @@ var/temp_bleed = 0 //Bleeding out for(var/obj/item/bodypart/iter_part as anything in bodyparts) - var/iter_bleed_rate = iter_part.get_modified_bleed_rate() + var/iter_bleed_rate = iter_part.cached_bleed_rate temp_bleed += iter_bleed_rate * seconds_per_tick if(iter_part.generic_bleedstacks) // If you don't have any bleedstacks, don't try and heal them @@ -142,7 +142,7 @@ var/bleed_amt = 0 for(var/X in bodyparts) var/obj/item/bodypart/iter_bodypart = X - bleed_amt += iter_bodypart.get_modified_bleed_rate() + bleed_amt += iter_bodypart.cached_bleed_rate return bleed_amt /mob/living/carbon/human/get_bleed_rate() diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 5235b696102f..65e5c6f958a3 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1141,7 +1141,7 @@ var/obj/item/bodypart/new_bp = new limb2add() 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) + qdel(BP) else to_chat(usr, "Failed to replace bodypart! They might be incompatible.") admin_ticket_log("[key_name_admin(usr)] has attempted to modify the bodyparts of [src]") @@ -1232,7 +1232,7 @@ if(HAS_TRAIT(src, TRAIT_NOBLOOD)) return FALSE for(var/obj/item/bodypart/part as anything in bodyparts) - if(part.get_modified_bleed_rate()) + if(part.cached_bleed_rate) return TRUE return FALSE @@ -1243,7 +1243,7 @@ var/total_bleed_rate = 0 for(var/obj/item/bodypart/part as anything in bodyparts) - total_bleed_rate += part.get_modified_bleed_rate() + total_bleed_rate += part.cached_bleed_rate return total_bleed_rate diff --git a/code/modules/mob/living/carbon/carbon_context.dm b/code/modules/mob/living/carbon/carbon_context.dm index b376ea3e113f..ca9e518c1d9f 100644 --- a/code/modules/mob/living/carbon/carbon_context.dm +++ b/code/modules/mob/living/carbon/carbon_context.dm @@ -15,7 +15,7 @@ else if (human_user == src) context[SCREENTIP_CONTEXT_LMB] = "Check injuries" - if (get_bodypart(human_user.zone_selected)?.get_modified_bleed_rate()) + if (get_bodypart(human_user.zone_selected)?.cached_bleed_rate) context[SCREENTIP_CONTEXT_CTRL_LMB] = "Grab limb" if (human_user != src) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 62c3d01092de..aea6c1ae7a21 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -575,7 +575,7 @@ to_chat(src, span_danger("You can't grasp your [grasped_part.name] with itself!")) return - var/bleed_rate = grasped_part.get_modified_bleed_rate() + var/bleed_rate = grasped_part.cached_bleed_rate var/bleeding_text = (bleed_rate ? ", trying to stop the bleeding" : "") to_chat(src, span_warning("You try grasping at your [grasped_part.name][bleeding_text]...")) if(!do_after(src, 0.75 SECONDS)) @@ -591,7 +591,7 @@ /// If TRUE, the owner of this bodypart can try grabbing it to slow bleeding, as well as various other effects. /obj/item/bodypart/proc/can_be_grasped() - if (get_modified_bleed_rate()) + if (cached_bleed_rate) return TRUE for (var/datum/wound/iterated_wound as anything in wounds) @@ -644,7 +644,7 @@ RegisterSignal(user, COMSIG_QDELETING, PROC_REF(qdel_void)) RegisterSignals(grasped_part, list(COMSIG_CARBON_REMOVE_LIMB, COMSIG_QDELETING), PROC_REF(qdel_void)) - var/bleed_rate = grasped_part.get_modified_bleed_rate() + var/bleed_rate = grasped_part.cached_bleed_rate var/bleeding_text = (bleed_rate ? ", trying to stop the bleeding" : "") user.visible_message(span_danger("[user] grasps at [user.p_their()] [grasped_part.plaintext_zone][bleeding_text]."), span_notice("You grab hold of your [grasped_part.plaintext_zone] tightly."), vision_distance=COMBAT_MESSAGE_RANGE) playsound(get_turf(src), 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index ae6164b81456..85bb98439a3a 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -176,7 +176,7 @@ var/list/obj/item/bodypart/grasped_limbs = list() for(var/obj/item/bodypart/body_part as anything in bodyparts) - if(!body_part.current_gauze && body_part.get_modified_bleed_rate()) + if(!body_part.current_gauze && body_part.cached_bleed_rate) bleeding_limbs += body_part.plaintext_zone if(body_part.grasped_by) grasped_limbs += body_part.plaintext_zone diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index d0d17fa4ef08..f2d41d319c8b 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -513,7 +513,7 @@ SHOULD_NOT_OVERRIDE(TRUE) var/covered_flags = NONE - for(var/obj/item/worn_item in get_equipped_items(INCLUDE_ABSTRACT)) + for(var/obj/item/worn_item in get_equipped_items(/*INCLUDE_ABSTRACT*/)) covered_flags |= worn_item.body_parts_covered return covered_flags @@ -529,7 +529,7 @@ return FALSE var/covered_flags = NONE - for(var/obj/item/worn_item in get_equipped_items(INCLUDE_ABSTRACT)) + 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 diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index af85066889f6..30c046e2bd88 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1180,7 +1180,7 @@ return 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) +/datum/reagent/space_cleaner/sterilizine/on_burn_wound_processing(datum/wound/flesh/burn_wound) burn_wound.sanitization += 0.9 /datum/reagent/iron diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index eb9486b89f80..fb48af8623e7 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -153,7 +153,7 @@ /// So we know if we need to scream if this limb hits max damage var/last_maxed /// Our current bleed rate. Cached, update with refresh_bleed_rate() - VAR_PRIVATE/cached_bleed_rate = 0 + var/cached_bleed_rate = 0 /// How much generic bleedstacks we have on this bodypart var/generic_bleedstacks = 0 /// If we have a gauze wrapping currently applied (not including splints) @@ -879,6 +879,7 @@ )) UnregisterSignal(old_owner, COMSIG_ATOM_RESTYLE) + UnregisterSignal(old_owner, 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)) @@ -898,24 +899,24 @@ if(HAS_TRAIT(owner, TRAIT_NOLIMBDISABLE)) set_can_be_disabled(FALSE) + // Listen to disable traits being added RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_NOLIMBDISABLE), PROC_REF(on_owner_nolimbdisable_trait_loss)) RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOLIMBDISABLE), PROC_REF(on_owner_nolimbdisable_trait_gain)) - // Listen to no blood traits being added - RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_NOBLOOD), PROC_REF(on_owner_nobleed_loss)) - RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOBLOOD), PROC_REF(on_owner_nobleed_gain)) + RegisterSignals(owner, list(SIGNAL_REMOVETRAIT(TRAIT_NOBLOOD), SIGNAL_ADDTRAIT(TRAIT_NOBLOOD)), PROC_REF(refresh_bleed_rate)) if(can_be_disabled) update_disabled() RegisterSignal(owner, COMSIG_ATOM_RESTYLE, PROC_REF(on_attempt_feature_restyle_mob)) + RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(refresh_bleed_rate)) 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) + // 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) @@ -1277,7 +1278,7 @@ return // We don't need to do anything with projectile embedding, because it will never reach this point embedded_objects += embed - RegisterSignal(embed, COMSIG_ITEM_EMBEDDING_UPDATE, PROC_REF(embedded_object_changed)) + RegisterSignal(embed, COMSIG_ITEM_EMBEDDING_UPDATE, PROC_REF(refresh_bleed_rate)) refresh_bleed_rate() /// INTERNAL PROC, DO NOT USE @@ -1287,11 +1288,6 @@ UnregisterSignal(unembed, COMSIG_ITEM_EMBEDDING_UPDATE) refresh_bleed_rate() -/obj/item/bodypart/proc/embedded_object_changed(obj/item/embedded_source) - SIGNAL_HANDLER - /// Embedded objects effect bleed rate, gotta refresh lads - refresh_bleed_rate() - /// Sets our generic bleedstacks /obj/item/bodypart/proc/setBleedStacks(set_to) SHOULD_CALL_PARENT(TRUE) @@ -1310,17 +1306,10 @@ || old_bleedstacks > 0 && generic_bleedstacks <= 0) refresh_bleed_rate() -/obj/item/bodypart/proc/on_owner_nobleed_loss(datum/source) - SIGNAL_HANDLER - refresh_bleed_rate() - -/obj/item/bodypart/proc/on_owner_nobleed_gain(datum/source) - SIGNAL_HANDLER - refresh_bleed_rate() - /// Refresh the cache of our rate of bleeding sans any modifiers /// ANYTHING ADDED TO THIS PROC NEEDS TO CALL IT WHEN IT'S EFFECT CHANGES /obj/item/bodypart/proc/refresh_bleed_rate() + SIGNAL_HANDLER SHOULD_NOT_OVERRIDE(TRUE) var/old_bleed_rate = cached_bleed_rate @@ -1366,29 +1355,21 @@ for(var/datum/wound/iter_wound as anything in wounds) cached_bleed_rate += iter_wound.blood_flow + if(owner.body_position == LYING_DOWN) + cached_bleed_rate *= 0.75 + + if(grasped_by) + cached_bleed_rate *= 0.7 + + // Flat multiplier applied to all bleeding for pacing purposes + cached_bleed_rate *= 0.5 + // Our bleed overlay is based directly off bleed_rate, so go aheead and update that would you? if(cached_bleed_rate != old_bleed_rate) update_part_wound_overlay() return cached_bleed_rate -/// Flat multiplier applied to bleed rate -/// I did this rather than tweak existing bleed rates because -/// 1. laziness -/// 2. so blood wounds could take longer to decay without killing you faster -#define TOTAL_BLEED_RATE_MOD 0.5 - -/// Returns our bleed rate, taking into account laying down and grabbing the limb -/obj/item/bodypart/proc/get_modified_bleed_rate() - var/bleed_rate = cached_bleed_rate * TOTAL_BLEED_RATE_MOD - if(owner.body_position == LYING_DOWN) - bleed_rate *= 0.75 - if(grasped_by) - bleed_rate *= 0.7 - return bleed_rate - -#undef TOTAL_BLEED_RATE_MOD - // how much blood the limb needs to be losing per tick (not counting laying down/self grasping modifiers) to get the different bleed icons #define BLEED_OVERLAY_LOW 0.5 #define BLEED_OVERLAY_MED 1.5 @@ -1403,7 +1384,7 @@ owner.update_wound_overlays() return FALSE - var/bleed_rate = get_modified_bleed_rate() + var/bleed_rate = cached_bleed_rate var/new_bleed_icon = null switch(bleed_rate) @@ -1596,10 +1577,10 @@ 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)) + // 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)) diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 5cebc2754820..bfa617cd22df 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -221,7 +221,7 @@ owner.dropItemToGround(head_item, force = TRUE) //Handle dental implants - for(var/datum/action/item_action/hands_free/activate_pill/pill_action in owner.actions) + for(var/datum/action/item_action/activate_pill/pill_action in owner.actions) pill_action.Remove(owner) var/obj/pill = pill_action.target if(pill) @@ -342,7 +342,7 @@ //Handle dental implants for(var/obj/item/reagent_containers/pill/pill in src) - for(var/datum/action/item_action/hands_free/activate_pill/pill_action in pill.actions) + for(var/datum/action/item_action/activate_pill/pill_action in pill.actions) pill.forceMove(new_head_owner) pill_action.Grant(new_head_owner) break diff --git a/code/modules/surgery/bodyparts/wounds.dm b/code/modules/surgery/bodyparts/wounds.dm index f27f773faf8c..c243487ec405 100644 --- a/code/modules/surgery/bodyparts/wounds.dm +++ b/code/modules/surgery/bodyparts/wounds.dm @@ -339,7 +339,7 @@ current_gauze.forceMove(remove_to) else current_gauze.moveToNullspace() - if(can_bleed() && get_modified_bleed_rate()) + if(can_bleed() && cached_bleed_rate) current_gauze.add_mob_blood(owner) current_gauze.worn_icon_state = initial(current_gauze.worn_icon_state) current_gauze.update_appearance() diff --git a/code/modules/surgery/internal_bleeding.dm b/code/modules/surgery/internal_bleeding.dm index 3c5c7a878b8f..45124db9e6f3 100644 --- a/code/modules/surgery/internal_bleeding.dm +++ b/code/modules/surgery/internal_bleeding.dm @@ -1,90 +1,72 @@ /// Repair internal bleeding - -/datum/surgery/internal_bleeding - name = "Repair Internal Bleeding" - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB | SURGERY_IGNORE_CLOTHES - targetable_wound = /datum/wound/bleed_internal - 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/retract_skin, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/repair_veins, - /datum/surgery_step/close, - ) - -/datum/surgery_step/repair_veins - name = "repair arterial bleeding (hemostat/blood filter)" +/datum/surgery_operation/limb/internal_bleeding + name = "repair internal bleeding" + desc = "Repair arterial damage which is causing internal bleeding in a limb." + rnd_desc = "A surgery that repairs internal bleeding in a limb caused by severe trauma / arterial damage." + time = 5 SECONDS + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_IGNORE_CLOTHES | OPERATION_NOTABLE | OPERATION_LOOPING implements = list( - TOOL_HEMOSTAT = 100, - TOOL_BLOODFILTER = 100, - TOOL_WIRECUTTER = 40, - /obj/item/stack/sticky_tape/surgical = 30, + TOOL_HEMOSTAT = 1, + TOOL_BLOODFILTER = 1, + TOOL_WIRECUTTER = 2, + /obj/item/stack/sticky_tape/surgical = 5, /obj/item/stack/cable_coil = 10, /obj/item/stack/sticky_tape = 10, ) - preop_sound = 'sound/surgery/hemostat1.ogg' - success_sound = 'sound/surgery/organ2.ogg' - time = 6 SECONDS - repeatable = TRUE + all_surgery_states_required = SURGERY_SKIN_OPEN -/datum/surgery_step/repair_veins/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/in_where = "[target]'s [parse_zone(target_zone)]" +/datum/surgery_operation/limb/internal_bleeding/state_check(obj/item/bodypart/limb) + for(var/datum/wound/bleed_internal/wound in operating_on.wounds) + if(wound.severity >= WOUND_SEVERITY_TRIVIAL) + return TRUE + return FALSE + +/datum/surgery_operation/limb/internal_bleeding/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) display_results( - user, - target, - span_notice("You begin repair the arteries in [in_where]..."), - span_notice("[user] begins to repair the arteries in [in_where] with [tool]."), - span_notice("[user] begins to repair the arteries in [in_where]."), + surgeon, + limb.owner, + span_notice("You begin to repair the arterial damage within [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to repair the arterial damage within [limb.owner]'s [limb.plaintext_zone] with [tool]."), + span_notice("[surgeon] begins to repair the arterial damage within [limb.owner]'s [limb.plaintext_zone]."), ) display_pain( - target = target, - target_zone = target_zone, - pain_message = "You feel a horrible stabbing pain in your [parse_zone(target_zone)]!", + target = limb.owner, + target_zone = limb.body_zone, + pain_message = "You feel a horrible stabbing pain in your [limb.plaintext_zone]!", pain_amount = SURGERY_PAIN_LOW, ) -/datum/surgery_step/repair_veins/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) - var/in_where = "[target]'s [parse_zone(target_zone)]" - if((surgery.operated_wound?.severity - 1) <= WOUND_SEVERITY_TRIVIAL) - qdel(surgery.operated_wound) +/datum/surgery_operation/limb/internal_bleeding/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + var/datum/wound/bleed_internal/target_wound = locate() in limb.wounds + target_wound.severity-- + if(target_wound.severity <= WOUND_SEVERITY_TRIVIAL) + qdel(target_wound) display_results( - user, - target, - span_green("You've finished repairing all the arterial damage in [in_where]."), - span_green("[user] finished repairing all the arterial damage in [in_where] with [tool]!"), - span_green("[user] finished repairing all the arterial damage in [in_where]!"), + surgeon, + limb.owner, + span_green("You've finished repairing all the arterial damage within [limb.owner]'s [limb.plaintext_zone]."), + span_green("[surgeon] finished repairing all the arterial damage within [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_green("[surgeon] finished repairing all the arterial damage within [limb.owner]'s [limb.plaintext_zone]!"), ) - repeatable = FALSE - return ..() + return - surgery.operated_wound.severity-- display_results( - user, - target, - span_notice("You successfully repair some of the arteries in [in_where] with [tool]."), - span_notice("[user] successfully repairs some of the arteries in [in_where] with [tool]!"), - span_notice("[user] successfully repairs some of the arteries in [in_where]!"), + surgeon, + limb.owner, + span_notice("You successfully repair some of the arteries within [limb.owner]'s [limb.plaintext_zone] with [tool]."), + span_notice("[surgeon] successfully repairs some of the arteries within [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully repairs some of the arteries within [limb.owner]'s [limb.plaintext_zone]!"), ) - target.apply_damage(3, BRUTE, surgery.operated_bodypart, wound_bonus = CANT_WOUND, attacking_item = tool) - return ..() + limb.receive_damage(3, BRUTE, wound_bonus = CANT_WOUND, attacking_item = tool) -/datum/surgery_step/repair_veins/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob = 0) - var/in_where = "[target]'s [parse_zone(target_zone)]" +/datum/surgery_operation/limb/internal_bleeding/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args, fail_prob = 0) + var/datum/wound/bleed_internal/target_wound = locate() in limb.wounds + target_wound?.severity++ display_results( - user, - target, - span_warning("You tear some of the arteries in [in_where]!"), - span_warning("[user] tears some of the arteries in [in_where] with [tool]!"), - span_warning("[user] tears some of the arteries in [in_where]!"), + surgeon, + limb.owner, + span_warning("You tear some of the arteries within [limb.owner]'s [limb.plaintext_zone]!"), + span_warning("[surgeon] tears some of the arteries within [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_warning("[surgeon] tears some of the arteries within [limb.owner]'s [limb.plaintext_zone]!"), ) - target.apply_damage(rand(4, 8), BRUTE, surgery.operated_bodypart, wound_bonus = 10, sharpness = SHARP_EDGED, attacking_item = tool) - return FALSE + limb.receive_damage(rand(4, 8), BRUTE, wound_bonus = 10, sharpness = SHARP_EDGED, attacking_item = tool) diff --git a/code/modules/surgery/operations/_operation.dm b/code/modules/surgery/operations/_operation.dm index 12fe44087233..6efced823feb 100644 --- a/code/modules/surgery/operations/_operation.dm +++ b/code/modules/surgery/operations/_operation.dm @@ -204,7 +204,7 @@ 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."))) + to_chat(surgeon, examine_block(span_info("No available surgeries."))) return var/list/operations_info = list() @@ -213,7 +213,7 @@ 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, "
    ")]"))) + to_chat(surgeon, examine_block(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") @@ -751,7 +751,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) basemod *= mod_amt if(HAS_TRAIT(patient, TRAIT_SURGICALLY_ANALYZED)) basemod *= 0.8 - if(HAS_TRAIT(patient, TRAIT_ANALGESIA)) + if(!CAN_FEEL_PAIN(patient)) // NON-MODULE CHANGE basemod *= 0.8 return basemod @@ -939,8 +939,18 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) alt_msg = span_notice("You feel [you_feel] as you are operated on."), ) +// NON-MODULE CHANGE /// 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) +/datum/surgery_operation/proc/display_pain( + mob/living/target, + target_zone, + pain_message, + pain_amount = 0, + pain_type = BRUTE, + pain_overlay_severity = 1, + mechanical_surgery = FALSE, + surgery_moodlet, +) SHOULD_NOT_OVERRIDE(TRUE) PROTECTED_PROC(TRUE) diff --git a/code/modules/surgery/operations/operation_bioware.dm b/code/modules/surgery/operations/operation_bioware.dm index d772c1820345..e9cd295c893a 100644 --- a/code/modules/surgery/operations/operation_bioware.dm +++ b/code/modules/surgery/operations/operation_bioware.dm @@ -224,7 +224,7 @@ ) // NON-MODULE CHANGE display_pain( - target = target, + target = limb.owner, target_zone = BODY_ZONES_ALL, pain_message = "You regain feeling in your body! You feel energzed!", pain_amount = -0.5 * SURGERY_PAIN_HIGH, @@ -393,7 +393,7 @@ pain_amount = SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, ) - limb.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 60) + limb.owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 60) limb.owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) /datum/surgery_operation/limb/bioware/cortex_folding/mechanic @@ -461,7 +461,7 @@ pain_amount = SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, ) - limb.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 60) + limb.owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 60) limb.owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) /datum/surgery_operation/limb/bioware/cortex_imprint/mechanic diff --git a/code/modules/surgery/operations/operation_brainwash.dm b/code/modules/surgery/operations/operation_brainwash.dm index a03ad7283006..3d2d323ee05d 100644 --- a/code/modules/surgery/operations/operation_brainwash.dm +++ b/code/modules/surgery/operations/operation_brainwash.dm @@ -22,7 +22,7 @@ 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) + return image(/atom/movable/screen/alert/hypnosis) // NON-MODULE CHANGE /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) @@ -48,7 +48,7 @@ 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)) + if(HAS_MIND_TRAIT(organ.owner, TRAIT_MINDSHIELD)) // NON-MODULE CHANGE to_chat(surgeon, span_warning("[organ.owner] seems resistant to the brainwashing...")) return ..() @@ -85,7 +85,7 @@ pain_message = "Your head throbs with horrible pain!", pain_amount = SURGERY_PAIN_SEVERE, ) - organ.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 40) + organ.owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 40) /datum/surgery_operation/organ/brainwash/mechanic name = "reprogram" diff --git a/code/modules/surgery/operations/operation_cavity_implant.dm b/code/modules/surgery/operations/operation_cavity_implant.dm index e6d76db7b240..7a66ea3b5216 100644 --- a/code/modules/surgery/operations/operation_cavity_implant.dm +++ b/code/modules/surgery/operations/operation_cavity_implant.dm @@ -31,8 +31,8 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.zone, - pain_message = "You can feel pressure as your [limb.plaintext_zone] is being opened wide!" + target_zone = limb.body_zone, + pain_message = "You can feel pressure as your [limb.plaintext_zone] is being opened wide!", pain_amount = SURGERY_PAIN_MEDIUM, surgery_moodlet = /datum/mood_event/surgery, ) @@ -105,7 +105,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.zone, + target_zone = limb.body_zone, pain_message = "You can feel something being inserted into your [limb.plaintext_zone], it hurts like hell!", pain_amount = SURGERY_PAIN_MEDIUM, surgery_moodlet = /datum/mood_event/surgery, @@ -184,7 +184,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.zone, + target_zone = limb.body_zone, pain_message = "You feel a serious pain in your [limb.plaintext_zone]!", pain_amount = SURGERY_PAIN_MEDIUM, surgery_moodlet = /datum/mood_event/surgery, @@ -214,8 +214,8 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.zone, - pain_message = "You can feel [implant.name] being pulled out of you!" + target_zone = limb.body_zone, + pain_message = "You can feel [implant.name] being pulled out of you!", pain_amount = SURGERY_PAIN_TRIVIAL, ) surgeon.put_in_hands(implant) diff --git a/code/modules/surgery/operations/operation_core.dm b/code/modules/surgery/operations/operation_core.dm index 7eff5dc4099c..52543b9abbc5 100644 --- a/code/modules/surgery/operations/operation_core.dm +++ b/code/modules/surgery/operations/operation_core.dm @@ -12,7 +12,7 @@ required_biotype = NONE /datum/surgery_operation/basic/core_removal/get_default_radial_image() - return image(/mob/living/basic/slime) + return image(/mob/living/simple_animal/slime) // NON-MODULE CHANGE /datum/surgery_operation/basic/core_removal/all_required_strings() return list("operate on a deceased slime") + ..() @@ -45,15 +45,15 @@ if(patient.cores > 0) patient.cores-- display_results( - user, - target, - span_notice("You successfully extract a core from [target]. [patient.cores] core\s remaining."), - span_notice("[user] successfully extracts a core from [target]!"), - span_notice("[user] successfully extracts a core from [target]!"), + surgeon, + patient, + span_notice("You successfully extract a core from [patient]. [patient.cores] core\s remaining."), + span_notice("[surgeon] successfully extracts a core from [patient]!"), + span_notice("[surgeon] successfully extracts a core from [patient]!"), ) new patient.slime_type.core_type(patient.loc) patient.regenerate_icons() else - to_chat(user, span_warning("There aren't any cores left in [target]!")) + 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 index 7e8ef284c8a9..f568fa3489e4 100644 --- a/code/modules/surgery/operations/operation_debride.dm +++ b/code/modules/surgery/operations/operation_debride.dm @@ -18,26 +18,26 @@ failure_sound = 'sound/items/handling/surgery/organ1.ogg' /// How much infestation is removed per step (positive number) - var/infestation_removed = 4 + var/infection_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) + return image(/obj/item/reagent_containers/pill/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 + var/datum/wound/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) +/datum/surgery_operation/limb/debride/proc/get_progress(datum/wound/flesh/wound) if(wound?.infection <= 0) return null - var/estimated_remaining_steps = wound.infection / infestation_removed + var/estimated_remaining_steps = wound.infection / infection_removed var/progress_text switch(estimated_remaining_steps) @@ -70,8 +70,8 @@ /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 + var/datum/wound/flesh/wound = locate() in limb.wounds + wound?.infection -= infection_removed wound?.sanitization += sanitization_added display_results( surgeon, diff --git a/code/modules/surgery/operations/operation_dental.dm b/code/modules/surgery/operations/operation_dental.dm index 4974106d15e3..c3071ab0ac92 100644 --- a/code/modules/surgery/operations/operation_dental.dm +++ b/code/modules/surgery/operations/operation_dental.dm @@ -43,7 +43,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.zone, + target_zone = limb.body_zone, pain_message = "Something's being jammed into your mouth!", pain_amount = SURGERY_PAIN_TRIVIAL, ) @@ -98,7 +98,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.zone, + target_zone = limb.body_zone, pain_message = "You feel fingers poke around at your teeth.", ) diff --git a/code/modules/surgery/operations/operation_generic.dm b/code/modules/surgery/operations/operation_generic.dm index 5d21af6929da..11161387e0d9 100644 --- a/code/modules/surgery/operations/operation_generic.dm +++ b/code/modules/surgery/operations/operation_generic.dm @@ -64,7 +64,7 @@ if(!limb.can_bleed()) return - var/blood_name = limb.owner.get_bloodtype()?.get_blood_name() || "Blood" + var/blood_name = limb.owner.get_blood_type()?.name || "Blood" display_results( surgeon, limb.owner, diff --git a/code/modules/surgery/operations/operation_generic_mechanic.dm b/code/modules/surgery/operations/operation_generic_mechanic.dm index d4eba7622a22..fc35513400da 100644 --- a/code/modules/surgery/operations/operation_generic_mechanic.dm +++ b/code/modules/surgery/operations/operation_generic_mechanic.dm @@ -39,7 +39,7 @@ target = limb.owner, target_zone = limb.body_zone, pain_message = "You feel your [limb.plaintext_zone] grow numb as the shell is unscrewed.", - mechanical = TRUE, + mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/mechanical_incision/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -77,7 +77,7 @@ target = limb.owner, target_zone = limb.body_zone, pain_message = "The last faint pricks of tactile sensation fade from your [limb.plaintext_zone] as the hatch is opened.", - mechanical = TRUE, + mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/mechanical_open/on_success(obj/item/bodypart/limb) @@ -128,8 +128,8 @@ display_pain( target = limb.owner, target_zone = limb.body_zone, - pain_message = "You feel the faint pricks of sensation return as your [limb.plaintext_zone]'s shell is screwed in." - mechanical = TRUE, + pain_message = "You feel the faint pricks of sensation return as your [limb.plaintext_zone]'s shell is screwed in.", + mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/mechanical_close/on_success(obj/item/bodypart/limb) @@ -168,7 +168,7 @@ target = limb.owner, target_zone = limb.body_zone, pain_message = "You can feel a faint buzz in your [limb.plaintext_zone] as the electronics reboot.", - mechanical = TRUE, + mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/prepare_electronics/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -206,7 +206,7 @@ target = limb.owner, target_zone = limb.body_zone, pain_message = "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to loosen.", - mechanical = TRUE, + mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/mechanic_unwrench/on_success(obj/item/bodypart/limb) @@ -249,7 +249,7 @@ target = limb.owner, target_zone = limb.body_zone, pain_message = "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to tighten.", - mechanical = TRUE, + mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/mechanic_wrench/on_success(obj/item/bodypart/limb) diff --git a/code/modules/surgery/operations/operation_healing.dm b/code/modules/surgery/operations/operation_healing.dm index 9da7e9d29541..1b1d88d4c367 100644 --- a/code/modules/surgery/operations/operation_healing.dm +++ b/code/modules/surgery/operations/operation_healing.dm @@ -36,7 +36,7 @@ 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 + return patient.getBruteLoss() > 0 || patient.getFireLoss() > 0 /datum/surgery_operation/basic/tend_wounds/get_default_radial_image() return image(/obj/item/storage/medkit) @@ -61,7 +61,7 @@ "[OPERATION_BURN_MULTIPLIER]" = healing_multiplier, ) - if((can_heal & BRUTE_SURGERY) && patient.get_brute_loss() > 0) + if((can_heal & BRUTE_SURGERY) && patient.getBruteLoss() > 0) var/datum/radial_menu_choice/brute_healing = LAZYACCESS(cached_healing_options, "[BRUTE_SURGERY]") if(!brute_healing) brute_healing = new() @@ -76,7 +76,7 @@ "[OPERATION_BRUTE_MULTIPLIER]" = healing_multiplier, ) - if((can_heal & BURN_SURGERY) && patient.get_fire_loss() > 0) + if((can_heal & BURN_SURGERY) && patient.getFireLoss() > 0) var/datum/radial_menu_choice/burn_healing = LAZYACCESS(cached_healing_options, "[BURN_SURGERY]") if(!burn_healing) burn_healing = new() @@ -100,11 +100,11 @@ 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 + return patient.getBruteLoss() > 0 || patient.getFireLoss() > 0 else if(brute_heal) - return patient.get_brute_loss() > 0 + return patient.getBruteLoss() > 0 else if(burn_heal) - return patient.get_fire_loss() > 0 + return patient.getFireLoss() > 0 return FALSE /datum/surgery_operation/basic/tend_wounds/on_preop(mob/living/patient, mob/living/surgeon, tool, list/operation_args) @@ -139,17 +139,17 @@ /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)) + estimated_remaining_steps = max(0, (patient.getBruteLoss() / 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 + estimated_remaining_steps = max(estimated_remaining_steps, (patient.getFireLoss() / burn_healed)) // whichever is higher between brute or burn steps var/progress_text if(show_stats(surgeon, patient)) - 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()]" + if(brute_healed > 0 && patient.getBruteLoss() > 0) + progress_text += ". Remaining brute: [patient.getBruteLoss()]" + if(burn_healed > 0 && patient.getFireLoss() > 0) + progress_text += ". Remaining burn: [patient.getFireLoss()]" return progress_text switch(estimated_remaining_steps) @@ -204,8 +204,8 @@ 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) + brute_healed += round(patient.getBruteLoss() * brute_multiplier, DAMAGE_PRECISION) + burn_healed += round(patient.getFireLoss() * burn_multiplier, DAMAGE_PRECISION) patient.heal_bodypart_damage(brute_healed, burn_healed) @@ -236,8 +236,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) + brute_dealt += round(patient.getBruteLoss() * brute_multiplier, 0.1) + burn_dealt += round(patient.getFireLoss() * burn_multiplier, 0.1) patient.take_bodypart_damage(brute_dealt, burn_dealt, wound_bonus = CANT_WOUND) diff --git a/code/modules/surgery/operations/operation_implant_removal.dm b/code/modules/surgery/operations/operation_implant_removal.dm index af8c00eb3fe4..524cb710c9aa 100644 --- a/code/modules/surgery/operations/operation_implant_removal.dm +++ b/code/modules/surgery/operations/operation_implant_removal.dm @@ -29,7 +29,7 @@ if(LAZYLEN(patient.implants)) display_pain( target = patient, - target_zone = BODY_ZONE_TORSO, + target_zone = BODY_ZONE_CHEST, pain_message = "You feel a serious pain as [surgeon] digs around inside you!", pain_amount = SURGERY_PAIN_MEDIUM, ) diff --git a/code/modules/surgery/operations/operation_lipo.dm b/code/modules/surgery/operations/operation_lipo.dm index 8eec4656e5a6..87a38f8a3a3c 100644 --- a/code/modules/surgery/operations/operation_lipo.dm +++ b/code/modules/surgery/operations/operation_lipo.dm @@ -55,7 +55,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone + target_zone = limb.body_zone, pain_message = "You feel a stabbing in your [limb.plaintext_zone]!", pain_amount = SURGERY_PAIN_TRIVIAL, ) diff --git a/code/modules/surgery/operations/operation_organ_repair.dm b/code/modules/surgery/operations/operation_organ_repair.dm index f3427847710c..8e6a98ec5b6a 100644 --- a/code/modules/surgery/operations/operation_organ_repair.dm +++ b/code/modules/surgery/operations/operation_organ_repair.dm @@ -193,7 +193,7 @@ display_pain( target = organ.owner, target_zone = organ.zone, - pain_message = The pain in your abdomen intensifies!", + pain_message = "The pain in your abdomen intensifies!", pain_amount = SURGERY_PAIN_HIGH, ) @@ -266,7 +266,7 @@ /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" + var/blood_name = LOWER_TEXT(organ.owner.get_blood_type()?.name) || "blood" display_results( surgeon, organ.owner, @@ -434,8 +434,8 @@ /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) + var/deaf_change = 40 SECONDS - organ.deaf + organ.adjustEarDamage(0, deaf_change) display_results( surgeon, organ.owner, @@ -468,7 +468,7 @@ pain_message = "You feel a visceral stabbing pain right through your [parse_zone(organ.zone)], into your brain!", pain_amount = SURGERY_PAIN_MEDIUM, ) - organ.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 70) + organ.owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 70) else display_results( surgeon, @@ -558,7 +558,7 @@ pain_message = "You feel a visceral stabbing pain right through your [parse_zone(organ.zone)], into your brain!", pain_amount = SURGERY_PAIN_MEDIUM, ) - organ.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 70) + organ.owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 70) else display_results( diff --git a/code/modules/surgery/operations/operation_pacify.dm b/code/modules/surgery/operations/operation_pacify.dm index 472a2b74f680..e6b969b96e83 100644 --- a/code/modules/surgery/operations/operation_pacify.dm +++ b/code/modules/surgery/operations/operation_pacify.dm @@ -18,7 +18,7 @@ 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) + return image(/atom/movable/screen/alert/status_effect/high) // NON-MODULE CHANGE /datum/surgery_operation/organ/pacify/on_preop(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) display_results( diff --git a/code/modules/surgery/operations/operation_pump.dm b/code/modules/surgery/operations/operation_pump.dm index e765356b5d2e..4d7c91f38b9c 100644 --- a/code/modules/surgery/operations/operation_pump.dm +++ b/code/modules/surgery/operations/operation_pump.dm @@ -12,7 +12,7 @@ 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) + return image(/atom/movable/screen/alert/disgusted) // NON-MODULE CHANGE /datum/surgery_operation/organ/stomach_pump/all_required_strings() return ..() + list("the patient must not be husked") @@ -31,7 +31,7 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = BDOY_ZONES_CHEST, + target_zone = BODY_ZONE_CHEST, pain_message = "You feel a horrible sloshing feeling in your gut! You're going to be sick!", pain_amount = SURGERY_PAIN_LOW, ) diff --git a/code/modules/surgery/operations/operation_replace_limb.dm b/code/modules/surgery/operations/operation_replace_limb.dm index 93a6d944a284..e798782a8fbd 100644 --- a/code/modules/surgery/operations/operation_replace_limb.dm +++ b/code/modules/surgery/operations/operation_replace_limb.dm @@ -61,7 +61,7 @@ ) display_pain( target = limb.owner, - target_zone = BODY_ZONE_CHEST, + target_zone = list(limb.body_zone, BODY_ZONE_CHEST), pain_message = "You feel a horrible pain in your chest and [limb.plaintext_zone]!", pain_amount = SURGERY_PAIN_MEDIUM, ) @@ -94,8 +94,8 @@ ) display_pain( target = patient, - target_zone = BODY_ZONE_CHEST, + target_zone = list(limb.body_zone, BODY_ZONE_CHEST), pain_message = "Your [limb.plaintext_zone] comes awash with synthetic sensation!", - mechanical = TRUE, + mechanical_surgery = 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 index 6f52060ab838..cac3b4d60696 100644 --- a/code/modules/surgery/operations/operation_revival.dm +++ b/code/modules/surgery/operations/operation_revival.dm @@ -76,7 +76,7 @@ span_notice("[surgeon] sends a powerful shock to [patient]'s brain..."), ) patient.grab_ghost() - patient.adjust_oxy_loss(-50) + // NON-MODULE CHANGE if(patient.getOxyLoss() > 50) patient.setOxyLoss(50) if(patient.getToxLoss() > 50) @@ -99,12 +99,12 @@ 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) + patient.adjustOrganLoss(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 + patient.adjustOrganLoss(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( diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index b93958c9e1b8..ec9e562861e4 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -78,7 +78,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) if(organ_flags & ORGAN_EDIBLE) AddComponent(/datum/component/edible,\ initial_reagents = food_reagents, \ - foodtypes = foodtypes, \ + foodtypes = foodtype_flags, \ volume = reagent_vol, \ tastes = food_tastes, \ after_eat = CALLBACK(src, PROC_REF(OnEatFrom)), \ diff --git a/code/modules/unit_tests/surgeries.dm b/code/modules/unit_tests/surgeries.dm index d980839d9693..fe94aaf65a7c 100644 --- a/code/modules/unit_tests/surgeries.dm +++ b/code/modules/unit_tests/surgeries.dm @@ -133,7 +133,7 @@ // Test that tending wounds actually lowers damage 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.getBruteLoss() < 100, "Tending brute wounds didn't lower brute damage ([patient.get_brute_loss()])") + TEST_ASSERT(patient.getBruteLoss() < 100, "Tending brute wounds didn't lower brute damage ([patient.getBruteLoss()])") // Test that wearing clothing lowers heal amount var/mob/living/carbon/human/naked_patient = allocate(/mob/living/carbon/human/consistent) @@ -146,7 +146,7 @@ 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.getBruteLoss() < clothed_patient.get_brute_loss(), "Naked patient did not heal more from wounds tending than a clothed patient") + TEST_ASSERT(naked_patient.getBruteLoss() < clothed_patient.getBruteLoss(), "Naked patient did not heal more from wounds tending than a clothed patient") /// Tests items-as-prosthetic-limbs can apply /datum/unit_test/prosthetic_item diff --git a/maplestation.dme b/maplestation.dme index 5bd466ae7384..bbc5796b1af5 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -533,6 +533,7 @@ #include "code\_globalvars\lists\client.dm" #include "code\_globalvars\lists\color.dm" #include "code\_globalvars\lists\crafting.dm" +#include "code\_globalvars\lists\engineering.dm" #include "code\_globalvars\lists\flavor_misc.dm" #include "code\_globalvars\lists\icons.dm" #include "code\_globalvars\lists\keybindings.dm" @@ -1232,7 +1233,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\tackle.dm" @@ -1520,6 +1520,7 @@ #include "code\datums\elements\squish.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\temporary_atom.dm" #include "code\datums\elements\tenacious.dm" @@ -2989,7 +2990,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" @@ -3278,7 +3278,6 @@ #include "code\modules\antagonists\traitor\objectives\kill_pet.dm" #include "code\modules\antagonists\traitor\objectives\locate_weakpoint.dm" #include "code\modules\antagonists\traitor\objectives\sabotage_machinery.dm" -#include "code\modules\antagonists\traitor\objectives\sleeper_protocol.dm" #include "code\modules\antagonists\traitor\objectives\steal.dm" #include "code\modules\antagonists\traitor\objectives\abstract\target_player.dm" #include "code\modules\antagonists\traitor\objectives\final_objective\battlecruiser.dm" @@ -5856,54 +5855,9 @@ #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\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\internal_bleeding.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\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.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\dismemberment.dm" #include "code\modules\surgery\bodyparts\head.dm" @@ -5919,6 +5873,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/maplestation_modules/code/modules/antagonists/advanced_ling/changeling_rebalancing.dm b/maplestation_modules/code/modules/antagonists/advanced_ling/changeling_rebalancing.dm index deffbc8257c5..d7946087ed1e 100644 --- a/maplestation_modules/code/modules/antagonists/advanced_ling/changeling_rebalancing.dm +++ b/maplestation_modules/code/modules/antagonists/advanced_ling/changeling_rebalancing.dm @@ -15,11 +15,11 @@ // Disables spread infestation. /datum/action/changeling/spiders - dna_cost = -1 + dna_cost = CHANGELING_POWER_UNOBTAINABLE // Disables transform sting. /datum/action/changeling/sting/transformation - dna_cost = -1 + dna_cost = CHANGELING_POWER_UNOBTAINABLE /// Extension of attempt_absorb, for changeling cannot absorb their own spawn /datum/action/changeling/absorb_dna/can_sting(mob/living/carbon/user) diff --git a/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm b/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm index 099a5633ff23..bacc6d9ffe74 100644 --- a/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm +++ b/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm @@ -15,82 +15,66 @@ desc = "An dangerous experimental surgery that can potentially neuter a changeling's hostile abilities \ - but massively harms the internal organs of non-changelings." id = "surgery_neuter_ling" - surgery = /datum/surgery/advanced/lobotomy + surgery = /datum/surgery_operation/limb/neuter_ling research_icon_state = "surgery_chest" -// The surgery itself. -/datum/surgery/advanced/neuter_ling - name = "Neuter Changeling" - desc = "An experimental surgery designed to neuter the abilities of a changeling by crippling the headslug within. \ - Can only be done on unconscious patients who have had their head removed prior. \ - If the patient was not a changeling, causes massive internal organ damage." - steps = list( - /datum/surgery_step/incise, - /datum/surgery_step/retract_skin, - /datum/surgery_step/saw, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/drill, // yes drill! - /datum/surgery_step/neuter_ling, - /datum/surgery_step/clamp_bleeders, - /datum/surgery_step/close, - ) - - target_mobtypes = list(/mob/living/carbon/human) - possible_locs = list(BODY_ZONE_CHEST) - requires_bodypart_type = 0 - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_IGNORE_CLOTHES - -/datum/surgery/advanced/neuter_ling/can_start(mob/user, mob/living/carbon/target) - . = ..() - if(!.) - return - if(target.stat <= UNCONSCIOUS) - return FALSE - if(target.get_bodypart(BODY_ZONE_HEAD)) - return FALSE - -// The surgical step behind the surgery. -/datum/surgery_step/neuter_ling - name = "neuter headslug" - time = 10 SECONDS +/datum/surgery_operation/limb/neuter_ling + name = "neuter changeling" + desc = "Attempt to neuter a changeling's headslug." + rnd_name = "Gymnotripsy (Neuter Changeling)" + rnd_desc = "An experimental surgery that attempts to neuter the headslug of a changeling by operating within their chest cavity. \ + Successful surgery will remove the changeling's abilities, but failed surgery will only enrage it further. \ + If the target is not a changeling, this surgery will cause massive internal organ damage." + time = 15 SECONDS + operation_flags = OPERATION_ALWAYS_FAILABLE | OPERATION_AFFECTS_MOOD | OPERATION_IGNORE_CLOTHES | OPERATION_NOTABLE | OPERATION_MORBID | OPERATION_LOCKED implements = list( - TOOL_RETRACTOR = 70, // Even a retractor is not too good at it, better get sterile - TOOL_HEMOSTAT = 60, - TOOL_SCREWDRIVER = 20, - TOOL_WIRECUTTER = 15 - ) - -/// MELBERT TODO: This acts a bit strangely the way it's called in the surgery chain. -/datum/surgery_step/neuter_ling/try_op(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail) - var/obj/item/other_item = user.get_inactive_held_item() - if(!other_item?.get_sharpness()) - to_chat(user, span_warning("You need a sharp object in your inactive hand to do this step!")) + TOOL_SCALPEL = 1.33, + TOOL_RETRACTOR = 1.33, + TOOL_HEMOSTAT = 1.5, + TOOL_SCREWDRIVER = 5.0, + TOOL_WIRECUTTER = 6.66, + ) + all_surgery_states_required = SURGERY_SKIN_OPEN | SURGERY_VESSELS_CLAMPED | SURGERY_ORGANS_CUT | SURGERY_BONE_DRILLED + +/datum/surgery_operation/limb/neuter_ling/snowflake_check_availability(obj/item/bodypart/limb, mob/living/surgeon, tool, operated_zone) + var/obj/item/offhand = surgeon.get_inactive_held_item() + return !!offhand?.get_sharpness() + +/datum/surgery_operation/limb/state_check(obj/item/bodypart/limb) + if(limb.body_zone != BODY_ZONE_CHEST) return FALSE - . = ..() - -/datum/surgery_step/neuter_ling/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, - span_notice("You begin operate within [target]'s chest, looking for a changeling headslug..."), - span_notice("[user] begins to operate within [target]'s chest, looking for a changeling headslug."), - span_notice("[user] begins to work within [target]'s chest.")) - -/// Successfully neutering the changeling removes the changeling datum and gives them the neutered changelings datum. -/datum/surgery_step/neuter_ling/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) + if(limb.owner.stat < UNCONSCIOUS) + return FALSE + return TRUE + +/datum/surgery_operation/limb/neuter_ling/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You begin operate within [limb.owner]'s [limb.plaintext_zone], looking for a changeling headslug..."), + span_notice("[surgeon] begins to operate within [limb.owner]'s [limb.plaintext_zone], looking for a changeling headslug."), + span_notice("[surgeon] begins to work within [limb.owner]'s [limb.plaintext_zone]."), + ) + +/datum/surgery_operation/limb/neuter_ling/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + var/mob/living/carbon/target = limb.owner if(is_neutered_changeling(target)) - to_chat(user, span_notice("The changeling headslug inside has already been neutered!")) - return TRUE + to_chat(surgeon, span_notice("The changeling headslug inside has already been neutered!")) + return if(is_fallen_changeling(target)) - to_chat(user, span_notice("The changeling headslug inside is dead!")) - return TRUE + to_chat(surgeon, span_notice("The changeling headslug inside is dead!")) + return var/datum/antagonist/changeling/old_ling_datum = is_any_changeling(target) if(old_ling_datum) // It was a ling, good job bucko! The changeling is neutered. - display_results(user, target, - span_notice("You locate and succeed in neutering the headslug within [target]'s chest."), - span_notice("[user] successfully locates and neuters the headslug within [target]'s chest!"), - span_notice("[user] finishes working within [target]'s chest.")) - + display_results( + surgeon, + target, + span_notice("You locate and succeed in neutering the headslug within [target]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully locates and neuters the headslug within [target]'s [limb.plaintext_zone]!"), + span_notice("[surgeon] finishes working within [target]'s [limb.plaintext_zone]."), + ) var/ling_id = old_ling_datum.changeling_id target.mind.remove_antag_datum(/datum/antagonist/changeling) @@ -102,7 +86,7 @@ var/revival_message_end = "and limply" if(target.get_organ_slot(ORGAN_SLOT_HEART)) revival_message_end = "as their heart beats once more" - if(target.heal_and_revive((target.stat == DEAD ? 50 : 75), span_danger("[target] begins to write unnaturally [revival_message_end], their body struggling to regenerate!"))) + if(target.heal_and_revive((target.stat == DEAD ? 50 : 75), span_danger("[target] begins to writhe unnaturally [revival_message_end], their body struggling to regenerate!"))) new_ling_datum.chem_charges += 15 var/datum/action/changeling/regenerate/regenerate_action = locate() in target.actions regenerate_action?.sting_action(target) // Regenerate ourselves after revival, for heads / organs / whatever @@ -111,49 +95,46 @@ target.cause_pain(BODY_ZONE_HEAD, 40) target.cause_pain(BODY_ZONES_LIMBS, 25) to_chat(target, span_big(span_green("Our headslug has been neutered! Our powers are lost... The hive screams in agony before going silent."))) - - message_admins("[ADMIN_LOOKUPFLW(user)] neutered [ADMIN_LOOKUPFLW(target)]'s changeling abilities via surgery.") - target.log_message("has has their changeling abilities neutered by [key_name(user)] via surgery", LOG_ATTACK) - log_game("[key_name(user)] neutered [key_name(target)]'s changeling abilities via surgery.") + message_admins("[ADMIN_LOOKUPFLW(surgeon)] neutered [ADMIN_LOOKUPFLW(target)]'s changeling abilities via surgery.") + target.log_message("has has their changeling abilities neutered by [key_name(surgeon)] via surgery", LOG_ATTACK) + log_game("[key_name(surgeon)] neutered [key_name(target)]'s changeling abilities via surgery.") else // It wasn't a ling, idiot! Now you have a headless, all-chest-organs-destroyed body of an innocent person to fix up! - display_results(user, target, - span_danger("You succeed in operating within [target]'s chest...but find no headslug, causing heavy internal damage!"), - span_danger("[user] finishes operating within [target]'s chest...but finds no headslug, causing heavy internal damage!"), - span_notice("[user] finishes working within [target]'s chest."), TRUE) - + display_results( + surgeon, + target, + span_danger("You succeed in operating within [target]'s [limb.plaintext_zone]...but find no headslug, causing heavy internal damage!"), + span_danger("[surgeon] finishes operating within [target]'s [limb.plaintext_zone]...but finds no headslug, causing heavy internal damage!"), + span_notice("[surgeon] finishes working within [target]'s [limb.plaintext_zone]."), + TRUE, + ) target.cause_pain(BODY_ZONE_CHEST, 60) target.cause_pain(BODY_ZONES_LIMBS, 25) - for(var/obj/item/organ/stabbed_organ as anything in target.organs) + for(var/obj/item/organ/stabbed_organ as anything in limb) if(stabbed_organ.organ_flags & ORGAN_EXTERNAL) continue - if(deprecise_zone(stabbed_organ.zone) != BODY_ZONE_CHEST) - continue stabbed_organ.apply_organ_damage(105) // Breaks all normal organs, severely damages cyber organs + message_admins("[ADMIN_LOOKUPFLW(surgeon)] attempted to changeling neuter a non-changeling, [ADMIN_LOOKUPFLW(target)] via surgery.") - message_admins("[ADMIN_LOOKUPFLW(user)] attempted to changeling neuter a non-changeling, [ADMIN_LOOKUPFLW(target)] via surgery.") - - return ..() - -/// Failing to neuter the changeling gives them full chemicals. -/datum/surgery_step/neuter_ling/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) +/datum/surgery_operation/limb/neuter_ling/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + var/mob/living/carbon/target = limb.owner // Failure means they couldn't find a headslug, but there may be one in there... - display_results(user, target, + display_results( + surgeon, + target, span_danger("You fail to locate a headslug within [target], causing internal damage!"), - span_danger("[user] fails to locate a headslug within [target], causing internal damage!"), - span_notice("[user] fails to locate a headslug!"), TRUE) - + span_danger("[surgeon] fails to locate a headslug within [target], causing internal damage!"), + span_notice("[surgeon] fails to locate a headslug!"), + TRUE, + ) // ...And if there is, the changeling gets pissed var/datum/antagonist/changeling/our_changeling = is_any_changeling(target) if(our_changeling) - to_chat(target, span_changeling("[user] has attempted and failed to neuter our changeling abilities! We feel invigorated, we must break free!")) + to_chat(target, span_changeling("[surgeon] has attempted and failed to neuter our changeling abilities! We feel invigorated, we must break free!")) target.do_jitter_animation(50) - our_changeling.chem_charges = our_changeling.total_chem_storage - + our_changeling.adjust_chemicals(INFINITY) // Causes organ damage nonetheless - for(var/obj/item/organ/stabbed_organ as anything in target.organs) + for(var/obj/item/organ/stabbed_organ as anything in limb) if(stabbed_organ.organ_flags & ORGAN_EXTERNAL) continue - if(deprecise_zone(stabbed_organ.zone) != BODY_ZONE_CHEST) - continue stabbed_organ.apply_organ_damage(25) From 58b2b5efeae41b1b6fd40948285448ecefdf5bb1 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 18:45:34 -0600 Subject: [PATCH 03/35] Fixes --- code/datums/wounds/pierce.dm | 2 +- .../machinery/computer/operating_computer.dm | 4 +- code/game/objects/items/robot/items/tools.dm | 34 ++++++++++---- code/game/objects/structures/tables_racks.dm | 2 - .../virtual_domain/domains/heretic_hunt.dm | 2 +- .../mob/living/basic/bots/medbot/medbot.dm | 1 - code/modules/mob/living/carbon/carbon.dm | 2 +- code/modules/surgery/bodyparts/head.dm | 3 ++ .../species_parts/ethereal_bodyparts.dm | 1 + .../species_parts/lizard_bodyparts.dm | 1 + .../bodyparts/species_parts/misc_bodyparts.dm | 5 ++ .../bodyparts/species_parts/moth_bodyparts.dm | 1 + code/modules/surgery/internal_bleeding.dm | 6 +-- code/modules/surgery/operations/_operation.dm | 10 ++-- .../operations/operation_amputation.dm | 10 ++-- .../surgery/operations/operation_asthma.dm | 4 +- .../surgery/operations/operation_bioware.dm | 4 +- .../operations/operation_bone_repair.dm | 2 +- .../surgery/operations/operation_brainwash.dm | 12 ++--- .../operations/operation_cavity_implant.dm | 12 ++--- .../surgery/operations/operation_debride.dm | 8 ++-- .../surgery/operations/operation_dental.dm | 10 ++-- .../surgery/operations/operation_generic.dm | 44 +++++++++--------- .../operations/operation_generic_basic.dm | 26 +++++------ .../operations/operation_generic_mechanic.dm | 14 +++--- .../surgery/operations/operation_healing.dm | 10 ++-- .../operations/operation_implant_removal.dm | 2 +- .../surgery/operations/operation_lipo.dm | 10 ++-- .../surgery/operations/operation_lobotomy.dm | 6 +-- .../operations/operation_organ_manip.dm | 8 ++-- .../operations/operation_organ_repair.dm | 46 +++++++++---------- .../surgery/operations/operation_pacify.dm | 6 +-- .../operations/operation_plastic_surgery.dm | 10 ++-- .../surgery/operations/operation_puncture.dm | 4 +- .../surgery/operations/operation_virus.dm | 4 +- code/modules/surgery/surgery_tools.dm | 2 +- 36 files changed, 178 insertions(+), 150 deletions(-) diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index 5604bb6feb7a..30f2a0810a1d 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -184,7 +184,7 @@ abstract = TRUE required_limb_biostate = BIO_FLESH - required_wounding_type = WOUND_PIERCE + required_wounding_types = list(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 183383ee323f..541963583cb9 100644 --- a/code/game/machinery/computer/operating_computer.dm +++ b/code/game/machinery/computer/operating_computer.dm @@ -78,7 +78,7 @@ 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) + playsound(src, 'sound/misc/compiler-stage2.ogg', 50, FALSE, SILENCED_SOUND_EXTRARANGE) balloon_alert(user, "surgeries loaded") return ITEM_INTERACT_SUCCESS @@ -137,7 +137,7 @@ 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) + if(get_dist(user, src) > 2 || user.incapacitated()) return UI_DISABLED return UI_INTERACTIVE diff --git a/code/game/objects/items/robot/items/tools.dm b/code/game/objects/items/robot/items/tools.dm index 01ef8abf03bb..861e5b64a812 100644 --- a/code/game/objects/items/robot/items/tools.dm +++ b/code/game/objects/items/robot/items/tools.dm @@ -182,6 +182,21 @@ return ..() +/** + * Sets the new internal tool to be used + * Arguments + * + * * obj/item/ref - typepath for the new internal omnitool + */ +/obj/item/borg/cyborg_omnitool/proc/set_internal_tool(obj/item/tool) + SHOULD_NOT_OVERRIDE(TRUE) + + for(var/obj/item/internal_tool as anything in omni_toolkit) + if(internal_tool == tool) + reference = internal_tool + tool_behaviour = initial(internal_tool.tool_behaviour) + break + /obj/item/borg/cyborg_omnitool/get_all_tool_behaviours() . = list() for(var/obj/item/tool as anything in omni_toolkit) @@ -216,20 +231,21 @@ /obj/item/borg/cyborg_omnitool/attack_self(mob/user) //build the radial menu options var/list/radial_menu_options = list() + var/list/tool_map = list() for(var/obj/item as anything in omni_toolkit) - radial_menu_options[initial(item.name)] = image(icon = initial(item.icon), icon_state = initial(item.icon_state)) + var/tool_name = initial(item.name) + radial_menu_options[tool_name] = image(icon = initial(item.icon), icon_state = initial(item.icon_state)) + tool_map[tool_name] = item //assign the new tool behaviour - var/toolkit_menu = show_radial_menu(user, src, radial_menu_options, require_near = TRUE, tooltips = TRUE) + var/internal_tool_name = show_radial_menu(user, src, radial_menu_options, require_near = TRUE, tooltips = TRUE) + if(!internal_tool_name) + return //set the reference & update icons - for(var/obj/item/tool as anything in omni_toolkit) - if(initial(tool.name) == toolkit_menu) - reference = tool - tool_behaviour = initial(tool.tool_behaviour) - update_appearance(UPDATE_ICON_STATE) - playsound(src, 'sound/items/change_jaws.ogg', 50, TRUE) - break + set_internal_tool(tool_map[internal_tool_name]) + update_appearance(UPDATE_ICON_STATE) + playsound(src, 'sound/items/tools/change_jaws.ogg', 50, TRUE) /obj/item/borg/cyborg_omnitool/update_icon_state() if (reference) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 6b5427ce918f..caf71d996e49 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -921,8 +921,6 @@ COMSIG_LIVING_SURGERY_FINISHED, COMSIG_LIVING_UPDATING_SURGERY_STATE, )) - if (patient.external && patient.external == air_tank) - patient.close_externals() patient = new_patient update_appearance() diff --git a/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm b/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm index 43225d18e38b..2abd96e94c18 100644 --- a/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm +++ b/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm @@ -182,7 +182,7 @@ name = "engineer" death_spawner = /obj/effect/mob_spawn/corpse/human/engineer weapon = /obj/item/weldingtool - attack_sound = 'sound/items/tools/welder.ogg' + attack_sound = 'sound/items/welder.ogg' melee_damage_type = BURN damage_coeff = list(BRUTE = 1, BURN = 0.9, TOX = 1, STAMINA = 1, OXY = 1) diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm index 10a596ee67df..edde350dc1ec 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot.dm @@ -221,7 +221,6 @@ . = ..() if(. || !isliving(ui.user) || !(bot_access_flags & BOT_CONTROL_PANEL_OPEN) && !(ui.user.has_unlimited_silicon_privilege)) return - var/mob/living/our_user = ui.user switch(action) if("heal_threshold") var/adjust_num = round(text2num(params["threshold"])) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 65e5c6f958a3..0eb81e1f1ae3 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1140,7 +1140,7 @@ 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)) - admin_ticket_log("[key_name_admin(usr)] has replaced [src]'s [part.type] with [new_bp.type]") + admin_ticket_log("[key_name_admin(usr)] has replaced [src]'s [BP.type] with [new_bp.type]") qdel(BP) else to_chat(usr, "Failed to replace bodypart! They might be incompatible.") diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 2bd2f6cfdc06..2f687583521b 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -69,6 +69,9 @@ ///Current lipstick trait, if any (such as TRAIT_KISS_OF_DEATH) var/stored_lipstick_trait + /// How many teeth the head's species has, humans have 32 so that's the default. Used for a limit to dental pill implants. + var/teeth_count = 32 + /// Offset to apply to equipment worn on the ears var/datum/worn_feature_offset/worn_ears_offset /// Offset to apply to equipment worn on the eyes diff --git a/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm index 20e4b5866079..7aa15942b804 100644 --- a/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm @@ -103,3 +103,4 @@ icon_state = "lustrous_head" limb_id = SPECIES_ETHEREAL_LUSTROUS head_flags = NONE + teeth_count = 0 diff --git a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm index afe8ea9335f0..dfbd66e7eae8 100644 --- a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm @@ -3,6 +3,7 @@ limb_id = SPECIES_LIZARD is_dimorphic = FALSE head_flags = HEAD_LIPS|HEAD_EYESPRITES|HEAD_EYECOLOR|HEAD_EYEHOLES|HEAD_DEBRAIN + teeth_count = 72 /obj/item/bodypart/chest/lizard icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi' diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index 16e85bbb9102..ddc0cb618482 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -51,6 +51,7 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE head_flags = NONE + teeth_count = 0 /obj/item/bodypart/chest/abductor limb_id = SPECIES_ABDUCTOR @@ -216,6 +217,7 @@ is_dimorphic = TRUE burn_modifier = 1.25 head_flags = HEAD_EYESPRITES|HEAD_EYECOLOR|HEAD_EYEHOLES|HEAD_DEBRAIN + teeth_count = 0 /obj/item/bodypart/chest/pod limb_id = SPECIES_PODPERSON @@ -258,6 +260,7 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE head_flags = HEAD_EYESPRITES|HEAD_EYEHOLES|HEAD_DEBRAIN + teeth_count = 0 /obj/item/bodypart/chest/fly limb_id = SPECIES_FLYPERSON @@ -375,6 +378,7 @@ is_dimorphic = TRUE burn_modifier = 1.25 head_flags = NONE + teeth_count = 0 /obj/item/bodypart/chest/mushroom limb_id = SPECIES_MUSHROOM @@ -444,6 +448,7 @@ should_draw_greyscale = FALSE dmg_overlay_type = null head_flags = NONE + teeth_count = 0 /obj/item/bodypart/head/golem/Initialize(mapload) worn_ears_offset = new( diff --git a/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm index e6af4080fc76..e73d7f6ffea2 100644 --- a/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm @@ -6,6 +6,7 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE head_flags = HEAD_LIPS|HEAD_EYESPRITES|HEAD_EYEHOLES|HEAD_DEBRAIN //what the fuck, moths have lips? + teeth_count = 0 /obj/item/bodypart/chest/moth icon = 'icons/mob/human/species/moth/bodyparts.dmi' diff --git a/code/modules/surgery/internal_bleeding.dm b/code/modules/surgery/internal_bleeding.dm index 45124db9e6f3..b000a3e99b2a 100644 --- a/code/modules/surgery/internal_bleeding.dm +++ b/code/modules/surgery/internal_bleeding.dm @@ -16,7 +16,7 @@ all_surgery_states_required = SURGERY_SKIN_OPEN /datum/surgery_operation/limb/internal_bleeding/state_check(obj/item/bodypart/limb) - for(var/datum/wound/bleed_internal/wound in operating_on.wounds) + for(var/datum/wound/bleed_internal/wound in limb.wounds) if(wound.severity >= WOUND_SEVERITY_TRIVIAL) return TRUE return FALSE @@ -57,7 +57,7 @@ span_notice("[surgeon] successfully repairs some of the arteries within [limb.owner]'s [limb.plaintext_zone] with [tool]!"), span_notice("[surgeon] successfully repairs some of the arteries within [limb.owner]'s [limb.plaintext_zone]!"), ) - limb.receive_damage(3, BRUTE, wound_bonus = CANT_WOUND, attacking_item = tool) + limb.receive_damage(3, BRUTE, wound_bonus = CANT_WOUND, damage_source = tool) /datum/surgery_operation/limb/internal_bleeding/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args, fail_prob = 0) var/datum/wound/bleed_internal/target_wound = locate() in limb.wounds @@ -69,4 +69,4 @@ span_warning("[surgeon] tears some of the arteries within [limb.owner]'s [limb.plaintext_zone] with [tool]!"), span_warning("[surgeon] tears some of the arteries within [limb.owner]'s [limb.plaintext_zone]!"), ) - limb.receive_damage(rand(4, 8), BRUTE, wound_bonus = 10, sharpness = SHARP_EDGED, attacking_item = tool) + limb.receive_damage(rand(4, 8), BRUTE, wound_bonus = 10, sharpness = SHARP_EDGED, damage_source = tool) diff --git a/code/modules/surgery/operations/_operation.dm b/code/modules/surgery/operations/_operation.dm index 6efced823feb..e23427a0831f 100644 --- a/code/modules/surgery/operations/_operation.dm +++ b/code/modules/surgery/operations/_operation.dm @@ -137,7 +137,7 @@ 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) + playsound(src, istype(tool, /obj/item/stack/medical/suture) ? 'maplestation_modules/sound/items/snip.ogg' : 'sound/surgery/cautery1.ogg', 50, TRUE) if(!do_after( user = src, delay = /datum/surgery_operation/limb/close_skin::time * 2 * tool.toolspeed, @@ -153,7 +153,7 @@ 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) + playsound(src, istype(tool, /obj/item/stack/medical/suture) ? 'maplestation_modules/sound/items/snip.ogg' : 'sound/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 @@ -312,7 +312,9 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) unlocked = list() locked = list() - for(var/operation_type in valid_subtypesof(/datum/surgery_operation)) + for(var/datum/surgery_operation/operation_type as anything in subtypesof(/datum/surgery_operation)) // future todo : make this use valid_subtypesof + if(operation_type::abstract_type == operation_type) + continue var/datum/surgery_operation/operation = new operation_type() if(isnull(operation.name)) stack_trace("Surgery operation '[operation_type]' is missing a name!") @@ -377,7 +379,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) * 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 + var/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 diff --git a/code/modules/surgery/operations/operation_amputation.dm b/code/modules/surgery/operations/operation_amputation.dm index 76fb2a9615c0..b6b136428c36 100644 --- a/code/modules/surgery/operations/operation_amputation.dm +++ b/code/modules/surgery/operations/operation_amputation.dm @@ -16,10 +16,10 @@ ) 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', + /obj/item/circular_saw = 'sound/surgery/saw.ogg', + /obj/item = 'sound/surgery/scalpel1.ogg', ) - success_sound = 'sound/items/handling/surgery/organ2.ogg' + success_sound = 'sound/surgery/organ2.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED|SURGERY_VESSELS_CLAMPED /datum/surgery_operation/limb/amputate/get_recommended_tool() @@ -89,7 +89,7 @@ TOOL_SAW = 2, ) time = 2 SECONDS //WAIT I NEED THAT!! - preop_sound = 'sound/items/tools/ratchet.ogg' + preop_sound = 'sound/items/ratchet.ogg' preop_sound = 'sound/machines/airlock/doorclick.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN @@ -120,7 +120,7 @@ ) time = 3 SECONDS preop_sound = list( - /obj/item/circular_saw = 'sound/items/handling/surgery/saw.ogg', + /obj/item/circular_saw = 'sound/surgery/saw.ogg', /obj/item = 'sound/items/weapons/bladeslice.ogg', ) success_sound = 'sound/items/handling/materials/wood_drop.ogg' diff --git a/code/modules/surgery/operations/operation_asthma.dm b/code/modules/surgery/operations/operation_asthma.dm index 427832b9659e..2d48585786b1 100644 --- a/code/modules/surgery/operations/operation_asthma.dm +++ b/code/modules/surgery/operations/operation_asthma.dm @@ -10,8 +10,8 @@ TOOL_WIRECUTTER = 2.25, ) time = 8 SECONDS - preop_sound = 'sound/items/handling/surgery/retractor1.ogg' - success_sound = 'sound/items/handling/surgery/retractor2.ogg' + preop_sound = 'sound/surgery/retractor1.ogg' + success_sound = 'sound/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. diff --git a/code/modules/surgery/operations/operation_bioware.dm b/code/modules/surgery/operations/operation_bioware.dm index e9cd295c893a..af61189bdbfc 100644 --- a/code/modules/surgery/operations/operation_bioware.dm +++ b/code/modules/surgery/operations/operation_bioware.dm @@ -126,7 +126,7 @@ ) // NON-MODULE CHANGE display_pain( - target = target, + target = limb.owner, target_zone = BODY_ZONES_ALL, pain_message = "You can feel your heartbeat's powerful pulses ripple through your body!", surgery_moodlet = /datum/mood_event/surgery/major, @@ -253,7 +253,7 @@ ) // NON-MODULE CHANGE display_pain( - target = target, + target = limb.owner, target_zone = BODY_ZONES_LIMBS, pain_message = "Your limbs burn with severe pain!", pain_amount = SURGERY_PAIN_MEDIUM, diff --git a/code/modules/surgery/operations/operation_bone_repair.dm b/code/modules/surgery/operations/operation_bone_repair.dm index 023be2f5153e..2dd4a003d575 100644 --- a/code/modules/surgery/operations/operation_bone_repair.dm +++ b/code/modules/surgery/operations/operation_bone_repair.dm @@ -163,7 +163,7 @@ TOOL_SCREWDRIVER = 2.5, ) time = 2.4 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + preop_sound = 'sound/surgery/hemostat1.ogg' /datum/surgery_operation/limb/prepare_cranium_repair/get_default_radial_image() return image(/obj/item/hemostat) diff --git a/code/modules/surgery/operations/operation_brainwash.dm b/code/modules/surgery/operations/operation_brainwash.dm index 3d2d323ee05d..df1979702617 100644 --- a/code/modules/surgery/operations/operation_brainwash.dm +++ b/code/modules/surgery/operations/operation_brainwash.dm @@ -13,9 +13,9 @@ /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' + preop_sound = 'sound/surgery/hemostat1.ogg' + success_sound = 'sound/surgery/hemostat1.ogg' + failure_sound = 'sound/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 @@ -107,9 +107,9 @@ /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' + preop_sound = 'sound/surgery/hemostat1.ogg' + success_sound = 'sound/surgery/hemostat1.ogg' + failure_sound = 'sound/surgery/organ2.ogg' var/list/possible_objectives = list( "You love the Syndicate.", diff --git a/code/modules/surgery/operations/operation_cavity_implant.dm b/code/modules/surgery/operations/operation_cavity_implant.dm index 7a66ea3b5216..3adeb36a5fa3 100644 --- a/code/modules/surgery/operations/operation_cavity_implant.dm +++ b/code/modules/surgery/operations/operation_cavity_implant.dm @@ -6,8 +6,8 @@ TOOL_CROWBAR = 1.5, ) time = 4.8 SECONDS - preop_sound = 'sound/items/handling/surgery/retractor1.ogg' - success_sound = 'sound/items/handling/surgery/retractor2.ogg' + preop_sound = 'sound/surgery/retractor1.ogg' + success_sound = 'sound/surgery/retractor2.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT any_surgery_states_blocked = SURGERY_CAVITY_WIDENED @@ -49,8 +49,8 @@ /obj/item = 1, ) time = 3.2 SECONDS - preop_sound = 'sound/items/handling/surgery/organ1.ogg' - success_sound = 'sound/items/handling/surgery/organ2.ogg' + preop_sound = 'sound/surgery/organ1.ogg' + success_sound = 'sound/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 @@ -144,8 +144,8 @@ ) time = 3.2 SECONDS - preop_sound = 'sound/items/handling/surgery/organ1.ogg' - success_sound = 'sound/items/handling/surgery/organ2.ogg' + preop_sound = 'sound/surgery/organ1.ogg' + success_sound = 'sound/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() diff --git a/code/modules/surgery/operations/operation_debride.dm b/code/modules/surgery/operations/operation_debride.dm index f568fa3489e4..f28297baae3f 100644 --- a/code/modules/surgery/operations/operation_debride.dm +++ b/code/modules/surgery/operations/operation_debride.dm @@ -11,11 +11,11 @@ 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', + TOOL_SCALPEL = 'sound/surgery/scalpel1.ogg', + TOOL_HEMOSTAT = 'sound/surgery/hemostat1.ogg', ) - success_sound = 'sound/items/handling/surgery/retractor2.ogg' - failure_sound = 'sound/items/handling/surgery/organ1.ogg' + success_sound = 'sound/surgery/retractor2.ogg' + failure_sound = 'sound/surgery/organ1.ogg' /// How much infestation is removed per step (positive number) var/infection_removed = 4 diff --git a/code/modules/surgery/operations/operation_dental.dm b/code/modules/surgery/operations/operation_dental.dm index c3071ab0ac92..29d8043bf635 100644 --- a/code/modules/surgery/operations/operation_dental.dm +++ b/code/modules/surgery/operations/operation_dental.dm @@ -2,7 +2,7 @@ name = "add dental implant" desc = "Implant a pill into a patient's teeth." implements = list( - /obj/item/reagent_containers/applicator/pill = 1, + /obj/item/reagent_containers/pill = 1, ) time = 1.6 SECONDS all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_DRILLED @@ -26,7 +26,7 @@ if(teeth_receptangle.teeth_count <= 0) return FALSE var/count = 0 - for(var/obj/item/reagent_containers/applicator/pill/dental in limb) + for(var/obj/item/reagent_containers/pill/dental in limb) count++ if(count >= teeth_receptangle.teeth_count) return FALSE @@ -76,7 +76,7 @@ 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) + return image(/obj/item/reagent_containers/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 @@ -104,7 +104,7 @@ /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) + for(var/obj/item/reagent_containers/pill/dental in limb) pills += dental if(!length(pills)) display_results( @@ -116,7 +116,7 @@ ) return - var/obj/item/reagent_containers/applicator/pill/yoinked = pick(pills) + var/obj/item/reagent_containers/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) diff --git a/code/modules/surgery/operations/operation_generic.dm b/code/modules/surgery/operations/operation_generic.dm index 11161387e0d9..075ec3798a31 100644 --- a/code/modules/surgery/operations/operation_generic.dm +++ b/code/modules/surgery/operations/operation_generic.dm @@ -17,8 +17,8 @@ /obj/item = 3.33, ) time = 1.6 SECONDS - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/scalpel2.ogg' + preop_sound = 'sound/surgery/scalpel1.ogg' + success_sound = 'sound/surgery/scalpel2.ogg' operation_flags = OPERATION_AFFECTS_MOOD any_surgery_states_blocked = ALL_SURGERY_SKIN_STATES /// We can't cut mobs with this biostate @@ -110,8 +110,8 @@ /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' + preop_sound = 'sound/surgery/retractor1.ogg' + success_sound = 'sound/surgery/retractor2.ogg' all_surgery_states_required = SURGERY_SKIN_CUT /datum/surgery_operation/limb/retract_skin/get_default_radial_image() @@ -157,12 +157,12 @@ ) time = 2.4 SECONDS preop_sound = list( - /obj/item/stack/medical/suture = SFX_SUTURE_BEGIN, - /obj/item = 'sound/items/handling/surgery/cautery1.ogg', + /obj/item/stack/medical/suture = 'maplestation_modules/sound/items/snip.ogg', + /obj/item = 'sound/surgery/cautery1.ogg', ) success_sound = list( - /obj/item/stack/medical/suture = SFX_SUTURE_END, - /obj/item = 'sound/items/handling/surgery/cautery2.ogg', + /obj/item/stack/medical/suture = 'maplestation_modules/sound/items/snip.ogg', + /obj/item = 'sound/surgery/cautery2.ogg', ) any_surgery_states_required = ALL_SURGERY_SKIN_STATES @@ -229,7 +229,7 @@ /obj/item/stack/cable_coil = 6.67, ) time = 2.4 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + preop_sound = 'sound/surgery/hemostat1.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/limb/clamp_bleeders/get_default_radial_image() @@ -276,7 +276,7 @@ /obj/item/stack/cable_coil = 6.67, ) time = 2.4 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + preop_sound = 'sound/surgery/hemostat1.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED /datum/surgery_operation/limb/unclamp_bleeders/get_default_radial_image() @@ -329,14 +329,14 @@ ) 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' + /obj/item/circular_saw = 'sound/surgery/saw.ogg', + /obj/item/melee/arm_blade = 'sound/surgery/scalpel1.ogg', + /obj/item/fireaxe = 'sound/surgery/scalpel1.ogg', + /obj/item/hatchet = 'sound/surgery/scalpel1.ogg', + /obj/item/knife/butcher = 'sound/surgery/scalpel1.ogg', + /obj/item = 'sound/surgery/scalpel1.ogg', + ) + success_sound = 'sound/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 @@ -449,8 +449,8 @@ /obj/item = 6.67, ) time = 3 SECONDS - preop_sound = 'sound/items/handling/surgery/saw.ogg' - success_sound = 'sound/items/handling/surgery/organ2.ogg' + preop_sound = 'sound/surgery/saw.ogg' + success_sound = 'sound/surgery/organ2.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED @@ -505,8 +505,8 @@ /obj/item = 3.33, ) time = 2.4 SECONDS - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/organ1.ogg' + preop_sound = 'sound/surgery/scalpel1.ogg' + success_sound = 'sound/surgery/organ1.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_ORGANS_CUT diff --git a/code/modules/surgery/operations/operation_generic_basic.dm b/code/modules/surgery/operations/operation_generic_basic.dm index 8c8840ece78b..3ab0b6bb57fc 100644 --- a/code/modules/surgery/operations/operation_generic_basic.dm +++ b/code/modules/surgery/operations/operation_generic_basic.dm @@ -15,8 +15,8 @@ /obj/item = 3.33, ) time = 1.6 SECONDS - preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' - success_sound = 'sound/items/handling/surgery/scalpel2.ogg' + preop_sound = 'sound/surgery/scalpel1.ogg' + success_sound = 'sound/surgery/scalpel2.ogg' operation_flags = OPERATION_AFFECTS_MOOD any_surgery_states_blocked = ALL_SURGERY_SKIN_STATES target_zone = null @@ -71,14 +71,14 @@ ) 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', + /obj/item/circular_saw = 'sound/surgery/saw.ogg', + /obj/item/melee/arm_blade = 'sound/surgery/scalpel1.ogg', + /obj/item/fireaxe = 'sound/surgery/scalpel1.ogg', + /obj/item/hatchet = 'sound/surgery/scalpel1.ogg', + /obj/item/knife/butcher = 'sound/surgery/scalpel1.ogg', + /obj/item = 'sound/surgery/scalpel1.ogg', ) - success_sound = 'sound/items/handling/surgery/organ2.ogg' + success_sound = 'sound/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 @@ -137,12 +137,12 @@ ) time = 2.4 SECONDS preop_sound = list( - /obj/item/stack/medical/suture = SFX_SUTURE_BEGIN, - /obj/item = 'sound/items/handling/surgery/cautery1.ogg', + /obj/item/stack/medical/suture = 'maplestation_modules/sound/items/snip.ogg', + /obj/item = 'sound/surgery/cautery1.ogg', ) success_sound = list( - /obj/item/stack/medical/suture = SFX_SUTURE_END, - /obj/item = 'sound/items/handling/surgery/cautery2.ogg', + /obj/item/stack/medical/suture = 'maplestation_modules/sound/items/snip.ogg', + /obj/item = 'sound/surgery/cautery2.ogg', ) any_surgery_states_required = ALL_SURGERY_STATES_UNSET_ON_CLOSE // we're not picky target_zone = null diff --git a/code/modules/surgery/operations/operation_generic_mechanic.dm b/code/modules/surgery/operations/operation_generic_mechanic.dm index fc35513400da..2d9b17696322 100644 --- a/code/modules/surgery/operations/operation_generic_mechanic.dm +++ b/code/modules/surgery/operations/operation_generic_mechanic.dm @@ -13,8 +13,8 @@ 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' + preop_sound = 'sound/items/screwdriver.ogg' + success_sound = 'sound/items/screwdriver2.ogg' any_surgery_states_blocked = ALL_SURGERY_SKIN_STATES /datum/surgery_operation/limb/mechanical_incision/get_any_tool() @@ -58,7 +58,7 @@ ) operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC time = 1 SECONDS - preop_sound = 'sound/items/tools/ratchet.ogg' + preop_sound = 'sound/items/ratchet.ogg' success_sound = 'sound/machines/airlock/doorclick.ogg' all_surgery_states_required = SURGERY_SKIN_CUT @@ -100,8 +100,8 @@ ) 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' + preop_sound = 'sound/items/screwdriver.ogg' + success_sound = 'sound/items/screwdriver2.ogg' any_surgery_states_required = ALL_SURGERY_SKIN_STATES /datum/surgery_operation/limb/mechanical_close/get_any_tool() @@ -187,7 +187,7 @@ ) operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC time = 2.4 SECONDS - preop_sound = 'sound/items/tools/ratchet.ogg' + preop_sound = 'sound/items/ratchet.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED @@ -225,7 +225,7 @@ ) operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC time = 2.4 SECONDS - preop_sound = 'sound/items/tools/ratchet.ogg' + preop_sound = 'sound/items/ratchet.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED /datum/surgery_operation/limb/mechanic_wrench/state_check(obj/item/bodypart/limb) diff --git a/code/modules/surgery/operations/operation_healing.dm b/code/modules/surgery/operations/operation_healing.dm index 1b1d88d4c367..86da5a0999a1 100644 --- a/code/modules/surgery/operations/operation_healing.dm +++ b/code/modules/surgery/operations/operation_healing.dm @@ -17,8 +17,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' + success_sound = 'sound/surgery/retractor2.ogg' + failure_sound = 'sound/surgery/organ2.ogg' required_biotype = MOB_ORGANIC|MOB_HUMANOID required_bodytype = NONE any_surgery_states_required = ALL_SURGERY_SKIN_STATES @@ -174,7 +174,7 @@ /datum/surgery_operation/basic/tend_wounds/proc/show_stats(mob/living/surgeon, mob/living/patient) if(surgeon.is_holding_item_of_type(/obj/item/healthanalyzer)) return TRUE - for(var/obj/machinery/vitals_reader/vitals in view(4, patient)) + for(var/obj/machinery/computer/vitals_reader/vitals in view(4, patient)) if(vitals.patient == patient) return TRUE for(var/obj/machinery/computer/operating/op_pc in range(1, patient)) @@ -239,7 +239,9 @@ brute_dealt += round(patient.getBruteLoss() * brute_multiplier, 0.1) burn_dealt += round(patient.getFireLoss() * burn_multiplier, 0.1) - patient.take_bodypart_damage(brute_dealt, burn_dealt, wound_bonus = CANT_WOUND) + // NON-MODULE CHANGE + patient.damage_random_bodypart(brute_dealt, BRUTE, wound_bonus = CANT_WOUND) + patient.damage_random_bodypart(burn_dealt, BURN, wound_bonus = CANT_WOUND) /datum/surgery_operation/basic/tend_wounds/upgraded rnd_name = parent_type::rnd_name + "+" diff --git a/code/modules/surgery/operations/operation_implant_removal.dm b/code/modules/surgery/operations/operation_implant_removal.dm index 524cb710c9aa..418c4080079e 100644 --- a/code/modules/surgery/operations/operation_implant_removal.dm +++ b/code/modules/surgery/operations/operation_implant_removal.dm @@ -9,7 +9,7 @@ /obj/item/kitchen/fork = 2.85, ) time = 6.4 SECONDS - success_sound = 'sound/items/handling/surgery/hemostat1.ogg' + success_sound = 'sound/surgery/hemostat1.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED /datum/surgery_operation/basic/implant_removal/get_default_radial_image() diff --git a/code/modules/surgery/operations/operation_lipo.dm b/code/modules/surgery/operations/operation_lipo.dm index 87a38f8a3a3c..396e731d1de8 100644 --- a/code/modules/surgery/operations/operation_lipo.dm +++ b/code/modules/surgery/operations/operation_lipo.dm @@ -15,10 +15,10 @@ 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', + /obj/item/circular_saw = 'sound/surgery/saw.ogg', + /obj/item = 'sound/surgery/scalpel1.ogg', ) - success_sound = 'sound/items/handling/surgery/organ2.ogg' + success_sound = 'sound/surgery/organ2.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED /datum/surgery_operation/limb/lipoplasty/get_any_tool() @@ -104,7 +104,7 @@ TOOL_SCALPEL = 4, /obj/item = 5, ) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/items/handling/surgery/organ2.ogg' + preop_sound = 'sound/items/ratchet.ogg' + success_sound = 'sound/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 index d89f93991951..9b762b4c1d64 100644 --- a/code/modules/surgery/operations/operation_lobotomy.dm +++ b/code/modules/surgery/operations/operation_lobotomy.dm @@ -13,9 +13,9 @@ ) 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' + preop_sound = 'sound/surgery/scalpel1.ogg' + success_sound = 'sound/surgery/scalpel2.ogg' + failure_sound = 'sound/surgery/organ2.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_SAWED /datum/surgery_operation/organ/lobotomy/get_any_tool() diff --git a/code/modules/surgery/operations/operation_organ_manip.dm b/code/modules/surgery/operations/operation_organ_manip.dm index 0ba5e6a51e7a..8f4aa5c789f5 100644 --- a/code/modules/surgery/operations/operation_organ_manip.dm +++ b/code/modules/surgery/operations/operation_organ_manip.dm @@ -10,13 +10,13 @@ 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' + var/insert_preop_sound = 'sound/surgery/organ2.ogg' /// Sound played when starting to remove an organ - var/remove_preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + var/remove_preop_sound = 'sound/surgery/hemostat1.ogg' /// Sound played when successfully inserting an organ - var/insert_success_sound = 'sound/items/handling/surgery/organ1.ogg' + var/insert_success_sound = 'sound/surgery/organ1.ogg' /// Sound played when successfully removing an organ - var/remove_success_sound = 'sound/items/handling/surgery/organ2.ogg' + var/remove_success_sound = 'sound/surgery/organ2.ogg' /// Implements used to insert organs var/list/insert_implements = list( diff --git a/code/modules/surgery/operations/operation_organ_repair.dm b/code/modules/surgery/operations/operation_organ_repair.dm index 8e6a98ec5b6a..ab7028f80b6f 100644 --- a/code/modules/surgery/operations/operation_organ_repair.dm +++ b/code/modules/surgery/operations/operation_organ_repair.dm @@ -55,9 +55,9 @@ /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' + preop_sound = 'sound/surgery/scalpel1.ogg' + success_sound = 'sound/surgery/organ1.ogg' + failure_sound = 'sound/surgery/organ2.ogg' target_type = /obj/item/organ/lungs failure_damage_percent = 0.1 @@ -124,8 +124,8 @@ /obj/item/knife = 2.25, /obj/item/shard = 2.85, ) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' + preop_sound = 'sound/items/ratchet.ogg' + success_sound = 'sound/machines/doorclick.ogg' required_organ_flag = ORGAN_ROBOTIC operation_flags = parent_type::operation_flags | OPERATION_MECHANIC @@ -140,9 +140,9 @@ /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' + preop_sound = 'sound/surgery/scalpel1.ogg' + success_sound = 'sound/surgery/organ1.ogg' + failure_sound = 'sound/surgery/organ2.ogg' target_type = /obj/item/organ/liver heal_to_percent = 0.1 failure_damage_percent = 0.15 @@ -207,8 +207,8 @@ /obj/item/knife = 2.25, /obj/item/shard = 2.85, ) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' + preop_sound = 'sound/items/ratchet.ogg' + success_sound = 'sound/machines/doorclick.ogg' required_organ_flag = ORGAN_ROBOTIC operation_flags = parent_type::operation_flags | OPERATION_MECHANIC @@ -223,9 +223,9 @@ /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' + preop_sound = 'sound/surgery/hemostat1.ogg' + success_sound = 'sound/surgery/hemostat1.ogg' + failure_sound = 'sound/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) @@ -294,8 +294,8 @@ /obj/item/knife = 2.25, /obj/item/shard = 2.85, ) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' + preop_sound = 'sound/items/ratchet.ogg' + success_sound = 'sound/machines/doorclick.ogg' required_organ_flag = ORGAN_ROBOTIC operation_flags = parent_type::operation_flags | OPERATION_MECHANIC @@ -311,9 +311,9 @@ /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' + preop_sound = 'sound/surgery/scalpel1.ogg' + success_sound = 'sound/surgery/organ1.ogg' + failure_sound = 'sound/surgery/organ2.ogg' target_type = /obj/item/organ/stomach heal_to_percent = 0.2 failure_damage_percent = 0.15 @@ -386,8 +386,8 @@ /obj/item/shard = 2.85, /obj/item = 4, ) - preop_sound = 'sound/items/tools/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' + preop_sound = 'sound/items/ratchet.ogg' + success_sound = 'sound/machines/doorclick.ogg' required_organ_flag = ORGAN_ROBOTIC operation_flags = parent_type::operation_flags | OPERATION_MECHANIC @@ -579,9 +579,9 @@ /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' + preop_sound = 'sound/surgery/hemostat1.ogg' + success_sound = 'sound/surgery/hemostat1.ogg' + failure_sound = 'sound/surgery/organ2.ogg' operation_flags = parent_type::operation_flags | OPERATION_LOOPING target_type = /obj/item/organ/brain heal_to_percent = 0.25 diff --git a/code/modules/surgery/operations/operation_pacify.dm b/code/modules/surgery/operations/operation_pacify.dm index e6b969b96e83..4341dde1281d 100644 --- a/code/modules/surgery/operations/operation_pacify.dm +++ b/code/modules/surgery/operations/operation_pacify.dm @@ -10,9 +10,9 @@ /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' + preop_sound = 'sound/surgery/hemostat1.ogg' + success_sound = 'sound/surgery/hemostat1.ogg' + failure_sound = 'sound/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 diff --git a/code/modules/surgery/operations/operation_plastic_surgery.dm b/code/modules/surgery/operations/operation_plastic_surgery.dm index 091992628452..7ef6d7f9886e 100644 --- a/code/modules/surgery/operations/operation_plastic_surgery.dm +++ b/code/modules/surgery/operations/operation_plastic_surgery.dm @@ -11,8 +11,8 @@ ) 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' + preop_sound = 'sound/surgery/scalpel1.ogg' + success_sound = 'sound/surgery/scalpel2.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN /datum/surgery_operation/limb/plastic_surgery/all_required_strings() @@ -136,9 +136,9 @@ ) 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' + preop_sound = 'sound/effects/blobattack.ogg' + success_sound = 'sound/effects/attackblob.ogg' + failure_sound = 'sound/effects/blobattack.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_PLASTIC_APPLIED diff --git a/code/modules/surgery/operations/operation_puncture.dm b/code/modules/surgery/operations/operation_puncture.dm index 27ef9591f3d0..c530f8b6f14a 100644 --- a/code/modules/surgery/operations/operation_puncture.dm +++ b/code/modules/surgery/operations/operation_puncture.dm @@ -7,7 +7,7 @@ TOOL_WIRECUTTER = 2.5, ) time = 3 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + preop_sound = 'sound/surgery/hemostat1.ogg' operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_PRIORITY_NEXT_STEP all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT @@ -84,7 +84,7 @@ /obj/item = 3.33, ) time = 3.2 SECONDS - preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' + preop_sound = 'sound/surgery/hemostat1.ogg' operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_PRIORITY_NEXT_STEP all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT diff --git a/code/modules/surgery/operations/operation_virus.dm b/code/modules/surgery/operations/operation_virus.dm index 5c19263ee253..69f3eb0dc981 100644 --- a/code/modules/surgery/operations/operation_virus.dm +++ b/code/modules/surgery/operations/operation_virus.dm @@ -10,8 +10,8 @@ /obj/item = 3.33, ) time = 10 SECONDS - preop_sound = 'sound/items/handling/surgery/cautery1.ogg' - success_sound = 'sound/items/handling/surgery/cautery2.ogg' + preop_sound = 'sound/surgery/cautery1.ogg' + success_sound = 'sound/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( diff --git a/code/modules/surgery/surgery_tools.dm b/code/modules/surgery/surgery_tools.dm index f0f0b75e6270..4a647227e068 100644 --- a/code/modules/surgery/surgery_tools.dm +++ b/code/modules/surgery/surgery_tools.dm @@ -377,7 +377,7 @@ /obj/item/surgical_processor/cyborg_unequip(mob/user) . = ..() - 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)) From 1a3d14b43a52b307e1f3161d8e4fb237e3f459b3 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 18:46:51 -0600 Subject: [PATCH 04/35] Fixes --- code/modules/surgery/operations/operation_generic.dm | 6 +++--- .../surgery/operations/operation_generic_mechanic.dm | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/modules/surgery/operations/operation_generic.dm b/code/modules/surgery/operations/operation_generic.dm index 075ec3798a31..0f951d43e279 100644 --- a/code/modules/surgery/operations/operation_generic.dm +++ b/code/modules/surgery/operations/operation_generic.dm @@ -398,9 +398,9 @@ ) 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', + /obj/item/stack/sticky_tape/surgical = 'sound/items/duct_tape_rip.ogg', + /obj/item/stack/sticky_tape/super = 'sound/items/duct_tape_rip.ogg', + /obj/item/stack/sticky_tape = 'sound/items/duct_tape_rip.ogg', ) time = 4 SECONDS all_surgery_states_required = SURGERY_SKIN_OPEN diff --git a/code/modules/surgery/operations/operation_generic_mechanic.dm b/code/modules/surgery/operations/operation_generic_mechanic.dm index 2d9b17696322..4d6267b3768d 100644 --- a/code/modules/surgery/operations/operation_generic_mechanic.dm +++ b/code/modules/surgery/operations/operation_generic_mechanic.dm @@ -59,7 +59,7 @@ operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC time = 1 SECONDS preop_sound = 'sound/items/ratchet.ogg' - success_sound = 'sound/machines/airlock/doorclick.ogg' + success_sound = 'sound/machines/doorclick.ogg' all_surgery_states_required = SURGERY_SKIN_CUT /datum/surgery_operation/limb/mechanical_open/get_default_radial_image() From a3d64860bd0fb73a254afb507197e4b9c0f8260d Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Thu, 19 Jun 2025 13:02:07 -0500 Subject: [PATCH 05/35] Use any item as a prosthetic arm, what's the worst that could happen? (#91573) --- code/__DEFINES/dcs/signals/signals_object.dm | 7 + code/datums/components/prosthetic_item.dm | 153 +++++++++++++++ code/datums/components/transforming.dm | 6 +- code/datums/elements/prosthetic_icon.dm | 57 ++++++ code/game/objects/items/chainsaw.dm | 175 ++++++++---------- code/game/objects/items/dualsaber.dm | 10 +- .../job_types/chaplain/chaplain_nullrod.dm | 42 ++++- code/modules/mob/inventory.dm | 8 + code/modules/mob/living/carbon/examine.dm | 7 + .../surgery/bodyparts/dismemberment.dm | 3 +- code/modules/surgery/bodyparts/helpers.dm | 3 + tgstation.dme | 2 + 12 files changed, 356 insertions(+), 117 deletions(-) create mode 100644 code/datums/components/prosthetic_item.dm create mode 100644 code/datums/elements/prosthetic_icon.dm diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 706a9ccc82de..9a3d0055de0b 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -577,3 +577,10 @@ /// Sent from /datum/wires/attach_assembly() : (atom/holder) #define COMSIG_ASSEMBLY_PRE_ATTACH "assembly_pre_attach" #define COMPONENT_CANCEL_ATTACH (1<<0) + +/// Before an item has been equipped as a prosthetic limb +#define COMSIG_ITEM_PRE_USED_AS_PROSTHETIC "item_used_as_prosthetic" +/// After an item has been equipped as a prosthetic limb +#define COMSIG_ITEM_POST_USED_AS_PROSTHETIC "item_post_used_as_prosthetic" +/// Item has been unequipped from a mob as a prosthetic limb +#define COMSIG_ITEM_DROPPED_FROM_PROSTHETIC "item_dropped_from_prosthetic" diff --git a/code/datums/components/prosthetic_item.dm b/code/datums/components/prosthetic_item.dm new file mode 100644 index 000000000000..b8876f5484f9 --- /dev/null +++ b/code/datums/components/prosthetic_item.dm @@ -0,0 +1,153 @@ +/** + * This makes an arbitrary item into a "prosthetic limb" + */ +/datum/component/item_as_prosthetic_limb + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + /// The item that is the prosthetic limb + VAR_PRIVATE/obj/item/item_limb + /// Prob of losing the arm on attack + var/drop_prob = 100 + +/datum/component/item_as_prosthetic_limb/Initialize(obj/item/prosthetic_item, drop_prob = 100) + if(!isbodypart(parent)) + return COMPONENT_INCOMPATIBLE + var/obj/item/bodypart/bodyparent = parent + if(bodyparent.body_zone != BODY_ZONE_L_ARM && bodyparent.body_zone != BODY_ZONE_R_ARM) + return COMPONENT_INCOMPATIBLE + if(isnull(bodyparent.owner)) + return COMPONENT_INCOMPATIBLE + + bodyparent.bodypart_flags |= BODYPART_PSEUDOPART|BODYPART_IMPLANTED + src.item_limb = prosthetic_item // calls register_item() in registerWithParent() + src.drop_prob = drop_prob + +/datum/component/item_as_prosthetic_limb/InheritComponent(datum/component/new_comp, i_am_original, obj/item/prosthetic_item, drop_prob = 100) + if(prosthetic_item) + unregister_item(item_limb) + register_item(prosthetic_item) + + src.drop_prob = drop_prob + +/datum/component/item_as_prosthetic_limb/proc/register_item(obj/item/prosthetic_item) + SEND_SIGNAL(prosthetic_item, COMSIG_ITEM_PRE_USED_AS_PROSTHETIC, parent) + item_limb = prosthetic_item + RegisterSignal(prosthetic_item, COMSIG_QDELETING, PROC_REF(qdel_limb)) + RegisterSignal(prosthetic_item, COMSIG_MOVABLE_MOVED, PROC_REF(limb_moved)) + // adds a bunch of flags to the item to make it act like a limb + prosthetic_item.item_flags |= (HAND_ITEM|ABSTRACT) + prosthetic_item.interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP + ADD_TRAIT(prosthetic_item, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + // update the name of the real limb to match the item, so you see "their chainsaw arm is wounded" + var/obj/item/bodypart/bodyparent = parent + // ensures the item is in the proper place + switch(bodyparent.body_zone) + if(BODY_ZONE_R_ARM) + bodyparent.name = "right [prosthetic_item.name]" + bodyparent.plaintext_zone = "right [prosthetic_item.name]" + bodyparent.owner.put_in_r_hand(prosthetic_item) + if(BODY_ZONE_L_ARM) + bodyparent.name = "left [prosthetic_item.name]" + bodyparent.plaintext_zone = "left [prosthetic_item.name]" + bodyparent.owner.put_in_l_hand(prosthetic_item) + SEND_SIGNAL(prosthetic_item, COMSIG_ITEM_POST_USED_AS_PROSTHETIC, parent) + +/datum/component/item_as_prosthetic_limb/proc/unregister_item(obj/item/prosthetic_item) + item_limb = null + UnregisterSignal(prosthetic_item, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED)) + // nothing to be done if it's being thrown away + if(QDELING(prosthetic_item)) + return + + // reset all the flags + prosthetic_item.item_flags &= ~(HAND_ITEM|ABSTRACT) + prosthetic_item.interaction_flags_item |= INTERACT_ITEM_ATTACK_HAND_PICKUP + REMOVE_TRAIT(prosthetic_item, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + // note: default behavior is to keep the item in the contents of the arm + // so if the limb is deleted by some unknown means (supermatter?), the item will also be deleted + SEND_SIGNAL(prosthetic_item, COMSIG_ITEM_DROPPED_FROM_PROSTHETIC, parent) + +/datum/component/item_as_prosthetic_limb/proc/register_limb(obj/item/bodypart/bodyparent) + RegisterSignal(bodyparent, COMSIG_BODYPART_REMOVED, PROC_REF(clear_comp)) + RegisterSignals(bodyparent.owner, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_LIVING_UNARMED_ATTACK), PROC_REF(pop_limb)) + +/datum/component/item_as_prosthetic_limb/proc/unregister_limb(obj/item/bodypart/bodyparent) + UnregisterSignal(bodyparent, COMSIG_BODYPART_REMOVED) + if(bodyparent.owner) // may be null frim removal + UnregisterSignal(bodyparent.owner, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_LIVING_UNARMED_ATTACK)) + +/datum/component/item_as_prosthetic_limb/RegisterWithParent() + register_limb(parent) + register_item(item_limb) + +/datum/component/item_as_prosthetic_limb/UnregisterFromParent() + unregister_limb(parent) + unregister_item(item_limb) + +/// If the fake limb (the item) is deleted, the real limb goes with it. +/datum/component/item_as_prosthetic_limb/proc/qdel_limb(obj/item/source) + SIGNAL_HANDLER + + if(QDELING(parent)) + return + qdel(parent) // which nulls all the references and signals + +/// If the item is removed from our hands somehow, the real limb has to go +/datum/component/item_as_prosthetic_limb/proc/limb_moved(obj/item/source) + SIGNAL_HANDLER + + if(QDELING(parent)) + return + var/obj/item/bodypart/bodyparent = parent + if(source.loc == bodyparent.owner) + return + qdel(parent) // which nulls all the references and signals + +/// When the bodypart is removed, we will drop the item on the ground, and then delete the the real limb. +/datum/component/item_as_prosthetic_limb/proc/clear_comp(datum/source, mob/living/carbon/owner) + SIGNAL_HANDLER + + UnregisterSignal(owner, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_LIVING_UNARMED_ATTACK)) + if(item_limb.loc == owner) + item_limb.forceMove(owner.drop_location()) + if(QDELING(parent)) + return + qdel(parent) // which nulls all the references and signals + +/// Attacking with the fake limb (the item) can cause it to fall off, which in turn will result in the real limb being deleted. +/datum/component/item_as_prosthetic_limb/proc/pop_limb(mob/living/source, ...) + SIGNAL_HANDLER + + if(source.get_active_hand() != parent) + return NONE + if(!prob(drop_prob)) + return NONE + + var/obj/item/bodypart/bodyparent = parent + source.visible_message( + span_warning("As [source] attempts to swing with [source.p_their()] [bodyparent.name], it falls right off!"), + span_warning("As you attempt to swing with [source.p_their()] [bodyparent.name], it falls right off!"), + visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, + ) + bodyparent.dismember(silent = TRUE) // which removes the limb, which qdels us (which nulls all the references and signals) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/** + * Makes the passed item into a "prosthetic limb" of this mod, replacing any existing arm + * + * Returns the created pseudopart + */ +/mob/living/carbon/proc/make_item_prosthetic(obj/item/some_thing, target_zone = BODY_ZONE_R_ARM, fall_prob = 0) + var/obj/item/bodypart/existing = get_bodypart(target_zone) + existing?.drop_limb(special = TRUE) + + var/obj/item/bodypart/bodypart_to_attach = newBodyPart(target_zone) + bodypart_to_attach.change_appearance(icon = 'icons/mob/augmentation/surplus_augments.dmi', id = BODYPART_ID_ROBOTIC, greyscale = FALSE, dimorphic = FALSE) + bodypart_to_attach.try_attach_limb(src) + bodypart_to_attach.AddComponent(/datum/component/item_as_prosthetic_limb, some_thing, fall_prob) + + if(bodypart_to_attach.owner != src) + stack_trace("make_item_prosthetic failed to attach to owner!") + qdel(bodypart_to_attach) + return null + + return bodypart_to_attach diff --git a/code/datums/components/transforming.dm b/code/datums/components/transforming.dm index cb782d958f16..d800ee041f7b 100644 --- a/code/datums/components/transforming.dm +++ b/code/datums/components/transforming.dm @@ -212,6 +212,7 @@ source.icon_state = "[source.icon_state]_on" if(inhand_icon_change && source.inhand_icon_state) source.inhand_icon_state = "[source.inhand_icon_state]_on" + source.update_appearance() source.update_inhand_icon() /* @@ -243,9 +244,8 @@ else source.icon_state = initial(source.icon_state) // NON-MODULE CHANGE END source.inhand_icon_state = initial(source.inhand_icon_state) - if(ismob(source.loc)) - var/mob/loc_mob = source.loc - loc_mob.update_held_items() + source.update_appearance() + source.update_inhand_icon() /* * If [clumsy_check] is set to TRUE, attempt to cause a side effect for clumsy people activating this item. diff --git a/code/datums/elements/prosthetic_icon.dm b/code/datums/elements/prosthetic_icon.dm new file mode 100644 index 000000000000..28c8353755ca --- /dev/null +++ b/code/datums/elements/prosthetic_icon.dm @@ -0,0 +1,57 @@ +/// Allows you to modify an item's icon state when it is used as a prosthetic limb. +/datum/element/prosthetic_icon + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// Prefix to use for the icon state of the prosthetic limb. + var/icon_state_prefix = "" + /// Modifies the angle of the icon for attack animations. + var/icon_angle + /// In built support for the transforming component + var/transforming = FALSE + /// In built support for the two handed component + var/wielding = FALSE + +/datum/element/prosthetic_icon/Attach(obj/item/target, icon_state_prefix, icon_angle, transforming = FALSE, wielding = FALSE) + . = ..() + if(!isitem(target)) + return ELEMENT_INCOMPATIBLE + + src.icon_state_prefix = icon_state_prefix + src.icon_angle = icon_angle + src.transforming = transforming + src.wielding = wielding + + RegisterSignal(target, COMSIG_ATOM_UPDATE_ICON, PROC_REF(on_update_icon)) + RegisterSignals(target, list(COMSIG_ITEM_POST_USED_AS_PROSTHETIC, COMSIG_ITEM_DROPPED_FROM_PROSTHETIC), PROC_REF(update_source)) + target.update_appearance() + +/datum/element/prosthetic_icon/Detach(obj/item/target) + . = ..() + UnregisterSignal(target, list(COMSIG_ATOM_UPDATE_ICON, COMSIG_ITEM_POST_USED_AS_PROSTHETIC, COMSIG_ITEM_DROPPED_FROM_PROSTHETIC)) + target.update_appearance() + +/datum/element/prosthetic_icon/proc/on_update_icon(obj/item/source) + SIGNAL_HANDLER + if(!HAS_TRAIT_FROM(source, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)) + source.inhand_icon_state = source.base_icon_state + if(transforming && HAS_TRAIT(source, TRAIT_TRANSFORM_ACTIVE)) + source.inhand_icon_state += "_on" + if(wielding) + source.inhand_icon_state += "[HAS_TRAIT(source, TRAIT_WIELDED)]" + source.icon_angle = initial(source.icon_angle) + return NONE + + source.inhand_icon_state = "[icon_state_prefix]_[source.base_icon_state]" + if(transforming && HAS_TRAIT(source, TRAIT_TRANSFORM_ACTIVE)) + source.inhand_icon_state += "_on" + if(wielding) + source.inhand_icon_state += "[HAS_TRAIT(source, TRAIT_WIELDED)]" + if(isnum(icon_angle)) + source.icon_angle = icon_angle + source.update_inhand_icon() + return COMSIG_ATOM_NO_UPDATE_ICON_STATE + +/datum/element/prosthetic_icon/proc/update_source(obj/item/source) + SIGNAL_HANDLER + source.update_appearance() + source.update_inhand_icon() diff --git a/code/game/objects/items/chainsaw.dm b/code/game/objects/items/chainsaw.dm index cdbe2b0934dd..519569b29081 100644 --- a/code/game/objects/items/chainsaw.dm +++ b/code/game/objects/items/chainsaw.dm @@ -4,7 +4,9 @@ name = "chainsaw" desc = "A versatile power tool. Useful for limbing trees and delimbing humans." icon = 'icons/obj/weapons/chainsaw.dmi' - icon_state = "chainsaw_off" + icon_state = "chainsaw" + base_icon_state = "chainsaw" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -44,48 +46,15 @@ /obj/item/chainsaw/Initialize(mapload) . = ..() chainsaw_loop = new(src) - apply_components() - -/obj/item/chainsaw/suicide_act(mob/living/carbon/user) - if(on) - user.visible_message(span_suicide("[user] begins to tear [user.p_their()] head off with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) - playsound(src, 'sound/weapons/chainsawhit.ogg', 100, TRUE) - var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) - if(myhead) - myhead.dismember() - else - user.visible_message(span_suicide("[user] smashes [src] into [user.p_their()] neck, destroying [user.p_their()] esophagus! It looks like [user.p_theyre()] trying to commit suicide!")) - playsound(src, 'sound/weapons/genhit1.ogg', 100, TRUE) - return BRUTELOSS - -/obj/item/chainsaw/attack_self(mob/user) - on = !on - to_chat(user, "As you pull the starting cord dangling from [src], [on ? "it begins to whirr." : "the chain stops moving."]") - force = on ? force_on : initial(force) - throwforce = on ? force_on : initial(force) - icon_state = "chainsaw_[on ? "on" : "off"]" - var/datum/component/butchering/butchering = src.GetComponent(/datum/component/butchering) - butchering.butchering_enabled = on - - if(on) - hitsound = 'sound/weapons/chainsawhit.ogg' - chainsaw_loop.start() - else - hitsound = SFX_SWING_HIT - chainsaw_loop.stop() - - toolspeed = on ? 0.5 : initial(toolspeed) //Turning it on halves the speed - if(src == user.get_active_held_item()) //update inhands - user.update_held_items() - update_item_action_buttons() - -/** - * Handles adding components to the chainsaw. Added in Initialize() - * - * Applies components to the chainsaw. Added as a seperate proc to allow for - * variance between subtypes - */ -/obj/item/chainsaw/proc/apply_components() + AddComponent( \ + /datum/component/transforming, \ + force_on = force_on, \ + throwforce_on = force_on, \ + throw_speed_on = throw_speed, \ + sharpness_on = SHARP_EDGED, \ + hitsound_on = 'sound/items/weapons/chainsawhit.ogg', \ + w_class_on = w_class, \ + ) AddComponent(/datum/component/butchering, \ speed = 3 SECONDS, \ effectiveness = 100, \ @@ -93,35 +62,75 @@ butcher_sound = 'sound/weapons/chainsawhit.ogg', \ disabled = TRUE, \ ) - AddComponent(/datum/component/two_handed, require_twohands=TRUE) + AddElement(/datum/element/prosthetic_icon, "mounted", 180, TRUE) + AddComponent(/datum/component/two_handed, require_twohands = TRUE) + RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform)) + RegisterSignal(src, COMSIG_ITEM_PRE_USED_AS_PROSTHETIC, PROC_REF(disable_twohanded_comp)) + RegisterSignal(src, COMSIG_ITEM_DROPPED_FROM_PROSTHETIC, PROC_REF(enable_twohanded_comp)) + +/obj/item/chainsaw/proc/on_transform(obj/item/source, mob/user, active) + SIGNAL_HANDLER + + to_chat(user, span_notice("As you pull the starting cord dangling from [src], [active ? "it begins to whirr" : "the chain stops moving"].")) + var/datum/component/butchering/butchering = GetComponent(/datum/component/butchering) + butchering.butchering_enabled = active + if (active) + chainsaw_loop.start() + else + chainsaw_loop.stop() -/obj/item/chainsaw/doomslayer - name = "THE GREAT COMMUNICATOR" - desc = "VRRRRRRR!!!" - armour_penetration = 100 - force_on = 30 + toolspeed = active ? 0.5 : initial(toolspeed) + update_item_action_buttons() -/obj/item/chainsaw/doomslayer/attack(mob/living/target_mob, mob/living/user, params) - if (target_mob.stat != DEAD) - return ..() + return COMPONENT_NO_DEFAULT_MESSAGE - if (user.zone_selected != BODY_ZONE_HEAD) - return ..() +/obj/item/chainsaw/proc/disable_twohanded_comp() + SIGNAL_HANDLER - var/obj/item/bodypart/head = target_mob.get_bodypart(BODY_ZONE_HEAD) - if (isnull(head)) - return ..() + qdel(GetComponent(/datum/component/two_handed)) - playsound(user, 'sound/weapons/slice.ogg', vol = 80, vary = TRUE) +/obj/item/chainsaw/proc/enable_twohanded_comp() + SIGNAL_HANDLER - target_mob.balloon_alert(user, "cutting off head...") - if (!do_after(user, 2 SECONDS, target_mob, extra_checks = CALLBACK(src, PROC_REF(has_same_head), target_mob, head))) - return TRUE + AddComponent(/datum/component/two_handed, require_twohands = TRUE) + +/obj/item/chainsaw/get_demolition_modifier(obj/target) + return HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE) ? demolition_mod : 0.8 - head.dismember(silent = FALSE) - user.put_in_hands(head) +/obj/item/chainsaw/suicide_act(mob/living/carbon/user) + if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) + user.visible_message(span_suicide("[user] smashes [src] into [user.p_their()] neck, destroying [user.p_their()] esophagus! It looks like [user.p_theyre()] trying to commit suicide!")) + playsound(src, 'sound/items/weapons/genhit1.ogg', 100, TRUE) + return BRUTELOSS + + user.visible_message(span_suicide("[user] begins to tear [user.p_their()] head off with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) + playsound(src, 'sound/items/weapons/chainsawhit.ogg', 100, TRUE) + var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) + if(myhead) + myhead.dismember() + return BRUTELOSS + +// /obj/item/chainsaw/doomslayer/attack(mob/living/target_mob, mob/living/user, params) +// if (target_mob.stat != DEAD) +// return ..() + +// if (user.zone_selected != BODY_ZONE_HEAD) +// return ..() + +// var/obj/item/bodypart/head = target_mob.get_bodypart(BODY_ZONE_HEAD) +// if (isnull(head)) +// return ..() - return TRUE +// playsound(user, 'sound/weapons/slice.ogg', vol = 80, vary = TRUE) + +// target_mob.balloon_alert(user, "cutting off head...") +// if (!do_after(user, 2 SECONDS, target_mob, extra_checks = CALLBACK(src, PROC_REF(has_same_head), target_mob, head))) +// return TRUE + +// head.dismember(silent = FALSE) +// user.put_in_hands(head) + +// return TRUE /obj/item/chainsaw/doomslayer/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) if(attack_type == PROJECTILE_ATTACK) @@ -130,43 +139,5 @@ return TRUE return FALSE -/obj/item/chainsaw/doomslayer/proc/has_same_head(mob/living/target_mob, obj/item/bodypart/head) - return target_mob.get_bodypart(BODY_ZONE_HEAD) == head - -/obj/item/chainsaw/mounted_chainsaw - name = "mounted chainsaw" - desc = "A chainsaw that has replaced your arm." - inhand_icon_state = "mounted_chainsaw" - item_flags = ABSTRACT | DROPDEL - throwforce = 0 - throw_range = 0 - throw_speed = 0 - toolspeed = 1 - -/obj/item/chainsaw/mounted_chainsaw/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) - -/obj/item/chainsaw/mounted_chainsaw/Destroy() - var/obj/item/bodypart/part - new /obj/item/chainsaw(get_turf(src)) - if(iscarbon(loc)) - var/mob/living/carbon/holder = loc - var/index = holder.get_held_index_of_item(src) - if(index) - part = holder.hand_bodyparts[index] - . = ..() - if(part) - part.drop_limb() - -/obj/item/chainsaw/mounted_chainsaw/apply_components() - AddComponent(/datum/component/butchering, \ - speed = 3 SECONDS, \ - effectiveness = 100, \ - bonus_modifier = 0, \ - butcher_sound = 'sound/weapons/chainsawhit.ogg', \ - disabled = TRUE, \ - ) - /datum/action/item_action/startchainsaw name = "Pull The Starting Cord" diff --git a/code/game/objects/items/dualsaber.dm b/code/game/objects/items/dualsaber.dm index 3c11933b68d3..5c4366246ee5 100644 --- a/code/game/objects/items/dualsaber.dm +++ b/code/game/objects/items/dualsaber.dm @@ -56,10 +56,12 @@ /// Triggered on wield of two handed item /// Specific hulk checks due to reflection chance for balance issues and switches hitsounds. /obj/item/dualsaber/proc/on_wield(obj/item/source, mob/living/carbon/user) - if(user?.has_dna()) - if(user.dna.check_mutation(/datum/mutation/human/hulk)) - to_chat(user, span_warning("You lack the grace to wield this!")) - return COMPONENT_TWOHANDED_BLOCK_WIELD + if(user && HAS_TRAIT(user, TRAIT_HULK)) + to_chat(user, span_warning("You lack the grace to wield this!")) + return COMPONENT_TWOHANDED_BLOCK_WIELD + if(HAS_TRAIT_FROM(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)) + to_chat(user, span_warning("You can't seem to hold [src] properly!")) + return COMPONENT_TWOHANDED_BLOCK_WIELD update_weight_class(w_class_on) hitsound = 'sound/weapons/blade1.ogg' START_PROCESSING(SSobj, src) diff --git a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm index e02bc9971379..7957538cf5c7 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm @@ -54,11 +54,20 @@ AddComponent(/datum/component/subtype_picker, rods, CALLBACK(src, PROC_REF(on_holy_weapon_picked))) /// Callback for subtype picker, invoked when the chaplain picks a new nullrod -/obj/item/nullrod/proc/on_holy_weapon_picked(obj/item/new_holy_weapon, mob/living/picker) +/obj/item/nullrod/proc/on_holy_weapon_picked(obj/item/nullrod/new_holy_weapon, mob/living/picker) + new_holy_weapon.on_selected(src, picker) + if(!station_holy_item) + return GLOB.holy_weapon_type = new_holy_weapon.type SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NULLROD_PICKED) SSblackbox.record_feedback("tally", "chaplain_weapon", 1, "[new_holy_weapon.name]") +/// Called on a new instance of a nullrod when selected +/// Override this to add behavior when a nullrod is picked +/obj/item/nullrod/proc/on_selected(obj/item/nullrod/old_weapon, mob/living/picker) + return + +/// Callback for effect remover, invoked when a cult rune is cleared /obj/item/nullrod/proc/on_cult_rune_removed(obj/effect/target, mob/living/user) if(!istype(target, /obj/effect/rune)) return @@ -388,7 +397,7 @@ desc = "Good? Bad? You're the guy with the chainsaw hand." icon = 'icons/obj/weapons/chainsaw.dmi' icon_state = "chainsaw_on" - inhand_icon_state = "mounted_chainsaw" + base_icon_state = "chainsaw_on" lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi' w_class = WEIGHT_CLASS_HUGE @@ -406,14 +415,33 @@ /obj/item/nullrod/chainsaw/Initialize(mapload) . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + AddElement(/datum/element/prosthetic_icon, "mounted", 180) AddComponent(/datum/component/butchering, \ - speed = 3 SECONDS, \ - effectiveness = 100, \ - bonus_modifier = 0, \ - butcher_sound = hitsound, \ + speed = 3 SECONDS, \ + effectiveness = 100, \ + bonus_modifier = 0, \ + butcher_sound = hitsound, \ ) +/obj/item/nullrod/chainsaw/on_selected(obj/item/nullrod/old_weapon, mob/living/picker) + if(!iscarbon(picker)) + return + var/obj/item/bodypart/active = picker.get_active_hand() + var/mob/living/carbon/new_hero = picker + new_hero.make_item_prosthetic(src, active.body_zone) + +/obj/item/nullrod/chainsaw/equipped(mob/living/carbon/user, slot, initial) + . = ..() + if(!iscarbon(user)) + return + if(!(slot & ITEM_SLOT_HANDS)) + return + to_chat(user, span_warning("As you lay your hands on [src], it latches onto your arm!")) + var/obj/item/bodypart/active = user.get_active_hand() + user.make_item_prosthetic(src, active.body_zone) + +// Clown Dagger - Nothing special, just honks. + /obj/item/nullrod/clown name = "clown dagger" desc = "Used for absolutely hilarious sacrifices." diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 2d7c3afb71d4..d59edc51db20 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -82,6 +82,14 @@ /mob/proc/get_held_index_of_item(obj/item/I) return held_items.Find(I) +/// Returns what body zone is holding the passed item +/mob/proc/get_hand_zone_of_item(obj/item/I) + var/hand_index = get_held_index_of_item(I) + if(!hand_index) + return null + if(IS_RIGHT_INDEX(hand_index)) + return BODY_ZONE_R_ARM + return BODY_ZONE_L_ARM ///Find number of held items, multihand compatible /mob/proc/get_num_held_items() diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index 85bb98439a3a..ce8996bdba63 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -399,6 +399,13 @@ if(clothes[CLOTHING_SLOT(HANDS)]) clothes[CLOTHING_SLOT(HANDS)] += "
    " clothes[CLOTHING_SLOT(HANDS)] += "[t_He] [t_is] holding [held_thing.examine_title(user, href = TRUE)] in [t_his] [get_held_index_name(get_held_index_of_item(held_thing))]." + for(var/obj/item/bodypart/arm/part in bodyparts) + if(!(part.bodypart_flags & BODYPART_PSEUDOPART)) + continue + var/obj/item/corresponding_item = get_item_for_held_index(part.held_index) || part + if(clothes[CLOTHING_SLOT(HANDS)]) + clothes[CLOTHING_SLOT(HANDS)] += "
    " + clothes[CLOTHING_SLOT(HANDS)] += "[t_He] [t_has] a [corresponding_item.examine_title(user, href = TRUE)] in place of [t_his] [initial(part.plaintext_zone)]." //gloves if(gloves && !(obscured_slots & HIDEGLOVES) && !HAS_TRAIT(gloves, TRAIT_EXAMINE_SKIP)) clothes[CLOTHING_SLOT(GLOVES)] = "[t_He] [t_has] [gloves.examine_title(user, href = TRUE)] on [t_his] hands." diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index bfa617cd22df..62aa98d51175 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -122,7 +122,8 @@ if(bodypart_flags & BODYPART_PSEUDOPART) drop_organs(phantom_owner) //Psuedoparts shouldn't have organs, but just in case - qdel(src) + if(!QDELING(src)) // we might be removed as a part of something qdeling us + qdel(src) return if(move_to_floor) diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index 3eafc75b0459..3bae2f433f3e 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -80,6 +80,9 @@ /mob/living/carbon/alien/larva/has_right_hand(check_disabled = TRUE) return TRUE +/// Returns the bodypart holding the passed item +/mob/living/carbon/proc/get_hand_of_item(obj/item/I) + return get_bodypart(get_hand_zone_of_item(I)) /mob/living/carbon/proc/get_missing_limbs() RETURN_TYPE(/list) diff --git a/tgstation.dme b/tgstation.dme index ca825ca6448f..c70bb1f0a2fd 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1173,6 +1173,7 @@ #include "code\datums\components\plundering_attacks.dm" #include "code\datums\components\pricetag.dm" #include "code\datums\components\profound_fisher.dm" +#include "code\datums\components\prosthetic_item.dm" #include "code\datums\components\punchcooldown.dm" #include "code\datums\components\puzzgrid.dm" #include "code\datums\components\radiation_countdown.dm" @@ -1500,6 +1501,7 @@ #include "code\datums\elements\proficient_miner.dm" #include "code\datums\elements\projectile_drop.dm" #include "code\datums\elements\projectile_shield.dm" +#include "code\datums\elements\prosthetic_icon.dm" #include "code\datums\elements\quality_food_ingredient.dm" #include "code\datums\elements\radiation_protected_clothing.dm" #include "code\datums\elements\radioactive.dm" From ca572455cf0109548b6e98d9e75615a75a91c7c2 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 18:56:29 -0600 Subject: [PATCH 06/35] Fixes --- code/datums/components/prosthetic_item.dm | 3 +++ code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/code/datums/components/prosthetic_item.dm b/code/datums/components/prosthetic_item.dm index b8876f5484f9..1537535974f6 100644 --- a/code/datums/components/prosthetic_item.dm +++ b/code/datums/components/prosthetic_item.dm @@ -137,6 +137,9 @@ * Returns the created pseudopart */ /mob/living/carbon/proc/make_item_prosthetic(obj/item/some_thing, target_zone = BODY_ZONE_R_ARM, fall_prob = 0) + if(HAS_TRAIT_FROM(some_thing, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)) + CRASH("make_item_prosthetic given an item that is already a prosthetic limb!") + var/obj/item/bodypart/existing = get_bodypart(target_zone) existing?.drop_limb(special = TRUE) diff --git a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm index 7957538cf5c7..f76f9058ff51 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm @@ -426,13 +426,14 @@ /obj/item/nullrod/chainsaw/on_selected(obj/item/nullrod/old_weapon, mob/living/picker) if(!iscarbon(picker)) return + to_chat(picker, span_warning("[src] takes the place of your arm!")) var/obj/item/bodypart/active = picker.get_active_hand() var/mob/living/carbon/new_hero = picker new_hero.make_item_prosthetic(src, active.body_zone) /obj/item/nullrod/chainsaw/equipped(mob/living/carbon/user, slot, initial) . = ..() - if(!iscarbon(user)) + if(!iscarbon(user) || HAS_TRAIT_FROM(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)) return if(!(slot & ITEM_SLOT_HANDS)) return From fb1b9c1d9d61c386fdbbb88f5984ebb1ce0daae2 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 23 Mar 2024 16:01:28 -0500 Subject: [PATCH 07/35] Makes Bioware into Status Effects because they're just Status Effects but their own datum (#81989) --- code/__DEFINES/mobs.dm | 7 ---- .../status_effects/buffs/bioware/_bioware.dm | 28 +++++++++++++++ .../buffs/bioware/circulation.dm | 23 ++++++++++++ .../status_effects/buffs/bioware/cortex.dm | 20 +++++++++++ .../status_effects/buffs/bioware/ligaments.dm | 21 +++++++++++ .../status_effects/buffs/bioware/nerves.dm | 25 +++++++++++++ code/modules/mob/living/carbon/human/human.dm | 2 -- .../mob/living/carbon/human/human_defines.dm | 2 -- .../surgery/advanced/bioware/bioware.dm | 36 ------------------- tgstation.dme | 5 +++ 10 files changed, 122 insertions(+), 47 deletions(-) create mode 100644 code/datums/status_effects/buffs/bioware/_bioware.dm create mode 100644 code/datums/status_effects/buffs/bioware/circulation.dm create mode 100644 code/datums/status_effects/buffs/bioware/cortex.dm create mode 100644 code/datums/status_effects/buffs/bioware/ligaments.dm create mode 100644 code/datums/status_effects/buffs/bioware/nerves.dm delete mode 100644 code/modules/surgery/advanced/bioware/bioware.dm diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 6c5ffa373e2e..74e55babbc4e 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -194,13 +194,6 @@ #define BRAIN_DAMAGE_INTEGRITY_MULTIPLIER 0.5 -//Surgery Defines -#define BIOWARE_GENERIC "generic" -#define BIOWARE_NERVES "nerves" -#define BIOWARE_CIRCULATION "circulation" -#define BIOWARE_LIGAMENTS "ligaments" -#define BIOWARE_CORTEX "cortex" - //Health hud screws for carbon mobs #define SCREWYHUD_NONE 0 #define SCREWYHUD_CRIT 1 diff --git a/code/datums/status_effects/buffs/bioware/_bioware.dm b/code/datums/status_effects/buffs/bioware/_bioware.dm new file mode 100644 index 000000000000..c2f7259f2707 --- /dev/null +++ b/code/datums/status_effects/buffs/bioware/_bioware.dm @@ -0,0 +1,28 @@ +/** + * ## Bioware status effect + * + * Simple holder status effects that grants the owner mob basic buffs + */ +/datum/status_effect/bioware + id = "bioware" + alert_type = null + duration = -1 + tick_interval = -1 + +/datum/status_effect/bioware/on_apply() + if(!ishuman(owner)) + return FALSE + + bioware_gained() + return TRUE + +/datum/status_effect/bioware/on_remove() + bioware_lost() + +/// Called when applying to the mob. +/datum/status_effect/bioware/proc/bioware_gained() + return + +/// Called when removing from the mob. +/datum/status_effect/bioware/proc/bioware_lost() + return diff --git a/code/datums/status_effects/buffs/bioware/circulation.dm b/code/datums/status_effects/buffs/bioware/circulation.dm new file mode 100644 index 000000000000..c20efd3aa93e --- /dev/null +++ b/code/datums/status_effects/buffs/bioware/circulation.dm @@ -0,0 +1,23 @@ +// Bioware that affects the heart / circulatory system +/datum/status_effect/bioware/heart + id = "circulation" + +/// Muscled veins - Removes the need to have a heart +/datum/status_effect/bioware/heart/muscled_veins + +/datum/status_effect/bioware/heart/muscled_veins/bioware_gained() + ADD_TRAIT(owner, TRAIT_STABLEHEART, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/bioware/heart/muscled_veins/bioware_lost() + REMOVE_TRAIT(owner, TRAIT_STABLEHEART, TRAIT_STATUS_EFFECT(id)) + +/// Threaded veins - Bleed way less +/datum/status_effect/bioware/heart/threaded_veins + +/datum/status_effect/bioware/heart/threaded_veins/bioware_gained() + var/mob/living/carbon/human/human_owner = owner + human_owner.physiology.bleed_mod *= 0.25 + +/datum/status_effect/bioware/heart/threaded_veins/bioware_lost() + var/mob/living/carbon/human/human_owner = owner + human_owner.physiology.bleed_mod *= 4 diff --git a/code/datums/status_effects/buffs/bioware/cortex.dm b/code/datums/status_effects/buffs/bioware/cortex.dm new file mode 100644 index 000000000000..de11dab1eac4 --- /dev/null +++ b/code/datums/status_effects/buffs/bioware/cortex.dm @@ -0,0 +1,20 @@ +// Bioware that affects the brain +/datum/status_effect/bioware/cortex + id = "cortex" + +// Folded brain - Grants a bonus chance to getting special traumas via lobotomy +/datum/status_effect/bioware/cortex/folded + +/datum/status_effect/bioware/cortex/folded/bioware_gained() + ADD_TRAIT(owner, TRAIT_SPECIAL_TRAUMA_BOOST, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/bioware/cortex/folded/bioware_lost() + REMOVE_TRAIT(owner, TRAIT_SPECIAL_TRAUMA_BOOST, TRAIT_STATUS_EFFECT(id)) + +// Imprinted brain - Cures basic traumas continuously +/datum/status_effect/bioware/cortex/imprinted + tick_interval = 2 SECONDS + +/datum/status_effect/bioware/cortex/imprinted/tick(seconds_between_ticks) + var/mob/living/carbon/human/human_owner = owner + human_owner.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC) diff --git a/code/datums/status_effects/buffs/bioware/ligaments.dm b/code/datums/status_effects/buffs/bioware/ligaments.dm new file mode 100644 index 000000000000..2b41ad01e7ad --- /dev/null +++ b/code/datums/status_effects/buffs/bioware/ligaments.dm @@ -0,0 +1,21 @@ +// Bioware that affects the player's limbs +/datum/status_effect/bioware/ligaments + id = "ligaments" + +// Hooked ligaments - Easier to dismember, but easier to reattach +/datum/status_effect/bioware/ligaments/hooked + +/datum/status_effect/bioware/ligaments/hooked/bioware_gained() + owner.add_traits(list(TRAIT_LIMBATTACHMENT, TRAIT_EASYDISMEMBER), TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/bioware/ligaments/hooked/bioware_lost() + owner.remove_traits(list(TRAIT_LIMBATTACHMENT, TRAIT_EASYDISMEMBER), TRAIT_STATUS_EFFECT(id)) + +// Reinforced ligaments - Easier to break, but cannot be dismembered +/datum/status_effect/bioware/ligaments/reinforced + +/datum/status_effect/bioware/ligaments/reinforced/bioware_gained() + owner.add_traits(list(TRAIT_NODISMEMBER, TRAIT_EASILY_WOUNDED), TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/bioware/ligaments/reinforced/bioware_lost() + owner.remove_traits(list(TRAIT_NODISMEMBER, TRAIT_EASILY_WOUNDED), TRAIT_STATUS_EFFECT(id)) diff --git a/code/datums/status_effects/buffs/bioware/nerves.dm b/code/datums/status_effects/buffs/bioware/nerves.dm new file mode 100644 index 000000000000..36392ca63644 --- /dev/null +++ b/code/datums/status_effects/buffs/bioware/nerves.dm @@ -0,0 +1,25 @@ +// Bioware that affects the CNS +/datum/status_effect/bioware/nerves + id = "nerves" + +// Grounded Nerves - Immunity to being zapped +/datum/status_effect/bioware/nerves/grounded + +/datum/status_effect/bioware/nerves/grounded/bioware_gained() + ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/bioware/nerves/grounded/bioware_lost() + REMOVE_TRAIT(owner, TRAIT_SHOCKIMMUNE, TRAIT_STATUS_EFFECT(id)) + +// Spliced Nerves - Reduced stun time and stamina damage taken +/datum/status_effect/bioware/nerves/spliced + +/datum/status_effect/bioware/nerves/spliced/bioware_gained() + var/mob/living/carbon/human/human_owner = owner + human_owner.physiology.stun_mod *= 0.5 + human_owner.physiology.stamina_mod *= 0.8 + +/datum/status_effect/bioware/nerves/spliced/bioware_lost() + var/mob/living/carbon/human/human_owner = owner + human_owner.physiology.stun_mod *= 2 + human_owner.physiology.stamina_mod *= 1.25 diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index fc8a4bb9b18a..78dc6329775d 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -71,8 +71,6 @@ /mob/living/carbon/human/Destroy() QDEL_NULL(physiology) - if(biowares) - QDEL_LIST(biowares) GLOB.human_list -= src if (mob_mood) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 367dc277e9a2..c70a0cd66fd9 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -70,8 +70,6 @@ var/datum/physiology/physiology - var/list/datum/bioware/biowares - var/account_id var/hardcore_survival_score = 0 diff --git a/code/modules/surgery/advanced/bioware/bioware.dm b/code/modules/surgery/advanced/bioware/bioware.dm deleted file mode 100644 index 4a679e746314..000000000000 --- a/code/modules/surgery/advanced/bioware/bioware.dm +++ /dev/null @@ -1,36 +0,0 @@ -//Bioware -//Body modifications applied through surgery. They generally affect physiology. - -/datum/bioware - var/name = "Generic Bioware" - var/mob/living/carbon/human/owner - var/desc = "If you see this something's wrong, warn a coder." - var/active = FALSE - var/can_process = FALSE - var/mod_type = BIOWARE_GENERIC - -/datum/bioware/New(mob/living/carbon/human/new_owner) - owner = new_owner - for(var/datum/bioware/bioware as anything in owner.biowares) - if(bioware.mod_type == mod_type) - qdel(src) - return - LAZYADD(owner.biowares, src) - on_gain() - -/datum/bioware/Destroy() - if(owner) - LAZYREMOVE(owner.biowares, src) - owner = null - if(active) - on_lose() - return ..() - -/datum/bioware/proc/on_gain() - active = TRUE - if(can_process) - START_PROCESSING(SSobj, src) - -/datum/bioware/proc/on_lose() - STOP_PROCESSING(SSobj, src) - return diff --git a/tgstation.dme b/tgstation.dme index c70bb1f0a2fd..302705aa6734 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1842,6 +1842,11 @@ #include "code\datums\status_effects\buffs\food_traits.dm" #include "code\datums\status_effects\buffs\stop_drop_roll.dm" #include "code\datums\status_effects\buffs\stun_absorption.dm" +#include "code\datums\status_effects\buffs\bioware\_bioware.dm" +#include "code\datums\status_effects\buffs\bioware\circulation.dm" +#include "code\datums\status_effects\buffs\bioware\cortex.dm" +#include "code\datums\status_effects\buffs\bioware\ligaments.dm" +#include "code\datums\status_effects\buffs\bioware\nerves.dm" #include "code\datums\status_effects\debuffs\blindness.dm" #include "code\datums\status_effects\debuffs\block_radio.dm" #include "code\datums\status_effects\debuffs\choke.dm" From 7d43da7fd174225f37f239392d05175e90b008da Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 19:02:59 -0600 Subject: [PATCH 08/35] Bioware stuff --- .../signals/signals_mob/signals_mob_carbon.dm | 2 ++ .../status_effects/buffs/bioware/cortex.dm | 17 ++++++++++++++--- code/modules/mob/living/brain/brain_item.dm | 4 +++- maplestation.dme | 5 +++++ 4 files changed, 24 insertions(+), 4 deletions(-) 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 ffded4af1dca..3e9dcca12775 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -85,6 +85,8 @@ #define COMSIG_CARBON_LOSE_ADDICTION "carbon_lose_addiction" ///Called when a carbon gets a brain trauma (source = carbon, trauma = what trauma was added) - this is before on_gain() #define COMSIG_CARBON_GAIN_TRAUMA "carbon_gain_trauma" + /// Return if you want to prevent the carbon from gaining the brain trauma. + #define COMSIG_CARBON_BLOCK_TRAUMA (1 << 0) ///Called when a carbon loses a brain trauma (source = carbon, trauma = what trauma was removed) #define COMSIG_CARBON_LOSE_TRAUMA "carbon_lose_trauma" ///Called when a carbon's health hud is updated. (source = carbon, shown_health_amount) diff --git a/code/datums/status_effects/buffs/bioware/cortex.dm b/code/datums/status_effects/buffs/bioware/cortex.dm index de11dab1eac4..dbbfce7c5c71 100644 --- a/code/datums/status_effects/buffs/bioware/cortex.dm +++ b/code/datums/status_effects/buffs/bioware/cortex.dm @@ -13,8 +13,19 @@ // Imprinted brain - Cures basic traumas continuously /datum/status_effect/bioware/cortex/imprinted - tick_interval = 2 SECONDS -/datum/status_effect/bioware/cortex/imprinted/tick(seconds_between_ticks) +/datum/status_effect/bioware/cortex/imprinted/bioware_gained() + . = ..() var/mob/living/carbon/human/human_owner = owner - human_owner.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC) + human_owner.cure_all_traumas(resilience = TRAUMA_RESILIENCE_BASIC) + RegisterSignal(human_owner, COMSIG_CARBON_GAIN_TRAUMA, PROC_REF(on_gain_trauma)) + +/datum/status_effect/bioware/cortex/imprinted/bioware_lost() + UnregisterSignal(owner, COMSIG_CARBON_GAIN_TRAUMA) + +/datum/status_effect/bioware/cortex/imprinted/proc/on_gain_trauma(datum/source, datum/brain_trauma/trauma, resilience) + SIGNAL_HANDLER + if(isnull(resilience)) + resilience = trauma.resilience + if(resilience <= TRAUMA_RESILIENCE_BASIC) // there SHOULD be nothing lower than TRAUMA_RESILIENCE_BASIC, but I'd prefer to not make assumptions in case this ever gets some sort of refactor + return COMSIG_CARBON_BLOCK_TRAUMA diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index 8f8ddb57ada4..7a5dc4876ced 100644 --- a/code/modules/mob/living/brain/brain_item.dm +++ b/code/modules/mob/living/brain/brain_item.dm @@ -605,8 +605,10 @@ add_trauma_to_traumas(actual_trauma) if(owner) + if(SEND_SIGNAL(owner, COMSIG_CARBON_GAIN_TRAUMA, trauma, resilience) & COMSIG_CARBON_BLOCK_TRAUMA) + qdel(actual_trauma) + return null actual_trauma.owner = owner - SEND_SIGNAL(owner, COMSIG_CARBON_GAIN_TRAUMA, actual_trauma) actual_trauma.on_gain() if(resilience) actual_trauma.resilience = resilience diff --git a/maplestation.dme b/maplestation.dme index bbc5796b1af5..6dc60daca328 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -1839,6 +1839,11 @@ #include "code\datums\status_effects\buffs\food_traits.dm" #include "code\datums\status_effects\buffs\stop_drop_roll.dm" #include "code\datums\status_effects\buffs\stun_absorption.dm" +#include "code\datums\status_effects\buffs\bioware\_bioware.dm" +#include "code\datums\status_effects\buffs\bioware\circulation.dm" +#include "code\datums\status_effects\buffs\bioware\cortex.dm" +#include "code\datums\status_effects\buffs\bioware\ligaments.dm" +#include "code\datums\status_effects\buffs\bioware\nerves.dm" #include "code\datums\status_effects\debuffs\blindness.dm" #include "code\datums\status_effects\debuffs\block_radio.dm" #include "code\datums\status_effects\debuffs\choke.dm" From 3f24f11052370d8c7349c84fb40ea1122dfa2358 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 16 Mar 2025 19:27:30 +0100 Subject: [PATCH 09/35] Generalizes multi-slot implants and makes autosurgeons support them (#89845) --- code/__DEFINES/inventory.dm | 4 + code/__DEFINES/living.dm | 8 +- code/modules/cargo/packs/medical.dm | 2 +- code/modules/mining/lavaland/tendril_loot.dm | 3 +- code/modules/surgery/organs/_organ.dm | 14 ++- code/modules/surgery/organs/autosurgeon.dm | 17 ++- .../organs/internal/cyberimp/augments_arms.dm | 111 ++++++++---------- .../code/datums/components/limbless_aid.dm | 6 +- .../modules/surgery/organs/augments_arms.dm | 2 +- 9 files changed, 91 insertions(+), 76 deletions(-) diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm index 72d52506f759..20c003b96e0e 100644 --- a/code/__DEFINES/inventory.dm +++ b/code/__DEFINES/inventory.dm @@ -133,6 +133,10 @@ DEFINE_BITFIELD(no_equip_flags, list( //defines for the index of hands #define LEFT_HANDS 1 #define RIGHT_HANDS 2 +/// Checks if the value is "right" - same as ISEVEN, but used primarily for hand or foot index contexts +#define IS_RIGHT_INDEX(value) (value % 2 == 0) +/// Checks if the value is "left" - same as ISODD, but used primarily for hand or foot index contexts +#define IS_LEFT_INDEX(value) (value % 2 != 0) //flags for female outfits: How much the game can safely "take off" the uniform without it looking weird /// For when there's simply no need for a female version of this uniform. diff --git a/code/__DEFINES/living.dm b/code/__DEFINES/living.dm index 56cc96b506f0..2eae6ae4a3ab 100644 --- a/code/__DEFINES/living.dm +++ b/code/__DEFINES/living.dm @@ -119,15 +119,9 @@ #define COLOR_BLOOD "#c90000" -/// Checks if the value is "left" -/// Used primarily for hand or foot indexes -#define IS_RIGHT(value) (value % 2 == 0) -/// Checks if the value is "right" -/// Used primarily for hand or foot indexes -#define IS_LEFT(value) (value % 2 != 0) /// Helper for picking between left or right when given a value /// Used primarily for hand or foot indexes -#define SELECT_LEFT_OR_RIGHT(value, left, right) (IS_LEFT(value) ? left : right) +#define SELECT_LEFT_OR_RIGHT(value, left, right) (IS_LEFT_INDEXvalue) ? left : right) // Used in ready menu anominity /// Hide ckey diff --git a/code/modules/cargo/packs/medical.dm b/code/modules/cargo/packs/medical.dm index babc781b9539..0cb8e0acd5e6 100644 --- a/code/modules/cargo/packs/medical.dm +++ b/code/modules/cargo/packs/medical.dm @@ -187,7 +187,7 @@ name = "Strong-Arm Implant Set" desc = "A crate containing two implants, which can be surgically implanted to empower the strength of human arms. Warranty void if exposed to electromagnetic pulses." cost = CARGO_CRATE_VALUE * 6 - contains = list(/obj/item/organ/cyberimp/arm/muscle = 2) + contains = list(/obj/item/organ/cyberimp/arm/strongarm = 2) crate_name = "Strong-Arm implant crate" discountable = SUPPLY_PACK_RARE_DISCOUNTABLE diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm index f5ef92bfe23c..56b0301dde3c 100644 --- a/code/modules/mining/lavaland/tendril_loot.dm +++ b/code/modules/mining/lavaland/tendril_loot.dm @@ -947,8 +947,7 @@ to_chat(user, span_userdanger("The mass goes up your arm and goes inside it!")) playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) var/index = user.get_held_index_of_item(src) - zone = (index == LEFT_HANDS ? BODY_ZONE_L_ARM : BODY_ZONE_R_ARM) - SetSlotFromZone() + swap_zone(IS_LEFT_INDEX(index) ? BODY_ZONE_L_ARM : BODY_ZONE_R_ARM) user.temporarilyRemoveItemFromInventory(src, TRUE) Insert(user) diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index ec9e562861e4..fff6a9c7bfb0 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -66,6 +66,8 @@ var/list/organ_effects /// String displayed when the organ has decayed. var/failing_desc = "has decayed for too long, and has turned a sickly color. It probably won't work without repairs." + /// Assoc list of alternate zones where this can organ be slotted to organ slot for that zone + var/list/valid_zones = null // Players can look at prefs before atoms SS init, and without this // they would not be able to see external organs, such as moth wings. @@ -156,7 +158,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /obj/item/organ/examine(mob/user) . = ..() - . += span_notice("It should be inserted in the [parse_zone(zone)].") + . += zones_tip() if(organ_flags & ORGAN_FAILING) . += span_warning("[src] [failing_desc]") @@ -168,6 +170,16 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) return . += span_warning("[src] is starting to look discolored.") +/// Returns a line to be displayed regarding valid insertion zones +/obj/item/organ/proc/zones_tip() + if (!valid_zones) + return span_notice("It should be inserted in the [parse_zone(zone)].") + + var/list/fit_zones = list() + for (var/valid_zone in valid_zones) + fit_zones += parse_zone(valid_zone) + return span_notice("It should be inserted in the [english_list(fit_zones, and_text = " or ")].") + ///Used as callbacks by object pooling /obj/item/organ/proc/exit_wardrobe() if(!sprite_accessory_override) diff --git a/code/modules/surgery/organs/autosurgeon.dm b/code/modules/surgery/organs/autosurgeon.dm index 9df0678a6625..ea0ed8faa087 100644 --- a/code/modules/surgery/organs/autosurgeon.dm +++ b/code/modules/surgery/organs/autosurgeon.dm @@ -90,7 +90,22 @@ span_notice("You press a button on [src] as it plunges into your body."), ) - stored_organ.Insert(target)//insert stored organ into the user + if (stored_organ.valid_zones && user.get_held_index_of_item(src)) + var/list/checked_zones = list(user.zone_selected) + if (IS_RIGHT_INDEX(user.get_held_index_of_item(src))) + checked_zones += list(BODY_ZONE_R_ARM, BODY_ZONE_R_LEG) + else + checked_zones += list(BODY_ZONE_L_ARM, BODY_ZONE_L_LEG) + + for (var/check_zone in checked_zones) + if (stored_organ.valid_zones[check_zone]) + stored_organ.swap_zone(check_zone) + break + + if (!stored_organ.Insert(target)) // insert stored organ into the user + balloon_alert(user, "insertion failed!") + return + stored_organ = null name = initial(name) //get rid of the organ in the name playsound(target.loc, 'sound/weapons/circsawhit.ogg', 50, vary = TRUE) diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm index 71bd87ed94f8..2029d06c9733 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm @@ -2,9 +2,14 @@ name = "arm-mounted implant" desc = "You shouldn't see this! Adminhelp and report this as an issue on github!" zone = BODY_ZONE_R_ARM + slot = ORGAN_SLOT_RIGHT_ARM_AUG icon_state = "toolkit_generic" w_class = WEIGHT_CLASS_SMALL actions_types = list(/datum/action/item_action/organ_action/toggle) + valid_zones = list( + BODY_ZONE_R_ARM = ORGAN_SLOT_RIGHT_ARM_AUG, + BODY_ZONE_L_ARM = ORGAN_SLOT_LEFT_ARM_AUG, + ) ///A ref for the arm we're taking up. Mostly for the unregister signal upon removal var/obj/hand //A list of typepaths to create and insert into ourself on init @@ -28,9 +33,6 @@ var/atom/new_item = new typepath(src) items_list += WEAKREF(new_item) - update_appearance() - SetSlotFromZone() - /obj/item/organ/cyberimp/arm/Destroy() hand = null active_item = null @@ -45,37 +47,6 @@ /datum/action/item_action/organ_action/toggle/toolkit desc = "You can also activate your empty hand or the tool in your hand to open the tools radial menu." -/obj/item/organ/cyberimp/arm/proc/SetSlotFromZone() - switch(zone) - if(BODY_ZONE_L_ARM) - slot = ORGAN_SLOT_LEFT_ARM_AUG - if(BODY_ZONE_R_ARM) - slot = ORGAN_SLOT_RIGHT_ARM_AUG - else - CRASH("Invalid zone for [type]") - -/obj/item/organ/cyberimp/arm/update_icon() - . = ..() - transform = (zone == BODY_ZONE_R_ARM) ? null : matrix(-1, 0, 0, 0, 1, 0) - -/obj/item/organ/cyberimp/arm/examine(mob/user) - . = ..() - if(IS_ROBOTIC_ORGAN(src)) - . += span_info("[src] is assembled in the [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm configuration. You can use a screwdriver to reassemble it.") - -/obj/item/organ/cyberimp/arm/screwdriver_act(mob/living/user, obj/item/screwtool) - . = ..() - if(.) - return TRUE - screwtool.play_tool_sound(src) - if(zone == BODY_ZONE_R_ARM) - zone = BODY_ZONE_L_ARM - else - zone = BODY_ZONE_R_ARM - SetSlotFromZone() - to_chat(user, span_notice("You modify [src] to be installed on the [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.")) - update_appearance() - /obj/item/organ/cyberimp/arm/on_mob_insert(mob/living/carbon/arm_owner) . = ..() var/side = zone == BODY_ZONE_R_ARM? RIGHT_HANDS : LEFT_HANDS @@ -126,8 +97,8 @@ return FALSE if(owner) owner.visible_message( - span_notice("[owner] retracts [active_item] back into [owner.p_their()] [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."), - span_notice("[active_item] snaps back into your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."), + span_notice("[owner] retracts [active_item] back into [owner.p_their()] [parse_zone(zone)]."), + span_notice("[active_item] snaps back into your [parse_zone(zone)]."), span_hear("You hear a short mechanical noise."), ) @@ -146,13 +117,12 @@ return active_item = augment - active_item.resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF ADD_TRAIT(active_item, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) active_item.slot_flags = null active_item.set_custom_materials(null) - var/side = zone == BODY_ZONE_R_ARM? RIGHT_HANDS : LEFT_HANDS + var/side = zone == BODY_ZONE_R_ARM ? RIGHT_HANDS : LEFT_HANDS var/hand = owner.get_empty_held_index_for_side(side) if(hand) owner.put_in_hand(active_item, hand) @@ -172,8 +142,8 @@ for(var/i in failure_message) to_chat(owner, i) return - owner.visible_message(span_notice("[owner] extends [active_item] from [owner.p_their()] [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."), - span_notice("You extend [active_item] from your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."), + owner.visible_message(span_notice("[owner] extends [active_item] from [owner.p_their()] [parse_zone(zone)]."), + span_notice("You extend [active_item] from your [parse_zone(zone)]."), span_hear("You hear a short mechanical noise.")) playsound(get_turf(owner), extend_sound, 50, TRUE) @@ -209,40 +179,32 @@ else Retract() - /obj/item/organ/cyberimp/arm/gun/emp_act(severity) . = ..() if(. & EMP_PROTECT_SELF) return if(prob(30/severity) && owner && !(organ_flags & ORGAN_FAILING)) Retract() - owner.visible_message(span_danger("A loud bang comes from [owner]\'s [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm!")) + owner.visible_message(span_danger("A loud bang comes from [owner]\'s [parse_zone(zone)]!")) playsound(get_turf(owner), 'sound/weapons/flashbang.ogg', 100, TRUE) - to_chat(owner, span_userdanger("You feel an explosion erupt inside your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm as your implant breaks!")) + to_chat(owner, span_userdanger("You feel an explosion erupt inside your [parse_zone(zone)] as your implant breaks!")) owner.adjust_fire_stacks(20) owner.ignite_mob() owner.adjustFireLoss(25) organ_flags |= ORGAN_FAILING - /obj/item/organ/cyberimp/arm/gun/laser name = "arm-mounted laser implant" desc = "A variant of the arm cannon implant that fires lethal laser beams. The cannon emerges from the subject's arm and remains inside when not in use." icon_state = "arm_laser" items_to_create = list(/obj/item/gun/energy/laser/mounted/augment) -/obj/item/organ/cyberimp/arm/gun/laser/l - zone = BODY_ZONE_L_ARM - /obj/item/organ/cyberimp/arm/gun/taser name = "arm-mounted taser implant" desc = "A variant of the arm cannon implant that fires electrodes and disabler shots. The cannon emerges from the subject's arm and remains inside when not in use." icon_state = "arm_taser" items_to_create = list(/obj/item/gun/energy/e_gun/advtaser/mounted) -/obj/item/organ/cyberimp/arm/gun/taser/l - zone = BODY_ZONE_L_ARM - /obj/item/organ/cyberimp/arm/toolset name = "integrated toolset implant" desc = "A stripped-down version of the engineering cyborg toolset, designed to be installed on subject's arm. Contain advanced versions of every tool." @@ -257,9 +219,6 @@ /obj/item/multitool/cyborg, ) -/obj/item/organ/cyberimp/arm/toolset/l - zone = BODY_ZONE_L_ARM - /obj/item/organ/cyberimp/arm/toolset/emag_act(mob/user, obj/item/card/emag/emag_card) for(var/datum/weakref/created_item in items_list) var/obj/potential_knife = created_item.resolve() @@ -280,7 +239,6 @@ desc = "A cybernetic implant that allows the user to project a healing beam from their hand." items_to_create = list(/obj/item/gun/medbeam) - /obj/item/organ/cyberimp/arm/flash name = "integrated high-intensity photon projector" //Why not desc = "An integrated projector mounted onto a user's arm that is able to be used as a powerful flash." @@ -358,13 +316,19 @@ /obj/item/knife/combat/cyborg, ) -/obj/item/organ/cyberimp/arm/muscle +#define DOAFTER_SOURCE_STRONGARM_INTERACTION "strongarm interaction" + +/obj/item/organ/cyberimp/arm/strongarm name = "\proper Strong-Arm empowered musculature implant" desc = "When implanted, this cybernetic implant will enhance the muscles of the arm to deliver more power-per-action." icon_state = "muscle_implant" zone = BODY_ZONE_R_ARM slot = ORGAN_SLOT_RIGHT_ARM_AUG + valid_zones = list( + BODY_ZONE_R_ARM = ORGAN_SLOT_RIGHT_ARM_AUG, + BODY_ZONE_L_ARM = ORGAN_SLOT_LEFT_ARM_AUG, + ) actions_types = list() @@ -380,17 +344,25 @@ var/throw_power_max = 4 ///How long will the implant malfunction if it is EMP'd var/emp_base_duration = 9 SECONDS + ///How long before we get another slam punch; consider that these usually come in pairs of two + var/slam_cooldown_duration = 5 SECONDS + ///Tracks how soon we can perform another slam attack + COOLDOWN_DECLARE(slam_cooldown) + +/obj/item/organ/cyberimp/arm/strongarm/Initialize(mapload) + . = ..() + AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/strongarm) -/obj/item/organ/cyberimp/arm/muscle/on_mob_insert(mob/living/carbon/arm_owner) +/obj/item/organ/cyberimp/arm/strongarm/on_mob_insert(mob/living/carbon/arm_owner) . = ..() if(ishuman(arm_owner)) //Sorry, only humans RegisterSignal(arm_owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand)) -/obj/item/organ/cyberimp/arm/muscle/on_mob_remove(mob/living/carbon/arm_owner) +/obj/item/organ/cyberimp/arm/strongarm/on_mob_remove(mob/living/carbon/arm_owner) . = ..() UnregisterSignal(arm_owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK) -/obj/item/organ/cyberimp/arm/muscle/emp_act(severity) +/obj/item/organ/cyberimp/arm/strongarm/emp_act(severity) . = ..() if((organ_flags & ORGAN_FAILING) || . & EMP_PROTECT_SELF) return @@ -398,11 +370,11 @@ organ_flags |= ORGAN_FAILING addtimer(CALLBACK(src, PROC_REF(reboot)), 90 / severity) -/obj/item/organ/cyberimp/arm/muscle/proc/reboot() +/obj/item/organ/cyberimp/arm/strongarm/proc/reboot() organ_flags &= ~ORGAN_FAILING owner.balloon_alert(owner, "your arm stops spasming!") -/obj/item/organ/cyberimp/arm/muscle/proc/on_attack_hand(mob/living/carbon/human/source, atom/target, proximity, modifiers) +/obj/item/organ/cyberimp/arm/strongarm/proc/on_attack_hand(mob/living/carbon/human/source, atom/target, proximity, modifiers) SIGNAL_HANDLER if(source.get_active_hand() != hand || !proximity) @@ -468,3 +440,22 @@ log_combat(source, target, "[picked_hit_type]ed", "muscle implant") return COMPONENT_CANCEL_ATTACK_CHAIN + +/datum/status_effect/organ_set_bonus/strongarm + id = "organ_set_bonus_strongarm" + organs_needed = 2 + bonus_activate_text = span_notice("Your improved arms allow you to open airlocks by force with your bare hands!") + bonus_deactivate_text = span_notice("You can no longer force open airlocks with your bare hands.") + required_biotype = NONE + +/datum/status_effect/organ_set_bonus/strongarm/enable_bonus(obj/item/organ/inserted_organ) + . = ..() + if(!.) + return + owner.AddElement(/datum/element/door_pryer, pry_time = 6 SECONDS, interaction_key = DOAFTER_SOURCE_STRONGARM_INTERACTION) + +/datum/status_effect/organ_set_bonus/strongarm/disable_bonus(obj/item/organ/removed_organ) + . = ..() + owner.RemoveElement(/datum/element/door_pryer, pry_time = 6 SECONDS, interaction_key = DOAFTER_SOURCE_STRONGARM_INTERACTION) + +#undef DOAFTER_SOURCE_STRONGARM_INTERACTION diff --git a/maplestation_modules/code/datums/components/limbless_aid.dm b/maplestation_modules/code/datums/components/limbless_aid.dm index df66bcb93ad6..f3abb5f3f791 100644 --- a/maplestation_modules/code/datums/components/limbless_aid.dm +++ b/maplestation_modules/code/datums/components/limbless_aid.dm @@ -77,7 +77,7 @@ if(required_slot & ITEM_SLOT_HANDS) // this is not backwards intentionally: // if you're missing the left leg, you need the left leg braced - var/side = IS_RIGHT(source.get_held_index_of_item(parent)) ? BODY_ZONE_R_LEG : BODY_ZONE_L_LEG + var/side = IS_RIGHT_INDEX(source.get_held_index_of_item(parent)) ? BODY_ZONE_R_LEG : BODY_ZONE_L_LEG leg = source.get_bodypart(side) if(isnull(leg) || leg.bodypart_disabled) @@ -90,7 +90,7 @@ if(required_slot & ITEM_SLOT_HANDS) // note this is backwards intentionally: // see below - var/side = IS_RIGHT(source.get_held_index_of_item(parent)) ? BODY_ZONE_L_LEG : BODY_ZONE_R_LEG + var/side = IS_RIGHT_INDEX(source.get_held_index_of_item(parent)) ? BODY_ZONE_L_LEG : BODY_ZONE_R_LEG leg = source.get_bodypart(side) if(isnull(leg) || leg == affected_leg) @@ -103,7 +103,7 @@ if(required_slot & ITEM_SLOT_HANDS) // note this is backwards intentionally: // you use your right arm to brace your left leg, and vice versa - var/side = IS_RIGHT(source.get_held_index_of_item(parent)) ? BODY_ZONE_L_LEG : BODY_ZONE_R_LEG + var/side = IS_RIGHT_INDEX(source.get_held_index_of_item(parent)) ? BODY_ZONE_L_LEG : BODY_ZONE_R_LEG leg = source.get_bodypart(side) if(isnull(leg) || leg == next_leg) diff --git a/maplestation_modules/code/modules/surgery/organs/augments_arms.dm b/maplestation_modules/code/modules/surgery/organs/augments_arms.dm index e7c23e868ac6..83b84d1a44c4 100644 --- a/maplestation_modules/code/modules/surgery/organs/augments_arms.dm +++ b/maplestation_modules/code/modules/surgery/organs/augments_arms.dm @@ -188,7 +188,7 @@ /obj/item/organ/cyberimp/arm/surgery icon_state = "toolkit_surgical" -/obj/item/organ/cyberimp/arm/muscle +/obj/item/organ/cyberimp/arm/strongarm icon_state = "muscle_implant" /obj/item/organ/cyberimp/arm/gun/taser From 5a035e6ae156b27e011f6618c3205853c2f57ee2 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 19:13:33 -0600 Subject: [PATCH 10/35] Fixes --- code/__DEFINES/living.dm | 2 +- code/game/objects/items/chainsaw.dm | 6 +++--- code/game/objects/items/robot/items/tools.dm | 2 +- code/modules/mining/lavaland/tendril_loot.dm | 2 +- maplestation.dme | 2 ++ 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/code/__DEFINES/living.dm b/code/__DEFINES/living.dm index 2eae6ae4a3ab..2f1a6db2f7e9 100644 --- a/code/__DEFINES/living.dm +++ b/code/__DEFINES/living.dm @@ -121,7 +121,7 @@ /// Helper for picking between left or right when given a value /// Used primarily for hand or foot indexes -#define SELECT_LEFT_OR_RIGHT(value, left, right) (IS_LEFT_INDEXvalue) ? left : right) +#define SELECT_LEFT_OR_RIGHT(value, left, right) (IS_LEFT_INDEX(value) ? left : right) // Used in ready menu anominity /// Hide ckey diff --git a/code/game/objects/items/chainsaw.dm b/code/game/objects/items/chainsaw.dm index 519569b29081..647d50a8fedf 100644 --- a/code/game/objects/items/chainsaw.dm +++ b/code/game/objects/items/chainsaw.dm @@ -6,7 +6,7 @@ icon = 'icons/obj/weapons/chainsaw.dmi' icon_state = "chainsaw" base_icon_state = "chainsaw" - icon_angle = 180 + // icon_angle = 180 lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -100,11 +100,11 @@ /obj/item/chainsaw/suicide_act(mob/living/carbon/user) if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) user.visible_message(span_suicide("[user] smashes [src] into [user.p_their()] neck, destroying [user.p_their()] esophagus! It looks like [user.p_theyre()] trying to commit suicide!")) - playsound(src, 'sound/items/weapons/genhit1.ogg', 100, TRUE) + playsound(src, 'sound/weapons/genhit1.ogg', 100, TRUE) return BRUTELOSS user.visible_message(span_suicide("[user] begins to tear [user.p_their()] head off with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) - playsound(src, 'sound/items/weapons/chainsawhit.ogg', 100, TRUE) + playsound(src, 'sound/weapons/chainsawhit.ogg', 100, TRUE) var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) if(myhead) myhead.dismember() diff --git a/code/game/objects/items/robot/items/tools.dm b/code/game/objects/items/robot/items/tools.dm index 861e5b64a812..28e4fa0aac15 100644 --- a/code/game/objects/items/robot/items/tools.dm +++ b/code/game/objects/items/robot/items/tools.dm @@ -245,7 +245,7 @@ //set the reference & update icons set_internal_tool(tool_map[internal_tool_name]) update_appearance(UPDATE_ICON_STATE) - playsound(src, 'sound/items/tools/change_jaws.ogg', 50, TRUE) + playsound(src, 'sound/items/change_jaws.ogg', 50, TRUE) /obj/item/borg/cyborg_omnitool/update_icon_state() if (reference) diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm index 56b0301dde3c..9c90e8f7d43f 100644 --- a/code/modules/mining/lavaland/tendril_loot.dm +++ b/code/modules/mining/lavaland/tendril_loot.dm @@ -947,7 +947,7 @@ to_chat(user, span_userdanger("The mass goes up your arm and goes inside it!")) playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) var/index = user.get_held_index_of_item(src) - swap_zone(IS_LEFT_INDEX(index) ? BODY_ZONE_L_ARM : BODY_ZONE_R_ARM) + swap_zone(SELECT_LEFT_OR_RIGHT(index, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) user.temporarilyRemoveItemFromInventory(src, TRUE) Insert(user) diff --git a/maplestation.dme b/maplestation.dme index 6dc60daca328..d339af4ab25d 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -1174,6 +1174,7 @@ #include "code\datums\components\plundering_attacks.dm" #include "code\datums\components\pricetag.dm" #include "code\datums\components\profound_fisher.dm" +#include "code\datums\components\prosthetic_item.dm" #include "code\datums\components\punchcooldown.dm" #include "code\datums\components\puzzgrid.dm" #include "code\datums\components\radiation_countdown.dm" @@ -1501,6 +1502,7 @@ #include "code\datums\elements\proficient_miner.dm" #include "code\datums\elements\projectile_drop.dm" #include "code\datums\elements\projectile_shield.dm" +#include "code\datums\elements\prosthetic_icon.dm" #include "code\datums\elements\quality_food_ingredient.dm" #include "code\datums\elements\radiation_protected_clothing.dm" #include "code\datums\elements\radioactive.dm" From 7b7d0ee8ddc052a130514b544eef3e6e78f3a39b Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sun, 13 Apr 2025 04:08:17 -0500 Subject: [PATCH 11/35] Demolition mod is inverted vs "soft" structures like spider webs (#90464) --- code/__DEFINES/traits/declarations.dm | 3 +++ code/_globalvars/traits/_traits.dm | 1 + code/_onclick/item_attack.dm | 2 +- code/game/objects/effects/spiderwebs.dm | 1 + code/game/objects/items/fireaxe.dm | 3 +++ code/game/objects/items/melee/energy.dm | 3 +++ code/game/objects/obj_defense.dm | 7 +++--- code/game/objects/objs.dm | 26 +++++++++++++++----- code/game/objects/structures/curtains.dm | 3 ++- code/modules/unit_tests/mecha_damage.dm | 2 +- code/modules/vehicles/mecha/mecha_defense.dm | 2 +- 11 files changed, 40 insertions(+), 13 deletions(-) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index c2bf531e175e..35e4fe22d3d6 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1249,4 +1249,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Doesn't need to be applied to any turfs that override can_cross_safely #define TRAIT_AI_AVOID_TURF "warning_turf" +/// Demolition modifier when hitting this object is inverted (ie, 1 / demolition) +#define TRAIT_INVERTED_DEMOLITION "demolition_inverted" + // END TRAIT DEFINES diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 09ac1d941ca3..fc31d8b282f6 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -47,6 +47,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_HEARING_SENSITIVE" = TRAIT_HEARING_SENSITIVE, "TRAIT_HYPERSPACED" = TRAIT_HYPERSPACED, "TRAIT_IMMERSED" = TRAIT_IMMERSED, + "TRAIT_INVERTED_DEMOLITION" = TRAIT_INVERTED_DEMOLITION, "TRAIT_IRRADIATED" = TRAIT_IRRADIATED, "TRAIT_LAVA_IMMUNE" = TRAIT_LAVA_IMMUNE, "TRAIT_MOVE_FLOATING" = TRAIT_MOVE_FLOATING, diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index a9216b6f2383..c5fb095d034b 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -314,7 +314,7 @@ var/damage = attacking_item.force * user.outgoing_damage_mod if(mob_biotypes & MOB_ROBOTIC) - damage *= attacking_item.demolition_mod + damage *= attacking_item.get_demolition_modifier(src) var/wounding = attacking_item.wound_bonus if((attacking_item.item_flags & SURGICAL_TOOL) && !user.combat_mode && HAS_TRAIT(user, TRAIT_READY_TO_OPERATE)) diff --git a/code/game/objects/effects/spiderwebs.dm b/code/game/objects/effects/spiderwebs.dm index 5023f9bd8254..79f8562357e1 100644 --- a/code/game/objects/effects/spiderwebs.dm +++ b/code/game/objects/effects/spiderwebs.dm @@ -10,6 +10,7 @@ /obj/structure/spider/Initialize(mapload) . = ..() AddElement(/datum/element/atmos_sensitive, mapload) + ADD_TRAIT(src, TRAIT_INVERTED_DEMOLITION, INNATE_TRAIT) /obj/structure/spider/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) if(damage_type == BURN)//the stickiness of the web mutes all attack sounds except fire damage type diff --git a/code/game/objects/items/fireaxe.dm b/code/game/objects/items/fireaxe.dm index 06cd100dc017..e9804096a82a 100644 --- a/code/game/objects/items/fireaxe.dm +++ b/code/game/objects/items/fireaxe.dm @@ -46,6 +46,9 @@ //axes are not known for being precision butchering tools AddComponent(/datum/component/two_handed, force_unwielded=force_unwielded, force_wielded=force_wielded, icon_wielded="[base_icon_state]1") +/obj/item/fireaxe/get_demolition_modifier(obj/target) + return HAS_TRAIT(src, TRAIT_WIELDED) ? demolition_mod : 0.8 + /obj/item/fireaxe/update_icon_state() icon_state = "[base_icon_state]0" return ..() diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index a283c08ca8ce..13b9ba2bd32c 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -91,6 +91,9 @@ playsound(loc, hitsound, get_clamped_volume(), TRUE, -1) add_fingerprint(user) +/obj/item/melee/energy/get_demolition_modifier(obj/target) + return HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE) ? demolition_mod : 1 + /obj/item/melee/energy/update_icon_state() . = ..() if(!sword_color_icon) diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index e0f2d91673ad..10ccfd7207e7 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -1,9 +1,9 @@ -/obj/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) +/obj/hitby(atom/movable/hit_by, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) . = ..() if(QDELETED(src)) return - hit_by_damage(AM, throwingdatum) + hit_by_damage(hit_by, throwingdatum) /obj/proc/hit_by_damage(atom/movable/hitting_us, datum/thrownthing/throwingdatum) var/base_dam = hitting_us.throwforce @@ -16,6 +16,7 @@ if(isitem(hitting_us)) var/obj/item/hit_item = hitting_us base_dam += (5 * max(0, hit_item.w_class - 2)) + base_dam *= as_item.get_demolition_modifier(src) // no armor penetration take_damage(base_dam, BRUTE, MELEE, TRUE, get_dir(src, hitting_us), 0) @@ -112,7 +113,7 @@ var/damage_sustained = 0 if(!QDELETED(src)) //Bullet on_hit effect might have already destroyed this object damage_sustained = take_damage( - hitting_projectile.damage * hitting_projectile.demolition_mod, + hitting_projectile.damage * hitting_projectile.get_demolition_modifier(src), hitting_projectile.damage_type, hitting_projectile.armor_flag, FALSE, diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index b8cb334f962c..633b271b08c1 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -24,7 +24,8 @@ /// If this attacks a human with no wound armor on the affected body part, add this to the wound mod. Some attacks may be significantly worse at wounding if there's even a slight layer of armor to absorb some of it vs bare flesh var/bare_wound_bonus = 0 - /// A multiplier to an objecet's force when used against a stucture, vechicle, machine, or robot. + /// A multiplier to an object's force when used against a structure, vehicle, machine, or robot. + /// Use [/obj/proc/get_demolition_modifier] to get the value. var/demolition_mod = 1 // Access levels, used in modules\jobs\access.dm @@ -82,15 +83,22 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag) if(!attacking_item.force) return - var/total_force = (attacking_item.force * attacking_item.demolition_mod) + var/demo_mod = attacking_item.get_demolition_modifier(src) + var/total_force = (attacking_item.force * demo_mod) + var/damage = take_damage(total_force, attacking_item.damtype, MELEE, TRUE, get_dir(src, user), attacking_item.armour_penetration) - var/damage = take_damage(total_force, attacking_item.damtype, MELEE, 1, get_dir(src, user)) + // Sanity in case one is null for some reason + var/picked_index = rand(max(length(attacking_item.attack_verb_simple), length(attacking_item.attack_verb_continuous))) var/damage_verb = "hit" - if(attacking_item.demolition_mod > 1 && damage) - damage_verb = "pulverise" - if(attacking_item.demolition_mod < 1) + if(demo_mod > 1 && prob(damage * 5)) + if(HAS_TRAIT(src, TRAIT_INVERTED_DEMOLITION)) + damage_verb = "shred" + else + damage_verb = "pulverise" + + if(demo_mod < 1) damage_verb = "ineffectively pierce" user.visible_message(span_danger("[user] [damage_verb][plural_s(damage_verb)] [src] with [attacking_item][damage ? "." : ", [no_damage_feedback]!"]"), \ @@ -378,3 +386,9 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag) pixel_z = anchored_tabletop_offset else pixel_z = initial(pixel_z) + +/// Returns modifier to how much damage this object does to a target considered vulnerable to "demolition" (other objects, robots, etc) +/obj/proc/get_demolition_modifier(obj/target) + if(HAS_TRAIT(target, TRAIT_INVERTED_DEMOLITION)) + return (1 / demolition_mod) + return demolition_mod diff --git a/code/game/objects/structures/curtains.dm b/code/game/objects/structures/curtains.dm index aead6fafb017..046f0598b11c 100644 --- a/code/game/objects/structures/curtains.dm +++ b/code/game/objects/structures/curtains.dm @@ -22,7 +22,8 @@ // see-through curtains should let emissives shine through if(!opaque_closed) blocks_emissive = EMISSIVE_BLOCK_NONE - return ..() + . = ..() + ADD_TRAIT(src, TRAIT_INVERTED_DEMOLITION, INNATE_TRAIT) /obj/structure/curtain/proc/toggle() open = !open diff --git a/code/modules/unit_tests/mecha_damage.dm b/code/modules/unit_tests/mecha_damage.dm index 9dd82dddc41e..147f78cd8a82 100644 --- a/code/modules/unit_tests/mecha_damage.dm +++ b/code/modules/unit_tests/mecha_damage.dm @@ -26,7 +26,7 @@ // The energy axe is chosen here due to having a high base force, to make sure we get over the equipment DT. var/obj/item/dummy_melee = allocate(/obj/item/melee/energy/axe) dummy_melee.force = 150 - var/expected_melee_damage = round(dummy_melee.force * (1 - expected_melee_armor / 100) * dummy_melee.demolition_mod * demo_mech.facing_modifiers[MECHA_FRONT_ARMOUR], DAMAGE_PRECISION) + var/expected_melee_damage = round(dummy_melee.force * (1 - expected_melee_armor / 100) * dummy_melee.get_demolition_modifier(demo_mech) * demo_mech.facing_modifiers[MECHA_FRONT_ARMOUR], DAMAGE_PRECISION) // Get a sample laser weapon. // The captain's laser gun here is chosen primarily because it deals more damage than normal lasers. diff --git a/code/modules/vehicles/mecha/mecha_defense.dm b/code/modules/vehicles/mecha/mecha_defense.dm index 5a335b58a609..c37ea8381754 100644 --- a/code/modules/vehicles/mecha/mecha_defense.dm +++ b/code/modules/vehicles/mecha/mecha_defense.dm @@ -324,7 +324,7 @@ var/old_internals = internal_damage - var/damage_taken = take_damage(attacking_item.force * attacking_item.demolition_mod, attacking_item.damtype, MELEE, 1, get_dir(src, user)) + var/damage_taken = take_damage(attacking_item.force * attacking_item.get_demolition_modifier(src), attacking_item.damtype, MELEE, 1, get_dir(src, user)) try_damage_component(damage_taken, user.zone_selected) var/hit_verb = length(attacking_item.attack_verb_simple) ? "[pick(attacking_item.attack_verb_simple)]" : "hit" From be5970691fdbd2a191b752dcab9a88829a574b38 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:47:55 -0600 Subject: [PATCH 12/35] Removes Plasmeme innate damage vulnerability, replaces it with demolition vulnerability (#94328) --- code/__DEFINES/mobs.dm | 8 ++++++++ code/_onclick/item_attack.dm | 2 +- code/modules/mob/living/basic/festivus_pole.dm | 2 ++ code/modules/mob/living/basic/heretic/raw_prophet.dm | 1 + code/modules/mob/living/basic/heretic/rust_walker.dm | 1 + .../mob/living/basic/icemoon/ice_demon/ice_demon.dm | 1 + .../mob/living/basic/lavaland/basilisk/basilisk.dm | 1 + .../mob/living/basic/ruin_defender/living_floor.dm | 2 +- .../mob/living/basic/ruin_defender/skeleton.dm | 2 +- .../mob/living/basic/space_fauna/statue/statue.dm | 2 +- .../living/basic/space_fauna/supermatter_spider.dm | 3 ++- code/modules/mob/living/carbon/human/_species.dm | 8 ++++++++ .../living/carbon/human/species_types/plasmamen.dm | 2 +- .../living/carbon/human/species_types/skeletons.dm | 3 ++- .../hostile/megafauna/clockwork_knight.dm | 1 + .../mob/living/simple_animal/hostile/mimic.dm | 2 +- .../hostile/mining_mobs/elites/pandora.dm | 1 + .../bodyparts/species_parts/plasmaman_bodyparts.dm | 12 ------------ 18 files changed, 34 insertions(+), 20 deletions(-) diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 74e55babbc4e..b97a330cbf04 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -73,6 +73,14 @@ #define MOB_PLANT (1 << 10) ///The mob is a goopy creature, probably coming from xenobiology. #define MOB_SLIME (1 << 11) +///The mob is fish or water-related. +#define MOB_AQUATIC (1 << 12) +///The mob is a mining-related mob. It's the plasma, you see. Gets in ya bones. +#define MOB_MINING (1 << 13) +///The mob is a crustacean. Like crabs. Or lobsters. +#define MOB_CRUSTACEAN (1 << 14) +///The mob is all boney +#define MOB_SKELETAL (1 << 15) //Lung respiration type flags #define RESPIRATION_OXYGEN (1 << 0) diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index c5fb095d034b..adbecdee6884 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -313,7 +313,7 @@ ), ARMOR_MAX_BLOCK) var/damage = attacking_item.force * user.outgoing_damage_mod - if(mob_biotypes & MOB_ROBOTIC) + if(mob_biotypes & (MOB_ROBOTIC|MOB_MINERAL|MOB_SKELETAL)) // this should probably check hit bodypart for humanoids damage *= attacking_item.get_demolition_modifier(src) var/wounding = attacking_item.wound_bonus diff --git a/code/modules/mob/living/basic/festivus_pole.dm b/code/modules/mob/living/basic/festivus_pole.dm index 1c1a88dd31fe..210dd5df789c 100644 --- a/code/modules/mob/living/basic/festivus_pole.dm +++ b/code/modules/mob/living/basic/festivus_pole.dm @@ -13,6 +13,8 @@ gender = NEUTER gold_core_spawnable = HOSTILE_SPAWN basic_mob_flags = DEL_ON_DEATH + status_flags = CANPUSH + mob_biotypes = MOB_MINERAL response_help_continuous = "rubs" response_help_simple = "rub" diff --git a/code/modules/mob/living/basic/heretic/raw_prophet.dm b/code/modules/mob/living/basic/heretic/raw_prophet.dm index 1d0d0f93f203..7a74cd82b898 100644 --- a/code/modules/mob/living/basic/heretic/raw_prophet.dm +++ b/code/modules/mob/living/basic/heretic/raw_prophet.dm @@ -15,6 +15,7 @@ maxHealth = 65 health = 65 sight = SEE_MOBS|SEE_OBJS|SEE_TURFS + mob_biotypes = MOB_ORGANIC /// List of innate abilities we have to add. var/static/list/innate_abilities = list( /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/long = null, diff --git a/code/modules/mob/living/basic/heretic/rust_walker.dm b/code/modules/mob/living/basic/heretic/rust_walker.dm index cb3e2c56b755..9a26e3b4d29e 100644 --- a/code/modules/mob/living/basic/heretic/rust_walker.dm +++ b/code/modules/mob/living/basic/heretic/rust_walker.dm @@ -13,6 +13,7 @@ sight = SEE_TURFS speed = 1 ai_controller = /datum/ai_controller/basic_controller/rust_walker + mob_biotypes = MOB_ROBOTIC|MOB_MINERAL /mob/living/basic/heretic_summon/rust_walker/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm index d8f071adb20e..af3540c3fb5f 100644 --- a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm @@ -8,6 +8,7 @@ mob_biotypes = MOB_ORGANIC|MOB_BEAST mouse_opacity = MOUSE_OPACITY_ICON basic_mob_flags = DEL_ON_DEATH + mob_biotypes = MOB_ORGANIC|MOB_MINERAL|MOB_MINING speed = 2 maxHealth = 150 health = 150 diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm index 2a13e004625d..0e3102ed6604 100644 --- a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm +++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm @@ -7,6 +7,7 @@ icon_dead = "basilisk_dead" speak_emote = list("chimes") damage_coeff = list(BRUTE = 1, BURN = 0.1, TOX = 1, STAMINA = 0, OXY = 1) + mob_biotypes = parent_type::mob_biotypes | MOB_MINERAL speed = 20 maxHealth = 200 health = 200 diff --git a/code/modules/mob/living/basic/ruin_defender/living_floor.dm b/code/modules/mob/living/basic/ruin_defender/living_floor.dm index fa9b0ba3eae9..657f66fe8718 100644 --- a/code/modules/mob/living/basic/ruin_defender/living_floor.dm +++ b/code/modules/mob/living/basic/ruin_defender/living_floor.dm @@ -25,7 +25,7 @@ icon_state = "floor" icon_living = "floor" mob_size = MOB_SIZE_HUGE - mob_biotypes = MOB_SPECIAL + mob_biotypes = MOB_SPECIAL|MOB_MINERAL status_flags = GODMODE //nothing but crowbars may kill us death_message = "" unsuitable_atmos_damage = 0 diff --git a/code/modules/mob/living/basic/ruin_defender/skeleton.dm b/code/modules/mob/living/basic/ruin_defender/skeleton.dm index 08446ce8f5aa..b10646f916e7 100644 --- a/code/modules/mob/living/basic/ruin_defender/skeleton.dm +++ b/code/modules/mob/living/basic/ruin_defender/skeleton.dm @@ -3,7 +3,7 @@ desc = "A real bonefied skeleton, doesn't seem like it wants to socialize." gender = NEUTER icon = 'icons/mob/simple/simple_human.dmi' - mob_biotypes = MOB_UNDEAD|MOB_HUMANOID + mob_biotypes = MOB_UNDEAD|MOB_HUMANOID|MOB_SKELETAL speak_emote = list("rattles") maxHealth = 40 health = 40 diff --git a/code/modules/mob/living/basic/space_fauna/statue/statue.dm b/code/modules/mob/living/basic/space_fauna/statue/statue.dm index c737908cf497..cdc8b1e18aff 100644 --- a/code/modules/mob/living/basic/space_fauna/statue/statue.dm +++ b/code/modules/mob/living/basic/space_fauna/statue/statue.dm @@ -9,7 +9,7 @@ icon_dead = "human_male" gender = NEUTER combat_mode = TRUE - mob_biotypes = MOB_HUMANOID + mob_biotypes = MOB_HUMANOID|MOB_MINERAL gold_core_spawnable = NO_SPAWN response_help_continuous = "touches" diff --git a/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm b/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm index 16260856cab2..a8ce1c93be4e 100644 --- a/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm +++ b/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm @@ -9,7 +9,8 @@ icon_dead = "smspider_dead" gender = NEUTER - mob_biotypes = MOB_BUG|MOB_ROBOTIC + status_flags = CANPUSH + mob_biotypes = MOB_BUG|MOB_ROBOTIC|MOB_MINERAL speak_emote = list("vibrates") diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 3277df806e13..e9947b2b405e 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -1279,6 +1279,14 @@ GLOBAL_LIST_EMPTY(features_by_species) SPECIES_PERK_DESC = "[plural_form] are resilient to being shocked.", )) + if(inherent_biotypes & (MOB_ROBOTIC|MOB_MINERAL)) + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = FA_ICON_HAMMER, + SPECIES_PERK_NAME = "Tough Frame", + SPECIES_PERK_DESC = "[plural_form] are more resistant to slashing and stabbing, but more vulnerable to impacts.", + )) + return to_add /** diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index b1fc52ef0a34..683c8c2eb7fc 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -16,7 +16,7 @@ TRAIT_UNHUSKABLE, ) - inherent_biotypes = MOB_HUMANOID|MOB_MINERAL + inherent_biotypes = MOB_HUMANOID|MOB_MINERAL|MOB_SKELETAL inherent_respiration_type = RESPIRATION_PLASMA mutantlungs = /obj/item/organ/lungs/plasmaman mutanttongue = /obj/item/organ/tongue/bone/plasmaman diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm index de472bc6ee55..327db34e4c87 100644 --- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm +++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm @@ -23,7 +23,8 @@ TRAIT_UNHUSKABLE, TRAIT_XENO_IMMUNE, ) - inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID + inherent_factions = list(FACTION_SKELETON) + inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID|MOB_SKELETAL mutanttongue = /obj/item/organ/tongue/bone mutantstomach = /obj/item/organ/stomach/bone mutantappendix = null diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/clockwork_knight.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/clockwork_knight.dm index 4ce57dda3c48..f58fc0669c04 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/clockwork_knight.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/clockwork_knight.dm @@ -25,6 +25,7 @@ I'd rather there be something than the clockwork ruin be entirely empty though s armour_penetration = 40 melee_damage_lower = 20 melee_damage_upper = 20 + mob_biotypes = MOB_ROBOTIC|MOB_SPECIAL|MOB_MINING|MOB_MINERAL vision_range = 9 aggro_vision_range = 9 speed = 5 diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm index 32038b51fb9e..e6bc24e14d3e 100644 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm @@ -21,7 +21,7 @@ GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( maxHealth = 250 health = 250 gender = NEUTER - mob_biotypes = NONE + mob_biotypes = MOB_MINERAL pass_flags = PASSFLAPS harm_intent_damage = 5 diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm index a9babf2eccaf..162aaa15fdef 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm @@ -36,6 +36,7 @@ speed = 3 move_to_delay = 10 mouse_opacity = MOUSE_OPACITY_ICON + mob_biotypes = MOB_ROBOTIC|MOB_MINING|MOB_MINERAL death_sound = 'sound/magic/repulse.ogg' death_message = "'s lights flicker, before its top part falls down." loot_drop = /obj/item/clothing/accessory/pandora_hope diff --git a/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm index e18edc6c3868..e3e883fa01e9 100644 --- a/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm @@ -7,8 +7,6 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE dmg_overlay_type = null - brute_modifier = 1.5 //Plasmemes are weak - burn_modifier = 1.5 //Plasmemes are weak head_flags = HEAD_EYESPRITES bodypart_flags = BODYPART_UNHUSKABLE @@ -25,8 +23,6 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE dmg_overlay_type = null - brute_modifier = 1.5 //Plasmemes are weak - burn_modifier = 1.5 //Plasmemes are weak bodypart_flags = BODYPART_UNHUSKABLE wing_types = NONE @@ -45,8 +41,6 @@ limb_id = SPECIES_PLASMAMAN should_draw_greyscale = FALSE dmg_overlay_type = null - brute_modifier = 1.5 //Plasmemes are weak - burn_modifier = 1.5 //Plasmemes are weak bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/arm/left/plasmaman/Initialize(mapload) @@ -61,8 +55,6 @@ limb_id = SPECIES_PLASMAMAN should_draw_greyscale = FALSE dmg_overlay_type = null - brute_modifier = 1.5 //Plasmemes are weak - burn_modifier = 1.5 //Plasmemes are weak bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/arm/right/plasmaman/Initialize(mapload) @@ -77,8 +69,6 @@ limb_id = SPECIES_PLASMAMAN should_draw_greyscale = FALSE dmg_overlay_type = null - brute_modifier = 1.5 //Plasmemes are weak - burn_modifier = 1.5 //Plasmemes are weak bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/leg/left/plasmaman/Initialize(mapload) @@ -93,8 +83,6 @@ limb_id = SPECIES_PLASMAMAN should_draw_greyscale = FALSE dmg_overlay_type = null - brute_modifier = 1.5 //Plasmemes are weak - burn_modifier = 1.5 //Plasmemes are weak bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/leg/right/plasmaman/Initialize(mapload) From 04a936325ded9d524ffa1fd54ee1422b64f65640 Mon Sep 17 00:00:00 2001 From: necromanceranne <40847847+necromanceranne@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:24:43 +1100 Subject: [PATCH 13/35] Adds a new INHALE reagent transfer method and applies it to smoking and smoke inhalation. Nitrous oxide (the reagent version) inhalation causes brain damage. (#87306) --- code/__DEFINES/reagents.dm | 6 +++++- .../fluid_spread/effects_smoke.dm | 2 +- code/game/objects/items/cigs_lighters.dm | 6 +++--- code/game/objects/items/drug_items.dm | 2 ++ code/modules/mob/living/living_defense.dm | 4 ++-- .../reagents/chemistry/holder/holder.dm | 2 +- .../chemistry/reagents/food_reagents.dm | 6 +++--- .../chemistry/reagents/medicine_reagents.dm | 4 ++-- .../chemistry/reagents/other_reagents.dm | 19 +++++++++++-------- .../chemistry/reagents/toxin_reagents.dm | 10 +++++----- .../reagents/reagent_containers/cups/_cup.dm | 8 ++++++-- code/modules/unit_tests/reagent_mob_expose.dm | 7 +++++++ 12 files changed, 48 insertions(+), 28 deletions(-) diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm index 30acc2d37fd6..e5fa6c9bf152 100644 --- a/code/__DEFINES/reagents.dm +++ b/code/__DEFINES/reagents.dm @@ -25,7 +25,7 @@ // Reagent exposure methods. /// Used for splashing. #define TOUCH (1<<0) -/// Used for ingesting the reagents. Food, drinks, inhaling smoke. +/// Used for ingesting the reagents. Food and drinks. #define INGEST (1<<1) /// Used by foams, sprays, and blob attacks. #define VAPOR (1<<2) @@ -33,6 +33,10 @@ #define PATCH (1<<3) /// Used for direct injection of reagents. #define INJECT (1<<4) +/// Exclusive to just plumbing. if set we use the round robin technique else we use proportional +#define LINEAR (1<<5) +/// Used by smoke or inhaling from a source. Smoke and cigarettes. +#define INHALE (1<<6) /// When returned by on_mob_life(), on_mob_dead(), overdose_start() or overdose_processed(), will cause the mob to updatehealth() afterwards #define UPDATE_MOB_HEALTH 1 diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm index fe2a41f4cfb0..7a699ea87363 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm @@ -391,7 +391,7 @@ var/fraction = (seconds_per_tick SECONDS) / initial(lifetime) reagents.copy_to(smoker, reagents.total_volume, fraction) - reagents.expose(smoker, INGEST, fraction) + reagents.expose(smoker, INHALE, fraction) return TRUE /// Helper to quickly create a cloud of reagent smoke diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm index 30532e6915d2..135b5f73333a 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -472,12 +472,12 @@ CIGARETTE PACKETS ARE IN FANCY.DM var/to_smoke = smoke_all ? (reagents.total_volume * (dragtime / smoketime)) : REAGENTS_METABOLISM // Check that we are worn by a carbon with lungs, and either in their mask slot or in the contents of their mask slot // If all of those are true give them the reagents. If any fail just delete the reagents straight up. - if(!can_be_smoked_by(smoker) || !reagents.trans_to(smoker, to_smoke, methods = INGEST, ignore_stomach = TRUE)) + if(!can_be_smoked_by(smoker) || !reagents.trans_to(smoker, to_smoke, methods = INHALE, ignore_stomach = TRUE)) reagents.remove_any(to_smoke) return how_long_have_we_been_smokin += seconds_per_tick * (1 SECONDS) - reagents.expose(smoker, INGEST, min(to_smoke / reagents.total_volume, 1)) + reagents.expose(smoker, INHALE, min(to_smoke / reagents.total_volume, 1)) smoker.adjustOrganLoss(ORGAN_SLOT_LUNGS, lung_harm * (HAS_TRAIT(smoker, TRAIT_SMOKER) ? 0.5 : 1), required_organ_flag = ORGAN_ORGANIC) /obj/item/cigarette/process(seconds_per_tick) @@ -1352,7 +1352,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM e.start(src) qdel(src) - if(!reagents.trans_to(vaper, REAGENTS_METABOLISM, methods = INGEST, ignore_stomach = TRUE)) + if(!reagents.trans_to(vaper, REAGENTS_METABOLISM, methods = INHALE, ignore_stomach = TRUE)) reagents.remove_any(REAGENTS_METABOLISM) /obj/item/vape/process(seconds_per_tick) diff --git a/code/game/objects/items/drug_items.dm b/code/game/objects/items/drug_items.dm index f89d82d2078b..67251189140a 100644 --- a/code/game/objects/items/drug_items.dm +++ b/code/game/objects/items/drug_items.dm @@ -39,6 +39,8 @@ reagent_flags = TRANSPARENT spillable = FALSE list_reagents = list(/datum/reagent/drug/blastoff = 10) + reagent_consumption_method = INHALE + consumption_sound = 'sound/effects/spray2.ogg' /obj/item/reagent_containers/cup/blastoff_ampoule/update_icon_state() . = ..() diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 424d8c1eff8c..f4a6ed238123 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -623,7 +623,7 @@ /** Handles exposing a mob to reagents. * - * If the methods include INGEST the mob tastes the reagents. + * If the methods include INGEST or INHALE, the mob tastes the reagents. * If the methods include VAPOR it incorporates permiability protection. */ /mob/living/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, volume_modifier=1, show_message=TRUE, exposed_zone = BODY_ZONE_CHEST) @@ -631,7 +631,7 @@ if(. & COMPONENT_NO_EXPOSE_REAGENTS) return - if(methods & INGEST) + if(methods & (INGEST | INHALE)) taste(source) var/touch_protection = (methods & VAPOR) ? getarmor(null, BIO) * 0.01 : 0 diff --git a/code/modules/reagents/chemistry/holder/holder.dm b/code/modules/reagents/chemistry/holder/holder.dm index be47216b75c3..8f299912a56a 100644 --- a/code/modules/reagents/chemistry/holder/holder.dm +++ b/code/modules/reagents/chemistry/holder/holder.dm @@ -807,7 +807,7 @@ * * Arguments * - Atom/target: What mob/turf/object is being exposed to reagents? This is your reaction target. - * - Methods: What reaction type is the reagent itself going to call on the reaction target? Types are TOUCH, INGEST, VAPOR, PATCH, and INJECT. + * - Methods: What reaction type is the reagent itself going to call on the reaction target? Types are TOUCH, INGEST, VAPOR, PATCH, INJECT and INHALE. * - Volume_modifier: What is the reagent volume multiplied by when exposed? Note that this is called on the volume of EVERY reagent in the base body, so factor in your Maximum_Volume if necessary! * - Show_message: Whether to display anything to mobs when they are exposed. * - list/datum/reagent/r_to_expose: list of reagents to expose. if null will expose the reagents present in this holder instead diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index fc52a584ecc2..6ed5cdbe1522 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -418,7 +418,7 @@ return var/mob/living/carbon/victim = exposed_mob - if(methods & (TOUCH|VAPOR)) + if(methods & (TOUCH|VAPOR|INHALE)) //check for protection //actually handle the pepperspray effects if (!victim.is_pepper_proof()) // you need both eye and mouth protection @@ -561,7 +561,7 @@ return var/mob/living/carbon/victim = exposed_mob - if(methods & (TOUCH | VAPOR)) + if(methods & (TOUCH | VAPOR | INHALE)) var/tear_proof = victim.is_eyes_covered() if (!tear_proof) to_chat(exposed_mob, span_warning("Your eyes sting!")) @@ -945,7 +945,7 @@ /datum/reagent/consumable/liquidelectricity/enriched/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) //can't be on life because of the way blood works. . = ..() - if(!(methods & (INGEST|INJECT|PATCH)) || !iscarbon(exposed_mob)) + if(!(methods & (INGEST|INJECT|PATCH|INHALE)) || !iscarbon(exposed_mob)) return var/mob/living/carbon/exposed_carbon = exposed_mob diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 03bf31476660..24d7a4ebee04 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -382,7 +382,7 @@ if(exposed_mob.stat == DEAD) return - if(methods & (INGEST|VAPOR|INJECT)) + if(methods & (INGEST|VAPOR|INJECT|INHALE)) exposed_mob.adjust_nutrition(-2.5 * reac_volume) if(show_message) to_chat(exposed_mob, span_warning("Your stomach feels empty and cramps!")) @@ -1026,7 +1026,7 @@ exposed_mob.visible_message(span_warning("[exposed_mob]'s body does not react...")) return - if(iscarbon(exposed_mob) && !(methods & INGEST)) //simplemobs can still be splashed + if(iscarbon(exposed_mob) && !(methods & (INGEST|INHALE))) //simplemobs can still be splashed return ..() if(HAS_TRAIT(exposed_mob, TRAIT_HUSK)) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 30c046e2bd88..18655731e0f5 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -59,7 +59,7 @@ exposed_mob.ForceContractDisease(strain) - else if((methods & VAPOR) && (strain.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS)) + else if((methods & (VAPOR|INHALE)) && (strain.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS)) if(!strain.has_required_infectious_organ(exposed_mob, ORGAN_SLOT_LUNGS)) continue @@ -68,7 +68,7 @@ else if((methods & TOUCH) && (strain.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS)) exposed_mob.ContactContractDisease(strain) - if(length(data?["resistances"]) && (methods & (INGEST|INJECT))) + if(length(data?["resistances"]) && (methods & (INGEST|INJECT|INHALE))) for(var/datum/disease/infection as anything in exposed_mob.diseases) if(infection.GetDiseaseID() in data["resistances"]) if(!infection.bypasses_immunity) @@ -1471,7 +1471,7 @@ /datum/reagent/cyborg_mutation_nanomachines/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) . = ..() - if((methods & (PATCH|INGEST|INJECT)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) + if((methods & (PATCH|INGEST|INJEC|INHALE)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) exposed_mob.ForceContractDisease(new /datum/disease/transformation/robot(), FALSE, TRUE) /datum/reagent/xenomicrobes @@ -1483,7 +1483,7 @@ /datum/reagent/xenomicrobes/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) . = ..() - if((methods & (PATCH|INGEST|INJECT)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) + if((methods & (PATCH|INGEST|INJECT|INHALE)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) exposed_mob.ForceContractDisease(new /datum/disease/transformation/xeno(), FALSE, TRUE) /datum/reagent/fungalspores @@ -1496,7 +1496,7 @@ /datum/reagent/fungalspores/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) . = ..() - if((methods & (PATCH|INGEST|INJECT)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) + if((methods & (PATCH|INGEST|INJECT|INHALE)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) exposed_mob.ForceContractDisease(new /datum/disease/tuberculosis(), FALSE, TRUE) /datum/reagent/snail @@ -1509,7 +1509,7 @@ /datum/reagent/snail/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) . = ..() - if((methods & (PATCH|INGEST|INJECT)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) + if((methods & (PATCH|INGEST|INJECT|INHALE)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) exposed_mob.ForceContractDisease(new /datum/disease/gastrolosis(), FALSE, TRUE) /datum/reagent/fluorosurfactant//foam precursor @@ -1606,10 +1606,13 @@ /datum/reagent/nitrous_oxide/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() - if(methods & VAPOR) + if(methods & (VAPOR|INHALE)) // apply 2 seconds of drowsiness per unit applied, with a min duration of 4 seconds var/drowsiness_to_apply = max(round(reac_volume, 1) * 2 SECONDS, 4 SECONDS) exposed_mob.adjust_drowsiness(drowsiness_to_apply) + if(methods & INHALE) + exposed_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.25 * reac_volume, required_organ_flag = affected_organ_flags) + exposed_mob.adjust_hallucinations(10 SECONDS * reac_volume) /datum/reagent/nitrous_oxide/on_mob_end_metabolize(mob/living/affected_mob) . = ..() @@ -2645,7 +2648,7 @@ /datum/reagent/gondola_mutation_toxin/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) . = ..() - if((methods & (PATCH|INGEST|INJECT)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) + if((methods & (PATCH|INGEST|INJECT|INHALE)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) exposed_mob.ForceContractDisease(new gondola_disease, FALSE, TRUE) diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index ec6fc5e2701a..0bc8e26eaf13 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -52,7 +52,7 @@ . = ..() if(!exposed_mob.can_mutate()) return //No robots, AIs, aliens, Ians or other mobs should be affected by this. - if(((methods & VAPOR) && prob(min(33, reac_volume))) || (methods & (INGEST|PATCH|INJECT))) + if(((methods & VAPOR) && prob(min(33, reac_volume))) || (methods & (INGEST|PATCH|INJECT|INHALE))) exposed_mob.random_mutate_unique_identity() exposed_mob.random_mutate_unique_features() if(prob(98)) @@ -258,7 +258,7 @@ /datum/reagent/toxin/zombiepowder/on_mob_metabolize(mob/living/holder_mob) . = ..() holder_mob.adjustOxyLoss(0.5*REM, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - if((data?["method"] & INGEST) && holder_mob.stat != DEAD) + if((data?["method"] & (INGEST|INHALE)) && holder_mob.stat != DEAD) holder_mob.fakedeath(type) /datum/reagent/toxin/zombiepowder/on_mob_end_metabolize(mob/living/affected_mob) @@ -268,10 +268,10 @@ /datum/reagent/toxin/zombiepowder/on_transfer(atom/target_atom, methods, trans_volume) . = ..() var/datum/reagent/zombiepowder = target_atom.reagents.has_reagent(/datum/reagent/toxin/zombiepowder) - if(!zombiepowder || !(methods & INGEST)) + if(!zombiepowder || !(methods & (INGEST|INHALE))) return LAZYINITLIST(zombiepowder.data) - zombiepowder.data["method"] |= INGEST + zombiepowder.data["method"] |= (INGEST|INHALE) /datum/reagent/toxin/zombiepowder/on_mob_life(mob/living/affected_mob, seconds_per_tick, times_fired) . = ..() @@ -1126,7 +1126,7 @@ if(!istype(exposed_carbon)) return reac_volume = round(reac_volume,0.1) - if(methods & INGEST) + if(methods & (INGEST|INHALE)) exposed_carbon.adjustBruteLoss(min(6*toxpwr, reac_volume * toxpwr), required_bodytype = affected_bodytype) return if(methods & INJECT) diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm index 1e47a9aab084..6c3458740bcb 100644 --- a/code/modules/reagents/reagent_containers/cups/_cup.dm +++ b/code/modules/reagents/reagent_containers/cups/_cup.dm @@ -18,6 +18,10 @@ var/gulp_size = 5 ///Whether the 'bottle' is made of glass or not so that milk cartons dont shatter when someone gets hit by it. var/isGlass = FALSE + ///What kind of chem transfer method does this cup use. Defaults to INGEST + var/reagent_consumption_method = INGEST + ///What sound does our consumption play on consuming from the container? + var/consumption_sound = 'sound/items/drink.ogg' /obj/item/reagent_containers/cup/examine(mob/user) . = ..() @@ -103,9 +107,9 @@ SEND_SIGNAL(src, COMSIG_GLASS_DRANK, target_mob, user) var/fraction = min(gulp_size/reagents.total_volume, 1) - reagents.trans_to(target_mob, gulp_size, transferred_by = user, methods = INGEST) + reagents.trans_to(target_mob, gulp_size, transferred_by = user, methods = reagent_consumption_method) checkLiked(fraction, target_mob) - playsound(target_mob.loc,'sound/items/drink.ogg', rand(10,50), TRUE) + playsound(target_mob.loc, consumption_sound, rand(10,50), TRUE) if(!iscarbon(target_mob)) return var/mob/living/carbon/carbon_drinker = target_mob diff --git a/code/modules/unit_tests/reagent_mob_expose.dm b/code/modules/unit_tests/reagent_mob_expose.dm index 050515c87edc..bf31291e3654 100644 --- a/code/modules/unit_tests/reagent_mob_expose.dm +++ b/code/modules/unit_tests/reagent_mob_expose.dm @@ -58,6 +58,13 @@ syringe.melee_attack_chain(human, human) TEST_ASSERT_EQUAL(human.health, 80, "Human health did not update after injection from syringe") + // INHALE + TEST_ASSERT_NULL(human.has_status_effect(/datum/status_effect/hallucination), "Human is drowsy at the start of testing") + drink.reagents.clear_reagents() + drink.reagents.add_reagent(/datum/reagent/nitrous_oxide, 10) + drink.reagents.trans_to(human, 10, methods = INHALE) + TEST_ASSERT_NOTNULL(human.has_status_effect(/datum/status_effect/hallucination), "Human is not drowsy after exposure to vapors") + /datum/unit_test/reagent_mob_expose/Destroy() SSmobs.ignite() return ..() From f1303b83155b9ceb14ed4e14dd51ada5d2b16f78 Mon Sep 17 00:00:00 2001 From: nikothedude <59709059+nikothedude@users.noreply.github.com> Date: Sun, 7 Sep 2025 21:31:21 -0400 Subject: [PATCH 14/35] Asthma quirk, inhalers - Electric Boogaloo (#92747) --- code/__DEFINES/alerts.dm | 2 + .../signals/signals_mob/signals_mob_carbon.dm | 3 + code/__DEFINES/diseases.dm | 2 + code/__DEFINES/reagents.dm | 2 +- code/_onclick/hud/alert.dm | 10 + code/datums/diseases/_disease.dm | 14 +- code/datums/diseases/asthma_attack.dm | 262 +++++++++++++++ .../datums/quirks/negative_quirks/allergic.dm | 4 +- code/datums/quirks/negative_quirks/asthma.dm | 249 ++++++++++++++ .../fluid_spread/effects_smoke.dm | 1 + code/modules/mob/living/emote.dm | 6 + .../chemistry/reagents/medicine_reagents.dm | 81 +++++ .../chemistry/recipes/cat2_medicines.dm | 2 +- .../reagents/chemistry/recipes/medicine.dm | 10 +- .../reagents/reagent_containers/inhaler.dm | 314 ++++++++++++++++++ .../research/designs/medical_designs.dm | 24 ++ .../research/techweb/nodes/medbay_nodes.dm | 2 + code/modules/surgery/asthmatic_bypass.dm | 96 ++++++ code/modules/surgery/organs/_organ.dm | 1 + .../surgery/organs/internal/lungs/_lungs.dm | 70 +++- code/modules/vending/medical.dm | 1 + icons/hud/screen_alert.dmi | Bin 147364 -> 142874 bytes icons/obj/medical/chemical.dmi | Bin 70968 -> 67414 bytes tgstation.dme | 3 + 24 files changed, 1144 insertions(+), 15 deletions(-) create mode 100644 code/datums/diseases/asthma_attack.dm create mode 100644 code/datums/quirks/negative_quirks/asthma.dm create mode 100644 code/modules/reagents/reagent_containers/inhaler.dm create mode 100644 code/modules/surgery/asthmatic_bypass.dm diff --git a/code/__DEFINES/alerts.dm b/code/__DEFINES/alerts.dm index 17db402c6c04..d873e50b3749 100644 --- a/code/__DEFINES/alerts.dm +++ b/code/__DEFINES/alerts.dm @@ -14,6 +14,8 @@ #define ALERT_TOO_MUCH_NITRO "too_much_nitro" #define ALERT_NOT_ENOUGH_NITRO "not_enough_nitro" +#define ALERT_BRONCHODILATION "bronchodilation" + /** Mob related */ #define ALERT_SUCCUMB "succumb" #define ALERT_BUCKLED "buckled" 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 3e9dcca12775..dc2f80927a3e 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -180,3 +180,6 @@ #define COMSIG_HUMAN_GOT_PUNCHED "human_got_punched" /// Called from /datum/species/proc/harm(): (mob/living/carbon/human/attacked, damage, attack_type, obj/item/bodypart/affecting, final_armor_block, kicking) #define COMSIG_HUMAN_PUNCHED "human_punched" + +//from base of [/obj/effect/particle_effect/fluid/smoke/proc/smoke_mob]: (seconds_per_tick) +#define COMSIG_CARBON_EXPOSED_TO_SMOKE "carbon_exposed_to_smoke" diff --git a/code/__DEFINES/diseases.dm b/code/__DEFINES/diseases.dm index 047bb79582de..26bb3e65d22f 100644 --- a/code/__DEFINES/diseases.dm +++ b/code/__DEFINES/diseases.dm @@ -16,6 +16,8 @@ DEFINE_BITFIELD(visibility_flags, list( #define CAN_CARRY (1<<1) #define CAN_RESIST (1<<2) #define CHRONIC (1<<3) +/// Instead of instantly curing the disease, cures will simply reduce the stage +#define INCREMENTAL_CURE (1<<4) //Spread Flags #define DISEASE_SPREAD_SPECIAL (1<<0) diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm index e5fa6c9bf152..9d045b6a9353 100644 --- a/code/__DEFINES/reagents.dm +++ b/code/__DEFINES/reagents.dm @@ -35,7 +35,7 @@ #define INJECT (1<<4) /// Exclusive to just plumbing. if set we use the round robin technique else we use proportional #define LINEAR (1<<5) -/// Used by smoke or inhaling from a source. Smoke and cigarettes. +/// Used by smoke or inhaling from a source. Smoke, cigarettes, and inhalers. #define INHALE (1<<6) /// When returned by on_mob_life(), on_mob_dead(), overdose_start() or overdose_processed(), will cause the mob to updatehealth() afterwards diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 063444bb95d2..d773cf9c486d 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -193,6 +193,16 @@ //End gas alerts +/atom/movable/screen/alert/bronchodilated + name = "Bronchodilated" + desc = "You feel like your lungs are larger than usual! You're taking deeper breaths!" + icon_state = "bronchodilated" + +/atom/movable/screen/alert/bronchoconstricted + name = "Bronchocontracted" + desc = "You feel like your lungs are smaller than usual! You might need a higher pressure environment/internals to breathe!" + icon_state = "bronchoconstricted" + /atom/movable/screen/alert/gross name = "Grossed out." desc = "That was kind of gross..." diff --git a/code/datums/diseases/_disease.dm b/code/datums/diseases/_disease.dm index bc60049e73ea..178b4f730bcd 100644 --- a/code/datums/diseases/_disease.dm +++ b/code/datums/diseases/_disease.dm @@ -93,11 +93,13 @@ update_stage(1) to_chat(affected_mob, span_notice("Your chronic illness is alleviated a little, though it can't be cured!")) return - if(SPT_PROB(cure_mod, seconds_per_tick)) - update_stage(max(stage - 1, 1)) if(disease_flags & CURABLE && SPT_PROB(cure_mod, seconds_per_tick)) - cure() - return FALSE + if(disease_flags & INCREMENTAL_CURE) + if (!update_stage(stage - 1)) + return FALSE + else + cure() + return FALSE if(stage == max_stages && stage_peaked != TRUE) //mostly a sanity check in case we manually set a virus to max stages stage_peaked = TRUE @@ -207,6 +209,10 @@ stage = new_stage if(new_stage == max_stages && !(stage_peaked)) //once a virus has hit its peak, set it to have done so stage_peaked = TRUE + if (stage <= 0) + cure() + return FALSE + return TRUE /datum/disease/proc/has_cure() if(!(disease_flags & (CURABLE | CHRONIC))) diff --git a/code/datums/diseases/asthma_attack.dm b/code/datums/diseases/asthma_attack.dm new file mode 100644 index 000000000000..7adefd2f7464 --- /dev/null +++ b/code/datums/diseases/asthma_attack.dm @@ -0,0 +1,262 @@ +/datum/disease/asthma_attack + form = "Bronchitis" + name = "Asthma attack" + desc = "Subject is undergoing a autoimmune response which threatens to close the esophagus and halt all respiration, leading to death. \ + Minor asthma attacks may disappear on their own, but all are dangerous." + cure_text = "Albuterol/Surgical intervention" + cures = list(/datum/reagent/medicine/albuterol) + agent = "Inflammatory" + viable_mobtypes = list(/mob/living/carbon/human) + disease_flags = CURABLE + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + spread_text = "Inflammatory" + visibility_flags = HIDDEN_PANDEMIC + bypasses_immunity = TRUE + disease_flags = CURABLE|INCREMENTAL_CURE + required_organ = ORGAN_SLOT_LUNGS + infectable_biotypes = MOB_ROBOTIC|MOB_ORGANIC|MOB_MINERAL|MOB_UNDEAD + + /// The world.time after which we will begin remission. + var/time_to_start_remission + + /// The max time, after initial infection, it will take for us to begin remission + var/max_time_til_remission + /// The min time, after initial infection, it will take for us to begin remission + var/min_time_til_remission + + /// Are we in remission, where we stop progressing and instead slowly degrade in intensity until we remove ourselves? + var/in_remission = FALSE + + /// The current progress to stage demotion. Resets to 0 and reduces our stage by 1 when it exceeds [progress_needed_to_demote]. Only increases when in remission. + var/progress_to_stage_demotion = 0 + /// The amount of demotion progress we receive per second while in remission. + var/progress_to_demotion_per_second = 1 + /// Once [progress_to_stage_demotion] exceeds or meets this, we reduce our stage. + var/progress_needed_to_demote = 10 + + /// Do we alert ghosts when we are applied? + var/alert_ghosts = FALSE + + /// A assoc list of (severity -> string), where string will be suffixed to our name in (suffix) format. + var/static/list/severity_to_suffix = list( + DISEASE_SEVERITY_MEDIUM = "Minor", + DISEASE_SEVERITY_HARMFUL = "Moderate", + DISEASE_SEVERITY_DANGEROUS = "Severe", + DISEASE_SEVERITY_BIOHAZARD = "EXTREME", + ) + /// A assoc list of (stringified number -> number), where the key is the stage and the number is how much inflammation we will cause the asthmatic per second. + var/list/stage_to_inflammation_per_second + +/datum/disease/asthma_attack/New() + . = ..() + + suffix_name() + + time_to_start_remission = world.time + rand(min_time_til_remission, max_time_til_remission) + +/datum/disease/asthma_attack/try_infect(mob/living/infectee, make_copy) + if (!get_asthma_quirk()) + return FALSE + if (HAS_TRAIT(infectee, TRAIT_NOBREATH)) + return FALSE + + return ..() + +/// Adds our suffix via [severity_to_suffix] in the format of (suffix) to our name. +/datum/disease/asthma_attack/proc/suffix_name() + name += " ([severity_to_suffix[severity]])" + +/// Returns the asthma quirk of our victim. As we can only be applied to asthmatics, this should never return null. +/datum/disease/asthma_attack/proc/get_asthma_quirk(mob/living/target = affected_mob) + RETURN_TYPE(/datum/quirk/item_quirk/asthma) + + return (locate(/datum/quirk/item_quirk/asthma) in target.quirks) + +/datum/disease/asthma_attack/stage_act(seconds_per_tick, times_fired) + . = ..() + if (!.) + return + + if (HAS_TRAIT(affected_mob, TRAIT_NOBREATH)) + cure() + return FALSE + + var/datum/quirk/item_quirk/asthma/asthma_quirk = get_asthma_quirk() + var/inflammation = stage_to_inflammation_per_second["[stage]"] + if (inflammation) + asthma_quirk.adjust_inflammation(inflammation * seconds_per_tick) + + if (!(world.time >= time_to_start_remission)) + return + + if (!in_remission) + in_remission = TRUE + stage_prob = 0 + name += " (Remission)" + desc += " The attack has entered remission. It will slowly decrease in intensity before vanishing." + progress_to_stage_demotion += (progress_to_demotion_per_second * seconds_per_tick) + if (progress_to_stage_demotion >= progress_needed_to_demote) + progress_to_stage_demotion = 0 + update_stage(stage - 1) + +// TYPES OF ASTHMA ATTACK + +/datum/disease/asthma_attack/minor + severity = DISEASE_SEVERITY_MEDIUM + stage_prob = 4 + + max_time_til_remission = 120 SECONDS + min_time_til_remission = 80 SECONDS + max_stages = 3 + + cure_chance = 20 + + stage_to_inflammation_per_second = list( + "2" = 0.3, + "3" = 0.6, + ) + +/datum/disease/asthma_attack/minor/stage_act(seconds_per_tick, times_fired) + . = ..() + if (!.) + return FALSE + + if (SPT_PROB(5, seconds_per_tick)) + to_chat(affected_mob, span_warning(pick("Mucous runs down the back of your throat.", "You swallow excess mucus."))) + +/datum/disease/asthma_attack/moderate + severity = DISEASE_SEVERITY_HARMFUL + stage_prob = 5 + + max_time_til_remission = 120 SECONDS + min_time_til_remission = 80 SECONDS + max_stages = 4 + + cure_chance = 20 + + stage_to_inflammation_per_second = list( + "2" = 1, + "3" = 2, + "4" = 4, + ) + +/datum/disease/asthma_attack/moderate/stage_act(seconds_per_tick, times_fired) + . = ..() + if (!.) + return FALSE + + if (SPT_PROB(15, seconds_per_tick)) + to_chat(affected_mob, span_warning(pick("Mucous runs down the back of your throat.", "You swallow excess mucus."))) + + if (stage < 4 || !SPT_PROB(10, seconds_per_tick)) + return + to_chat(affected_mob, span_warning("You briefly choke on the mucus piling in your throat!")) + affected_mob.losebreath++ + + +/datum/disease/asthma_attack/severe + severity = DISEASE_SEVERITY_DANGEROUS + stage_prob = 6 + + max_time_til_remission = 80 SECONDS + min_time_til_remission = 60 SECONDS + max_stages = 5 + + cure_chance = 20 + + stage_to_inflammation_per_second = list( + "2" = 1, + "3" = 3, + "4" = 6, + "5" = 8, + ) + + visibility_flags = HIDDEN_SCANNER + alert_ghosts = TRUE + +/datum/disease/asthma_attack/severe/stage_act(seconds_per_tick, times_fired) + . = ..() + if (!.) + return FALSE + + if (stage > 1) + visibility_flags &= ~HIDDEN_SCANNER // revealed + + if (SPT_PROB(15, seconds_per_tick)) + to_chat(affected_mob, span_warning(pick("Mucous runs down the back of your throat.", "You swallow excess mucus."))) + else if (SPT_PROB(20, seconds_per_tick)) + affected_mob.emote("cough") + + if (stage < 4 || !SPT_PROB(15, seconds_per_tick)) + return + to_chat(affected_mob, span_warning("You briefly choke on the mucus piling in your throat!")) + affected_mob.losebreath++ + +/datum/disease/asthma_attack/critical + severity = DISEASE_SEVERITY_BIOHAZARD + stage_prob = 85 + + max_time_til_remission = 60 SECONDS // this kills you extremely quickly, so its fair + min_time_til_remission = 40 SECONDS + max_stages = 6 + + cure_chance = 30 + + stage_to_inflammation_per_second = list( + "1" = 5, + "2" = 6, + "3" = 7, + "4" = 10, + "5" = 20, + "6" = 500, // youre fucked frankly + ) + + /// Have we warned our user of the fact they are at stage 5? If no, and are at or above stage five, we send a warning and set this to true. + var/warned_user = FALSE + /// Have we ever reached our max stage? If no, and we are at our max stage, we send a ominous message warning them of their imminent demise. + var/max_stage_reached = FALSE + +/datum/disease/asthma_attack/critical/stage_act(seconds_per_tick, times_fired) + . = ..() + if (!.) + return FALSE + + if (stage < 5) + if (SPT_PROB(75, seconds_per_tick)) + to_chat(affected_mob, span_warning(pick("Mucous runs down the back of your throat.", "You swallow excess mucus."))) + + var/wheeze_chance + if (!warned_user && stage >= 5) + to_chat(affected_mob, span_userdanger("You feel like your lungs are filling with fluid! It's getting incredibly hard to breathe!")) + warned_user = TRUE + + switch (stage) + if (1) + wheeze_chance = 0 + if (2) + wheeze_chance = 20 + if (3) + wheeze_chance = 40 + if (4) + wheeze_chance = 60 + if (5) + wheeze_chance = 80 + if (!in_remission) + stage_prob = 10 // slow it down significantly + if (6) + if (!max_stage_reached) + max_stage_reached = TRUE + to_chat(affected_mob, span_userdanger("You feel your windpipe squeeze shut!")) + wheeze_chance = 0 + if (SPT_PROB(10, seconds_per_tick)) + affected_mob.emote("gag") + var/datum/quirk/item_quirk/asthma/asthma_quirk = get_asthma_quirk() + asthma_quirk.adjust_inflammation(INFINITY) + + if (SPT_PROB(wheeze_chance, seconds_per_tick)) + affected_mob.emote("wheeze") + + if (stage < 4 || !SPT_PROB(15, seconds_per_tick)) + return + to_chat(affected_mob, span_warning("You briefly choke on the mucus piling in your throat!")) + affected_mob.losebreath++ diff --git a/code/datums/quirks/negative_quirks/allergic.dm b/code/datums/quirks/negative_quirks/allergic.dm index d4372bafca19..7f105261fed3 100644 --- a/code/datums/quirks/negative_quirks/allergic.dm +++ b/code/datums/quirks/negative_quirks/allergic.dm @@ -19,7 +19,9 @@ /datum/reagent/medicine/cordiolis_hepatico, /datum/reagent/medicine/synaphydramine, /datum/reagent/medicine/diphenhydramine, - /datum/reagent/medicine/sansufentanyl + /datum/reagent/medicine/sansufentanyl, + /datum/reagent/medicine/salglu_solution, + /datum/reagent/medicine/albuterol, ) var/allergy_string diff --git a/code/datums/quirks/negative_quirks/asthma.dm b/code/datums/quirks/negative_quirks/asthma.dm new file mode 100644 index 000000000000..6683869a6467 --- /dev/null +++ b/code/datums/quirks/negative_quirks/asthma.dm @@ -0,0 +1,249 @@ +/datum/quirk/item_quirk/asthma + name = "Asthma" + desc = "You suffer from asthma, a inflammatory disorder that causes your airpipe to squeeze shut! Be careful around smoke!" + icon = FA_ICON_LUNGS_VIRUS + value = -4 // trivialized by NOBREATH but still quite dangerous + gain_text = span_danger("You have a harder time breathing.") + lose_text = span_notice("You suddenly feel like your lungs just got a lot better at breathing!") + medical_record_text = "Patient suffers from asthma." + hardcore_value = 2 + quirk_flags = QUIRK_HUMAN_ONLY + mail_goodies = list(/obj/item/reagent_containers/inhaler_canister/albuterol) + + /// At this percentage of inflammation, our lung pressure mult reaches 0. From 0-1. + var/hit_max_mult_at_inflammation_percent = 0.9 + + /// Current inflammation of the lungs. + var/inflammation = 0 + /// Highest possible inflammation. Interacts with [hit_max_mult_at_inflammation_percent] + var/max_inflammation = 500 + + /// The amount [inflammation] reduces every second while our owner is off stasis and alive. + var/passive_inflammation_reduction = 0.15 + + /// The amount of inflammation we will receive when our owner breathes smoke. + var/inflammation_on_smoke = 7.5 + + /// If our owner is metabolizing histamine, inflammation will increase by this per tick. + var/histamine_inflammation = 2 + /// If our owner is ODing on histamine, inflammation will increase by this per tick. + var/histamine_OD_inflammation = 10 // allergic reactions tend to fuck people up + + /// A tracker variable for how much albuterol has been inhaled. + var/inhaled_albuterol = 0 + /// If [inhaled_albuterol] is above 0, we will reduce inflammation by this much per tick. + var/albuterol_inflammation_reduction = 3 + /// When albuterol is inhaled, inflammation will be reduced via (inhaled_albuterol * albuterol_inflammation_reduction * albuterol_immediate_reduction_mult) + var/albuterol_immediate_reduction_mult = 4 + + /// The current asthma attack trying to kill our owner. + var/datum/disease/asthma_attack/current_attack + /// Can we cause an asthma attack? + COOLDOWN_DECLARE(next_attack_cooldown) + + /// world.time + this is the time the first attack can happen. Used on spawn. + var/time_first_attack_can_happen = 10 MINUTES + + /// After an attack ends, this is the minimum time we must wait before we attack again. + var/min_time_between_attacks = 15 MINUTES + /// After an attack ends, this is the maximum time we must wait before we attack again. + var/max_time_between_attacks = 25 MINUTES + + /// Every second, an asthma attack can happen via this probability. 0-1. + var/chance_for_attack_to_happen_per_second = 0.05 + + /// Assoc list of (/datum/disease/asthma_attack typepath -> number). Used in pickweight for when we pick a random asthma attack to apply. + var/static/list/asthma_attack_rarities = list( + /datum/disease/asthma_attack/minor = 300, + /datum/disease/asthma_attack/moderate = 400, + /datum/disease/asthma_attack/severe = 100, + /datum/disease/asthma_attack/critical = 1, // this can quickly kill you, so its rarity is justified + ) + +/datum/quirk/item_quirk/asthma/add_unique(client/client_source) + . = ..() + + var/obj/item/inhaler/albuterol/asthma/rescue_inhaler = new(get_turf(quirk_holder)) + give_item_to_holder(rescue_inhaler, list(LOCATION_BACKPACK, LOCATION_HANDS), flavour_text = "You can use this to quickly relieve the symptoms of your asthma.") + + RegisterSignal(quirk_holder, COMSIG_CARBON_EXPOSED_TO_SMOKE, PROC_REF(holder_exposed_to_smoke)) + RegisterSignal(quirk_holder, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(organ_removed)) + RegisterSignal(quirk_holder, COMSIG_ATOM_EXPOSE_REAGENTS, PROC_REF(exposed_to_reagents)) + RegisterSignal(quirk_holder, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(on_full_heal)) + RegisterSignal(quirk_holder, COMSIG_LIVING_LIFE, PROC_REF(on_life)) + + COOLDOWN_START(src, next_attack_cooldown, time_first_attack_can_happen) + +/datum/quirk/item_quirk/asthma/remove() + . = ..() + + current_attack?.cure() + UnregisterSignal(quirk_holder, COMSIG_CARBON_EXPOSED_TO_SMOKE, COMSIG_CARBON_LOSE_ORGAN, COMSIG_ATOM_EXPOSE_REAGENTS, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_LIVING_LIFE) + +/datum/quirk/item_quirk/asthma/proc/on_life(mob/living/source, seconds_per_tick, times_fired) + SIGNAL_HANDLER + + if (quirk_holder.stat == DEAD) + return + + if (HAS_TRAIT(quirk_holder, TRAIT_STASIS) || HAS_TRAIT(quirk_holder, TRAIT_NO_TRANSFORM)) + return + + var/obj/item/organ/lungs/holder_lungs = quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS) + if (isnull(holder_lungs)) + return + + adjust_inflammation(-passive_inflammation_reduction * seconds_per_tick) + + var/datum/reagent/toxin/histamine/holder_histamine = quirk_holder.reagents.has_reagent(/datum/reagent/toxin/histamine) + if (holder_histamine) + if (holder_histamine.overdosed) // uh oh! + if (SPT_PROB(15, seconds_per_tick)) + to_chat(quirk_holder, span_boldwarning("You feel your neck swelling, squeezing on your windpipe more and more!")) + adjust_inflammation(histamine_OD_inflammation * seconds_per_tick) + else + if (SPT_PROB(5, seconds_per_tick)) + to_chat(quirk_holder, span_warning("You find yourself wheezing a little harder as your neck swells...")) + adjust_inflammation(histamine_inflammation * seconds_per_tick) + + var/datum/reagent/medicine/albuterol/albuterol = quirk_holder.reagents.has_reagent(/datum/reagent/medicine/albuterol) + if (!albuterol) // sanity - couldve been purged. can be 0 or null which is why we just use a ! + inhaled_albuterol = 0 + else + inhaled_albuterol = min(albuterol.volume, inhaled_albuterol) + + if (inhaled_albuterol > 0) + adjust_inflammation(-(albuterol_inflammation_reduction * seconds_per_tick)) + + // asthma attacks dont happen if theres no client, because they can just kill you and some need immediate response + else if (quirk_holder.client && isnull(current_attack) && COOLDOWN_FINISHED(src, next_attack_cooldown) && SPT_PROB(chance_for_attack_to_happen_per_second, seconds_per_tick)) + do_asthma_attack() + +/// Causes an asthma attack via infecting our owner with the attack disease. Notifies ghosts. +/datum/quirk/item_quirk/asthma/proc/do_asthma_attack() + var/datum/disease/asthma_attack/typepath = pick_weight(asthma_attack_rarities) + + current_attack = new typepath + current_attack.infect(quirk_holder, make_copy = FALSE) // dont leave make_copy on TRUE. worst mistake ive ever made + RegisterSignal(current_attack, COMSIG_QDELETING, PROC_REF(attack_deleting)) + + if (current_attack.alert_ghosts) + notify_ghosts("[quirk_holder] is having an asthma attack: [current_attack.name]!", source = quirk_holder, notify_flags = NOTIFY_CATEGORY_NOFLASH, header = "Asthma attack!") + +/// Setter proc for [inflammation]. Adjusts the amount by lung health, adjusts pressure mult, gives feedback messages if silent is FALSE. +/datum/quirk/item_quirk/asthma/proc/adjust_inflammation(amount, silent = FALSE) + var/old_inflammation = inflammation + + var/obj/item/organ/lungs/holder_lungs = quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS) + var/health_mult = get_lung_health_mult(holder_lungs) + if (amount > 0) // make it worse + amount *= (2 - health_mult) + else // reduce the reduction + amount *= health_mult + + var/old_pressure_mult = get_pressure_mult() + inflammation = (clamp(inflammation + amount, 0, max_inflammation)) + var/difference = (old_inflammation - inflammation) + if (difference != 0) + var/new_pressure_mult = get_pressure_mult() + var/pressure_difference = new_pressure_mult - old_pressure_mult + + holder_lungs?.adjust_received_pressure_mult(pressure_difference) + + if (!silent) + INVOKE_ASYNC(src, PROC_REF(do_inflammation_change_feedback), difference) + +/// Setter proc for [inhaled_albuterol]. Adjusts inflammation immediately. +/datum/quirk/item_quirk/asthma/proc/adjust_albuterol_levels(adjustment) + if (adjustment > 0) + var/obj/item/organ/lungs/holder_lungs = quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS) + + if (isnull(holder_lungs) || holder_lungs.received_pressure_mult <= 0) // it didnt go into the lungs get fucked + return + + adjust_inflammation(-(albuterol_inflammation_reduction * albuterol_immediate_reduction_mult)) + + inhaled_albuterol += adjustment + +/// Returns the pressure mult to be applied to our lungs. +/datum/quirk/item_quirk/asthma/proc/get_pressure_mult() + var/virtual_max = (max_inflammation * hit_max_mult_at_inflammation_percent) + + return (1 - (min(inflammation/virtual_max, 1))) + +/// Sends feedback to our owner of which direction our asthma is intensifying/recovering. +/datum/quirk/item_quirk/asthma/proc/do_inflammation_change_feedback(difference) + var/change_mult = 1 + (difference / 300) // 300 is arbitrary + if (difference > 0) // it decreased + if (prob(1 * change_mult)) + // in my experience with asthma an inhaler causes a bunch of mucous and you tend to cough it up + to_chat(quirk_holder, span_notice("The phlem in your throat forces you to cough!")) + quirk_holder.emote("cough") + + else if (difference < 0)// it increased + if (prob(1 * change_mult)) + quirk_holder.emote("wheeze") + if (prob(5 * change_mult)) + to_chat(quirk_holder, span_warning("You feel your windpipe tightening...")) + +/// Returns the % of health our lungs have, from 1-0. Used in reducing recovery and intensifying inflammation. +/datum/quirk/item_quirk/asthma/proc/get_lung_health_mult() + var/mob/living/carbon/carbon_quirk_holder = quirk_holder + var/obj/item/organ/lungs/holder_lungs = carbon_quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS) + if (isnull(holder_lungs)) + return 1 + if (holder_lungs.organ_flags & ORGAN_FAILING) + return 0 + return (1 - (holder_lungs.damage / holder_lungs.maxHealth)) + +/// Signal proc for when we are exposed to smoke. Increases inflammation. +/datum/quirk/item_quirk/asthma/proc/holder_exposed_to_smoke(datum/signal_source, seconds_per_tick) + SIGNAL_HANDLER + + adjust_inflammation(inflammation_on_smoke * seconds_per_tick) + +/// Signal proc for when our lungs are removed. Resets all our variables. +/datum/quirk/item_quirk/asthma/proc/organ_removed(datum/signal_source, obj/item/organ/removed) + SIGNAL_HANDLER + + if (istype(removed, /obj/item/organ/lungs)) + reset_asthma() + +/// Signal proc for when our owner receives reagents. If we receive albuterol via inhalation, we adjust inhaled albuterol by that amount. If we are smoking, we increase inflammation. +/datum/quirk/item_quirk/asthma/proc/exposed_to_reagents(atom/source, list/reagents, datum/reagents/source_reagents, methods, show_message) + SIGNAL_HANDLER + + var/final_total = 0 + + for (var/datum/reagent/reagent as anything in reagents) + var/amount = reagents[reagent] + if (istype(reagent, /datum/reagent/medicine/albuterol)) + adjust_albuterol_levels(amount) + final_total += amount + + if (!(methods & INHALE)) + return + if (istype(source_reagents.my_atom, /obj/item/cigarette)) // smoking is bad, kids + adjust_inflammation(inflammation_on_smoke * final_total * 5) + +/// Signal proc for when our asthma attack qdels. Unsets our refs to it and resets [next_attack_cooldown]. +/datum/quirk/item_quirk/asthma/proc/attack_deleting(datum/signal_source) + SIGNAL_HANDLER + + UnregisterSignal(current_attack, COMSIG_QDELETING) + current_attack = null + + COOLDOWN_START(src, next_attack_cooldown, rand(min_time_between_attacks, max_time_between_attacks)) + +/// Signal handler for COMSIG_LIVING_POST_FULLY_HEAL. Heals our asthma. +/datum/quirk/item_quirk/asthma/proc/on_full_heal(datum/signal_source, heal_flags) + SIGNAL_HANDLER + + if (heal_flags & HEAL_ORGANS) + reset_asthma() + +/// Resets our asthma to normal. No inflammation, no pressure mult. +/datum/quirk/item_quirk/asthma/proc/reset_asthma() + inflammation = 0 + var/obj/item/organ/lungs/holder_lungs = quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS) + holder_lungs?.set_received_pressure_mult(holder_lungs::received_pressure_mult) diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm index 7a699ea87363..95169137c08d 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm @@ -124,6 +124,7 @@ smoker.smoke_delay = TRUE addtimer(VARSET_CALLBACK(smoker, smoke_delay, FALSE), 1 SECONDS) + SEND_SIGNAL(smoker, COMSIG_CARBON_EXPOSED_TO_SMOKE, seconds_per_tick) return TRUE /** diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 0113118903cf..041a59865cfd 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -362,6 +362,12 @@ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5) return ..() +/datum/emote/living/wheeze + key = "wheeze" + key_third_person = "wheezes" + message = "wheezes!" + emote_type = EMOTE_AUDIBLE + /datum/emote/living/pout key = "pout" key_third_person = "pouts" diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 24d7a4ebee04..8fb31d65d2d7 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -619,6 +619,87 @@ if(need_mob_update) return UPDATE_MOB_HEALTH +/datum/reagent/medicine/albuterol + name = "Albuterol" + description = "A potent bronchodilator capable of increasing the amount of gas inhaled by the lungs. Is highly effective at shutting down asthma attacks, \ + but only when inhaled. Overdose causes over-dilation, resulting in reduced lung function. " + taste_description = "bitter and salty air" + overdose_threshold = 30 + color = "#8df5f0" + metabolization_rate = REAGENTS_METABOLISM + ph = 4 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + default_container = /obj/item/reagent_containers/inhaler_canister + + /// The decrement we will apply to the received_pressure_mult of our targets lungs. + var/pressure_mult_increment = 0.4 + /// After this many cycles of overdose, we activate secondary effects. + var/secondary_overdose_effect_cycle_threshold = 40 + /// We stop increasing stamina damage once we reach this number. + var/maximum_od_stamina_damage = 80 + +/datum/reagent/medicine/albuterol/on_mob_metabolize(mob/living/affected_mob) + . = ..() + + if (!iscarbon(affected_mob)) + return + + // has additional effects on asthma, but that's handled in the quirk + + RegisterSignal(affected_mob, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(holder_lost_organ)) + RegisterSignal(affected_mob, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(holder_gained_organ)) + var/mob/living/carbon/carbon_mob = affected_mob + var/obj/item/organ/lungs/holder_lungs = carbon_mob.get_organ_slot(ORGAN_SLOT_LUNGS) + holder_lungs?.adjust_received_pressure_mult(pressure_mult_increment) + +/datum/reagent/medicine/albuterol/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() + + if (!iscarbon(affected_mob)) + return + + UnregisterSignal(affected_mob, list(COMSIG_CARBON_LOSE_ORGAN, COMSIG_CARBON_GAIN_ORGAN)) + var/mob/living/carbon/carbon_mob = affected_mob + var/obj/item/organ/lungs/holder_lungs = carbon_mob.get_organ_slot(ORGAN_SLOT_LUNGS) + holder_lungs?.adjust_received_pressure_mult(-pressure_mult_increment) + +/datum/reagent/medicine/albuterol/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() + + if (!iscarbon(affected_mob)) + return + + var/mob/living/carbon/carbon_mob = affected_mob + if (SPT_PROB(25, seconds_per_tick)) + carbon_mob.adjust_jitter_up_to(2 SECONDS, 20 SECONDS) + if (SPT_PROB(35, seconds_per_tick)) + if (prob(60)) + carbon_mob.losebreath += 1 + to_chat(affected_mob, span_danger("Your diaphram spasms and you find yourself unable to breathe!")) + else + carbon_mob.breathe(seconds_per_tick, times_fired) + to_chat(affected_mob, span_danger("Your diaphram spasms and you unintentionally take a breath!")) + + if (current_cycle > secondary_overdose_effect_cycle_threshold) + if (SPT_PROB(30, seconds_per_tick)) + carbon_mob.adjust_eye_blur_up_to(6 SECONDS, 30 SECONDS) + if (carbon_mob.getStaminaLoss() < maximum_od_stamina_damage) + carbon_mob.adjustStaminaLoss(seconds_per_tick) + +/datum/reagent/medicine/albuterol/proc/holder_lost_organ(datum/source, obj/item/organ/lost) + SIGNAL_HANDLER + + if (istype(lost, /obj/item/organ/lungs)) + var/obj/item/organ/lungs/holder_lungs = lost + holder_lungs.adjust_received_pressure_mult(-pressure_mult_increment) + +/datum/reagent/medicine/albuterol/proc/holder_gained_organ(datum/source, obj/item/organ/gained) + SIGNAL_HANDLER + + if (istype(gained, /obj/item/organ/lungs)) + var/obj/item/organ/lungs/holder_lungs = gained + holder_lungs.adjust_received_pressure_mult(pressure_mult_increment) + /datum/reagent/medicine/ephedrine name = "Ephedrine" description = "Increases resistance to batons and movement speed, giving you hand cramps. Overdose deals toxin damage and inhibits breathing." diff --git a/code/modules/reagents/chemistry/recipes/cat2_medicines.dm b/code/modules/reagents/chemistry/recipes/cat2_medicines.dm index c61a77393773..d0c164ba85bb 100644 --- a/code/modules/reagents/chemistry/recipes/cat2_medicines.dm +++ b/code/modules/reagents/chemistry/recipes/cat2_medicines.dm @@ -179,7 +179,7 @@ H_ion_release = -1 rate_up_lim = 50 purity_min = 0.25 - reaction_flags = REACTION_PH_VOL_CONSTANT + reaction_flags = REACTION_PH_VOL_CONSTANT|REACTION_CLEAR_INVERSE reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_OXY /datum/chemical_reaction/medicine/convermol/reaction_step(datum/reagents/holder, datum/equilibrium/reaction, delta_t, delta_ph, step_reaction_vol) diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm index 0f67defbcc24..8f8c8c806b18 100644 --- a/code/modules/reagents/chemistry/recipes/medicine.dm +++ b/code/modules/reagents/chemistry/recipes/medicine.dm @@ -157,10 +157,9 @@ required_reagents = list(/datum/reagent/medicine/sal_acid = 1, /datum/reagent/lithium = 1, /datum/reagent/aluminium = 1, /datum/reagent/bromine = 1, /datum/reagent/ammonia = 1) reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_OXY -/* /datum/chemical_reaction/medicine/albuterol_creation results = list(/datum/reagent/medicine/albuterol = 15) - required_reagents = list(/datum/reagent/lithium = 3, /datum/reagent/aluminium = 3, /datum/reagent/bromine = 3, /datum/reagent/inverse/healing/convermol = 1) + required_reagents = list(/datum/reagent/lithium = 3, /datum/reagent/aluminium = 3, /datum/reagent/bromine = 3, /datum/reagent/medicine/c2/convermol = 1) reaction_tags = REACTION_TAG_MODERATE | REACTION_TAG_ORGAN | REACTION_TAG_OTHER required_temp = 400 optimal_temp = 600 @@ -188,8 +187,8 @@ overheat_temp = 800 mix_message = "The solution breaks apart, turning a deeper blue." -/datum/chemical_reaction/medicine/albuterol_to_inverse_convermol - results = list(/datum/reagent/inverse/healing/convermol = 1, /datum/reagent/lithium = 3, /datum/reagent/aluminium = 3, /datum/reagent/bromine = 3) +/datum/chemical_reaction/medicine/albuterol_to_convermol + results = list(/datum/reagent/medicine/c2/convermol = 1, /datum/reagent/lithium = 3, /datum/reagent/aluminium = 3, /datum/reagent/bromine = 3) required_catalysts = list(/datum/reagent/toxin/acid/fluacid = 1) required_reagents = list(/datum/reagent/medicine/albuterol = 5) reaction_tags = REACTION_TAG_MODERATE | REACTION_TAG_ORGAN | REACTION_TAG_OTHER | REACTION_TAG_ACTIVE @@ -199,10 +198,9 @@ thermic_constant = 25 mix_message = "The solution rapidly breaks apart, turning a mix of colors." -/datum/chemical_reaction/medicine/albuterol_to_inverse_convermol/overheated(datum/reagents/holder, datum/equilibrium/equilibrium, impure = FALSE) +/datum/chemical_reaction/medicine/albuterol_to_convermol/overheated(datum/reagents/holder, datum/equilibrium/equilibrium, impure = FALSE) var/bonus = impure ? 2 : 1 explode_smoke(holder, equilibrium, 7.5 * bonus, TRUE, TRUE) -*/ /datum/chemical_reaction/medicine/ephedrine results = list(/datum/reagent/medicine/ephedrine = 4) diff --git a/code/modules/reagents/reagent_containers/inhaler.dm b/code/modules/reagents/reagent_containers/inhaler.dm new file mode 100644 index 000000000000..5600625df6cf --- /dev/null +++ b/code/modules/reagents/reagent_containers/inhaler.dm @@ -0,0 +1,314 @@ +/obj/item/inhaler + name = "inhaler" + desc = "A small device capable of administering short bursts of aerosolized chemicals. Requires a canister to function." + w_class = WEIGHT_CLASS_SMALL + + icon = 'icons/obj/medical/chemical.dmi' + icon_state = "inhaler_generic" + + custom_materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 0.1) + + /// The currently installed canister, from which we get our reagents. Nullable. + var/obj/item/reagent_containers/inhaler_canister/canister + /// The path for our initial canister to be generated by. If not null, we start with that canister type. + var/obj/item/reagent_containers/inhaler_canister/initial_casister_path + + /// The underlay of our canister, if one is installed. + var/mutable_appearance/canister_underlay + /// The y offset to be applied to [canister_underlay]. + var/canister_underlay_y_offset = -2 + /// If true, we will show a rotary display with how many puffs we can be used for until the canister runs out. + var/show_puffs_left = TRUE // this is how real inhalers work + +/obj/item/inhaler/Initialize(mapload) + . = ..() + if (ispath(initial_casister_path, /obj/item/reagent_containers/inhaler_canister)) + set_canister(new initial_casister_path) + +/obj/item/inhaler/Destroy(force) + QDEL_NULL(canister) + + return ..() + +/obj/item/inhaler/handle_deconstruct(disassembled) + . = ..() + + canister?.forceMove(drop_location()) + +/obj/item/inhaler/proc/update_canister_underlay() + if (isnull(canister)) + underlays -= canister_underlay + canister_underlay = null + else if (isnull(canister_underlay)) + canister_underlay = mutable_appearance(canister.icon, canister.icon_state) + canister_underlay.pixel_z = canister_underlay_y_offset + underlays += canister_underlay + +/obj/item/inhaler/examine(mob/user) + . = ..() + + if (isnull(canister)) + return + + . += span_blue("It seems to have [canister] inserted.") + if (!show_puffs_left) + return + + var/puffs_left = canister.get_puffs_left() + if (puffs_left > 0) + puffs_left = span_blue("[puffs_left]") + else + puffs_left = span_danger("[puffs_left]") + . += "Its rotary display shows its canister can be used [puffs_left] more times." + +/obj/item/inhaler/Exited(atom/movable/gone, direction) + . = ..() + + if (gone == canister) + set_canister(null, move_canister = FALSE) + + +/obj/item/inhaler/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + if (!isliving(interacting_with)) + return ..() // default behavior + var/mob/living/target_mob = interacting_with + + if (!can_puff(target_mob, user)) + return NONE + + var/puff_timer = 0 + + var/pre_use_visible_message + var/pre_use_self_message + var/pre_use_target_message + + var/post_use_visible_message + var/post_use_self_message + var/post_use_target_message + + if (target_mob == user) // no need for a target message + puff_timer = canister.self_administer_delay + + pre_use_visible_message = span_notice("[user] puts [src] to [user.p_their()] lips, fingers on the canister...") + pre_use_self_message = span_notice("You put [src] to your lips and put pressure on the canister...") + + post_use_visible_message = span_notice("[user] takes a puff of [src]!") + post_use_self_message = span_notice("You take a puff of [src]!") + else + puff_timer = canister.other_administer_delay + + pre_use_visible_message = span_warning("[user] tries to force [src] between [target_mob]'s lips...") + pre_use_self_message = span_notice("You try to put [src] to [target_mob]'s lips...") + pre_use_target_message = span_userdanger("[user] tries to force [src] between your lips!") + + post_use_visible_message = span_warning("[user] forces [src] between [target_mob]'s lips and pushes the canister down!") + post_use_self_message = span_notice("You force [src] between [target_mob]'s lips and press on the canister!") + post_use_target_message = span_userdanger("[user] forces [src] between your lips and presses on the canister, filling your lungs with aerosol!") + + if (puff_timer > 0) + user.visible_message(pre_use_visible_message, ignored_mobs = list(user, target_mob)) + to_chat(user, pre_use_self_message) + if (pre_use_target_message) + to_chat(target_mob, pre_use_target_message) + if (!do_after(user, puff_timer, src)) + return NONE + if (!can_puff(target_mob, user)) // sanity + return NONE + + user.visible_message(post_use_visible_message, ignored_mobs = list(user, target_mob)) + to_chat(user, post_use_self_message) + if (post_use_target_message) + to_chat(target_mob, post_use_target_message) + + canister.puff(user, target_mob) + +/obj/item/inhaler/attack_self(mob/user, modifiers) + try_remove_canister(user, modifiers) + + return ..() + +/obj/item/inhaler/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if (istype(tool, /obj/item/reagent_containers/inhaler_canister)) + return try_insert_canister(tool, user, modifiers) + + return ..() + +/// Tries to remove the canister, if any is inserted. +/obj/item/inhaler/proc/try_remove_canister(mob/living/user, modifiers) + if (isnull(canister)) + balloon_alert(user, "no canister inserted!") + return FALSE + + if (canister.removal_time > 0) + balloon_alert(user, "removing canister...") + if (!do_after(user, canister.removal_time, src)) + return FALSE + + balloon_alert(user, "canister removed") + playsound(src, canister.post_insert_sound, canister.post_insert_volume) + set_canister(null, user) + +// Tries to insert a canister, if none is already inserted. +/obj/item/inhaler/proc/try_insert_canister(obj/item/reagent_containers/inhaler_canister/new_canister, mob/living/user, params) + if (!isnull(canister)) + balloon_alert(user, "remove the existing canister!") + return FALSE + + balloon_alert(user, "inserting canister...") + playsound(src, new_canister.pre_insert_sound, new_canister.pre_insert_volume) + if (!do_after(user, new_canister.insertion_time, src)) + return FALSE + playsound(src, new_canister.post_insert_sound, new_canister.post_insert_volume) + balloon_alert(user, "canister inserted") + set_canister(new_canister, user) + + return TRUE + +/// Setter proc for [canister]. Moves the existing canister out of the inhaler, while moving a new canister inside and registering it. +/obj/item/inhaler/proc/set_canister(obj/item/reagent_containers/inhaler_canister/new_canister, mob/living/user, move_canister = TRUE) + if (move_canister && !isnull(canister)) + if (iscarbon(loc)) + var/mob/living/carbon/carbon_loc = loc + INVOKE_ASYNC(carbon_loc, TYPE_PROC_REF(/mob/living/carbon, put_in_hands), canister) + else if (!isnull(loc)) + canister.forceMove(loc) + + canister = new_canister + canister?.forceMove(src) + update_canister_underlay() + +/// Determines if we can be used. Fails on no canister, empty canister, invalid targets, or non-breathing targets. +/obj/item/inhaler/proc/can_puff(mob/living/target_mob, mob/living/user, silent = FALSE) + if (isnull(canister)) + if (!silent) + balloon_alert(user, "no canister!") + return FALSE + if (isnull(canister.reagents) || canister.reagents.total_volume <= 0) + if (!silent) + balloon_alert(user, "canister is empty!") + return FALSE + if (!iscarbon(target_mob)) // maybe mix this into a general has mouth check + if (!silent) + balloon_alert(user, "not breathing!") + return FALSE + var/mob/living/carbon/carbon_target = target_mob + if (carbon_target.is_mouth_covered()) + if (!silent) + balloon_alert(user, "expose the mouth!") + return FALSE + if (HAS_TRAIT(carbon_target, TRAIT_NOBREATH)) + if (!silent) + balloon_alert(user, "not breathing!") + return FALSE + var/obj/item/organ/lungs/lungs = carbon_target.get_organ_slot(ORGAN_SLOT_LUNGS) + if (isnull(lungs) || lungs.received_pressure_mult <= 0) + if (!silent) + balloon_alert(user, "not breathing!") + return FALSE + + return TRUE + +/obj/item/reagent_containers/inhaler_canister + name = "inhaler canister" + desc = "A small canister filled with aerosolized reagents for use in a inhaler." + w_class = WEIGHT_CLASS_TINY + + icon = 'icons/obj/medical/chemical.dmi' + icon_state = "canister_generic" + + reagent_flags = SEALED_CONTAINER|DRAINABLE|REFILLABLE + has_variable_transfer_amount = FALSE + + max_integrity = 60 + + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 0.2) + + /// The sound that plays when we are used. + var/puff_sound = 'sound/effects/spray.ogg' + /// The volume of [puff_sound] + var/puff_volume = 20 + + /// The sound that plays when someone TRIES to insert us. + var/pre_insert_sound = 'sound/items/taperecorder/tape_flip.ogg' + /// The sound that plays when we are removed or inserted. + var/post_insert_sound = 'sound/items/taperecorder/taperecorder_close.ogg' + + /// The volume of [pre_insert_sound] + var/pre_insert_volume = 50 + /// The volume of [post_insert_sound] + var/post_insert_volume = 50 + + /// The time it takes to insert us into a inhaler. + var/insertion_time = 2 SECONDS + /// The time it takes to remove us from a inhaler. + var/removal_time = 0.5 SECONDS + + /// The time it takes for us to be used on someone else. + var/other_administer_delay = 3 SECONDS + /// The time it takes for us to be used on our owner. + var/self_administer_delay = 1 SECONDS + +/// Called when a inhaler we are in is used on someone. Transfers reagents and plays the puff sound. +/obj/item/reagent_containers/inhaler_canister/proc/puff(mob/living/user, mob/living/carbon/target) + playsound(src, puff_sound, puff_volume, TRUE, -6) + reagents.trans_to(target, amount_per_transfer_from_this, transferred_by = user, methods = INHALE) + +/// Returns a integer approximating how many puffs we can be used for. +/obj/item/reagent_containers/inhaler_canister/proc/get_puffs_left() + return ROUND_UP(reagents.total_volume / amount_per_transfer_from_this) + +/obj/item/reagent_containers/inhaler_canister/handle_deconstruct(disassembled) + if (!reagents?.total_volume) + return ..() + + var/datum/reagents/smoke_reagents = new/datum/reagents() // Lets be safe first, our own reagents may be qdelled if we get deleted + var/datum/effect_system/fluid_spread/smoke/chem/smoke_machine/smoke = new() + smoke_reagents.my_atom = src + for (var/datum/reagent/reagent as anything in reagents.reagent_list) + smoke_reagents.add_reagent(reagent.type, reagent.volume, added_purity = reagent.purity) + reagents.remove_reagent(reagent.type, reagent.volume) + if (smoke_reagents.reagent_list) + smoke.set_up(1, holder = src, location = get_turf(src), carry = smoke_reagents) + smoke.start(log = TRUE) + visible_message(span_warning("[src] breaks open and sprays its aerosilized contents everywhere!")) + else + visible_message(span_warning("[src] breaks open - but is empty!")) + + return ..() + +/obj/item/inhaler/medical + icon_state = "inhaler_medical" + +/obj/item/inhaler/salbutamol + name = "salbutamol inhaler" + icon_state = "inhaler_medical" + initial_casister_path = /obj/item/reagent_containers/inhaler_canister/salbutamol + +/obj/item/reagent_containers/inhaler_canister/salbutamol + name = "salbutamol canister" + icon_state = "canister_medical" + list_reagents = list(/datum/reagent/medicine/salbutamol = 30) + +/obj/item/inhaler/albuterol + name = "albuterol inhaler" + icon_state = "inhaler_medical" + initial_casister_path = /obj/item/reagent_containers/inhaler_canister/albuterol + +/obj/item/reagent_containers/inhaler_canister/albuterol + name = "albuterol canister" + desc = "A small canister filled with aerosolized reagents for use in a inhaler. This one contains albuterol, a potent bronchodilator that can stop \ + asthma attacks in their tracks." + icon_state = "canister_medical" + list_reagents = list(/datum/reagent/medicine/albuterol = 30) + +/obj/item/reagent_containers/inhaler_canister/albuterol/asthma + name = "low-pressure albuterol canister" + desc = "A small canister filled with aerosolized reagents for use in a inhaler. This one contains albuterol, a potent bronchodilator that can stop \ + asthma attacks in their tracks. It seems to be a lower-pressure variant, and can only hold 20u." + list_reagents = list(/datum/reagent/medicine/albuterol = 20) + volume = 20 + +/obj/item/inhaler/albuterol/asthma + name = "rescue inhaler" + icon_state = "inhaler_generic" + initial_casister_path = /obj/item/reagent_containers/inhaler_canister/albuterol/asthma diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index 691c42121ebd..96edb9f572d4 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -117,6 +117,30 @@ ) departmental_flags = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SCIENCE +/datum/design/inhaler + name = "Inhaler" + desc = "A small device capable of administering short bursts of aerosolized chemicals. Requires a canister to function." + id = "inhaler" + build_path = /obj/item/inhaler/medical + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 0.1) + category = list( + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MEDICAL + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + +/datum/design/inhaler_canister + name = "Inhaler Canister" + desc = "A small canister filled with aerosolized reagents for use in a inhaler." + id = "inhaler_canister" + build_path = /obj/item/reagent_containers/inhaler_canister + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 0.2) + category = list( + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MEDICAL + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + /datum/design/bluespacebodybag name = "Bluespace Body Bag" desc = "A bluespace body bag, powered by experimental bluespace technology. It can hold loads of bodies and the largest of creatures." diff --git a/code/modules/research/techweb/nodes/medbay_nodes.dm b/code/modules/research/techweb/nodes/medbay_nodes.dm index c3085549ca07..499efdcfde9f 100644 --- a/code/modules/research/techweb/nodes/medbay_nodes.dm +++ b/code/modules/research/techweb/nodes/medbay_nodes.dm @@ -46,6 +46,8 @@ prereq_ids = list(TECHWEB_NODE_MEDBAY_EQUIP) design_ids = list( "med_spray_bottle", + "inhaler", + "inhaler_canister", "medigel", "medipen_refiller", "soda_dispenser", diff --git a/code/modules/surgery/asthmatic_bypass.dm b/code/modules/surgery/asthmatic_bypass.dm new file mode 100644 index 000000000000..7e69c99a7bdb --- /dev/null +++ b/code/modules/surgery/asthmatic_bypass.dm @@ -0,0 +1,96 @@ +/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/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index fff6a9c7bfb0..1478ebda72ab 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -279,6 +279,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) lungs = new() lungs.Insert(src) lungs.set_organ_damage(0) + lungs.received_pressure_mult = lungs::received_pressure_mult var/obj/item/organ/heart/heart = get_organ_slot(ORGAN_SLOT_HEART) if(heart) diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 52d3bc50833e..720e995fa7c1 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -65,6 +65,10 @@ ///Whether these lungs react negatively to miasma var/suffers_miasma = TRUE + /// All incoming breaths will have their pressure multiplied against this. Higher values allow more air to be breathed at once, + /// while lower values can cause suffocation in low pressure environments. + var/received_pressure_mult = 1 + var/oxy_breath_dam_min = MIN_TOXIC_GAS_DAMAGE var/oxy_breath_dam_max = MAX_TOXIC_GAS_DAMAGE var/oxy_damage_type = OXY @@ -178,8 +182,9 @@ organ_owner.clear_alert(ALERT_NOT_ENOUGH_N2O) RegisterSignal(organ_owner, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(block_breath)) organ_owner.remove_status_effect(/datum/status_effect/lungless) + update_bronchodilation_alerts() -/obj/item/organ/lungs/on_mob_remove(mob/living/carbon/organ_owner, special) +/obj/item/organ/lungs/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() // This is very "manual" I realize, but it's useful to ensure cleanup for gases we're removing happens // Avoids stuck alerts and such @@ -667,7 +672,7 @@ // Build out our partial pressures, for use as we go var/list/partial_pressures = list() for(var/gas_id in breath_gases) - partial_pressures[gas_id] = breath.get_breath_partial_pressure(breath_gases[gas_id][MOLES]) + partial_pressures[gas_id] = breath.get_breath_partial_pressure(breath_gases[gas_id][MOLES] * received_pressure_mult) // Treat gas as other types of gas for(var/list/conversion_packet in treat_as) @@ -924,6 +929,35 @@ return span_boldwarning("Your lungs feel extremely tight[HAS_TRAIT(owner, TRAIT_NOBREATH) ? "" : ", and every breath is a struggle"].") return span_boldwarning("It feels extremely tight[HAS_TRAIT(owner, TRAIT_NOBREATH) ? "" : ", and every breath is a struggle"].") +/obj/item/organ/lungs/get_status_appendix(advanced, add_tooltips) + var/initial_pressure_mult = initial(received_pressure_mult) + if (received_pressure_mult == initial_pressure_mult) + return + + var/tooltip + var/dilation_text + var/beginning_text = "Lung Dilation: " + if (lungs.received_pressure_mult > initial_pressure_mult) // higher than usual + beginning_text = span_blue("[beginning_text]") + dilation_text = span_blue("[(lungs.received_pressure_mult * 100) - 100]%") + tooltip = "Subject's lungs are dilated and breathing more air than usual. \ + Increases the effectiveness of healium and other gases." + + else + beginning_text = span_danger("[beginning_text]") + if (lungs.received_pressure_mult <= 0) // lethal + dilation_text = span_bolddanger("[lungs.received_pressure_mult * 100]%") + tooltip = "Subject's lungs are completely shut. Subject is unable to breathe and requires emergency surgery. \ + If asthmatic, perform asthmatic bypass surgery and adminster albuterol inhalant. \ + Otherwise, replace lungs." + else + dilation_text = span_danger("[lungs.received_pressure_mult * 100]%") + tooltip = "Subject's lungs are partially shut. \ + If unable to breathe, administer a high-pressure internals tank or replace lungs. \ + If asthmatic, inhaled albuterol or bypass surgery will likely help." + + return beginning_text + conditional_tooltip(dilation_text, tooltip, add_tooltips) + #define SMOKER_ORGAN_HEALTH (STANDARD_ORGAN_THRESHOLD * 0.75) #define SMOKER_LUNG_HEALING (STANDARD_ORGAN_HEALING * 0.75) @@ -1079,6 +1113,38 @@ #undef GAS_TOLERANCE +/// Adjusting proc for [received_pressure_mult]. Updates bronchodilation alerts. +/obj/item/organ/lungs/proc/adjust_received_pressure_mult(adjustment) + received_pressure_mult = max(received_pressure_mult + adjustment, 0) + update_bronchodilation_alerts() + +/// Setter proc for [received_pressure_mult]. Updates bronchodilation alerts. +/obj/item/organ/lungs/proc/set_received_pressure_mult(new_value) + received_pressure_mult = max(new_value, 0) + update_bronchodilation_alerts() + +#define LUNG_CAPACITY_ALERT_BUFFER 0.003 +/// Depending on [received_pressure_mult], gives either a bronchocontraction or bronchoconstriction alert to our owner (if we have one), or clears the alert +/// if [received_pressure_mult] is near 1. +/obj/item/organ/lungs/proc/update_bronchodilation_alerts() + if (!owner) + return + + var/initial_value = initial(received_pressure_mult) + + // you wont really notice if youre only breathing a bit more or a bit less + var/dilated = (received_pressure_mult > (initial_value + LUNG_CAPACITY_ALERT_BUFFER)) + var/constricted = (received_pressure_mult < (initial_value - LUNG_CAPACITY_ALERT_BUFFER)) + + if (dilated) + owner.throw_alert(ALERT_BRONCHODILATION, /atom/movable/screen/alert/bronchodilated) + else if (constricted) + owner.throw_alert(ALERT_BRONCHODILATION, /atom/movable/screen/alert/bronchoconstricted) + else + owner.clear_alert(ALERT_BRONCHODILATION) + +#undef LUNG_CAPACITY_ALERT_BUFFER + /obj/item/organ/lungs/ethereal name = "aeration reticulum" desc = "These exotic lungs seem crunchier than most." diff --git a/code/modules/vending/medical.dm b/code/modules/vending/medical.dm index bc8098489564..8f45312f4adc 100644 --- a/code/modules/vending/medical.dm +++ b/code/modules/vending/medical.dm @@ -103,6 +103,7 @@ /obj/item/reagent_containers/medigel/synthflesh = 2, /obj/item/storage/pill_bottle/psicodine = 2, /obj/item/storage/pill_bottle/sansufentanyl = 1, + /obj/item/inhaler/albuterol = 2, ) default_price = 50 extra_price = 100 diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi index e1c5db84d22aed0c35a2caf6afd99b42afa4def5..a7ea0dd9dbf9f885a8fdfc2cd3e82c4ab4d7c701 100644 GIT binary patch literal 142874 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 literal 147364 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 c&SxoS2riSsh)58795Ep3*)`;=sF`7Dp(|3 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 z≦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

    1zT`=K91m({=Ft6LNdQc_@1x7>h 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 diff --git a/tgstation.dme b/tgstation.dme index 302705aa6734..8b5d991ac283 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1323,6 +1323,7 @@ #include "code\datums\diseases\adrenal_crisis.dm" #include "code\datums\diseases\anaphylaxis.dm" #include "code\datums\diseases\anxiety.dm" +#include "code\datums\diseases\asthma_attack.dm" #include "code\datums\diseases\beesease.dm" #include "code\datums\diseases\brainrot.dm" #include "code\datums\diseases\chronic_illness.dm" @@ -1707,6 +1708,7 @@ #include "code\datums\quirks\negative_quirks\addict.dm" #include "code\datums\quirks\negative_quirks\all_nighter.dm" #include "code\datums\quirks\negative_quirks\allergic.dm" +#include "code\datums\quirks\negative_quirks\asthma.dm" #include "code\datums\quirks\negative_quirks\bad_back.dm" #include "code\datums\quirks\negative_quirks\big_hands.dm" #include "code\datums\quirks\negative_quirks\blindness.dm" @@ -5609,6 +5611,7 @@ #include "code\modules\reagents\reagent_containers\condiment.dm" #include "code\modules\reagents\reagent_containers\dropper.dm" #include "code\modules\reagents\reagent_containers\hypospray.dm" +#include "code\modules\reagents\reagent_containers\inhaler.dm" #include "code\modules\reagents\reagent_containers\medigel.dm" #include "code\modules\reagents\reagent_containers\misc.dm" #include "code\modules\reagents\reagent_containers\patch.dm" From 696c223f880ad059b1e73b0a65814df3b9a5deb0 Mon Sep 17 00:00:00 2001 From: Lucy Grindler <110352801+LucyGrind@users.noreply.github.com> Date: Sun, 16 Jun 2024 16:11:44 -0700 Subject: [PATCH 15/35] Pegging Update (#83991) --- code/__DEFINES/mobs.dm | 6 +- code/__DEFINES/surgery.dm | 2 + code/__DEFINES/traits/declarations.dm | 3 + code/_globalvars/bitfields.dm | 2 + code/_globalvars/traits/_traits.dm | 3 + .../items/stacks/sheets/sheet_types.dm | 21 +++++ code/game/objects/items/tools/weldingtool.dm | 2 +- .../mob/living/carbon/human/human_helpers.dm | 26 ++++++ code/modules/mob/mob_helpers.dm | 13 --- code/modules/power/cable.dm | 2 +- code/modules/surgery/bodyparts/_bodyparts.dm | 51 ++++++----- .../modules/surgery/bodyparts/ghetto_parts.dm | 79 ++++++++++++++++++ icons/mob/human/bodyparts.dmi | Bin 52010 -> 52823 bytes icons/mob/human/species/ghetto.dmi | Bin 0 -> 746 bytes tgstation.dme | 1 + 15 files changed, 172 insertions(+), 39 deletions(-) create mode 100644 code/modules/surgery/bodyparts/ghetto_parts.dm create mode 100644 icons/mob/human/species/ghetto.dmi diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index b97a330cbf04..a7cedf7fcfb2 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -99,6 +99,8 @@ #define BODYTYPE_ALIEN (1<<3) ///The limb is from a golem #define BODYTYPE_GOLEM (1<<4) +//The limb is a peg limb +#define BODYTYPE_PEG (1<<5) // Bodyshape defines for how things can be worn, i.e., what "shape" the mob sprite is ///The limb fits the human mold. This is not meant to be literal, if the sprite "fits" on a human, it is "humanoid", regardless of origin. @@ -110,7 +112,7 @@ ///The limb is snouted. #define BODYSHAPE_SNOUTED (1<<3) -#define BODYTYPE_BIOSCRAMBLE_INCOMPATIBLE (BODYTYPE_ROBOTIC | BODYTYPE_LARVA_PLACEHOLDER | BODYTYPE_GOLEM) +#define BODYTYPE_BIOSCRAMBLE_INCOMPATIBLE (BODYTYPE_ROBOTIC | BODYTYPE_LARVA_PLACEHOLDER | BODYTYPE_GOLEM | BODYTYPE_PEG) #define BODYTYPE_CAN_BE_BIOSCRAMBLED(bodytype) (!(bodytype & BODYTYPE_BIOSCRAMBLE_INCOMPATIBLE)) // Defines for Species IDs. Used to refer to the name of a species, for things like bodypart names or species preferences. @@ -152,6 +154,8 @@ #define BODYPART_ID_LARVA "larva" #define BODYPART_ID_PSYKER "psyker" #define BODYPART_ID_MEAT "meat" +#define BODYPART_ID_PEG "peg" + // Preferences for leg types /// Legs that are normal diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm index 4f9b4b192471..4ddd2a99f16e 100644 --- a/code/__DEFINES/surgery.dm +++ b/code/__DEFINES/surgery.dm @@ -48,6 +48,8 @@ #define IS_ORGANIC_LIMB(limb) (limb.bodytype & BODYTYPE_ORGANIC) /// Helper to figure out if a limb is robotic #define IS_ROBOTIC_LIMB(limb) (limb.bodytype & BODYTYPE_ROBOTIC) +/// Helper to figure out if a limb is a peg limb +#define IS_PEG_LIMB(limb) (limb.bodytype & BODYTYPE_PEG) // Flags for the bodypart_flags var on /obj/item/bodypart /// Bodypart cannot be dismembered or amputated diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 35e4fe22d3d6..688268b672a3 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1252,4 +1252,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Demolition modifier when hitting this object is inverted (ie, 1 / demolition) #define TRAIT_INVERTED_DEMOLITION "demolition_inverted" +/// This trait lets you attach limbs to any player without surgery. +#define TRAIT_EASY_ATTACH "easy_attach" + // END TRAIT DEFINES diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 9bc2e8b27586..235ffe5f7a32 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -412,6 +412,7 @@ DEFINE_BITFIELD(bodytype, list( "BODYTYPE_LARVA_PLACEHOLDER" = BODYTYPE_LARVA_PLACEHOLDER, "BODYTYPE_ALIEN" = BODYTYPE_ALIEN, "BODYTYPE_GOLEM" = BODYTYPE_GOLEM, + "BODYTYPE_PEG" = BODYTYPE_PEG, )) DEFINE_BITFIELD(acceptable_bodytype, list( @@ -420,6 +421,7 @@ DEFINE_BITFIELD(acceptable_bodytype, list( "BODYTYPE_LARVA_PLACEHOLDER" = BODYTYPE_LARVA_PLACEHOLDER, "BODYTYPE_ALIEN" = BODYTYPE_ALIEN, "BODYTYPE_GOLEM" = BODYTYPE_GOLEM, + "BODYTYPE_PEG" = BODYTYPE_PEG, )) DEFINE_BITFIELD(bodyshape, list( diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index fc31d8b282f6..f991b2df5d3d 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -576,6 +576,9 @@ GLOBAL_LIST_INIT(traits_by_type, list( /obj/item/bodypart = list( "TRAIT_PARALYSIS" = TRAIT_PARALYSIS, ), + /obj/item/bodypart = list( + "TRAIT_EASY_ATTACH" = TRAIT_EASY_ATTACH, + ), /obj/item/card/id = list( "TRAIT_JOB_FIRST_ID_CARD" = TRAIT_JOB_FIRST_ID_CARD, "TRAIT_MAGNETIC_ID_CARD" = TRAIT_MAGNETIC_ID_CARD, diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 08e4cb8c4eae..ee09ab989e76 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -364,6 +364,12 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \ new /datum/stack_recipe("pew (left)", /obj/structure/chair/pew/left, 3, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), new /datum/stack_recipe("pew (right)", /obj/structure/chair/pew/right, 3, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE) )), + new/datum/stack_recipe_list("peg limbs", list( + new /datum/stack_recipe("peg arm (left)", /obj/item/bodypart/arm/left/ghetto, 2, crafting_flags = NONE, category = CAT_MISC), + new /datum/stack_recipe("peg arm (right)", /obj/item/bodypart/arm/right/ghetto, 2, crafting_flags = NONE, category = CAT_MISC), + new /datum/stack_recipe("peg leg (left)", /obj/item/bodypart/leg/left/ghetto, 2, crafting_flags = NONE, category = CAT_MISC), + new /datum/stack_recipe("peg leg (right)", /obj/item/bodypart/leg/right/ghetto, 2, crafting_flags = NONE, category = CAT_MISC) + )), null, \ )) @@ -396,6 +402,21 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \ /obj/item/stack/sheet/mineral/wood/fifty amount = 50 +/obj/item/stack/sheet/mineral/wood/interact_with_atom(mob/living/carbon/human/target, mob/user) + if(!istype(target)) + return NONE + + var/obj/item/bodypart/affecting = target.get_bodypart(check_zone(user.zone_selected)) + if(affecting && IS_PEG_LIMB(affecting)) + if(user == target) + user.visible_message(span_notice("[user] starts to fix their [affecting.name]."), span_notice("You start fixing [target == user ? "your" : "[target]'s"] [affecting.name].")) + if(!do_after(user, 5 SECONDS, target)) + return ITEM_INTERACT_FAILURE + if(target.item_heal(user, brute_heal = 15, burn_heal = 15, heal_message_brute = "splintering", heal_message_burn = "charring", required_bodytype = BODYTYPE_PEG)) + use(1) + return ITEM_INTERACT_SUCCESS + else + return NONE /* * Bamboo */ diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm index 5efd76328740..03f5c61c6ddb 100644 --- a/code/game/objects/items/tools/weldingtool.dm +++ b/code/game/objects/items/tools/weldingtool.dm @@ -160,7 +160,7 @@ if(!use_tool(attacked_humanoid, user, use_delay, volume=50, amount=1)) return ITEM_INTERACT_BLOCKING - item_heal_robotic(attacked_humanoid, user, 15, 0) + attacked_humanoid.item_heal(user, brute_heal = 15, burn_heal = 0, heal_message_brute = "dents", heal_message_burn = "burnt wires", required_bodytype = BODYTYPE_ROBOTIC) return ITEM_INTERACT_SUCCESS /obj/item/weldingtool/afterattack(atom/target, mob/user, click_parameters) diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 63df3039e9ac..67b84d821be9 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -309,3 +309,29 @@ clone.domutcheck() return clone + +/mob/living/carbon/human/proc/item_heal(mob/user, brute_heal, burn_heal, heal_message_brute, heal_message_burn, required_bodytype) + var/obj/item/bodypart/affecting = src.get_bodypart(check_zone(user.zone_selected)) + if (!affecting || !(affecting.bodytype == required_bodytype)) + to_chat(user, span_warning("[affecting] is already in good condition!")) + return FALSE + + var/brute_damaged = affecting.brute_dam > 0 + var/burn_damaged = affecting.burn_dam > 0 + + var/nothing_to_heal = ((brute_heal <= 0 || !brute_damaged) && (burn_heal <= 0 || !burn_damaged)) + if (nothing_to_heal) + to_chat(user, span_notice("[affecting] is already in good condition!")) + return FALSE + + var/message + if ((brute_damaged && brute_heal > 0) && (burn_damaged && burn_heal > 0)) + message = "[heal_message_brute] and [heal_message_burn] on" + else if (brute_damaged && brute_heal > 0) + message = "[heal_message_brute] on" + else + message = "[heal_message_burn] on" + affecting.heal_damage(brute_heal, burn_heal, required_bodytype) + user.visible_message(span_notice("[user] fixes some of the [message] [src]'s [affecting.name]."), \ + span_notice("You fix some of the [message] [src == user ? "your" : "[src]'s"] [affecting.name].")) + return TRUE diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 1c1a5bd661dd..4d15d747bf7f 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -330,19 +330,6 @@ toast.name = header toast.target_ref = WEAKREF(source) -/// Heals a robotic limb on a mob -/proc/item_heal_robotic(mob/living/carbon/human/human, mob/user, brute_heal, burn_heal) - var/obj/item/bodypart/affecting = human.get_bodypart(check_zone(user.zone_selected)) - if(!affecting || IS_ORGANIC_LIMB(affecting)) - to_chat(user, span_warning("[affecting] is already in good condition!")) - return FALSE - var/brute_damage = brute_heal > burn_heal //changes repair text based on how much brute/burn was supplied - if((brute_heal > 0 && affecting.brute_dam > 0) || (burn_heal > 0 && affecting.burn_dam > 0)) - if(affecting.heal_damage(brute_heal, burn_heal, required_bodytype = BODYTYPE_ROBOTIC)) - human.update_damage_overlays() - user.visible_message(span_notice("[user] fixes some of the [brute_damage ? "dents on" : "burnt wires in"] [human]'s [affecting.name]."), \ - span_notice("You fix some of the [brute_damage ? "dents on" : "burnt wires in"] [human == user ? "your" : "[human]'s"] [affecting.name].")) - return TRUE //successful heal ///Is the passed in mob a ghost with admin powers, doesn't check for AI interact like isAdminGhost() used to diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index e2c9fd86d58a..842d7f6b791c 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -577,7 +577,7 @@ GLOBAL_LIST_INIT(wire_node_generating_types, typecacheof(list(/obj/structure/gri user.visible_message(span_notice("[user] starts to fix some of the wires in [H]'s [affecting.name]."), span_notice("You start fixing some of the wires in [H == user ? "your" : "[H]'s"] [affecting.name].")) if(!do_after(user, 5 SECONDS, H)) return - if(item_heal_robotic(H, user, 0, 15)) + if(H.item_heal(user, 0, 15, "dents", "burnt wires", BODYTYPE_ROBOTIC)) use(1) return else diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index fb48af8623e7..22181657183a 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -508,32 +508,37 @@ /obj/item/bodypart/blob_act() receive_damage(max_damage, wound_bonus = CANT_WOUND) +/obj/item/bodypart/proc/can_manually_attach(mob/living/carbon/attach_to) + if(attach_to.get_bodypart(body_zone)) + return FALSE + if(HAS_TRAIT(attach_to, TRAIT_LIMBATTACHMENT)) + return TRUE + if(HAS_TRAIT(src, TRAIT_EASY_ATTACH)) + return TRUE + if(HAS_TRAIT(attach_to, TRAIT_ROBOTIC_LIMBATTACHMENT) && (bodytype & BODYTYPE_ROBOTIC)) + return TRUE + return FALSE + /obj/item/bodypart/attack(mob/living/carbon/victim, mob/user) SHOULD_CALL_PARENT(TRUE) - - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - // NON-MODULE CHANGE START - if (!HAS_TRAIT(victim, TRAIT_LIMBATTACHMENT) && !(bodytype & BODYTYPE_ROBOTIC)) //if we're trying to attach something that's not robotic, and we don't have the generic trait, end out + // NON-MODULE CHANGE START + if(can_manually_attach(victim)) + user.temporarilyRemoveItemFromInventory(src, TRUE) + if(!try_attach_limb(victim)) + to_chat(user, span_warning("[victim]'s body rejects [src]!")) + forceMove(victim.loc) return - if(HAS_TRAIT(victim, TRAIT_LIMBATTACHMENT) || HAS_TRAIT(victim, TRAIT_ROBOTIC_LIMBATTACHMENT)) - // NON-MODULE CHANGE END - if(!human_victim.get_bodypart(body_zone)) - user.temporarilyRemoveItemFromInventory(src, TRUE) - if(!try_attach_limb(victim)) - to_chat(user, span_warning("[human_victim]'s body rejects [src]!")) - forceMove(human_victim.loc) - return - if(check_for_frankenstein(victim)) - bodypart_flags |= BODYPART_IMPLANTED - if(human_victim == user) - human_victim.visible_message(span_warning("[human_victim] jams [src] into [human_victim.p_their()] empty socket!"),\ - span_notice("You force [src] into your empty socket, and it locks into place!")) - else - human_victim.visible_message(span_warning("[user] jams [src] into [human_victim]'s empty socket!"),\ - span_notice("[user] forces [src] into your empty socket, and it locks into place!")) - return - return ..() + if(check_for_frankenstein(victim)) + bodypart_flags |= BODYPART_IMPLANTED + if(victim == user) + victim.visible_message(span_warning("[victim] jams [src] into [victim.p_their()] empty socket!"),\ + span_notice("You force [src] into your empty socket, and it locks into place!")) + else + victim.visible_message(span_warning("[user] jams [src] into [victim]'s empty socket!"),\ + span_notice("[user] forces [src] into your empty socket, and it locks into place!")) + return + // NON-MODULE CHANGE END +return ..() /obj/item/bodypart/attackby(obj/item/weapon, mob/user, params) SHOULD_CALL_PARENT(TRUE) diff --git a/code/modules/surgery/bodyparts/ghetto_parts.dm b/code/modules/surgery/bodyparts/ghetto_parts.dm new file mode 100644 index 000000000000..36c0310dead7 --- /dev/null +++ b/code/modules/surgery/bodyparts/ghetto_parts.dm @@ -0,0 +1,79 @@ +/obj/item/bodypart/arm/left/ghetto + name = "left peg arm" + desc = "A roughly hewn wooden peg replaces where a forearm should be. It's simple and sturdy, clearly made in a hurry with whatever materials were at hand. Despite its crude appearance, it gets the job done." + icon = 'icons/mob/human/species/ghetto.dmi' + icon_static = 'icons/mob/human/species/ghetto.dmi' + limb_id = BODYPART_ID_PEG + icon_state = "peg_l_arm" + bodytype = BODYTYPE_PEG + should_draw_greyscale = FALSE + attack_verb_simple = list("bashed", "slashed") + unarmed_damage_low = 3 + unarmed_damage_high = 9 + unarmed_effectiveness = 5 + brute_modifier = 1.2 + burn_modifier = 1.5 + bodypart_traits = list(TRAIT_CHUNKYFINGERS) + +/obj/item/bodypart/arm/left/ghetto/Initialize(mapload, ...) + . = ..() + ADD_TRAIT(src, TRAIT_EASY_ATTACH, INNATE_TRAIT) + +/obj/item/bodypart/arm/right/ghetto + name = "right peg arm" + desc = "A roughly hewn wooden peg replaces where a forearm should be. It's simple and sturdy, clearly made in a hurry with whatever materials were at hand. Despite its crude appearance, it gets the job done." + icon = 'icons/mob/human/species/ghetto.dmi' + icon_static = 'icons/mob/human/species/ghetto.dmi' + limb_id = BODYPART_ID_PEG + icon_state = "peg_r_arm" + bodytype = BODYTYPE_PEG + should_draw_greyscale = FALSE + attack_verb_simple = list("bashed", "slashed") + unarmed_damage_low = 3 + unarmed_damage_high = 9 + unarmed_effectiveness = 5 + brute_modifier = 1.2 + burn_modifier = 1.5 + bodypart_traits = list(TRAIT_CHUNKYFINGERS) + +/obj/item/bodypart/arm/right/ghetto/Initialize(mapload, ...) + . = ..() + ADD_TRAIT(src, TRAIT_EASY_ATTACH, INNATE_TRAIT) + +/obj/item/bodypart/leg/left/ghetto + name = "left peg leg" + desc = "Fashioned from what looks suspiciously like a table leg, this peg leg brings a whole new meaning to 'dining on the go.' It's a bit wobbly and creaks ominously with every step, but at least you can claim to have the most well-balanced diet on the seven seas." + icon = 'icons/mob/human/species/ghetto.dmi' + icon_static = 'icons/mob/human/species/ghetto.dmi' + limb_id = BODYPART_ID_PEG + icon_state = "peg_l_leg" + bodytype = BODYTYPE_PEG + should_draw_greyscale = FALSE + unarmed_damage_low = 2 + unarmed_damage_high = 5 + unarmed_effectiveness = 10 + brute_modifier = 1.2 + burn_modifier = 1.5 + +/obj/item/bodypart/leg/left/ghetto/Initialize(mapload, ...) + . = ..() + ADD_TRAIT(src, TRAIT_EASY_ATTACH, INNATE_TRAIT) + +/obj/item/bodypart/leg/right/ghetto + name = "right peg leg" + desc = "Fashioned from what looks suspiciously like a table leg, this peg leg brings a whole new meaning to 'dining on the go.' It's a bit wobbly and creaks ominously with every step, but at least you can claim to have the most well-balanced diet on the seven seas." + icon = 'icons/mob/human/species/ghetto.dmi' + icon_static = 'icons/mob/human/species/ghetto.dmi' + limb_id = BODYPART_ID_PEG + icon_state = "peg_r_leg" + bodytype = BODYTYPE_PEG + should_draw_greyscale = FALSE + unarmed_damage_low = 2 + unarmed_damage_high = 5 + unarmed_effectiveness = 10 + brute_modifier = 1.2 + burn_modifier = 1.5 + +/obj/item/bodypart/leg/right/ghetto/Initialize(mapload, ...) + . = ..() + ADD_TRAIT(src, TRAIT_EASY_ATTACH, INNATE_TRAIT) diff --git a/icons/mob/human/bodyparts.dmi b/icons/mob/human/bodyparts.dmi index d6e4472973a328fa22250471a71c9a647190085b..78be880423c1ea7988b9dc3b75a7af1bac9e0466 100644 GIT binary patch delta 12912 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 XFXAXf=`Z|{_)|sc@qMI%`MduC@WUfP delta 12180 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�fqUNnXh9ly#3Cj7i^pk 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 literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index 8b5d991ac283..1407eb57b2ab 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5877,6 +5877,7 @@ #include "code\modules\surgery\surgery_tools.dm" #include "code\modules\surgery\bodyparts\_bodyparts.dm" #include "code\modules\surgery\bodyparts\dismemberment.dm" +#include "code\modules\surgery\bodyparts\ghetto_parts.dm" #include "code\modules\surgery\bodyparts\head.dm" #include "code\modules\surgery\bodyparts\head_hair_and_lips.dm" #include "code\modules\surgery\bodyparts\helpers.dm" From 5444bf64077a53695e1c352197ff15d40521f9eb Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 19:46:27 -0600 Subject: [PATCH 16/35] Fixes --- code/datums/elements/prosthetic_icon.dm | 6 +- code/game/objects/objs.dm | 3 - .../job_types/chaplain/chaplain_nullrod.dm | 36 +++---- .../chemistry/reagents/other_reagents.dm | 2 +- .../reagents/reagent_containers/inhaler.dm | 9 +- code/modules/surgery/asthmatic_bypass.dm | 96 ------------------- .../surgery/operations/operation_revival.dm | 4 +- .../surgery/organs/internal/lungs/_lungs.dm | 10 +- maplestation.dme | 5 +- .../surgery/operations}/internal_bleeding.dm | 0 10 files changed, 40 insertions(+), 131 deletions(-) delete mode 100644 code/modules/surgery/asthmatic_bypass.dm rename {code/modules/surgery => maplestation_modules/code/modules/surgery/operations}/internal_bleeding.dm (100%) diff --git a/code/datums/elements/prosthetic_icon.dm b/code/datums/elements/prosthetic_icon.dm index 28c8353755ca..3ad2ef8ec780 100644 --- a/code/datums/elements/prosthetic_icon.dm +++ b/code/datums/elements/prosthetic_icon.dm @@ -38,7 +38,7 @@ source.inhand_icon_state += "_on" if(wielding) source.inhand_icon_state += "[HAS_TRAIT(source, TRAIT_WIELDED)]" - source.icon_angle = initial(source.icon_angle) + // source.icon_angle = initial(source.icon_angle) return NONE source.inhand_icon_state = "[icon_state_prefix]_[source.base_icon_state]" @@ -46,8 +46,8 @@ source.inhand_icon_state += "_on" if(wielding) source.inhand_icon_state += "[HAS_TRAIT(source, TRAIT_WIELDED)]" - if(isnum(icon_angle)) - source.icon_angle = icon_angle + // if(isnum(icon_angle)) + // source.icon_angle = icon_angle source.update_inhand_icon() return COMSIG_ATOM_NO_UPDATE_ICON_STATE diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 633b271b08c1..cfc23d228866 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -87,9 +87,6 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag) var/total_force = (attacking_item.force * demo_mod) var/damage = take_damage(total_force, attacking_item.damtype, MELEE, TRUE, get_dir(src, user), attacking_item.armour_penetration) - // Sanity in case one is null for some reason - var/picked_index = rand(max(length(attacking_item.attack_verb_simple), length(attacking_item.attack_verb_continuous))) - var/damage_verb = "hit" if(demo_mod > 1 && prob(damage * 5)) diff --git a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm index f76f9058ff51..dbe6c65a0312 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm @@ -23,6 +23,8 @@ var/menu_description = "A standard chaplain's weapon. Fits in pockets. Can be worn on the belt." /// Lazylist, tracks refs()s to all cultists which have been crit or killed by this nullrod. var/list/cultists_slain + /// Affects GLOB.holy_weapon_type. Disable to allow null rods to change at will and without affecting the station's type. + var/station_holy_item = TRUE /obj/item/nullrod/Initialize(mapload) . = ..() @@ -36,22 +38,24 @@ ) AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) - if(!GLOB.holy_weapon_type && type == /obj/item/nullrod) - var/list/rods = list() - for(var/obj/item/nullrod/nullrod_type as anything in typesof(/obj/item/nullrod)) - if(!initial(nullrod_type.chaplain_spawnable)) - continue - rods[nullrod_type] = initial(nullrod_type.menu_description) - //special non-nullrod subtyped shit - rods[/obj/item/gun/ballistic/bow/divine/with_quiver] = "A divine bow and 10 quivered holy arrows." - rods[/obj/item/organ/cyberimp/arm/shard/scythe] = "A shard that implants itself into your arm, \ - allowing you to conjure forth a vorpal scythe. Allows you to behead targets for empowered strikes. \ - Harms you if you dismiss the scythe without first causing harm to a creature. \ - The shard also causes you to become Morbid, shifting your interests towards the macabre." - rods[/obj/item/gun/ballistic/revolver/chaplain] = "A .38 revolver which can hold 5 bullets. \ - You can pray while holding the weapon to refill spent rounds - it does not accept standard .38." - - AddComponent(/datum/component/subtype_picker, rods, CALLBACK(src, PROC_REF(on_holy_weapon_picked))) + if((GLOB.holy_weapon_type && station_holy_item) || type != /obj/item/nullrod) + return + + var/list/rods = list() + for(var/obj/item/nullrod/nullrod_type as anything in typesof(/obj/item/nullrod)) + if(!initial(nullrod_type.chaplain_spawnable)) + continue + rods[nullrod_type] = initial(nullrod_type.menu_description) + //special non-nullrod subtyped shit + rods[/obj/item/gun/ballistic/bow/divine/with_quiver] = "A divine bow and 10 quivered holy arrows." + rods[/obj/item/organ/cyberimp/arm/shard/scythe] = "A shard that implants itself into your arm, \ + allowing you to conjure forth a vorpal scythe. Allows you to behead targets for empowered strikes. \ + Harms you if you dismiss the scythe without first causing harm to a creature. \ + The shard also causes you to become Morbid, shifting your interests towards the macabre." + rods[/obj/item/gun/ballistic/revolver/chaplain] = "A .38 revolver which can hold 5 bullets. \ + You can pray while holding the weapon to refill spent rounds - it does not accept standard .38." + + AddComponent(/datum/component/subtype_picker, rods, CALLBACK(src, PROC_REF(on_holy_weapon_picked))) /// Callback for subtype picker, invoked when the chaplain picks a new nullrod /obj/item/nullrod/proc/on_holy_weapon_picked(obj/item/nullrod/new_holy_weapon, mob/living/picker) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 18655731e0f5..3302889c883f 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1471,7 +1471,7 @@ /datum/reagent/cyborg_mutation_nanomachines/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) . = ..() - if((methods & (PATCH|INGEST|INJEC|INHALE)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) + if((methods & (PATCH|INGEST|INJECT|INHALE)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) exposed_mob.ForceContractDisease(new /datum/disease/transformation/robot(), FALSE, TRUE) /datum/reagent/xenomicrobes diff --git a/code/modules/reagents/reagent_containers/inhaler.dm b/code/modules/reagents/reagent_containers/inhaler.dm index 5600625df6cf..f6d572095709 100644 --- a/code/modules/reagents/reagent_containers/inhaler.dm +++ b/code/modules/reagents/reagent_containers/inhaler.dm @@ -74,7 +74,7 @@ var/mob/living/target_mob = interacting_with if (!can_puff(target_mob, user)) - return NONE + return ITEM_INTERACT_BLOCKING var/puff_timer = 0 @@ -110,10 +110,10 @@ to_chat(user, pre_use_self_message) if (pre_use_target_message) to_chat(target_mob, pre_use_target_message) - if (!do_after(user, puff_timer, src)) - return NONE + if (!do_after(user, puff_timer, target_mob)) + return ITEM_INTERACT_BLOCKING if (!can_puff(target_mob, user)) // sanity - return NONE + return ITEM_INTERACT_BLOCKING user.visible_message(post_use_visible_message, ignored_mobs = list(user, target_mob)) to_chat(user, post_use_self_message) @@ -121,6 +121,7 @@ to_chat(target_mob, post_use_target_message) canister.puff(user, target_mob) + return ITEM_INTERACT_SUCCESS /obj/item/inhaler/attack_self(mob/user, modifiers) try_remove_canister(user, modifiers) diff --git a/code/modules/surgery/asthmatic_bypass.dm b/code/modules/surgery/asthmatic_bypass.dm deleted file mode 100644 index 7e69c99a7bdb..000000000000 --- 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/operations/operation_revival.dm b/code/modules/surgery/operations/operation_revival.dm index cac3b4d60696..844ef49573bc 100644 --- a/code/modules/surgery/operations/operation_revival.dm +++ b/code/modules/surgery/operations/operation_revival.dm @@ -11,10 +11,10 @@ operation_flags = OPERATION_MORBID | OPERATION_NOTABLE time = 5 SECONDS preop_sound = list( - /obj/item/shockpaddles = 'sound/machines/defib/defib_charge.ogg', + /obj/item/shockpaddles = 'sound/defib/defib_charge.ogg', /obj/item = null, ) - success_sound = 'sound/machines/defib/defib_zap.ogg' + success_sound = 'sound/defib/defib_zap.ogg' required_biotype = NONE target_zone = BODY_ZONE_HEAD all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 720e995fa7c1..f96c050afe46 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -937,21 +937,21 @@ var/tooltip var/dilation_text var/beginning_text = "Lung Dilation: " - if (lungs.received_pressure_mult > initial_pressure_mult) // higher than usual + if (received_pressure_mult > initial_pressure_mult) // higher than usual beginning_text = span_blue("[beginning_text]") - dilation_text = span_blue("[(lungs.received_pressure_mult * 100) - 100]%") + dilation_text = span_blue("[(received_pressure_mult * 100) - 100]%") tooltip = "Subject's lungs are dilated and breathing more air than usual. \ Increases the effectiveness of healium and other gases." else beginning_text = span_danger("[beginning_text]") - if (lungs.received_pressure_mult <= 0) // lethal - dilation_text = span_bolddanger("[lungs.received_pressure_mult * 100]%") + if (received_pressure_mult <= 0) // lethal + dilation_text = span_bolddanger("[received_pressure_mult * 100]%") tooltip = "Subject's lungs are completely shut. Subject is unable to breathe and requires emergency surgery. \ If asthmatic, perform asthmatic bypass surgery and adminster albuterol inhalant. \ Otherwise, replace lungs." else - dilation_text = span_danger("[lungs.received_pressure_mult * 100]%") + dilation_text = span_danger("[received_pressure_mult * 100]%") tooltip = "Subject's lungs are partially shut. \ If unable to breathe, administer a high-pressure internals tank or replace lungs. \ If asthmatic, inhaled albuterol or bypass surgery will likely help." diff --git a/maplestation.dme b/maplestation.dme index d339af4ab25d..9a9bea7ba23e 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -1324,6 +1324,7 @@ #include "code\datums\diseases\adrenal_crisis.dm" #include "code\datums\diseases\anaphylaxis.dm" #include "code\datums\diseases\anxiety.dm" +#include "code\datums\diseases\asthma_attack.dm" #include "code\datums\diseases\beesease.dm" #include "code\datums\diseases\brainrot.dm" #include "code\datums\diseases\chronic_illness.dm" @@ -1708,6 +1709,7 @@ #include "code\datums\quirks\negative_quirks\addict.dm" #include "code\datums\quirks\negative_quirks\all_nighter.dm" #include "code\datums\quirks\negative_quirks\allergic.dm" +#include "code\datums\quirks\negative_quirks\asthma.dm" #include "code\datums\quirks\negative_quirks\bad_back.dm" #include "code\datums\quirks\negative_quirks\big_hands.dm" #include "code\datums\quirks\negative_quirks\blindness.dm" @@ -5601,6 +5603,7 @@ #include "code\modules\reagents\reagent_containers\condiment.dm" #include "code\modules\reagents\reagent_containers\dropper.dm" #include "code\modules\reagents\reagent_containers\hypospray.dm" +#include "code\modules\reagents\reagent_containers\inhaler.dm" #include "code\modules\reagents\reagent_containers\medigel.dm" #include "code\modules\reagents\reagent_containers\misc.dm" #include "code\modules\reagents\reagent_containers\patch.dm" @@ -5862,7 +5865,6 @@ #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\internal_bleeding.dm" #include "code\modules\surgery\surgery_disks.dm" #include "code\modules\surgery\surgery_tools.dm" #include "code\modules\surgery\bodyparts\_bodyparts.dm" @@ -6762,6 +6764,7 @@ #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_digi.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_reskins.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_tech.dm" +#include "maplestation_modules\code\modules\surgery\operations\internal_bleeding.dm" #include "maplestation_modules\code\modules\surgery\organs\augments_arms.dm" #include "maplestation_modules\code\modules\surgery\organs\augments_chest.dm" #include "maplestation_modules\code\modules\surgery\organs\augments_eyes.dm" diff --git a/code/modules/surgery/internal_bleeding.dm b/maplestation_modules/code/modules/surgery/operations/internal_bleeding.dm similarity index 100% rename from code/modules/surgery/internal_bleeding.dm rename to maplestation_modules/code/modules/surgery/operations/internal_bleeding.dm From 8329e5e01869b47d4f21b249f025805262e75024 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 21:02:24 -0600 Subject: [PATCH 17/35] Grand fixes --- .../signals/signals_mob/signals_mob_living.dm | 4 + code/__DEFINES/surgery.dm | 2 + code/__DEFINES/wounds.dm | 10 +- code/datums/elements/organ_set_bonus.dm | 14 +- .../mood_events/generic_negative_events.dm | 12 +- .../dna_infuser/organ_sets/fly_organs.dm | 2 +- .../dna_infuser/organ_sets/roach_organs.dm | 4 +- code/game/objects/items/chainsaw.dm | 2 +- code/game/objects/items/stacks/medical.dm | 2 - code/game/objects/obj_defense.dm | 2 +- .../research/designs/medical_designs.dm | 2 +- code/modules/surgery/bodyparts/_bodyparts.dm | 2 +- .../operations/_basic_surgery_state.dm | 2 +- code/modules/surgery/operations/_operation.dm | 227 ++++++++++-------- .../surgery/operations/operation_add_limb.dm | 12 +- .../operations/operation_amputation.dm | 14 +- .../surgery/operations/operation_bioware.dm | 54 ++--- .../operations/operation_bone_repair.dm | 20 +- .../surgery/operations/operation_brainwash.dm | 8 +- .../operations/operation_cavity_implant.dm | 16 +- .../surgery/operations/operation_debride.dm | 4 +- .../surgery/operations/operation_dental.dm | 6 +- .../surgery/operations/operation_filter.dm | 2 +- .../surgery/operations/operation_generic.dm | 44 ++-- .../operations/operation_generic_mechanic.dm | 18 +- .../surgery/operations/operation_healing.dm | 4 +- .../operations/operation_implant_removal.dm | 8 +- .../surgery/operations/operation_lipo.dm | 4 +- .../surgery/operations/operation_lobotomy.dm | 12 +- .../operations/operation_organ_manip.dm | 16 +- .../operations/operation_organ_repair.dm | 82 +++---- .../surgery/operations/operation_pacify.dm | 12 +- .../operations/operation_plastic_surgery.dm | 18 +- .../surgery/operations/operation_pump.dm | 2 +- .../surgery/operations/operation_puncture.dm | 8 +- .../operations/operation_replace_limb.dm | 7 +- .../surgery/operations/operation_revival.dm | 4 +- .../surgery/operations/operation_virus.dm | 8 +- .../operations/operation_wing_repair.dm | 6 +- .../surgery/operations/operation_zombie.dm | 8 +- maplestation.dme | 1 + .../advanced_ling/neutered_ling.dm | 4 +- .../surgery/operations/internal_bleeding.dm | 6 +- 43 files changed, 354 insertions(+), 341 deletions(-) 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 8154768dee2b..a1303046e1f6 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -12,6 +12,10 @@ #define COMSIG_ORGAN_BEING_REPLACED "organ_being_replaced" /// Called when an organ gets surgically removed (mob/living/user, mob/living/carbon/old_owner, target_zone, obj/item/tool) #define COMSIG_ORGAN_SURGICALLY_REMOVED "organ_surgically_removed" +/// Called when an organ gets surgically removed (mob/living/user, mob/living/carbon/new_owner, target_zone, obj/item/tool) +#define COMSIG_ORGAN_SURGICALLY_INSERTED "organ_surgically_inserted" +/// Called when an organ finishes inserting into a bodypart (obj/item/bodypart/limb, movement_flags) +#define COMSIG_ORGAN_BODYPART_INSERTED "organ_bodypart_inserted" ///Called when movement intent is toggled. #define COMSIG_MOVE_INTENT_TOGGLED "move_intent_toggled" diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm index 4ddd2a99f16e..d244c25ef4d8 100644 --- a/code/__DEFINES/surgery.dm +++ b/code/__DEFINES/surgery.dm @@ -154,6 +154,8 @@ DEFINE_BITFIELD(operation_flags, list( // Operation argument indexes /// Total speed/failure modifier applied to the operation #define OPERATION_SPEED "speed_modifier" +/// Quality of the tool being used for the operation +#define OPERATION_TOOL_QUALITY "tool_quality" /// The action being performed, simply "default" for 95% of surgeries #define OPERATION_ACTION "action" /// Whether the operation should automatically fail diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm index fa7905e0d743..1adf0ffb6426 100644 --- a/code/__DEFINES/wounds.dm +++ b/code/__DEFINES/wounds.dm @@ -71,14 +71,16 @@ GLOBAL_LIST_INIT(wound_severities_chronological, list( #define BIO_FLESH (1<<1) /// Has metal - allows the victim to suffer robotic blunt and burn wounds #define BIO_METAL (1<<2) +/// Has wood - should probably be able to catch on fire, or something +#define BIO_WOOD (1<<3) /// Is wired internally - allows the victim to suffer electrical wounds (robotic T1-T3 slash/pierce) -#define BIO_WIRED (1<<3) +#define BIO_WIRED (1<<4) /// Has bloodflow - can suffer bleeding wounds and can bleed -#define BIO_BLOODED (1<<4) +#define BIO_BLOODED (1<<5) /// Is connected by a joint - can suffer T1 bone blunt wounds (dislocation) -#define BIO_JOINTED (1<<5) +#define BIO_JOINTED (1<<6) /// Skin is covered in thick chitin and is resistant to cutting -#define BIO_CHITIN (1<<6) +#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 diff --git a/code/datums/elements/organ_set_bonus.dm b/code/datums/elements/organ_set_bonus.dm index aeb63356fb48..a002be55aba7 100644 --- a/code/datums/elements/organ_set_bonus.dm +++ b/code/datums/elements/organ_set_bonus.dm @@ -30,7 +30,7 @@ var/datum/status_effect/organ_set_bonus/set_bonus = receiver.has_status_effect(bonus_type) if(!set_bonus) set_bonus = receiver.apply_status_effect(bonus_type) - set_bonus.set_organs(set_bonus.organs + 1) + set_bonus.set_organs(set_bonus.organs + 1, target) /datum/element/organ_set_bonus/proc/on_removed(obj/item/organ/target, mob/living/carbon/loser) SIGNAL_HANDLER @@ -38,7 +38,7 @@ //get status effect or remove it var/datum/status_effect/organ_set_bonus/set_bonus = loser.has_status_effect(bonus_type) if(set_bonus) - set_bonus.set_organs(set_bonus.organs - 1) + set_bonus.set_organs(set_bonus.organs - 1, target) /datum/status_effect/organ_set_bonus id = "organ_set_bonus" @@ -58,17 +58,17 @@ /// A list of traits added to the mob upon bonus activation, can be of any length. var/list/bonus_traits = list() -/datum/status_effect/organ_set_bonus/proc/set_organs(new_value) +/datum/status_effect/organ_set_bonus/proc/set_organs(new_value, obj/item/organ/organ) organs = new_value if(!organs) //initial value but won't kick in without calling the setter qdel(src) if(organs >= organs_needed) if(!bonus_active) - INVOKE_ASYNC(src, PROC_REF(enable_bonus)) + INVOKE_ASYNC(src, PROC_REF(enable_bonus), organ) else if(bonus_active) - INVOKE_ASYNC(src, PROC_REF(disable_bonus)) + INVOKE_ASYNC(src, PROC_REF(disable_bonus), organ) -/datum/status_effect/organ_set_bonus/proc/enable_bonus() +/datum/status_effect/organ_set_bonus/proc/enable_bonus(obj/item/organ/inserted_organ) SHOULD_CALL_PARENT(TRUE) if(required_biotype) if(!(owner.mob_biotypes & required_biotype)) @@ -82,7 +82,7 @@ to_chat(owner, bonus_activate_text) return TRUE -/datum/status_effect/organ_set_bonus/proc/disable_bonus() +/datum/status_effect/organ_set_bonus/proc/disable_bonus(obj/item/organ/removed_organ) SHOULD_CALL_PARENT(TRUE) bonus_active = FALSE if(length(bonus_traits)) diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 16c4f8d51785..c0778b6f33d2 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -372,12 +372,12 @@ mood_change *= people_laughing_at_you return ..() -//These are unused so far but I want to remember them to use them later -/datum/mood_event/surgery - description = "THEY'RE CUTTING ME OPEN!!" - mood_change = -8 - event_flags = MOOD_EVENT_FEAR - // var/surgery_completed = FALSE +// //These are unused so far but I want to remember them to use them later +// /datum/mood_event/surgery +// description = "THEY'RE CUTTING ME OPEN!!" +// mood_change = -8 +// event_flags = MOOD_EVENT_FEAR +// var/surgery_completed = FALSE // /datum/mood_event/surgery/success // description = "That surgery really hurt... Glad it worked, I guess..." 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 0d74debf080c..36650b7644d8 100644 --- a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm @@ -8,7 +8,7 @@ bonus_activate_text = null bonus_deactivate_text = null -/datum/status_effect/organ_set_bonus/fly/enable_bonus() +/datum/status_effect/organ_set_bonus/fly/enable_bonus(obj/item/organ/inserted_organ) . = ..() if(!. || !ishuman(owner)) return diff --git a/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm b/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm index eb221805d4e0..51646a849ffb 100644 --- a/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm @@ -21,7 +21,7 @@ /// Storing biotypes pre-organ bonus applied so we don't remove bug from mobs which should have it. var/old_biotypes = NONE -/datum/status_effect/organ_set_bonus/roach/enable_bonus() +/datum/status_effect/organ_set_bonus/roach/enable_bonus(obj/item/organ/inserted_organ) . = ..() if(!ishuman(owner)) return @@ -32,7 +32,7 @@ old_biotypes = human_owner.mob_biotypes human_owner.mob_biotypes |= MOB_BUG -/datum/status_effect/organ_set_bonus/roach/disable_bonus() +/datum/status_effect/organ_set_bonus/roach/disable_bonus(obj/item/organ/removed_organ) . = ..() if(!ishuman(owner) || QDELETED(owner)) return diff --git a/code/game/objects/items/chainsaw.dm b/code/game/objects/items/chainsaw.dm index 647d50a8fedf..826a3216e7e1 100644 --- a/code/game/objects/items/chainsaw.dm +++ b/code/game/objects/items/chainsaw.dm @@ -52,7 +52,7 @@ throwforce_on = force_on, \ throw_speed_on = throw_speed, \ sharpness_on = SHARP_EDGED, \ - hitsound_on = 'sound/items/weapons/chainsawhit.ogg', \ + hitsound_on = 'sound/weapons/chainsawhit.ogg', \ w_class_on = w_class, \ ) AddComponent(/datum/component/butchering, \ diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 3889c82dd163..bb40bbc3b581 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -502,8 +502,6 @@ span_green("You bandage the wounds on [user == patient ? "your" : "[patient]'s"] [limb.plaintext_zone]."), visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, ) - if(heal_end_sound) - playsound(patient, heal_end_sound, 75, TRUE, MEDIUM_RANGE_SOUND_EXTRARANGE) if(limb.cached_bleed_rate) add_mob_blood(patient) diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index 10ccfd7207e7..fa0eee3d94b5 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -16,7 +16,7 @@ if(isitem(hitting_us)) var/obj/item/hit_item = hitting_us base_dam += (5 * max(0, hit_item.w_class - 2)) - base_dam *= as_item.get_demolition_modifier(src) + base_dam *= hit_item.get_demolition_modifier(src) // no armor penetration take_damage(base_dam, BRUTE, MELEE, TRUE, get_dir(src, hitting_us), 0) diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index 96edb9f572d4..02331d4d14bc 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -1063,7 +1063,7 @@ ///////////////////// /datum/design/surgery - abstract_type = /datum/design/surgery + // abstract_type = /datum/design/surgery id = DESIGN_ID_IGNORE name = null desc = null diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 22181657183a..ec67b2cb6dba 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -538,7 +538,7 @@ span_notice("[user] forces [src] into your empty socket, and it locks into place!")) return // NON-MODULE CHANGE END -return ..() + return ..() /obj/item/bodypart/attackby(obj/item/weapon, mob/user, params) SHOULD_CALL_PARENT(TRUE) diff --git a/code/modules/surgery/operations/_basic_surgery_state.dm b/code/modules/surgery/operations/_basic_surgery_state.dm index 196062c381a1..fd94b433c7c3 100644 --- a/code/modules/surgery/operations/_basic_surgery_state.dm +++ b/code/modules/surgery/operations/_basic_surgery_state.dm @@ -3,7 +3,7 @@ id = "surgery_state" alert_type = null - tick_interval = STATUS_EFFECT_NO_TICK + tick_interval = -1 status_type = STATUS_EFFECT_REFRESH var/surgery_state = NONE diff --git a/code/modules/surgery/operations/_operation.dm b/code/modules/surgery/operations/_operation.dm index e23427a0831f..9da163ec649d 100644 --- a/code/modules/surgery/operations/_operation.dm +++ b/code/modules/surgery/operations/_operation.dm @@ -445,16 +445,16 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) /// 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 + // /// 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 @@ -720,18 +720,21 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) */ /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 + // NON-MODULE CHANGE // 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) + if(HAS_TRAIT(surgeon, TRAIT_IGNORE_SURGERY_MODIFIERS)) + return 1.0 + + var/total_mod = 1.0 + 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 total_mod + // NON-MODULE CHANGE END /// Returns a time modifier for morbid operations /datum/surgery_operation/proc/get_morbid_modifier(mob/living/surgeon, obj/item/tool) @@ -817,11 +820,14 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) var/was_sleeping = (patient.stat != DEAD && HAS_TRAIT(patient, TRAIT_KNOCKEDOUT)) var/result = NONE - update_surgery_mood(patient, SURGERY_STATE_STARTED) + // 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) + // NON-MODULE CHANGE + var/tool_quality = get_tool_quality(tool) + operation_args[OPERATION_SPEED] = round(get_time_modifiers(operating_on, surgeon, tool) * tool_quality, 0.01) + operation_args[OPERATION_TOOL_QUALITY] = tool_quality if(!do_after( user = surgeon, @@ -833,7 +839,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) interaction_key = HAS_TRAIT(surgeon, TRAIT_HIPPOCRATIC_OATH) ? patient : DOAFTER_SOURCE_SURGERY, )) result |= ITEM_INTERACT_BLOCKING - update_surgery_mood(patient, SURGERY_STATE_FAILURE) + // update_surgery_mood(patient, SURGERY_STATE_FAILURE) break if(ishuman(surgeon)) @@ -862,11 +868,11 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) 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) + // 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) + // update_surgery_mood(patient, SURGERY_STATE_SUCCESS) if(isstack(tool)) var/obj/item/stack/tool_stack = tool @@ -942,79 +948,87 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) ) // NON-MODULE CHANGE -/// Display pain message to the target based on their traits and condition +/// +/** + * Display pain message to the target based on their traits and condition + * + * * target - The mob feeling the pain + * * affected_locations - Either a body zone, bodypart, or organ - or a list of any of those - indicating what locations are affected + * * pain_message - The message to display to the target + * * pain_amount - The amount of pain to cause + * * pain_type - The type of pain to cause (BRUTE, BURN, etc) + * * pain_overlay_severity - The severity of the pain overlay to display + * * surgery_moodlet - The moodlet to apply to the target + */ /datum/surgery_operation/proc/display_pain( mob/living/target, - target_zone, + affected_locations, pain_message, pain_amount = 0, pain_type = BRUTE, pain_overlay_severity = 1, - mechanical_surgery = FALSE, - surgery_moodlet, + surgery_moodlet = /datum/mood_event/surgery, ) SHOULD_NOT_OVERRIDE(TRUE) PROTECTED_PROC(TRUE) - if(!pain_message) + if(!pain_message || !affected_locations) 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)) + // Not actually dealing pain - either just a feedback message or restoring pain + if(pain_amount <= 0) + if(pain_amount < 0) + target.heal_pain(pain_amount, operating_input_to_zones(affected_locations)) + if(!HAS_TRAIT(target, TRAIT_KNOCKEDOUT)) + if(pain_amount == 0 && pain_overlay_severity > 0) + target.flash_pain_overlay(pain_overlay_severity, 0.5 SECONDS) + target.pain_message(span_danger(pain_message)) 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.")) + + // Mechanical = only give a feedback message + if(operation_flags & OPERATION_MECHANIC) + target.pain_message(span_danger(pain_message)) return - to_chat(target, span_userdanger(pain_message)) - if(prob(30) && !mechanical_surgery) - target.emote("scream") - -// /datum/surgery_step/proc/display_pain( -// mob/living/carbon/target, -// pain_message, -// mechanical_surgery = FALSE, -// target_zone = BODY_ZONE_CHEST, // can be a list of zones -// pain_amount = 0, -// pain_type = BRUTE, -// surgery_moodlet = /datum/mood_event/surgery, -// pain_overlay_severity = pain_amount >= 20 ? 2 : 1, -// ) -// ASSERT(!isnull(target)) -// ASSERT(istext(pain_message)) -// // Not actually causing pain, just feedback -// if(pain_amount <= 0) -// target.cause_pain(target_zone, pain_amount) -// target.pain_message(span_danger(pain_message)) -// return -// // Only feels pain if we feels pain -// if(!CAN_FEEL_PAIN(target)) -// target.add_mood_event("surgery", /datum/mood_event/anesthetic) -// target.pain_message(span_danger(pain_message)) -// return -// // No pain from mechanics but still show the message (usually) -// if(mechanical_surgery) -// target.pain_message(span_danger(pain_message)) -// return -// if(implement_type && (implements[implement_type] > 0)) -// pain_amount = round(pain_amount * 1 / (sqrt(implements[implement_type]) * 0.1), 0.1) + // Only feels pain if we feels pain + if(!CAN_FEEL_PAIN(target)) + target.add_mood_event("surgery", /datum/mood_event/anesthetic) + target.pain_message(span_danger(pain_message)) + return -// target.cause_pain(target_zone, pain_amount, pain_type) -// if(target.IsSleeping() || target.stat >= UNCONSCIOUS) -// return -// // Replace this check with localized anesthesia in the future -// var/obj/item/bodypart/checked_bodypart = target.get_bodypart(target_zone) -// if(checked_bodypart && checked_bodypart.bodypart_pain_modifier * target.pain_controller.pain_modifier < 0.5) -// return -// target.add_mood_event("surgery", surgery_moodlet) -// target.flash_pain_overlay(pain_overlay_severity, 0.5 SECONDS) -// target.adjust_traumatic_shock(pain_amount * 0.33 * target.pain_controller.pain_modifier) -// target.pain_emote() -// target.pain_message(span_userdanger(pain_message)) + // Actual pain inflicted + if(pain_amount > 0) + target.cause_pain(operating_input_to_zones(affected_locations), pain_amount, pain_type) + // Check for anesthetic for the rest + if(HAS_TRAIT(target, TRAIT_KNOCKEDOUT)) + return + if(surgery_moodlet) + target.add_mood_event("surgery", surgery_moodlet) + if(pain_overlay_severity > 0) + target.flash_pain_overlay(pain_overlay_severity, 0.5 SECONDS) + if(pain_amount > 0) + // surgeries may jack up the pain amount if it's affecting multiple locations, so scale down shock accordingly + target.adjust_traumatic_shock(0.25 * pain_amount * (islist(affected_locations) ? (1 / length(affected_locations)) : 1)) + target.pain_emote() + target.pain_message(span_userdanger(pain_message)) + +/// Checks if the input is a body zone, bodypart, organ, text, or list and converts it to a list of body zones +/datum/surgery_operation/proc/operating_input_to_zones(operating_input) + PROTECTED_PROC(TRUE) + if(isbodypart(operating_input)) + var/obj/item/bodypart/bodypart = operating_input + return bodypart.body_zones + if(isorgan(operating_input)) + var/obj/item/organ/organ = operating_input + return organ.zone + if(istext(operating_input)) + return operating_input + if(islist(operating_input)) + var/list/zones = list() + for(var/part in operating_input) + zones += operating_input_to_zones(part) + return zones + return null /// 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) @@ -1059,31 +1073,32 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) 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 +// NON-MODULE CHANGE +// /// 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) +// // 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]\".") +// if(!CAN_FEEL_PAIN(patient) || 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 diff --git a/code/modules/surgery/operations/operation_add_limb.dm b/code/modules/surgery/operations/operation_add_limb.dm index f1be077011fd..b789e64c62b3 100644 --- a/code/modules/surgery/operations/operation_add_limb.dm +++ b/code/modules/surgery/operations/operation_add_limb.dm @@ -116,7 +116,7 @@ ) display_pain( target = chest.owner, - target_zone = BODY_ZONE_CHEST, + affected_locations = BODY_ZONE_CHEST, pain_message ="You feel an uncomfortable sensation where your [target_zone_readable] should be!", ) @@ -150,7 +150,7 @@ ) display_pain( target = patient, - target_zone = BODY_ZONE_CHEST, + affected_locations = BODY_ZONE_CHEST, pain_message = "You feel synthetic sensation wash from your [bodypart_to_attach.plaintext_zone], which you can feel again!", ) @@ -166,7 +166,7 @@ ) display_pain( target = patient, - target_zone = BODY_ZONE_CHEST, + affected_locations = BODY_ZONE_CHEST, pain_message = "You feel a strange sensation as [thing_to_attach] takes the place of your arm!", ) @@ -198,9 +198,8 @@ var/obj/item/bodypart/chest = limb.owner.get_bodypart(BODY_ZONE_CHEST) display_pain( target = limb.owner, - target_zone = BODY_ZONE_CHEST, + affected_locations = list(limb, chest), pain_message = "[surgeon] begins to [tool.singular_name] [limb] to your body!", - pain_amount = IS_ROBOTIC_LIMB(chest) ? 0 : SURGERY_PAIN_LOW, ) /datum/surgery_operation/limb/secure_arbitrary_prosthetic/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/stack/tool, list/operation_args) @@ -214,9 +213,8 @@ var/obj/item/bodypart/chest = limb.owner.get_bodypart(BODY_ZONE_CHEST) display_pain( target = limb.owner, - target_zone = BODY_ZONE_CHEST, + affected_locations = list(limb, chest), pain_message = "You feel more secure as your prosthetic is firmly attached to your body!", - pain_amount = IS_ROBOTIC_LIMB(chest) ? 0 : SURGERY_PAIN_LOW, ) limb.remove_surgical_state(SURGERY_PROSTHETIC_UNSECURED) limb.AddComponent(/datum/component/item_as_prosthetic_limb, null, 0) // updates drop probability to zero diff --git a/code/modules/surgery/operations/operation_amputation.dm b/code/modules/surgery/operations/operation_amputation.dm index b6b136428c36..29842c264408 100644 --- a/code/modules/surgery/operations/operation_amputation.dm +++ b/code/modules/surgery/operations/operation_amputation.dm @@ -48,9 +48,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a gruesome pain in your [limb.plaintext_zone]'s joint!", - pain_amount = SURGERY_PAIN_MEDIUM, // loss of the limb also applies pain to the chest, so we can afford to make this a bit lower + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, // loss of the limb also applies pain to the chest, so we can afford to make this a bit lower pain_overlay_severity = 2, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -66,9 +66,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You can no longer feel your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, pain_overlay_severity = 2, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -90,7 +90,7 @@ ) time = 2 SECONDS //WAIT I NEED THAT!! preop_sound = 'sound/items/ratchet.ogg' - preop_sound = 'sound/machines/airlock/doorclick.ogg' + preop_sound = 'sound/machines/doorclick.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN /datum/surgery_operation/limb/amputate/mechanic/state_check(obj/item/bodypart/limb) @@ -121,9 +121,9 @@ time = 3 SECONDS preop_sound = list( /obj/item/circular_saw = 'sound/surgery/saw.ogg', - /obj/item = 'sound/items/weapons/bladeslice.ogg', + /obj/item = 'sound/weapons/bladeslice.ogg', ) - success_sound = 'sound/items/handling/materials/wood_drop.ogg' + success_sound = 'sound/items/wood_drop.ogg' all_surgery_states_required = NONE /datum/surgery_operation/limb/amputate/pegleg/all_required_strings() diff --git a/code/modules/surgery/operations/operation_bioware.dm b/code/modules/surgery/operations/operation_bioware.dm index af61189bdbfc..e9845fc9b723 100644 --- a/code/modules/surgery/operations/operation_bioware.dm +++ b/code/modules/surgery/operations/operation_bioware.dm @@ -48,7 +48,7 @@ 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) +/datum/surgery_operation/limb/bioware/vein_threading/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) display_results( surgeon, limb.owner, @@ -59,9 +59,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_ALL, + affected_locations = BODY_ZONES_ALL, pain_message = "Your entire body burns in agony!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = 4 * SURGERY_PAIN_HIGH, pain_type = BURN, pain_overlay_severity = 2, surgery_moodlet = /datum/mood_event/surgery/major, @@ -79,7 +79,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_ALL, + affected_locations = BODY_ZONES_ALL, pain_message = "You can feel your blood pumping through reinforced veins!", surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -107,9 +107,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_ALL, + affected_locations = BODY_ZONES_ALL, pain_message = "Your entire body burns in agony!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = 4 * SURGERY_PAIN_HIGH, pain_type = BURN, pain_overlay_severity = 2, surgery_moodlet = /datum/mood_event/surgery/major, @@ -127,7 +127,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_ALL, + affected_locations = BODY_ZONES_ALL, pain_message = "You can feel your heartbeat's powerful pulses ripple through your body!", surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -155,9 +155,9 @@ ) display_pain( target = limb.owner, - target_zone = BODY_ZONES_ALL, + affected_locations = BODY_ZONES_ALL, pain_message = "Your entire body goes numb!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = 4 * SURGERY_PAIN_HIGH, pain_type = BURN, pain_overlay_severity = 2, surgery_moodlet = /datum/mood_event/surgery/major, @@ -175,9 +175,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_ALL, + affected_locations = BODY_ZONES_ALL, pain_message = "You regain feeling in your body; It feels like everything's happening around you in slow motion!", - pain_amount = -0.5 * SURGERY_PAIN_HIGH, + pain_amount = -2 * SURGERY_PAIN_HIGH, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -205,9 +205,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_ALL, + affected_locations = BODY_ZONES_ALL, pain_message = "Your entire body goes numb!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = 4 * SURGERY_PAIN_HIGH, pain_type = BURN, pain_overlay_severity = 2, surgery_moodlet = /datum/mood_event/surgery/major, @@ -225,9 +225,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_ALL, + affected_locations = BODY_ZONES_ALL, pain_message = "You regain feeling in your body! You feel energzed!", - pain_amount = -0.5 * SURGERY_PAIN_HIGH, + pain_amount = -2 * SURGERY_PAIN_HIGH, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -254,9 +254,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_LIMBS, + affected_locations = BODY_ZONES_LIMBS, pain_message = "Your limbs burn with severe pain!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = 4 * SURGERY_PAIN_MEDIUM, pain_type = BURN, pain_overlay_severity = 2, surgery_moodlet = /datum/mood_event/surgery/major, @@ -274,7 +274,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_LIMBS, + affected_locations = BODY_ZONES_LIMBS, pain_message = "Your limbs feel... strangely loose.", surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -303,9 +303,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_LIMBS, + affected_locations = BODY_ZONES_LIMBS, pain_message = "Your limbs burn with severe pain!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = 4 * SURGERY_PAIN_MEDIUM, pain_type = BURN, pain_overlay_severity = 2, surgery_moodlet = /datum/mood_event/surgery/major, @@ -323,7 +323,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONES_LIMBS, + affected_locations = BODY_ZONES_LIMBS, pain_message = "Your limbs feel more secure, but also more frail.", surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -352,7 +352,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONE_HEAD, + affected_locations = limb, pain_message = "Your head throbs with gruesome pain, it's nearly too much to handle!", pain_amount = SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, @@ -370,7 +370,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONE_HEAD, + affected_locations = limb, pain_message = "Your brain feels stronger... more flexible!", surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -388,7 +388,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONE_HEAD, + affected_locations = limb, pain_message = "Your head throbs with excruciating pain!", pain_amount = SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, @@ -420,7 +420,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONE_HEAD, + affected_locations = limb, pain_message = "Your head throbs with gruesome pain, it's nearly too much to handle!", pain_amount = SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, @@ -438,7 +438,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONE_HEAD, + affected_locations = limb, pain_message = "Your brain feels stronger... more resillient!", surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -456,7 +456,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONE_HEAD, + affected_locations = limb, pain_message = "Your brain throbs with intense pain; thinking hurts!", pain_amount = SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, diff --git a/code/modules/surgery/operations/operation_bone_repair.dm b/code/modules/surgery/operations/operation_bone_repair.dm index 2dd4a003d575..aef2955737bc 100644 --- a/code/modules/surgery/operations/operation_bone_repair.dm +++ b/code/modules/surgery/operations/operation_bone_repair.dm @@ -33,9 +33,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "Your [limb.plaintext_zone] aches with pain!", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) /datum/surgery_operation/limb/repair_hairline/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -85,9 +85,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "The aching pain in your [limb.plaintext_zone] is overwhelming!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, ) /datum/surgery_operation/limb/reset_compound/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -137,9 +137,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "The aching pain in your [limb.plaintext_zone] is overwhelming!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, ) /datum/surgery_operation/limb/repair_compound/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -187,9 +187,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "Your brain feels like it's getting stabbed by little shards of glass!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/limb/prepare_cranium_repair/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) @@ -232,9 +232,9 @@ display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You can feel pieces of your skull rubbing against your brain!", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) /datum/surgery_operation/limb/repair_cranium/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) diff --git a/code/modules/surgery/operations/operation_brainwash.dm b/code/modules/surgery/operations/operation_brainwash.dm index df1979702617..fc67596e9c41 100644 --- a/code/modules/surgery/operations/operation_brainwash.dm +++ b/code/modules/surgery/operations/operation_brainwash.dm @@ -39,9 +39,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your head pounds with unimaginable pain!", // Same message as other brain surgeries - pain_amount = SURGERY_PAIN_SEVERE, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_SEVERE, ) /datum/surgery_operation/organ/brainwash/on_success(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -81,9 +81,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your head throbs with horrible pain!", - pain_amount = SURGERY_PAIN_SEVERE, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_SEVERE, ) organ.owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 40) diff --git a/code/modules/surgery/operations/operation_cavity_implant.dm b/code/modules/surgery/operations/operation_cavity_implant.dm index 3adeb36a5fa3..7ebd72b7d6ab 100644 --- a/code/modules/surgery/operations/operation_cavity_implant.dm +++ b/code/modules/surgery/operations/operation_cavity_implant.dm @@ -31,9 +31,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You can feel pressure as your [limb.plaintext_zone] is being opened wide!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, surgery_moodlet = /datum/mood_event/surgery, ) @@ -105,9 +105,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You can feel something being inserted into your [limb.plaintext_zone], it hurts like hell!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, surgery_moodlet = /datum/mood_event/surgery, ) @@ -184,9 +184,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a serious pain in your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, surgery_moodlet = /datum/mood_event/surgery, ) @@ -214,8 +214,8 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You can feel [implant.name] being pulled out of you!", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, ) surgeon.put_in_hands(implant) diff --git a/code/modules/surgery/operations/operation_debride.dm b/code/modules/surgery/operations/operation_debride.dm index f28297baae3f..bfbb9115572e 100644 --- a/code/modules/surgery/operations/operation_debride.dm +++ b/code/modules/surgery/operations/operation_debride.dm @@ -62,9 +62,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "The infection in your [limb.plaintext_zone] stings like hell! It feels like you're being stabbed!", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, pain_type = BURN, ) diff --git a/code/modules/surgery/operations/operation_dental.dm b/code/modules/surgery/operations/operation_dental.dm index 29d8043bf635..d4bc021ff43a 100644 --- a/code/modules/surgery/operations/operation_dental.dm +++ b/code/modules/surgery/operations/operation_dental.dm @@ -43,9 +43,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "Something's being jammed into your mouth!", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, ) /datum/surgery_operation/limb/add_dental_implant/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -98,7 +98,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel fingers poke around at your teeth.", ) diff --git a/code/modules/surgery/operations/operation_filter.dm b/code/modules/surgery/operations/operation_filter.dm index cb4f352a4f37..f1c1791e00e2 100644 --- a/code/modules/surgery/operations/operation_filter.dm +++ b/code/modules/surgery/operations/operation_filter.dm @@ -29,7 +29,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = BODY_ZONE_CHEST, + affected_locations = BODY_ZONE_CHEST, pain_message = "You feel a throbbing pain in your chest!", pain_amount = SURGERY_PAIN_LOW, ) diff --git a/code/modules/surgery/operations/operation_generic.dm b/code/modules/surgery/operations/operation_generic.dm index 0f951d43e279..603f7216a9f9 100644 --- a/code/modules/surgery/operations/operation_generic.dm +++ b/code/modules/surgery/operations/operation_generic.dm @@ -53,9 +53,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a stabbing in your [limb.plaintext_zone].", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) /datum/surgery_operation/limb/incise_skin/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -127,9 +127,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a severe stinging pain spreading across your [limb.plaintext_zone] as the skin is pulled back.", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/limb/retract_skin/on_success(obj/item/bodypart/limb) @@ -198,9 +198,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "Your [limb.plaintext_zone] is being [istype(tool, /obj/item/stack/medical/suture) ? "pinched" : "burned"]!", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, pain_type = istype(tool, /obj/item/stack/medical/suture) ? BRUTE : BURN, ) @@ -245,9 +245,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a pinch as the bleeding in your [limb.plaintext_zone] is slowed.", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) /datum/surgery_operation/limb/clamp_bleeders/on_success(obj/item/bodypart/limb) @@ -298,9 +298,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a pressure release as blood starts flowing in your [limb.plaintext_zone] again.", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) /datum/surgery_operation/limb/unclamp_bleeders/on_success(obj/item/bodypart/limb) @@ -361,9 +361,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a horrid ache spread through the inside of your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/limb/saw_bones/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -379,9 +379,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "It feels like something just broke in your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /// Fixes sawed bones back together @@ -425,9 +425,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a grinding sensation in your [limb.plaintext_zone] as the bones are set back in place.", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) /datum/surgery_operation/limb/fix_bones/on_success(obj/item/bodypart/limb) @@ -474,9 +474,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a horrible piercing pain in your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/limb/drill_bones/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -530,9 +530,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a stabbing in your [limb.plaintext_zone].", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) /datum/surgery_operation/limb/incise_organs/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -548,9 +548,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a sharp pain from inside your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) /datum/surgery_operation/limb/incise_organs/abductor diff --git a/code/modules/surgery/operations/operation_generic_mechanic.dm b/code/modules/surgery/operations/operation_generic_mechanic.dm index 4d6267b3768d..668f8a9103cd 100644 --- a/code/modules/surgery/operations/operation_generic_mechanic.dm +++ b/code/modules/surgery/operations/operation_generic_mechanic.dm @@ -37,9 +37,8 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel your [limb.plaintext_zone] grow numb as the shell is unscrewed.", - mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/mechanical_incision/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -75,9 +74,8 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "The last faint pricks of tactile sensation fade from your [limb.plaintext_zone] as the hatch is opened.", - mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/mechanical_open/on_success(obj/item/bodypart/limb) @@ -127,9 +125,8 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel the faint pricks of sensation return as your [limb.plaintext_zone]'s shell is screwed in.", - mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/mechanical_close/on_success(obj/item/bodypart/limb) @@ -166,9 +163,8 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You can feel a faint buzz in your [limb.plaintext_zone] as the electronics reboot.", - mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/prepare_electronics/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -204,9 +200,8 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to loosen.", - mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/mechanic_unwrench/on_success(obj/item/bodypart/limb) @@ -247,9 +242,8 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to tighten.", - mechanical_surgery = TRUE, ) /datum/surgery_operation/limb/mechanic_wrench/on_success(obj/item/bodypart/limb) diff --git a/code/modules/surgery/operations/operation_healing.dm b/code/modules/surgery/operations/operation_healing.dm index 86da5a0999a1..0ac5a92b34cb 100644 --- a/code/modules/surgery/operations/operation_healing.dm +++ b/code/modules/surgery/operations/operation_healing.dm @@ -127,9 +127,9 @@ // NON-MODULE CHANGE display_pain( target = patient, - target_zone = BODY_ZONE_CHEST, + affected_locations = BODY_ZONE_CHEST, pain_message = "Your [woundtype] sting like hell!", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, pain_type = burn_heal ? BURN : BRUTE, ) diff --git a/code/modules/surgery/operations/operation_implant_removal.dm b/code/modules/surgery/operations/operation_implant_removal.dm index 418c4080079e..f0b7c65c055a 100644 --- a/code/modules/surgery/operations/operation_implant_removal.dm +++ b/code/modules/surgery/operations/operation_implant_removal.dm @@ -29,9 +29,9 @@ if(LAZYLEN(patient.implants)) display_pain( target = patient, - target_zone = BODY_ZONE_CHEST, + affected_locations = BODY_ZONE_CHEST, pain_message = "You feel a serious pain as [surgeon] digs around inside you!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/basic/implant_removal/on_success(mob/living/patient, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -55,9 +55,9 @@ ) display_pain( target = patient, - target_zone = BODY_ZONE_CHEST, + affected_locations = BODY_ZONE_CHEST, pain_message = "You can feel your [implant.name] pulled out of you!", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) implant.removed(patient) diff --git a/code/modules/surgery/operations/operation_lipo.dm b/code/modules/surgery/operations/operation_lipo.dm index 396e731d1de8..8d9368322713 100644 --- a/code/modules/surgery/operations/operation_lipo.dm +++ b/code/modules/surgery/operations/operation_lipo.dm @@ -55,9 +55,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a stabbing in your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, ) /datum/surgery_operation/limb/lipoplasty/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) diff --git a/code/modules/surgery/operations/operation_lobotomy.dm b/code/modules/surgery/operations/operation_lobotomy.dm index 9b762b4c1d64..3926c5f9e39d 100644 --- a/code/modules/surgery/operations/operation_lobotomy.dm +++ b/code/modules/surgery/operations/operation_lobotomy.dm @@ -36,9 +36,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your head pounds with unimaginable pain!", - pain_amount = SURGERY_PAIN_CRITICAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -53,9 +53,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your head goes totally numb for a moment, the pain is overwhelming!", - pain_amount = SURGERY_PAIN_CRITICAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -85,9 +85,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "The pain in your head only seems to get worse!", - pain_amount = SURGERY_PAIN_CRITICAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, ) organ.apply_organ_damage(80) diff --git a/code/modules/surgery/operations/operation_organ_manip.dm b/code/modules/surgery/operations/operation_organ_manip.dm index 8f4aa5c789f5..c9ee69bb7921 100644 --- a/code/modules/surgery/operations/operation_organ_manip.dm +++ b/code/modules/surgery/operations/operation_organ_manip.dm @@ -161,9 +161,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a tugging sensation in your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) if("insert") play_operation_sound(limb, surgeon, tool, insert_preop_sound) @@ -176,7 +176,7 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You can feel something being placed in your [limb.plaintext_zone]!", pain_amount = SURGERY_PAIN_TRIVIAL, ) @@ -185,10 +185,10 @@ 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) + on_success_remove_organ(limb, surgeon, operation_args[OPERATION_REMOVED_ORGAN], tool, operation_args) if("insert") play_operation_sound(limb, surgeon, tool, insert_success_sound) - on_success_insert_organ(limb, surgeon, tool) + on_success_insert_organ(limb, surgeon, tool, operation_args) if(HAS_MIND_TRAIT(surgeon, TRAIT_MORBID)) surgeon.add_mood_event("morbid_abominable_surgery_success", /datum/mood_event/morbid_abominable_surgery_success) @@ -202,9 +202,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "Your [limb.plaintext_zone] throbs with pain, you can't feel your [organ.name] anymore!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = get_tool_quality(tool) * SURGERY_PAIN_MEDIUM, ) log_combat(surgeon, limb.owner, "surgically removed [organ.name] from") organ.Remove(limb.owner) @@ -225,7 +225,7 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "Your [limb.plaintext_zone] throbs with pain as your new [organ.name] comes to life!", pain_amount = SURGERY_PAIN_MEDIUM, ) diff --git a/code/modules/surgery/operations/operation_organ_repair.dm b/code/modules/surgery/operations/operation_organ_repair.dm index ab7028f80b6f..2d47c24fe4f2 100644 --- a/code/modules/surgery/operations/operation_organ_repair.dm +++ b/code/modules/surgery/operations/operation_organ_repair.dm @@ -72,9 +72,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "You feel a stabbing pain in your [parse_zone(organ.zone)]!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -90,7 +90,7 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your [parse_zone(organ.zone)] hurts like hell, but breathing becomes slightly easier.", surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -108,9 +108,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "You feel a sharp stab in your [parse_zone(organ.zone)]; the wind is knocked out of you and it hurts to catch your breath!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -158,9 +158,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your abdomen burns in horrific stabbing pain!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/organ/repair/hepatectomy/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -175,9 +175,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "The pain in your abdomen receeds slightly.", - pain_amount = -1 * SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * -1 * SURGERY_PAIN_LOW, ) /datum/surgery_operation/organ/repair/hepatectomy/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -192,9 +192,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "The pain in your abdomen intensifies!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, ) /datum/surgery_operation/organ/repair/hepatectomy/mechanic @@ -239,9 +239,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "The pain in your [parse_zone(organ.zone)] is unbearable! You can barely take it anymore!", - pain_amount = 1.5 * SURGERY_PAIN_SEVERE, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 1.5 * SURGERY_PAIN_SEVERE, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -257,9 +257,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "The pain in your [parse_zone(organ.zone)] throbs, but your heart feels better than ever!", - pain_amount = -0.5 * SURGERY_PAIN_SEVERE, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * -0.5 * SURGERY_PAIN_SEVERE, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -277,9 +277,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your [parse_zone(organ.zone)] burns; you feel like you're going insane!", - pain_amount = SURGERY_PAIN_SEVERE, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_SEVERE, surgery_moodlet = /datum/mood_event/surgery/major, pain_type = BURN, ) @@ -336,9 +336,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "You feel a horrible stab in your gut!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/organ/repair/gastrectomy/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -353,9 +353,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "The pain in your gut recedes slightly!", - pain_amount = -0.5 * SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * -0.5 * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/organ/repair/gastrectomy/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -370,9 +370,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "The pain in your gut intensifies!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, ) /datum/surgery_operation/organ/repair/gastrectomy/mechanic @@ -427,9 +427,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "You feel a dizzying pain in your [parse_zone(organ.zone)]!", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, ) /datum/surgery_operation/organ/repair/ears/on_success(obj/item/organ/ears/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -446,9 +446,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your [parse_zone(organ.zone)] swims, but it seems like you can feel your hearing coming back!", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, ) /datum/surgery_operation/organ/repair/ears/on_failure(obj/item/organ/ears/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -464,9 +464,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "You feel a visceral stabbing pain right through your [parse_zone(organ.zone)], into your brain!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) organ.owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 70) else @@ -517,9 +517,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "You feel a stabbing pain in your eyes!", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, ) /datum/surgery_operation/organ/repair/eyes/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -536,9 +536,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your vision blurs, but it seems like you can see a little better now!", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, ) /datum/surgery_operation/organ/repair/eyes/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -554,9 +554,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "You feel a visceral stabbing pain right through your [parse_zone(organ.zone)], into your brain!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) organ.owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 70) @@ -603,9 +603,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your [parse_zone(organ.zone)] pounds with unimaginable pain!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -621,9 +621,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "The pain in your head recedes, thinking becomes a bit easier!", - pain_amount = -0.33 * SURGERY_PAIN_HIGH, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * -0.33 * SURGERY_PAIN_HIGH, ) organ.owner.mind?.remove_antag_datum(/datum/antagonist/brainwashed) organ.cure_all_traumas(TRAUMA_RESILIENCE_SURGERY) @@ -642,9 +642,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your head throbs with horrible pain; thinking hurts!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, surgery_moodlet = /datum/mood_event/surgery/major, ) organ.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) diff --git a/code/modules/surgery/operations/operation_pacify.dm b/code/modules/surgery/operations/operation_pacify.dm index 4341dde1281d..7ffe1bf528a8 100644 --- a/code/modules/surgery/operations/operation_pacify.dm +++ b/code/modules/surgery/operations/operation_pacify.dm @@ -31,9 +31,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your head pounds with unimaginable pain!", - pain_amount = SURGERY_PAIN_CRITICAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -48,9 +48,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your head pounds... the concept of violence flashes in your head, and nearly makes you hurl!", - pain_amount = SURGERY_PAIN_CRITICAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, ) organ.gain_trauma(/datum/brain_trauma/severe/pacifism, TRAUMA_RESILIENCE_LOBOTOMY) @@ -66,9 +66,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your head pounds, and it feels like it's getting worse!", - pain_amount = SURGERY_PAIN_CRITICAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, ) organ.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) diff --git a/code/modules/surgery/operations/operation_plastic_surgery.dm b/code/modules/surgery/operations/operation_plastic_surgery.dm index 7ef6d7f9886e..7b4dadce5483 100644 --- a/code/modules/surgery/operations/operation_plastic_surgery.dm +++ b/code/modules/surgery/operations/operation_plastic_surgery.dm @@ -61,9 +61,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel slicing pain across your face!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/limb/plastic_surgery/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -79,9 +79,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "The pain fades, your face feels normal again!", - pain_amount = -0.5 * SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * -0.5 * SURGERY_PAIN_MEDIUM, ) return @@ -98,9 +98,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "The pain fades, your face feels new and unfamiliar!", - pain_amount = -0.5 * SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * -0.5 * SURGERY_PAIN_MEDIUM, ) if(ishuman(limb.owner)) var/mob/living/carbon/human/human_target = limb.owner @@ -120,9 +120,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "Your face feels horribly scarred and deformed!", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) ADD_TRAIT(limb.owner, TRAIT_DISFIGURED, TRAIT_GENERIC) @@ -159,7 +159,7 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a strange sensation as something is applied to your face!", ) diff --git a/code/modules/surgery/operations/operation_pump.dm b/code/modules/surgery/operations/operation_pump.dm index 4d7c91f38b9c..eb4e5f315241 100644 --- a/code/modules/surgery/operations/operation_pump.dm +++ b/code/modules/surgery/operations/operation_pump.dm @@ -31,7 +31,7 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = BODY_ZONE_CHEST, + affected_locations = organ, pain_message = "You feel a horrible sloshing feeling in your gut! You're going to be sick!", pain_amount = SURGERY_PAIN_LOW, ) diff --git a/code/modules/surgery/operations/operation_puncture.dm b/code/modules/surgery/operations/operation_puncture.dm index c530f8b6f14a..07d246b467bc 100644 --- a/code/modules/surgery/operations/operation_puncture.dm +++ b/code/modules/surgery/operations/operation_puncture.dm @@ -34,9 +34,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a horrible stabbing pain in your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/limb/repair_puncture/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -121,9 +121,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a burning sensation in your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, pain_type = BURN, ) diff --git a/code/modules/surgery/operations/operation_replace_limb.dm b/code/modules/surgery/operations/operation_replace_limb.dm index e798782a8fbd..db2ecd69eb87 100644 --- a/code/modules/surgery/operations/operation_replace_limb.dm +++ b/code/modules/surgery/operations/operation_replace_limb.dm @@ -61,9 +61,9 @@ ) display_pain( target = limb.owner, - target_zone = list(limb.body_zone, BODY_ZONE_CHEST), + affected_locations = list(limb.body_zone, BODY_ZONE_CHEST), pain_message = "You feel a horrible pain in your chest and [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_MEDIUM, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, ) /datum/surgery_operation/limb/replace_limb/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/bodypart/tool, list/operation_args) @@ -94,8 +94,7 @@ ) display_pain( target = patient, - target_zone = list(limb.body_zone, BODY_ZONE_CHEST), + affected_locations = list(limb.body_zone, BODY_ZONE_CHEST), pain_message = "Your [limb.plaintext_zone] comes awash with synthetic sensation!", - mechanical_surgery = 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 index 844ef49573bc..0fe4c396ac6c 100644 --- a/code/modules/surgery/operations/operation_revival.dm +++ b/code/modules/surgery/operations/operation_revival.dm @@ -11,10 +11,10 @@ operation_flags = OPERATION_MORBID | OPERATION_NOTABLE time = 5 SECONDS preop_sound = list( - /obj/item/shockpaddles = 'sound/defib/defib_charge.ogg', + /obj/item/shockpaddles = 'sound/machines/defib_charge.ogg', /obj/item = null, ) - success_sound = 'sound/defib/defib_zap.ogg' + success_sound = 'sound/machines/defib_zap.ogg' required_biotype = NONE target_zone = BODY_ZONE_HEAD all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED diff --git a/code/modules/surgery/operations/operation_virus.dm b/code/modules/surgery/operations/operation_virus.dm index 69f3eb0dc981..dc41922660e6 100644 --- a/code/modules/surgery/operations/operation_virus.dm +++ b/code/modules/surgery/operations/operation_virus.dm @@ -55,9 +55,9 @@ // NON-MODULE CHANGE display_pain( target = patient, - target_zone = BODY_ZONE_CHEST, + affected_locations = BODY_ZONE_CHEST, pain_message = "You feel a searing heat spread through your chest!", - pain_amount = SURGERY_PAIN_HIGH, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, pain_type = BURN, pain_overlay_severity = 1, ) @@ -73,9 +73,9 @@ // NON-MODULE CHANGE display_pain( target = patient, - target_zone = BODY_ZONE_CHEST, + affected_locations = BODY_ZONE_CHEST, pain_message = "You feel a faint throbbing in your chest.", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, ) for(var/datum/disease/infected_disease as anything in patient.diseases) if(infected_disease.severity != DISEASE_SEVERITY_UNCURABLE) //no curing quirks, sweaty diff --git a/code/modules/surgery/operations/operation_wing_repair.dm b/code/modules/surgery/operations/operation_wing_repair.dm index 046bd3bf95b9..a3a8b5636326 100644 --- a/code/modules/surgery/operations/operation_wing_repair.dm +++ b/code/modules/surgery/operations/operation_wing_repair.dm @@ -43,9 +43,9 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "Your wings sting like hell!", - pain_amount = SURGERY_PAIN_TRIVIAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_TRIVIAL, pain_type = BURN, ) @@ -60,7 +60,7 @@ // NON-MODULE CHANGE display_pain( target = organ.owner, - target_zone = organ.zone, + affected_locations = organ, pain_message = "You can feel your wings again!", ) // heal the wings in question diff --git a/code/modules/surgery/operations/operation_zombie.dm b/code/modules/surgery/operations/operation_zombie.dm index 7a5436dab0b1..8088075769eb 100644 --- a/code/modules/surgery/operations/operation_zombie.dm +++ b/code/modules/surgery/operations/operation_zombie.dm @@ -57,9 +57,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "Your [limb.plaintext_zone] pounds with unimaginable pain!", // Same message as other brain surgeries - pain_amount = SURGERY_PAIN_CRITICAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, ) @@ -74,9 +74,9 @@ // NON-MODULE CHANGE display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "Your [limb.plaintext_zone] goes totally numb for a moment, the pain is overwhelming!", - pain_amount = SURGERY_PAIN_CRITICAL, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, surgery_moodlet = /datum/mood_event/surgery/major, ) if(locate(/obj/item/organ/zombie_infection) in limb) // they got another one mid surgery? whatever diff --git a/maplestation.dme b/maplestation.dme index 9a9bea7ba23e..d443c6e81746 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -5869,6 +5869,7 @@ #include "code\modules\surgery\surgery_tools.dm" #include "code\modules\surgery\bodyparts\_bodyparts.dm" #include "code\modules\surgery\bodyparts\dismemberment.dm" +#include "code\modules\surgery\bodyparts\ghetto_parts.dm" #include "code\modules\surgery\bodyparts\head.dm" #include "code\modules\surgery\bodyparts\head_hair_and_lips.dm" #include "code\modules\surgery\bodyparts\helpers.dm" diff --git a/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm b/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm index bacc6d9ffe74..bf2c364fe751 100644 --- a/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm +++ b/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm @@ -21,12 +21,12 @@ /datum/surgery_operation/limb/neuter_ling name = "neuter changeling" desc = "Attempt to neuter a changeling's headslug." - rnd_name = "Gymnotripsy (Neuter Changeling)" + rnd_name = "Mutatiotripsy (Neuter Changeling)" rnd_desc = "An experimental surgery that attempts to neuter the headslug of a changeling by operating within their chest cavity. \ Successful surgery will remove the changeling's abilities, but failed surgery will only enrage it further. \ If the target is not a changeling, this surgery will cause massive internal organ damage." time = 15 SECONDS - operation_flags = OPERATION_ALWAYS_FAILABLE | OPERATION_AFFECTS_MOOD | OPERATION_IGNORE_CLOTHES | OPERATION_NOTABLE | OPERATION_MORBID | OPERATION_LOCKED + operation_flags = OPERATION_ALWAYS_FAILABLE | OPERATION_IGNORE_CLOTHES | OPERATION_NOTABLE | OPERATION_MORBID | OPERATION_LOCKED implements = list( TOOL_SCALPEL = 1.33, TOOL_RETRACTOR = 1.33, diff --git a/maplestation_modules/code/modules/surgery/operations/internal_bleeding.dm b/maplestation_modules/code/modules/surgery/operations/internal_bleeding.dm index b000a3e99b2a..b08cd499436c 100644 --- a/maplestation_modules/code/modules/surgery/operations/internal_bleeding.dm +++ b/maplestation_modules/code/modules/surgery/operations/internal_bleeding.dm @@ -4,7 +4,7 @@ desc = "Repair arterial damage which is causing internal bleeding in a limb." rnd_desc = "A surgery that repairs internal bleeding in a limb caused by severe trauma / arterial damage." time = 5 SECONDS - operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_IGNORE_CLOTHES | OPERATION_NOTABLE | OPERATION_LOOPING + operation_flags = OPERATION_IGNORE_CLOTHES | OPERATION_NOTABLE | OPERATION_LOOPING implements = list( TOOL_HEMOSTAT = 1, TOOL_BLOODFILTER = 1, @@ -31,9 +31,9 @@ ) display_pain( target = limb.owner, - target_zone = limb.body_zone, + affected_locations = limb, pain_message = "You feel a horrible stabbing pain in your [limb.plaintext_zone]!", - pain_amount = SURGERY_PAIN_LOW, + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_LOW, ) /datum/surgery_operation/limb/internal_bleeding/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) From 0f5fda091d598db75105b6fbf6ccae537e11be21 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 21:36:41 -0600 Subject: [PATCH 18/35] Op PC update --- .../machinery/computer/operating_computer.dm | 5 +- code/modules/surgery/operations/_operation.dm | 14 +++- .../operations/operation_amputation.dm | 2 - .../surgery/operations/operation_bioware.dm | 18 ----- .../surgery/operations/operation_brainwash.dm | 7 +- .../operations/operation_cavity_implant.dm | 3 - .../surgery/operations/operation_lobotomy.dm | 3 - .../operations/operation_organ_repair.dm | 8 -- .../surgery/operations/operation_pacify.dm | 3 - .../surgery/operations/operation_zombie.dm | 2 - .../code/datums/pain/pain_effects.dm | 10 ++- .../advanced_ling/neutered_ling.dm | 3 +- tgui/packages/tgui/interfaces/CrewConsole.tsx | 7 +- .../tgui/interfaces/MedicalRecords/index.tsx | 10 ++- .../OperatingComputer/PatientStateView.tsx | 81 ++++++++++++++++++- .../interfaces/OperatingComputer/index.tsx | 2 +- .../interfaces/OperatingComputer/types.ts | 14 ++++ .../tgui/interfaces/_OperatingComputer.tsx | 2 +- 18 files changed, 139 insertions(+), 55 deletions(-) diff --git a/code/game/machinery/computer/operating_computer.dm b/code/game/machinery/computer/operating_computer.dm index 541963583cb9..5b956472e3a0 100644 --- a/code/game/machinery/computer/operating_computer.dm +++ b/code/game/machinery/computer/operating_computer.dm @@ -216,8 +216,9 @@ // NON-MODULE CHANGE var/obj/item/organ/patient_brain = table.patient.get_organ_slot(ORGAN_SLOT_BRAIN) data["patient"]["brain"] = isnull(patient_brain) ? 100 : ((patient_brain.damage / patient_brain.maxHealth) * 100) - data["patient"]["bloodVolumePercent"] = round((table.patient.blood_volume / BLOOD_VOLUME_NORMAL) * 100) - data["patient"]["heartRate"] = table.patient.get_bpm() + var/patient_bpm = table.patient.get_bpm() + data["patient"]["heartrate"] = patient_bpm + data["patient"]["heartratestate"] = patient_bpm >= 140 ? "bad" : (patient_bpm >= 120 ? "average" : (patient_bpm >= 60 ? "good" : (patient_bpm >= 40 ? "average" : "bad"))) // We can also show pain and stuff here if we want. var/tank_exists = !isnull(table.attached_tank) diff --git a/code/modules/surgery/operations/_operation.dm b/code/modules/surgery/operations/_operation.dm index 9da163ec649d..6d7b39a38c3e 100644 --- a/code/modules/surgery/operations/_operation.dm +++ b/code/modules/surgery/operations/_operation.dm @@ -967,7 +967,6 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) pain_amount = 0, pain_type = BRUTE, pain_overlay_severity = 1, - surgery_moodlet = /datum/mood_event/surgery, ) SHOULD_NOT_OVERRIDE(TRUE) PROTECTED_PROC(TRUE) @@ -990,7 +989,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) target.pain_message(span_danger(pain_message)) return - // Only feels pain if we feels pain + // Only feels pain if we feels pain (this is where anesthetic typically exits) if(!CAN_FEEL_PAIN(target)) target.add_mood_event("surgery", /datum/mood_event/anesthetic) target.pain_message(span_danger(pain_message)) @@ -1002,13 +1001,22 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) // Check for anesthetic for the rest if(HAS_TRAIT(target, TRAIT_KNOCKEDOUT)) return + + var/surgery_moodlet = /datum/mood_event/surgery + if(operation_flags & OPERATION_NOTABLE) + surgery_moodlet = /datum/mood_event/surgery/major + else if(!(operation_flags & OPERATION_AFFECTS_MOOD)) + surgery_moodlet = /datum/mood_event/surgery/minor if(surgery_moodlet) target.add_mood_event("surgery", surgery_moodlet) + if(pain_overlay_severity > 0) target.flash_pain_overlay(pain_overlay_severity, 0.5 SECONDS) + if(pain_amount > 0) // surgeries may jack up the pain amount if it's affecting multiple locations, so scale down shock accordingly target.adjust_traumatic_shock(0.25 * pain_amount * (islist(affected_locations) ? (1 / length(affected_locations)) : 1)) + target.pain_emote() target.pain_message(span_userdanger(pain_message)) @@ -1017,7 +1025,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) PROTECTED_PROC(TRUE) if(isbodypart(operating_input)) var/obj/item/bodypart/bodypart = operating_input - return bodypart.body_zones + return bodypart.body_zone if(isorgan(operating_input)) var/obj/item/organ/organ = operating_input return organ.zone diff --git a/code/modules/surgery/operations/operation_amputation.dm b/code/modules/surgery/operations/operation_amputation.dm index 29842c264408..c8b8b8ed7d66 100644 --- a/code/modules/surgery/operations/operation_amputation.dm +++ b/code/modules/surgery/operations/operation_amputation.dm @@ -52,7 +52,6 @@ pain_message = "You feel a gruesome pain in your [limb.plaintext_zone]'s joint!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, // loss of the limb also applies pain to the chest, so we can afford to make this a bit lower pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/amputate/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -70,7 +69,6 @@ pain_message = "You can no longer feel your [limb.plaintext_zone]!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, ) if(HAS_MIND_TRAIT(surgeon, TRAIT_MORBID)) surgeon.add_mood_event("morbid_dissection_success", /datum/mood_event/morbid_dissection_success) diff --git a/code/modules/surgery/operations/operation_bioware.dm b/code/modules/surgery/operations/operation_bioware.dm index e9845fc9b723..623154038302 100644 --- a/code/modules/surgery/operations/operation_bioware.dm +++ b/code/modules/surgery/operations/operation_bioware.dm @@ -64,7 +64,6 @@ pain_amount = 4 * SURGERY_PAIN_HIGH, pain_type = BURN, pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/vein_threading/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) @@ -81,7 +80,6 @@ target = limb.owner, affected_locations = BODY_ZONES_ALL, pain_message = "You can feel your blood pumping through reinforced veins!", - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/vein_threading/mechanic @@ -112,7 +110,6 @@ pain_amount = 4 * SURGERY_PAIN_HIGH, pain_type = BURN, pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/muscled_veins/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) @@ -129,7 +126,6 @@ target = limb.owner, affected_locations = BODY_ZONES_ALL, pain_message = "You can feel your heartbeat's powerful pulses ripple through your body!", - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/muscled_veins/mechanic @@ -160,7 +156,6 @@ pain_amount = 4 * SURGERY_PAIN_HIGH, pain_type = BURN, pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/nerve_splicing/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) @@ -178,7 +173,6 @@ affected_locations = BODY_ZONES_ALL, pain_message = "You regain feeling in your body; It feels like everything's happening around you in slow motion!", pain_amount = -2 * SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/nerve_splicing/mechanic @@ -210,7 +204,6 @@ pain_amount = 4 * SURGERY_PAIN_HIGH, pain_type = BURN, pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/nerve_grounding/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) @@ -228,7 +221,6 @@ affected_locations = BODY_ZONES_ALL, pain_message = "You regain feeling in your body! You feel energzed!", pain_amount = -2 * SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/nerve_grounding/mechanic @@ -259,7 +251,6 @@ pain_amount = 4 * SURGERY_PAIN_MEDIUM, pain_type = BURN, pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/ligament_hook/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) @@ -276,7 +267,6 @@ target = limb.owner, affected_locations = BODY_ZONES_LIMBS, pain_message = "Your limbs feel... strangely loose.", - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/ligament_hook/mechanic @@ -308,7 +298,6 @@ pain_amount = 4 * SURGERY_PAIN_MEDIUM, pain_type = BURN, pain_overlay_severity = 2, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/ligament_reinforcement/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) @@ -325,7 +314,6 @@ target = limb.owner, affected_locations = BODY_ZONES_LIMBS, pain_message = "Your limbs feel more secure, but also more frail.", - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/ligament_reinforcement/mechanic @@ -355,7 +343,6 @@ affected_locations = limb, pain_message = "Your head throbs with gruesome pain, it's nearly too much to handle!", pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/cortex_folding/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) @@ -372,7 +359,6 @@ target = limb.owner, affected_locations = limb, pain_message = "Your brain feels stronger... more flexible!", - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/cortex_folding/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool) @@ -391,7 +377,6 @@ affected_locations = limb, pain_message = "Your head throbs with excruciating pain!", pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) limb.owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 60) limb.owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) @@ -423,7 +408,6 @@ affected_locations = limb, pain_message = "Your head throbs with gruesome pain, it's nearly too much to handle!", pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/cortex_imprint/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) @@ -440,7 +424,6 @@ target = limb.owner, affected_locations = limb, pain_message = "Your brain feels stronger... more resillient!", - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bioware/cortex_imprint/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool) @@ -459,7 +442,6 @@ affected_locations = limb, pain_message = "Your brain throbs with intense pain; thinking hurts!", pain_amount = SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) limb.owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 60) limb.owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) diff --git a/code/modules/surgery/operations/operation_brainwash.dm b/code/modules/surgery/operations/operation_brainwash.dm index fc67596e9c41..48262f795d0f 100644 --- a/code/modules/surgery/operations/operation_brainwash.dm +++ b/code/modules/surgery/operations/operation_brainwash.dm @@ -138,7 +138,12 @@ 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 + display_pain( + target = organ.owner, + affected_locations = organ, + pain_message = "Your head pounds with unimaginable pain!", // Same message as other brain surgeries + pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_SEVERE, + ) /datum/surgery_operation/organ/brainwash/sleeper/on_brainwash(mob/living/carbon/brainwashed, mob/living/surgeon, obj/item/tool, list/operation_args) . = ..() diff --git a/code/modules/surgery/operations/operation_cavity_implant.dm b/code/modules/surgery/operations/operation_cavity_implant.dm index 7ebd72b7d6ab..0cfc256038f2 100644 --- a/code/modules/surgery/operations/operation_cavity_implant.dm +++ b/code/modules/surgery/operations/operation_cavity_implant.dm @@ -34,7 +34,6 @@ affected_locations = limb, pain_message = "You can feel pressure as your [limb.plaintext_zone] is being opened wide!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, - surgery_moodlet = /datum/mood_event/surgery, ) /datum/surgery_operation/limb/prepare_cavity/on_success(obj/item/bodypart/chest/limb, mob/living/surgeon, tool, list/operation_args) @@ -108,7 +107,6 @@ affected_locations = limb, pain_message = "You can feel something being inserted into your [limb.plaintext_zone], it hurts like hell!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, - surgery_moodlet = /datum/mood_event/surgery, ) /datum/surgery_operation/limb/cavity_implant/on_success(obj/item/bodypart/chest/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -187,7 +185,6 @@ affected_locations = limb, pain_message = "You feel a serious pain in your [limb.plaintext_zone]!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM, - surgery_moodlet = /datum/mood_event/surgery, ) /datum/surgery_operation/limb/undo_cavity_implant/on_success(obj/item/bodypart/chest/limb, mob/living/surgeon, obj/item/tool, list/operation_args) diff --git a/code/modules/surgery/operations/operation_lobotomy.dm b/code/modules/surgery/operations/operation_lobotomy.dm index 3926c5f9e39d..72295c8ac5d8 100644 --- a/code/modules/surgery/operations/operation_lobotomy.dm +++ b/code/modules/surgery/operations/operation_lobotomy.dm @@ -39,7 +39,6 @@ affected_locations = organ, pain_message = "Your head pounds with unimaginable pain!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/organ/lobotomy/on_success(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -56,7 +55,6 @@ affected_locations = organ, pain_message = "Your head goes totally numb for a moment, the pain is overwhelming!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) organ.cure_all_traumas(TRAUMA_RESILIENCE_LOBOTOMY) @@ -88,7 +86,6 @@ affected_locations = organ, pain_message = "The pain in your head only seems to get worse!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) organ.apply_organ_damage(80) switch(rand(1, 3)) diff --git a/code/modules/surgery/operations/operation_organ_repair.dm b/code/modules/surgery/operations/operation_organ_repair.dm index 2d47c24fe4f2..18e1983f66a8 100644 --- a/code/modules/surgery/operations/operation_organ_repair.dm +++ b/code/modules/surgery/operations/operation_organ_repair.dm @@ -75,7 +75,6 @@ affected_locations = organ, pain_message = "You feel a stabbing pain in your [parse_zone(organ.zone)]!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/organ/repair/lobectomy/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -92,7 +91,6 @@ target = organ.owner, affected_locations = organ, pain_message = "Your [parse_zone(organ.zone)] hurts like hell, but breathing becomes slightly easier.", - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/organ/repair/lobectomy/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -111,7 +109,6 @@ affected_locations = organ, pain_message = "You feel a sharp stab in your [parse_zone(organ.zone)]; the wind is knocked out of you and it hurts to catch your breath!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/organ/repair/lobectomy/mechanic @@ -242,7 +239,6 @@ affected_locations = organ, pain_message = "The pain in your [parse_zone(organ.zone)] is unbearable! You can barely take it anymore!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 1.5 * SURGERY_PAIN_SEVERE, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/organ/repair/coronary_bypass/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -260,7 +256,6 @@ affected_locations = organ, pain_message = "The pain in your [parse_zone(organ.zone)] throbs, but your heart feels better than ever!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * -0.5 * SURGERY_PAIN_SEVERE, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/organ/repair/coronary_bypass/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -280,7 +275,6 @@ affected_locations = organ, pain_message = "Your [parse_zone(organ.zone)] burns; you feel like you're going insane!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_SEVERE, - surgery_moodlet = /datum/mood_event/surgery/major, pain_type = BURN, ) @@ -606,7 +600,6 @@ affected_locations = organ, pain_message = "Your [parse_zone(organ.zone)] pounds with unimaginable pain!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/organ/repair/brain/on_success(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -645,7 +638,6 @@ affected_locations = organ, pain_message = "Your head throbs with horrible pain; thinking hurts!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH, - surgery_moodlet = /datum/mood_event/surgery/major, ) organ.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) diff --git a/code/modules/surgery/operations/operation_pacify.dm b/code/modules/surgery/operations/operation_pacify.dm index 7ffe1bf528a8..21aaa2be5b37 100644 --- a/code/modules/surgery/operations/operation_pacify.dm +++ b/code/modules/surgery/operations/operation_pacify.dm @@ -34,7 +34,6 @@ affected_locations = organ, pain_message = "Your head pounds with unimaginable pain!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/organ/pacify/on_success(obj/item/organ/brain/organ, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -51,7 +50,6 @@ affected_locations = organ, pain_message = "Your head pounds... the concept of violence flashes in your head, and nearly makes you hurl!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) organ.gain_trauma(/datum/brain_trauma/severe/pacifism, TRAUMA_RESILIENCE_LOBOTOMY) @@ -69,7 +67,6 @@ affected_locations = organ, pain_message = "Your head pounds, and it feels like it's getting worse!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) organ.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) diff --git a/code/modules/surgery/operations/operation_zombie.dm b/code/modules/surgery/operations/operation_zombie.dm index 8088075769eb..b48c8e89c0c4 100644 --- a/code/modules/surgery/operations/operation_zombie.dm +++ b/code/modules/surgery/operations/operation_zombie.dm @@ -60,7 +60,6 @@ affected_locations = limb, pain_message = "Your [limb.plaintext_zone] pounds with unimaginable pain!", // Same message as other brain surgeries pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) /datum/surgery_operation/limb/bionecrosis/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) @@ -77,7 +76,6 @@ affected_locations = limb, pain_message = "Your [limb.plaintext_zone] goes totally numb for a moment, the pain is overwhelming!", pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL, - surgery_moodlet = /datum/mood_event/surgery/major, ) if(locate(/obj/item/organ/zombie_infection) in limb) // they got another one mid surgery? whatever return diff --git a/maplestation_modules/code/datums/pain/pain_effects.dm b/maplestation_modules/code/datums/pain/pain_effects.dm index ef6976e50bf3..459648c7bfb1 100644 --- a/maplestation_modules/code/datums/pain/pain_effects.dm +++ b/maplestation_modules/code/datums/pain/pain_effects.dm @@ -74,10 +74,18 @@ // Applied by most surgeries if you get operated on without anesthetics /datum/mood_event/surgery - description = "They're operating on me while I'm awake!" + description = "Wait, they're operating on me while I'm awake!" mood_change = -6 timeout = 3 MINUTES +/datum/mood_event/surgery/be_replaced(datum/mood/home, datum/mood_event/new_event, ...) + return (new_event.mood_change > src.mood_change) ? ALLOW_NEW_MOOD : BLOCK_NEW_MOOD + +/datum/mood_event/surgery/minor + description = "Aren't they supposed to use anesthetic for this?" + mood_change = -4 + timeout = 3 MINUTES + // Applied by some surgeries that are especially bad without anesthetics /datum/mood_event/surgery/major description = "THEY'RE CUTTING ME OPEN!!" diff --git a/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm b/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm index bf2c364fe751..56d750dcee0f 100644 --- a/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm +++ b/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm @@ -28,7 +28,6 @@ time = 15 SECONDS operation_flags = OPERATION_ALWAYS_FAILABLE | OPERATION_IGNORE_CLOTHES | OPERATION_NOTABLE | OPERATION_MORBID | OPERATION_LOCKED implements = list( - TOOL_SCALPEL = 1.33, TOOL_RETRACTOR = 1.33, TOOL_HEMOSTAT = 1.5, TOOL_SCREWDRIVER = 5.0, @@ -40,7 +39,7 @@ var/obj/item/offhand = surgeon.get_inactive_held_item() return !!offhand?.get_sharpness() -/datum/surgery_operation/limb/state_check(obj/item/bodypart/limb) +/datum/surgery_operation/limb/neuter_ling/state_check(obj/item/bodypart/limb) if(limb.body_zone != BODY_ZONE_CHEST) return FALSE if(limb.owner.stat < UNCONSCIOUS) diff --git a/tgui/packages/tgui/interfaces/CrewConsole.tsx b/tgui/packages/tgui/interfaces/CrewConsole.tsx index b256795b6ca4..7a572ef31ce4 100644 --- a/tgui/packages/tgui/interfaces/CrewConsole.tsx +++ b/tgui/packages/tgui/interfaces/CrewConsole.tsx @@ -113,7 +113,12 @@ const HealthStat = (props: HealthStatProps) => { export const CrewConsole = () => { return ( - +

    diff --git a/tgui/packages/tgui/interfaces/MedicalRecords/index.tsx b/tgui/packages/tgui/interfaces/MedicalRecords/index.tsx index 8a50f45d7d41..ce124b4d9577 100644 --- a/tgui/packages/tgui/interfaces/MedicalRecords/index.tsx +++ b/tgui/packages/tgui/interfaces/MedicalRecords/index.tsx @@ -11,7 +11,12 @@ export const MedicalRecords = (props) => { const { authenticated } = data; return ( - + {!authenticated ? : } @@ -32,7 +37,8 @@ const UnauthorizedView = (props) => { - + {/* NON-MODULE CHANGE */} + Nanotrasen HealthPRO diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx b/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx index 64d2031639b2..22952fbcfb82 100644 --- a/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx +++ b/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx @@ -2,9 +2,11 @@ import { AnimatedNumber, Blink, Button, + Dimmer, Icon, LabeledList, NoticeBox, + NumberInput, ProgressBar, Section, Stack, @@ -56,6 +58,9 @@ export const PatientStateView = (props: PatientStateViewProps) => { + + + { return ( - - {patient.stat} + + {/* NON-MODULE CHANGE START */} + + {patient.stat} + + + {patient.heartrate} bpm + + + {/* NON-MODULE CHANGE END */} {patient.blood_type || 'Unable to determine blood type'} @@ -352,3 +365,67 @@ const PatientStateNextOperationsView = (
    ); }; + +const PatientStateAnesthesiaView = () => { + const { act, data } = useBackend(); + const { anesthesia } = data; + if (!anesthesia) return null; + + const failsafe_enabled = anesthesia.failsafe !== -1; + + return ( +
    + {!anesthesia.has_tank && No anesthesia tank attached.} + + + act('toggle_anesthesia')} + /> + + + + + + failsafe_enabled + ? act('disable_failsafe') + : act('set_failsafe', { new_failsafe_time: 360 }) + } + /> + + + {failsafe_enabled && ( + + + act('set_failsafe', { new_failsafe_time: value }) + } + /> + + )} + + + +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/index.tsx b/tgui/packages/tgui/interfaces/OperatingComputer/index.tsx index ca4040fd9843..2432a349ac8e 100644 --- a/tgui/packages/tgui/interfaces/OperatingComputer/index.tsx +++ b/tgui/packages/tgui/interfaces/OperatingComputer/index.tsx @@ -32,7 +32,7 @@ export const OperatingComputer = () => { return ( diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/types.ts b/tgui/packages/tgui/interfaces/OperatingComputer/types.ts index 5cc1e8ee1e2b..a44f887a4eb4 100644 --- a/tgui/packages/tgui/interfaces/OperatingComputer/types.ts +++ b/tgui/packages/tgui/interfaces/OperatingComputer/types.ts @@ -10,6 +10,8 @@ export type OperatingComputerData = { surgeries: OperationData[]; techwebs: Techweb[]; experiments: ExperimentData[]; + // NON-MODULE CHANGE + anesthesia: AnesthesiaStatus | null; }; export type PatientData = { @@ -27,6 +29,10 @@ export type PatientData = { standard_blood_level: number; target_zone: BodyZone; surgery_state: string[]; + // NON-MODULE CHANGE + brain: number; + heartrate: number; + heartratestate: 'good' | 'average' | 'bad'; }; export type OperationData = { @@ -42,6 +48,14 @@ export type OperationData = { show_in_list: BooleanLike; }; +// NON-MODULE CHANGE +type AnesthesiaStatus = { + has_tank: BooleanLike; + open: BooleanLike; + failsafe: number; + can_open_tank: BooleanLike; +}; + export type damageType = { label: string; type: 'bruteLoss' | 'fireLoss' | 'toxLoss' | 'oxyLoss'; diff --git a/tgui/packages/tgui/interfaces/_OperatingComputer.tsx b/tgui/packages/tgui/interfaces/_OperatingComputer.tsx index 158a8e464aca..0fb97464a8d2 100644 --- a/tgui/packages/tgui/interfaces/_OperatingComputer.tsx +++ b/tgui/packages/tgui/interfaces/_OperatingComputer.tsx @@ -124,7 +124,7 @@ const PatientStateView = (props: { procedures: Procedure[] | null; anesthesia: AnesthesiaStatus | null; }) => { - const { act, data } = useBackend(); + const { act } = useBackend(); const { patient, procedures, anesthesia } = props; const failsafe_enabled: boolean = anesthesia?.failsafe !== -1; From 3c66b05481f7c8ecf7d53fe924fac21e7c7a6dad Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 21:57:31 -0600 Subject: [PATCH 19/35] Fixes --- .../advanced_ling/neutered_ling.dm | 3 + .../tgui/interfaces/MedicalRecords/index.tsx | 2 +- .../OperatingComputer/PatientStateView.tsx | 70 ++-- .../SurgeryProceduresView.tsx | 2 +- .../interfaces/OperatingComputer/index.tsx | 2 +- .../tgui/interfaces/_OperatingComputer.tsx | 310 ------------------ 6 files changed, 43 insertions(+), 346 deletions(-) delete mode 100644 tgui/packages/tgui/interfaces/_OperatingComputer.tsx diff --git a/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm b/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm index 56d750dcee0f..e2a5f413bd72 100644 --- a/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm +++ b/maplestation_modules/code/modules/antagonists/advanced_ling/neutered_ling.dm @@ -35,6 +35,9 @@ ) all_surgery_states_required = SURGERY_SKIN_OPEN | SURGERY_VESSELS_CLAMPED | SURGERY_ORGANS_CUT | SURGERY_BONE_DRILLED +/datum/surgery_operation/limb/neuter_ling/get_recommended_tool() + return "[..()] + scalpel" + /datum/surgery_operation/limb/neuter_ling/snowflake_check_availability(obj/item/bodypart/limb, mob/living/surgeon, tool, operated_zone) var/obj/item/offhand = surgeon.get_inactive_held_item() return !!offhand?.get_sharpness() diff --git a/tgui/packages/tgui/interfaces/MedicalRecords/index.tsx b/tgui/packages/tgui/interfaces/MedicalRecords/index.tsx index ce124b4d9577..81093e59387b 100644 --- a/tgui/packages/tgui/interfaces/MedicalRecords/index.tsx +++ b/tgui/packages/tgui/interfaces/MedicalRecords/index.tsx @@ -39,7 +39,7 @@ const UnauthorizedView = (props) => { {/* NON-MODULE CHANGE */} - Nanotrasen HealthPRO + DefOS 1.0 © Nanotrasen-Deforest HealthPRO diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx b/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx index 22952fbcfb82..3531ab3898bb 100644 --- a/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx +++ b/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx @@ -58,15 +58,15 @@ export const PatientStateView = (props: PatientStateViewProps) => { - - - + + + { const failsafe_enabled = anesthesia.failsafe !== -1; return ( -
    +
    {!anesthesia.has_tank && No anesthesia tank attached.} - - + + act('toggle_anesthesia')} - /> - - + > + {anesthesia.open ? 'Close Tank' : 'Open Tank'} + + + + - { ? act('disable_failsafe') : act('set_failsafe', { new_failsafe_time: 360 }) } + > + Safety + + + + + act('set_failsafe', { new_failsafe_time: value }) + } /> - - {failsafe_enabled && ( - - - act('set_failsafe', { new_failsafe_time: value }) - } - /> - - )} - - + +
    ); }; diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/SurgeryProceduresView.tsx b/tgui/packages/tgui/interfaces/OperatingComputer/SurgeryProceduresView.tsx index 20fc350c71f4..f4bb7a994530 100644 --- a/tgui/packages/tgui/interfaces/OperatingComputer/SurgeryProceduresView.tsx +++ b/tgui/packages/tgui/interfaces/OperatingComputer/SurgeryProceduresView.tsx @@ -106,7 +106,7 @@ export const SurgeryProceduresView = (props: SurgeryProceduresViewProps) => { buttons={ <> { return ( diff --git a/tgui/packages/tgui/interfaces/_OperatingComputer.tsx b/tgui/packages/tgui/interfaces/_OperatingComputer.tsx deleted file mode 100644 index 0fb97464a8d2..000000000000 --- a/tgui/packages/tgui/interfaces/_OperatingComputer.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import { - AnimatedNumber, - Button, - Dimmer, - LabeledList, - NoticeBox, - NumberInput, - ProgressBar, - Section, - Tabs, -} from 'tgui-core/components'; -import type { BooleanLike } from 'tgui-core/react'; - -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', - }, - { - label: 'Brain', - type: 'brain', - }, -]; - -type Surgery = { - name: string; - desc: string; -}; - -// If no patient is detected, a patient with no values will be passed to us. -type Patient = { - stat: string | null; - statstate: string | null; - blood_type: string | null; - health: number | null; - minHealth: number | null; - maxHealth: number | null; - bruteLoss: number | null; - fireLoss: number | null; - toxLoss: number | null; - oxyLoss: number | null; - bloodVolumePercent: number | null; - heartRate: number | null; -}; - -type Procedure = { - name: string; - next_step: string; - chems_needed: string | null; - alternative_step: string | null; - alt_chems_needed: string | null; -}; - -type AnesthesiaStatus = { - has_tank: BooleanLike; - open: BooleanLike; - failsafe: number; - can_open_tank: BooleanLike; -}; - -type Data = { - // TG vars - surgeries: Surgery[]; - patient: Patient | null; - procedures: Procedure[] | null; - table: BooleanLike; - // Maple vars - anesthesia: AnesthesiaStatus | null; -}; - -export const _OperatingComputer = (props, context) => { - const { act, data } = useBackend(); - const [tab, setTab] = useSharedState('tab', 1); - - const { table, patient, procedures, surgeries, anesthesia } = data; - - return ( - - - - setTab(1)}> - Patient State - - setTab(2)}> - Surgery Procedures - - act('open_experiments')}> - Experiments - - - {tab === 1 && - (table ? ( - - ) : ( - No Table Detected - ))} - {tab === 2 && } - - - ); -}; - -const PatientStateView = (props: { - patient: Patient | null; - procedures: Procedure[] | null; - anesthesia: AnesthesiaStatus | null; -}) => { - const { act } = useBackend(); - const { patient, procedures, anesthesia } = props; - - const failsafe_enabled: boolean = anesthesia?.failsafe !== -1; - - const num_to_percent = (num: number) => { - return `${Math.round(num * 10) / 10}%`; - }; - - const num_to_color = (num: number | null) => { - if (!num || num <= 33) { - return 'bad'; - } - if (num <= 66) { - return 'average'; - } - return 'good'; - }; - - return ( - <> -
    - {patient ? ( - - - {patient.stat || 'No patient detected'} - - - {patient.blood_type || 'Unknown'} - - - {patient.heartRate ? `${patient.heartRate} BPM` : 'No pulse'} - - - - - - - - - - - - {damageTypes.map((type) => ( - - - - - - ))} - - ) : ( - 'No Patient Detected' - )} -
    - {anesthesia && ( -
    - {!anesthesia.has_tank && ( - No anesthesia tank attached. - )} - - - act('toggle_anesthesia')} - /> - - - - failsafe_enabled - ? act('disable_failsafe') - : act('set_failsafe', { new_failsafe_time: 360 }) - } - /> - {failsafe_enabled && ( - - act('set_failsafe', { new_failsafe_time: value }) - } - /> - )} - - -
    - )} - {!procedures || procedures.length === 0 ? ( -
    No Active Procedures
    - ) : ( - procedures.map((procedure) => ( -
    - - - {procedure.next_step} - {procedure.chems_needed && ( - <> -
    -
    - Required Chemicals: -
    - {procedure.chems_needed} - - )} -
    - {procedure.alternative_step && ( - - {procedure.alternative_step} - {procedure.alt_chems_needed && ( - <> -
    -
    - Required Chemicals: -
    - {procedure.alt_chems_needed} - - )} -
    - )} -
    -
    - )) - )} - - ); -}; - -const SurgeryProceduresView = (props: { surgeries: Surgery[] }) => { - const { act, data } = useBackend(); - const { surgeries } = props; - return ( -
    - - {surgeries.map((surgery) => ( -
    - {surgery.desc} -
    - ))} -
    - ); -}; From 4b7f7c5c1dad154855b8888234117dc80d2fec37 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:27:48 -0600 Subject: [PATCH 20/35] Adds an operation for treating a dislocation, also wound surgeries get wound scanner boost (#94727) ## About The Pull Request - Adds `"reset dislocation"` operation, which effectively does everything that manually treating a dislocation does, with the same tools. - All wound surgeries now get a speed boost for treating a scanned wound ## Why It's Good For The Game - A doctor who attempts to reset a dislocated limb of a patient undergoing surgery would fail due to attempting to "operate" on the patient with the bonesetter (surgery takes priority over wound treatment). I decided the best way to fix this was to add an operation analog to the manual wound treatment. Effectively the same thing (same steps, in fact), but doing the operation form is faster and doesn't leave residual brute damage, to reward people who go through the effort. - For consistency reasons this made sense to me - scanning a wound improves manual treatment rates, so it should also apply to surgeries which treat the wound. ## Changelog :cl: Melbert add: Adds "reset dislocation" operation, which is a surgical analog to resetting a dislocated limb. Same tools, same steps, but if you go through the effort of doing it surgically, it's faster and safer. add: All wound surgeries now gain a speed bonus from being wound scanned. /:cl: --- .../operations/operation_bone_repair.dm | 97 +++++++++++++++++++ .../surgery/operations/operation_debride.dm | 6 ++ .../surgery/operations/operation_puncture.dm | 12 +++ 3 files changed, 115 insertions(+) diff --git a/code/modules/surgery/operations/operation_bone_repair.dm b/code/modules/surgery/operations/operation_bone_repair.dm index aef2955737bc..402d770df754 100644 --- a/code/modules/surgery/operations/operation_bone_repair.dm +++ b/code/modules/surgery/operations/operation_bone_repair.dm @@ -1,3 +1,70 @@ +// Surgical analog to manual dislocation treatment +/datum/surgery_operation/limb/repair_dislocation + name = "reset dislocation" + desc = "Reset a dislocated bone in a patient's limb. \ + Similar to the field procedure, but quicker and safer due to being performed in a controlled environment." + operation_flags = OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED | OPERATION_AFFECTS_MOOD | OPERATION_STANDING_ALLOWED + implements = list( + TOOL_BONESET = 1, + TOOL_CROWBAR = 2, + IMPLEMENT_HAND = 5, + ) + time = 2.4 SECONDS + +/datum/surgery_operation/limb/repair_dislocation/get_time_modifiers(obj/item/bodypart/limb, mob/living/surgeon, tool) + . = ..() + for(var/datum/wound/blunt/bone/bone_wound in limb.wounds) + if(HAS_TRAIT(bone_wound, TRAIT_WOUND_SCANNED) && (TOOL_BONESET in bone_wound.treatable_tools)) + . *= 0.5 + +/datum/surgery_operation/limb/repair_dislocation/get_default_radial_image() + return image(/obj/item/bonesetter) + +/datum/surgery_operation/limb/repair_dislocation/all_required_strings() + return list("the limb must be dislocated") + ..() + +/datum/surgery_operation/limb/repair_dislocation/state_check(obj/item/bodypart/limb) + for(var/datum/wound/blunt/bone/bone_wound in limb.wounds) + if(TOOL_BONESET in bone_wound.treatable_tools) + return TRUE + + return FALSE + +/datum/surgery_operation/limb/repair_dislocation/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 dislocation in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to reset the dislocation in [FORMAT_LIMB_OWNER(limb)] with [tool]."), + span_notice("[surgeon] begins to reset the dislocation in [FORMAT_LIMB_OWNER(limb)]."), + ) + display_pain(limb.owner, "Your [limb.plaintext_zone] aches with pain!") + +/datum/surgery_operation/limb/repair_dislocation/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + for(var/datum/wound/blunt/bone/bone_wound in limb.wounds) + if(TOOL_BONESET in bone_wound.treatable_tools) + qdel(bone_wound) + + display_results( + surgeon, + limb.owner, + span_notice("You successfully reset the dislocation in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully resets the dislocation in [FORMAT_LIMB_OWNER(limb)]!"), + span_notice("[surgeon] successfully resets the dislocation in [FORMAT_LIMB_OWNER(limb)]!"), + ) + display_pain(limb.owner, "Your [limb.plaintext_zone] feels much better now!") + +/datum/surgery_operation/limb/repair_dislocation/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + display_results( + surgeon, + limb.owner, + span_notice("You fail to reset the dislocation in [FORMAT_LIMB_OWNER(limb)], causing further damage!"), + span_notice("[surgeon] fails to reset the dislocation in [FORMAT_LIMB_OWNER(limb)], causing further damage!"), + span_notice("[surgeon] fails to reset the dislocation in [FORMAT_LIMB_OWNER(limb)]!"), + ) + display_pain(limb.owner, "The pain in your [limb.plaintext_zone] intensifies!") + limb.receive_damage(25, damage_source = tool) + /datum/surgery_operation/limb/repair_hairline name = "repair hairline fracture" desc = "Mend a hairline fracture in a patient's bone." @@ -12,6 +79,12 @@ time = 4 SECONDS any_surgery_states_required = ALL_SURGERY_SKIN_STATES +/datum/surgery_operation/limb/repair_hairline/get_time_modifiers(obj/item/bodypart/limb, mob/living/surgeon, tool) + . = ..() + for(var/datum/wound/blunt/bone/critical/bone_wound in limb.wounds) + if(HAS_TRAIT(bone_wound, TRAIT_WOUND_SCANNED)) + . *= 0.5 + /datum/surgery_operation/limb/repair_hairline/get_default_radial_image() return image(/obj/item/bonesetter) @@ -63,6 +136,12 @@ time = 6 SECONDS all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED +/datum/surgery_operation/limb/reset_compound/get_time_modifiers(obj/item/bodypart/limb, mob/living/surgeon, tool) + . = ..() + for(var/datum/wound/blunt/bone/severe/bone_wound in limb.wounds) + if(HAS_TRAIT(bone_wound, TRAIT_WOUND_SCANNED)) + . *= 0.5 + /datum/surgery_operation/limb/reset_compound/get_default_radial_image() return image(/obj/item/bonesetter) @@ -115,6 +194,12 @@ time = 4 SECONDS any_surgery_states_required = ALL_SURGERY_SKIN_STATES +/datum/surgery_operation/limb/repair_compound/get_time_modifiers(obj/item/bodypart/limb, mob/living/surgeon, tool) + . = ..() + for(var/datum/wound/blunt/bone/critical/bone_wound in limb.wounds) + if(HAS_TRAIT(bone_wound, TRAIT_WOUND_SCANNED)) + . *= 0.5 + /datum/surgery_operation/limb/repair_compound/get_default_radial_image() return image(/obj/item/stack/medical/bone_gel) @@ -165,6 +250,12 @@ time = 2.4 SECONDS preop_sound = 'sound/surgery/hemostat1.ogg' +/datum/surgery_operation/limb/prepare_cranium_repair/get_time_modifiers(obj/item/bodypart/limb, mob/living/surgeon, tool) + . = ..() + for(var/datum/wound/cranial_fissure/fissure in limb.wounds) + if(HAS_TRAIT(fissure, TRAIT_WOUND_SCANNED)) + . *= 0.5 + /datum/surgery_operation/limb/prepare_cranium_repair/get_default_radial_image() return image(/obj/item/hemostat) @@ -209,6 +300,12 @@ ) time = 4 SECONDS +/datum/surgery_operation/limb/repair_cranium/get_time_modifiers(obj/item/bodypart/limb, mob/living/surgeon, tool) + . = ..() + for(var/datum/wound/cranial_fissure/fissure in limb.wounds) + if(HAS_TRAIT(fissure, TRAIT_WOUND_SCANNED)) + . *= 0.5 + /datum/surgery_operation/limb/repair_cranium/get_default_radial_image() return image(/obj/item/stack/medical/bone_gel) diff --git a/code/modules/surgery/operations/operation_debride.dm b/code/modules/surgery/operations/operation_debride.dm index bfbb9115572e..c9e90d3aace6 100644 --- a/code/modules/surgery/operations/operation_debride.dm +++ b/code/modules/surgery/operations/operation_debride.dm @@ -22,6 +22,12 @@ /// 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_time_modifiers(obj/item/bodypart/limb, mob/living/surgeon, tool) + . = ..() + for(var/datum/wound/burn/flesh/wound in limb.wounds) + if(HAS_TRAIT(wound, TRAIT_WOUND_SCANNED)) + . *= 0.5 + /datum/surgery_operation/limb/debride/get_default_radial_image() return image(/obj/item/reagent_containers/pill/patch/aiuri) diff --git a/code/modules/surgery/operations/operation_puncture.dm b/code/modules/surgery/operations/operation_puncture.dm index 07d246b467bc..5e83871ebb80 100644 --- a/code/modules/surgery/operations/operation_puncture.dm +++ b/code/modules/surgery/operations/operation_puncture.dm @@ -11,6 +11,12 @@ 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_time_modifiers(obj/item/bodypart/limb, mob/living/surgeon, tool) + . = ..() + for(var/datum/wound/pierce/bleed/pierce_wound in limb.wounds) + if(HAS_TRAIT(pierce_wound, TRAIT_WOUND_SCANNED)) + . *= 0.5 + /datum/surgery_operation/limb/repair_puncture/get_default_radial_image() return image(/obj/item/hemostat) @@ -88,6 +94,12 @@ 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_time_modifiers(obj/item/bodypart/limb, mob/living/surgeon, tool) + . = ..() + for(var/datum/wound/pierce/bleed/pierce_wound in limb.wounds) + if(HAS_TRAIT(pierce_wound, TRAIT_WOUND_SCANNED)) + . *= 0.5 + /datum/surgery_operation/limb/seal_veins/get_default_radial_image() return image(/obj/item/cautery) From e83243beee2cd61155a17f7819455171a848fce3 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 21:58:51 -0600 Subject: [PATCH 21/35] Op click --- code/game/objects/structures/tables_racks.dm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index caf71d996e49..fced0f13ec3f 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -857,6 +857,14 @@ /obj/structure/table/optable/make_climbable() AddElement(/datum/element/elevation, pixel_shift = 12) +// surgical tools cannot be placed on the op table while a patient is also on it +/obj/structure/table/optable/table_place_act(mob/living/user, obj/item/tool, list/modifiers) + if(!isnull(patient) && (tool.item_flags & SURGICAL_TOOL)) + tool.melee_attack_chain(user, patient, modifiers) + return ITEM_INTERACT_SUCCESS + + return ..() + /obj/structure/table/optable/tablepush(mob/living/user, mob/living/pushed_mob) pushed_mob.forceMove(loc) pushed_mob.set_resting(TRUE, TRUE) From 82ca7bd03b2a5645270b704748474623ae2e5864 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 3 Jan 2026 08:12:25 -0600 Subject: [PATCH 22/35] You can skip applying drapes to a mob bucked to operating tables or stasis beds (#94680) ## About The Pull Request Applies `/datum/component/free_operation` when buckling mobs to stasis beds or surgical tables ## Why It's Good For The Game The only reason we still have drapes is because otherwise, trying to help intent click on a mob with a saw will result in attempting surgery instead of attacking them In other words drapes serve as an explicit "I WANT TO PERFORM SURGERY" check However we can get that explicitness from other things, such as buckling someone to the designated surgery places This ultimately leaves drapes as **entirely optional** in a surgical setting (...Maybe it can provide a speed bonus for being sanitary or something?) ## Changelog :cl: Melbert add: You can skip the "apply drapes" step of surgery if the patient is buckled to an operating table or stasis bed! /:cl: --- code/game/machinery/stasis.dm | 2 ++ code/game/objects/structures/tables_racks.dm | 2 ++ strings/tips.txt | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/code/game/machinery/stasis.dm b/code/game/machinery/stasis.dm index 84335e38639d..bbaafba1793b 100644 --- a/code/game/machinery/stasis.dm +++ b/code/game/machinery/stasis.dm @@ -167,12 +167,14 @@ if(stasis_running() && check_nap_violations()) chill_out(L) update_appearance() + L.AddComponentFrom(type, /datum/component/free_operation) /obj/machinery/stasis/post_unbuckle_mob(mob/living/L) thaw_them(L) if(L == occupant) set_occupant(null) update_appearance() + L.RemoveComponentSource(type, /datum/component/free_operation) /obj/machinery/stasis/process() if(!(occupant && isliving(occupant) && check_nap_violations())) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index fced0f13ec3f..0d6d2bfd8696 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -873,10 +873,12 @@ ///Align the mob with the table when buckled. /obj/structure/table/optable/post_buckle_mob(mob/living/buckled) buckled.add_offsets(type, z_add = 6) + buckled.AddComponentFrom(type, /datum/component/free_operation) ///Disalign the mob with the table when unbuckled. /obj/structure/table/optable/post_unbuckle_mob(mob/living/buckled) buckled.remove_offsets(type) + buckled.RemoveComponentSource(type, /datum/component/free_operation) /// 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/potential_patient) diff --git a/strings/tips.txt b/strings/tips.txt index a8a3c8348ab4..2da93956ab57 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -55,6 +55,7 @@ As a Medical Doctor, better environments make for faster surgeries. Perform surg 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 - 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, 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 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! @@ -66,8 +67,8 @@ As a Medical Doctor, you can deal with patients who have absurd amounts of wound As a Medical Doctor, you can extract implants by holding an empty implant case in your offhand while performing the extraction step. 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 only need surgical drapes when doing field surgery. Patients buckled to stasis beds or operating tables do not require drapes to be operated on. 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 Morph, you can talk while disguised, but your words have a chance of being slurred, giving you away! From 3c970069cf4f8195a918ab53f9b642b8b757b2db Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 21:59:43 -0600 Subject: [PATCH 23/35] Comp --- code/datums/components/free_operation.dm | 33 ++++++++++++++++++++++++ maplestation.dme | 1 + 2 files changed, 34 insertions(+) create mode 100644 code/datums/components/free_operation.dm diff --git a/code/datums/components/free_operation.dm b/code/datums/components/free_operation.dm new file mode 100644 index 000000000000..f1bddc05c657 --- /dev/null +++ b/code/datums/components/free_operation.dm @@ -0,0 +1,33 @@ +/// Allows mobs with this component to have all of their limbs operated on without needing drapes +/datum/component/free_operation + dupe_mode = COMPONENT_DUPE_SOURCES + +/datum/component/free_operation/Initialize(check) + if (!iscarbon(parent)) + return COMPONENT_INCOMPATIBLE + ADD_TRAIT(parent, TRAIT_READY_TO_OPERATE, REF(src)) + var/mob/living/carbon/owner = parent + for (var/obj/item/bodypart/limb as anything in owner.bodyparts) + ADD_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src)) + +/datum/component/free_operation/Destroy(force) + REMOVE_TRAIT(parent, TRAIT_READY_TO_OPERATE, REF(src)) + var/mob/living/carbon/owner = parent + for (var/obj/item/bodypart/limb as anything in owner.bodyparts) + REMOVE_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src)) + return ..() + +/datum/component/free_operation/RegisterWithParent() + RegisterSignal(parent, COMSIG_CARBON_ATTACH_LIMB, PROC_REF(flag_limb)) + RegisterSignal(parent, COMSIG_CARBON_REMOVE_LIMB, PROC_REF(unflag_limb)) + +/datum/component/free_operation/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB)) + +/datum/component/free_operation/proc/flag_limb(mob/living/carbon/source, obj/item/bodypart/limb) + SIGNAL_HANDLER + ADD_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src)) + +/datum/component/free_operation/proc/unflag_limb(mob/living/carbon/source, obj/item/bodypart/limb) + SIGNAL_HANDLER + REMOVE_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src)) diff --git a/maplestation.dme b/maplestation.dme index d443c6e81746..2d4edab51ff0 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -1105,6 +1105,7 @@ #include "code\datums\components\food_storage.dm" #include "code\datums\components\force_move.dm" #include "code\datums\components\fov_handler.dm" +#include "code\datums\components\free_operation.dm" #include "code\datums\components\fullauto.dm" #include "code\datums\components\gas_leaker.dm" #include "code\datums\components\geiger_sound.dm" From c5e7c2f03065b2d602e0c5d98733f4651239fa5a Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 23:22:50 -0600 Subject: [PATCH 24/35] Lints --- .../operations/operation_bone_repair.dm | 20 +++++++++---------- .../surgery/operations/operation_debride.dm | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/code/modules/surgery/operations/operation_bone_repair.dm b/code/modules/surgery/operations/operation_bone_repair.dm index 402d770df754..460a0ed466c4 100644 --- a/code/modules/surgery/operations/operation_bone_repair.dm +++ b/code/modules/surgery/operations/operation_bone_repair.dm @@ -3,7 +3,7 @@ name = "reset dislocation" desc = "Reset a dislocated bone in a patient's limb. \ Similar to the field procedure, but quicker and safer due to being performed in a controlled environment." - operation_flags = OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED | OPERATION_AFFECTS_MOOD | OPERATION_STANDING_ALLOWED + operation_flags = OPERATION_PRIORITY_NEXT_STEP | OPERATION_AFFECTS_MOOD | OPERATION_STANDING_ALLOWED implements = list( TOOL_BONESET = 1, TOOL_CROWBAR = 2, @@ -34,9 +34,9 @@ display_results( surgeon, limb.owner, - span_notice("You begin to reset the dislocation in [FORMAT_LIMB_OWNER(limb)]..."), - span_notice("[surgeon] begins to reset the dislocation in [FORMAT_LIMB_OWNER(limb)] with [tool]."), - span_notice("[surgeon] begins to reset the dislocation in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("You begin to reset the dislocation in [limb.owner]'s [limb.plaintext_zone]..."), + span_notice("[surgeon] begins to reset the dislocation in [limb.owner]'s [limb.plaintext_zone] with [tool]."), + span_notice("[surgeon] begins to reset the dislocation in [limb.owner]'s [limb.plaintext_zone]."), ) display_pain(limb.owner, "Your [limb.plaintext_zone] aches with pain!") @@ -48,9 +48,9 @@ display_results( surgeon, limb.owner, - span_notice("You successfully reset the dislocation in [FORMAT_LIMB_OWNER(limb)]."), - span_notice("[surgeon] successfully resets the dislocation in [FORMAT_LIMB_OWNER(limb)]!"), - span_notice("[surgeon] successfully resets the dislocation in [FORMAT_LIMB_OWNER(limb)]!"), + span_notice("You successfully reset the dislocation in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[surgeon] successfully resets the dislocation in [limb.owner]'s [limb.plaintext_zone] with [tool]!"), + span_notice("[surgeon] successfully resets the dislocation in [limb.owner]'s [limb.plaintext_zone]!"), ) display_pain(limb.owner, "Your [limb.plaintext_zone] feels much better now!") @@ -58,9 +58,9 @@ display_results( surgeon, limb.owner, - span_notice("You fail to reset the dislocation in [FORMAT_LIMB_OWNER(limb)], causing further damage!"), - span_notice("[surgeon] fails to reset the dislocation in [FORMAT_LIMB_OWNER(limb)], causing further damage!"), - span_notice("[surgeon] fails to reset the dislocation in [FORMAT_LIMB_OWNER(limb)]!"), + span_notice("You fail to reset the dislocation in [limb.owner]'s [limb.plaintext_zone], causing further damage!"), + span_notice("[surgeon] fails to reset the dislocation in [limb.owner]'s [limb.plaintext_zone] with [tool], causing further damage!"), + span_notice("[surgeon] fails to reset the dislocation in [limb.owner]'s [limb.plaintext_zone], causing further damage!"), ) display_pain(limb.owner, "The pain in your [limb.plaintext_zone] intensifies!") limb.receive_damage(25, damage_source = tool) diff --git a/code/modules/surgery/operations/operation_debride.dm b/code/modules/surgery/operations/operation_debride.dm index c9e90d3aace6..33324d09e6f0 100644 --- a/code/modules/surgery/operations/operation_debride.dm +++ b/code/modules/surgery/operations/operation_debride.dm @@ -24,7 +24,7 @@ /datum/surgery_operation/limb/debride/get_time_modifiers(obj/item/bodypart/limb, mob/living/surgeon, tool) . = ..() - for(var/datum/wound/burn/flesh/wound in limb.wounds) + for(var/datum/wound/flesh/wound in limb.wounds) if(HAS_TRAIT(wound, TRAIT_WOUND_SCANNED)) . *= 0.5 From 8c10ad7d352f3a98fb2c17e01f6d57ad92c0b2b9 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 28 Jan 2026 23:45:27 -0600 Subject: [PATCH 25/35] Lints --- code/_globalvars/traits/_traits.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index f991b2df5d3d..47bd425e1270 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -426,18 +426,19 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_ROCK_METAMORPHIC" = TRAIT_ROCK_METAMORPHIC, "TRAIT_ROD_SUPLEX" = TRAIT_ROD_SUPLEX, "TRAIT_ROUGHRIDER" = TRAIT_ROUGHRIDER, + "TRAIT_PAPER_MASTER" = TRAIT_PAPER_MASTER, "TRAIT_SABRAGE_PRO" = TRAIT_SABRAGE_PRO, "TRAIT_SECURITY_HUD" = TRAIT_SECURITY_HUD, "TRAIT_SECURITY_HUD_ID_ONLY" = TRAIT_SECURITY_HUD_ID_ONLY, "TRAIT_SEE_WORN_COLOURS" = TRAIT_SEE_WORN_COLOURS, "TRAIT_SELF_AWARE" = TRAIT_SELF_AWARE, + "TRAIT_SELF_SURGERY" = TRAIT_SELF_SURGERY, "TRAIT_SETTLER" = TRAIT_SETTLER, "TRAIT_SHADED" = TRAIT_SHADED, "TRAIT_SHAVED" = TRAIT_SHAVED, "TRAIT_SHIFTY_EYES" = TRAIT_SHIFTY_EYES, "TRAIT_SHOCKIMMUNE" = TRAIT_SHOCKIMMUNE, "TRAIT_SIGN_LANG" = TRAIT_SIGN_LANG, - "TRAIT_PAPER_MASTER" = TRAIT_PAPER_MASTER, "TRAIT_SILENT_FOOTSTEPS" = TRAIT_SILENT_FOOTSTEPS, "TRAIT_SIXTHSENSE" = TRAIT_SIXTHSENSE, "TRAIT_SKITTISH" = TRAIT_SKITTISH, From ca5735c59460d9fb101e983c29c3e9374faa25a2 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:00:36 +0200 Subject: [PATCH 26/35] Chainsaw fix --- code/game/objects/items/chainsaw.dm | 82 +++++++++++++++++----------- icons/obj/weapons/chainsaw.dmi | Bin 1149 -> 1106 bytes 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/code/game/objects/items/chainsaw.dm b/code/game/objects/items/chainsaw.dm index 826a3216e7e1..73649544503d 100644 --- a/code/game/objects/items/chainsaw.dm +++ b/code/game/objects/items/chainsaw.dm @@ -11,7 +11,6 @@ righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY force = 13 - var/force_on = 24 w_class = WEIGHT_CLASS_HUGE throwforce = 13 throw_speed = 2 @@ -25,23 +24,15 @@ actions_types = list(/datum/action/item_action/startchainsaw) tool_behaviour = TOOL_SAW toolspeed = 1.5 //Turn it on first you dork + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 5, /datum/material/alloy/plasteel = SHEET_MATERIAL_AMOUNT * 5, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 3) drop_sound = 'maplestation_modules/sound/items/drop/metal_drop.ogg' pickup_sound = 'maplestation_modules/sound/items/pickup/metalweapon.ogg' - var/on = FALSE - ///The looping sound for our chainsaw when running - var/datum/looping_sound/chainsaw/chainsaw_loop - -/obj/item/chainsaw/apply_fantasy_bonuses(bonus) - . = ..() - force_on = modify_fantasy_variable("force_on", force_on, bonus) - if(on) - force = force_on -/obj/item/chainsaw/remove_fantasy_bonuses(bonus) - force_on = reset_fantasy_variable("force_on", force_on) - if(on) - force = force_on - return ..() + var/force_on = 24 + /// The looping sound for our chainsaw when running + var/datum/looping_sound/chainsaw/chainsaw_loop + /// How long it takes to behead someone with this chainsaw. + var/behead_time = 15 SECONDS /obj/item/chainsaw/Initialize(mapload) . = ..() @@ -104,33 +95,58 @@ return BRUTELOSS user.visible_message(span_suicide("[user] begins to tear [user.p_their()] head off with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) - playsound(src, 'sound/weapons/chainsawhit.ogg', 100, TRUE) var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) - if(myhead) - myhead.dismember() + if(!myhead) + visible_message(span_suicide("[user] realises that [user.p_they()] cannot cut off [user.p_their()] head because [user.p_they()] don't have one!")) + return SHAME + + playsound(src, 'sound/weapons/chainsawhit.ogg', 100, TRUE) + if(myhead.dismember()) + return BRUTELOSS + + var/datum/wound/slash/crit_wound = new () + crit_wound.apply_wound(myhead) + visible_message(span_suicide("[user] tries in vain to cut off [user.p_their()] head but perishes in the attempt!")) return BRUTELOSS -// /obj/item/chainsaw/doomslayer/attack(mob/living/target_mob, mob/living/user, params) -// if (target_mob.stat != DEAD) -// return ..() +/obj/item/chainsaw/attack(mob/living/target_mob, mob/living/user, list/modifiers, list/attack_modifiers) + if (target_mob.stat != DEAD) + return ..() + + if (user.zone_selected != BODY_ZONE_HEAD) + return ..() -// if (user.zone_selected != BODY_ZONE_HEAD) -// return ..() + var/obj/item/bodypart/head = target_mob.get_bodypart(BODY_ZONE_HEAD) + if (!head?.can_dismember()) + return ..() + + playsound(user, 'sound/weapons/slice.ogg', vol = 80, vary = TRUE) + + target_mob.balloon_alert(user, "cutting off head...") + if (!do_after(user, behead_time, target_mob, extra_checks = CALLBACK(src, PROC_REF(has_same_head), target_mob, head))) + return TRUE -// var/obj/item/bodypart/head = target_mob.get_bodypart(BODY_ZONE_HEAD) -// if (isnull(head)) -// return ..() + if (head.dismember(silent = FALSE)) + user.put_in_hands(head) -// playsound(user, 'sound/weapons/slice.ogg', vol = 80, vary = TRUE) + return TRUE -// target_mob.balloon_alert(user, "cutting off head...") -// if (!do_after(user, 2 SECONDS, target_mob, extra_checks = CALLBACK(src, PROC_REF(has_same_head), target_mob, head))) -// return TRUE +/obj/item/chainsaw/proc/has_same_head(mob/living/target_mob, obj/item/bodypart/head) + return target_mob.get_bodypart(BODY_ZONE_HEAD) == head -// head.dismember(silent = FALSE) -// user.put_in_hands(head) +/** + * Handles adding components to the chainsaw. Added in Initialize() + * + * Applies components to the chainsaw. Added as a separate proc to allow for + * variance between subtypes + */ -// return TRUE +/obj/item/chainsaw/doomslayer + name = "THE GREAT COMMUNICATOR" + desc = span_warning("VRRRRRRR!!!") + armour_penetration = 100 + force_on = 30 + behead_time = 2 SECONDS /obj/item/chainsaw/doomslayer/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) if(attack_type == PROJECTILE_ATTACK) diff --git a/icons/obj/weapons/chainsaw.dmi b/icons/obj/weapons/chainsaw.dmi index 1d48b63e45949d54ca165497c7a3d089a3c44c89..b2a0385e70b6f58830c9c1284afc33abb51445f7 100644 GIT binary patch delta 1085 zcmV-D1j76M2+|0UB!7c?R9JLGWpiV4X>fFDZ*Bkpc$`yKaB_9`^iy#0_2eo`Eh^5; z&r`5fFwryM;w;ZhDainGjE%TBGg33tGfE(w;*!LYR3K9+IU_MMuQ;(hK0i;1i!&v& zs2C_}$iy&*iuN@Ob1pEz9mYembvi3r&M|jv6+8B1qm`)O)aS) zg&qt8qlQIF1kQ&D!iM3dm_13_qhhGDjHb)lMyH*#dAV0~-niF&TRT5E`|jE9J-^@i z-S6Jr?R3@V|Mhdn7Y;Xgx1z>|4fGq$U+yHC=*hqQ<($pMmu~>)@inqJE1da5> zoL^A$B-qk&olH%A5c{FAVrqmI{Hij*@Au1yO(qjDn=gUe?Pl-y^*!2GL$u&mWk4_x zWYPH0M1TO0X4-oS4W$>^`_0Xl361^zkN3q0E%;N-W`DETnV4{qp`oEvL4Z*u@k#D@ z)q`08>lq!S=j6d=dNymf#yl`u;Dqs<)*Ro_X|+V zpCTo1ZMD!%D~-Y)k?!Z+3?EBEnfPbz8=M+WVJ#R8!q-$C45qzgt+@EaUUO&sAQ@Xp z)PkSg1b-=cFc7eI_w+BP!7D2(kkM&^lWze$4Vf^%yb*PRbidthQZ)1Egy!aE zW6sYE*b*=~IT3XbDSSR3WOP(RnKLwjDuBY{dQmv>c|kw;6O2Y9*zI;k+Su4g78VxR z=i7yBo&C19wwUvaUxQS{y7!0mopTJ%na+Ze_oma8W4S;!*29Bl9t6GJ z1Nr$!Sbt#PnQX%JZoQKU4#$X9DL0L;4veYhmpuni5fv5XR+r1&sU$|OM_8){n4uX+ zB3T=HshpIbF@r%bZZ9n@@0gA>6P0q)NDWiVuj)Z)r!yICUuj>9hbArfRT=O|4fGq$U+yHC=*x&|W3%~|909ybyxB=J#u)z($7JvZa09RfU?b@b zNa*?iEubm`0s%jayIkxq=k0$xCkhg}K0pg3bbWvpkX-_FY3g6%aM34roGwcThYAW| zEi2rnKTg9y+H)u^DPgV2Y>wN@L$)$RQ-8R&_B=QoBTyHL!%-sL&lVVglGgyMO*(db z`THDkEO+H99RB5H+ZQ(L;L6o&ahV080r&3SA^Tk)AYHDG7ogM!#FG)SOQ79$gJ9JM zU_tgRaMRWy)P%C3tgJ#&%#PFt6r}^qeD+Az=flN2i;G`aq!X5|&&LxIx;{V)@HYAY zYysHd24D-o1~&j(05-S**aEP@4Zs$F4Q>Fo0BrmP52nB9!TFjx00000NkvXXu0mjf D^I7*s delta 1128 zcmV-u1eg2L2>l3fFDZ*Bkpc${s^u?oW=6a~=P_Z639En-sF zx0Hn7ko-cCs#RzWs5JENJ9N!>hSTFVFJ{BE8v6KB)1JZ5Qjs{4YY~j=NFw_5jO~xw zZ1Z4rk7!e__PFo4BjHl1U<@Qev2S0&7)dz)vR^Qi)E6c7S$_`Ud=J!P-X=_|p&vwZ zE(>{C+rt0=1FlI#K~#90?V8VP9AzBFKa=bs6^YFuA!*Zvf`{G=38l@d^({U0w9}BI zl(?FIVi;Mv?s{m2Ou<8>LPdj!3B4o*^-#i~batfHQ?b>9V65Z^HYFt*+}-$k*xfjb zO|oGo`8H-gFn_%JgZF)&dG_6Rcjo;XuIqA;X^}P{O&ox<0cqj@qzyl+u~Z8x)I00Ips8 za90C~LO)ahm2w4u;9O9NcL{(sS~1QCUPYbKM~c;fq>o|>9! zH4J8(X+Q3|{o4EYA^S=@ZJkG0YwKXGt%G;JUSjr`%k9o{9O>y{e0)4NG&JUG zWBF#a4TIUjU!N0Jza=PcSvEk{vTTfPS|Iymt=dEB+nG$< zU@~!ozJI=#wmWwmXDm-(>}J|1^bOZ_ah$PyrCiQBSFhGatiQXt;l;&8o+~I$-h!oz z&-2T|>Xs*X^48{yl<#}mvTPnaxWDK634l~;Fl$*hmvXr+2MMyizRr5}E6C5ku5$dv z?hWJmk0*3b{z)>KWMX2%07(8cqBmQuJ+D|W&3|-uWo2dW^57eIs~B z$Z^KP#<#TXv70|U=Yrw1DHhy9~J>46R=jRumIvsuA)6HsoUa>GU zbAK}%R=**DouZV|&HnkQPro`*{Ar3pKU4t!2jYt%X#>*40Z1E=CJsQ_fHZLc(gvi7 z1CTZ#&4CXf)IGr{P)dpFANUy%2Ow=gnm7Py1Jc9+NE?tQ4j@ct>H2^u*w^&|QLwM; z1EOGG*9SyF!y4fGp5942ec}fI+7Gw$!hf;jEFZ2OcfY^Or-{!P=N?Vw!wINP=`X(gYS-%n=H}*5O6jKsAx3cDT%tZenuaytQZ8?t_hCJK z`gE$Pwxd=baH0+F{kRm^pRX40Jbd^YQ2luf?$195uL0uq0n!1)7emqpq=^HN uHXuzLfV2T=;sB%#ND~JjZ9tj>AHYA}RRjOTiBi%40000 Date: Wed, 28 Jan 2026 23:58:23 -0600 Subject: [PATCH 27/35] Nullrod fix --- code/modules/unit_tests/surgeries.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/unit_tests/surgeries.dm b/code/modules/unit_tests/surgeries.dm index fe94aaf65a7c..fd7d9deb2b9e 100644 --- a/code/modules/unit_tests/surgeries.dm +++ b/code/modules/unit_tests/surgeries.dm @@ -166,7 +166,7 @@ var/mob/living/carbon/human/picker = allocate(/mob/living/carbon/human/consistent) var/obj/item/nullrod/chainsaw/nullrod = allocate(/obj/item/nullrod/chainsaw) - nullrod.on_selected(null, null, picker) + nullrod.on_selected(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") From 7df488e06c5da989c37ea3ff88241f32f8bcafea Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 29 Jan 2026 00:01:06 -0600 Subject: [PATCH 28/35] Sting fix --- .../antagonists/changeling/powers/adrenaline.dm | 2 +- .../advanced_ling/changeling_rebalancing.dm | 16 +++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/code/modules/antagonists/changeling/powers/adrenaline.dm b/code/modules/antagonists/changeling/powers/adrenaline.dm index 5f96508d690c..844816b10987 100644 --- a/code/modules/antagonists/changeling/powers/adrenaline.dm +++ b/code/modules/antagonists/changeling/powers/adrenaline.dm @@ -13,7 +13,7 @@ ..() to_chat(user, span_notice("Energy rushes through us.")) user.SetKnockdown(0) - user.set_resting(FALSE) + user.set_resting(FALSE, instant = TRUE) user.reagents.add_reagent(/datum/reagent/medicine/changelingadrenaline, 4) //20 seconds user.reagents.add_reagent(/datum/reagent/medicine/changelinghaste, 3) //6 seconds, for a really quick burst of speed return TRUE diff --git a/maplestation_modules/code/modules/antagonists/advanced_ling/changeling_rebalancing.dm b/maplestation_modules/code/modules/antagonists/advanced_ling/changeling_rebalancing.dm index d7946087ed1e..084ff0e5a8b6 100644 --- a/maplestation_modules/code/modules/antagonists/advanced_ling/changeling_rebalancing.dm +++ b/maplestation_modules/code/modules/antagonists/advanced_ling/changeling_rebalancing.dm @@ -1,17 +1,5 @@ // -- Rebalancing of other ling actions -- -// Buffs adrenal sacs so they work like old adrenals. Increased chemical cost to compensate. -/datum/action/changeling/adrenaline - desc = "We evolve additional sacs of adrenaline throughout our body. Costs 40 chemicals." - chemical_cost = 40 - -/datum/action/changeling/adrenaline/sting_action(mob/living/user) - user.adjustStaminaLoss(-75) - user.set_resting(FALSE, instant = TRUE) - user.SetStun(0) - user.SetImmobilized(0) - user.SetParalyzed(0) - user.SetKnockdown(0) - . = ..() +#ifndef UNIT_TESTS // Disables spread infestation. /datum/action/changeling/spiders @@ -21,6 +9,8 @@ /datum/action/changeling/sting/transformation dna_cost = CHANGELING_POWER_UNOBTAINABLE +#endif // UNIT_TESTS + /// Extension of attempt_absorb, for changeling cannot absorb their own spawn /datum/action/changeling/absorb_dna/can_sting(mob/living/carbon/user) var/mob/living/target = user.pulling From a707e1a986078ffbb613b30adf2bf2a47ca07de7 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 29 Jan 2026 00:06:55 -0600 Subject: [PATCH 29/35] Bot fix --- code/modules/research/designs/medical_designs.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index 02331d4d14bc..d6dfd839843b 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -1041,7 +1041,7 @@ 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 = /mob/living/basic/bot/medbot::icon 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 From 4bc138ea16c80d4a4751d745af7f1c327fce8421 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 29 Jan 2026 00:11:29 -0600 Subject: [PATCH 30/35] Borg fix --- code/modules/unit_tests/omnitools.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/unit_tests/omnitools.dm b/code/modules/unit_tests/omnitools.dm index 5a4e66cd3c82..7c8951994122 100644 --- a/code/modules/unit_tests/omnitools.dm +++ b/code/modules/unit_tests/omnitools.dm @@ -11,7 +11,7 @@ omnitool = tool break TEST_ASSERT_NOTNULL(omnitool, "Could not find /obj/item/borg/cyborg_omnitool/engineering in borg inbuilt modules!") - borg.put_in_hand(omnitool, 1) + borg.equip_module_to_slot(omnitool, 1) borg.select_module(1) //these must match From 4f90033e7902dec857a6fffb9b2d225629fb3272 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 29 Jan 2026 00:24:45 -0600 Subject: [PATCH 31/35] Organ fix --- .../dna_infuser/organ_sets/gondola_organs.dm | 12 ++++++------ code/game/objects/items/body_egg.dm | 4 ++-- code/modules/antagonists/abductor/equipment/gland.dm | 4 ++-- code/modules/antagonists/changeling/headslug_eggs.dm | 4 ++-- .../job_types/chaplain/chaplain_vorpal_scythe.dm | 2 +- .../mining/equipment/monster_organs/monster_organ.dm | 6 ++---- code/modules/mob/living/carbon/alien/organs.dm | 4 ++-- .../surgery/organs/external/_visual_organs.dm | 10 ++++------ code/modules/surgery/organs/external/spines.dm | 4 ++-- .../organs/external/wings/functional_wings.dm | 4 ++-- .../organs/internal/cyberimp/augments_eyes.dm | 5 ++--- code/modules/surgery/organs/internal/eyes/_eyes.dm | 5 +---- .../surgery/organs/internal/heart/heart_ethereal.dm | 4 ++-- .../surgery/organs/internal/stomach/_stomach.dm | 4 ++-- .../surgery/organs/internal/tongue/_tongue.dm | 4 ++-- code/modules/surgery/organs/organ_movement.dm | 7 ++++--- 16 files changed, 38 insertions(+), 45 deletions(-) diff --git a/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm b/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm index 7f3219b2e505..84772e3ed947 100644 --- a/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm @@ -34,7 +34,7 @@ Fluoride Stare: After someone says 5 words, blah blah blah... AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/gondola) AddElement(/datum/element/noticable_organ, "radiate%PRONOUN_S an aura of serenity.") -/obj/item/organ/heart/gondola/mob_insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/heart/gondola/on_mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() if(!(FACTION_HOSTILE in receiver.faction)) factions_to_remove += FACTION_HOSTILE @@ -42,7 +42,7 @@ Fluoride Stare: After someone says 5 words, blah blah blah... factions_to_remove += FACTION_MINING receiver.faction |= list(FACTION_HOSTILE, FACTION_MINING) -/obj/item/organ/heart/gondola/mob_remove(mob/living/carbon/heartless, special, movement_flags) +/obj/item/organ/heart/gondola/on_mob_remove(mob/living/carbon/heartless, special, movement_flags) . = ..() for(var/faction in factions_to_remove) heartless.faction -= faction @@ -65,11 +65,11 @@ Fluoride Stare: After someone says 5 words, blah blah blah... AddElement(/datum/element/noticable_organ, "mouth is permanently affixed into a relaxed smile.", BODY_ZONE_PRECISE_MOUTH) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/gondola) -/obj/item/organ/tongue/gondola/mob_insert(mob/living/carbon/tongue_owner, special, movement_flags) +/obj/item/organ/tongue/gondola/on_mob_insert(mob/living/carbon/tongue_owner, special, movement_flags) . = ..() tongue_owner.add_mood_event("gondola_zen", /datum/mood_event/gondola_serenity) -/obj/item/organ/tongue/gondola/mob_remove(mob/living/carbon/tongue_owner, special, movement_flags) +/obj/item/organ/tongue/gondola/on_mob_remove(mob/living/carbon/tongue_owner, special, movement_flags) tongue_owner.clear_mood_event("gondola_zen") return ..() @@ -89,7 +89,7 @@ Fluoride Stare: After someone says 5 words, blah blah blah... AddElement(/datum/element/noticable_organ, "left arm has small needles breaching the skin all over it.", BODY_ZONE_L_ARM) AddElement(/datum/element/noticable_organ, "right arm has small needles breaching the skin all over it.", BODY_ZONE_R_ARM) -/obj/item/organ/liver/gondola/mob_insert(mob/living/carbon/liver_owner, special, movement_flags) +/obj/item/organ/liver/gondola/on_mob_insert(mob/living/carbon/liver_owner, special, movement_flags) . = ..() var/has_left = liver_owner.has_left_hand(check_disabled = FALSE) var/has_right = liver_owner.has_right_hand(check_disabled = FALSE) @@ -104,7 +104,7 @@ Fluoride Stare: After someone says 5 words, blah blah blah... RegisterSignal(liver_owner, COMSIG_LIVING_TRY_PULL, PROC_REF(on_owner_try_pull)) RegisterSignal(liver_owner, COMSIG_CARBON_HELPED, PROC_REF(on_hug)) -/obj/item/organ/liver/gondola/mob_remove(mob/living/carbon/liver_owner, special, movement_flags) +/obj/item/organ/liver/gondola/on_mob_remove(mob/living/carbon/liver_owner, special, movement_flags) . = ..() UnregisterSignal(liver_owner, list(COMSIG_HUMAN_EQUIPPING_ITEM, COMSIG_LIVING_TRY_PULL, COMSIG_CARBON_HELPED)) diff --git a/code/game/objects/items/body_egg.dm b/code/game/objects/items/body_egg.dm index a1571f637917..1b5b6eb39065 100644 --- a/code/game/objects/items/body_egg.dm +++ b/code/game/objects/items/body_egg.dm @@ -19,14 +19,14 @@ if(iscarbon(loc)) Insert(loc) -/obj/item/organ/body_egg/mob_insert(mob/living/carbon/egg_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) +/obj/item/organ/body_egg/on_mob_insert(mob/living/carbon/egg_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) . = ..() egg_owner.add_traits(list(TRAIT_XENO_HOST, TRAIT_XENO_IMMUNE), ORGAN_TRAIT) egg_owner.med_hud_set_status() INVOKE_ASYNC(src, PROC_REF(AddInfectionImages), egg_owner) -/obj/item/organ/body_egg/mob_remove(mob/living/carbon/egg_owner, special, movement_flags) +/obj/item/organ/body_egg/on_mob_remove(mob/living/carbon/egg_owner, special, movement_flags) . = ..() egg_owner.remove_traits(list(TRAIT_XENO_HOST, TRAIT_XENO_IMMUNE), ORGAN_TRAIT) egg_owner.med_hud_set_status() diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm index 179737223d76..d534432218f1 100644 --- a/code/modules/antagonists/abductor/equipment/gland.dm +++ b/code/modules/antagonists/abductor/equipment/gland.dm @@ -84,7 +84,7 @@ active_mind_control = FALSE return TRUE -/obj/item/organ/heart/gland/mob_remove(mob/living/carbon/gland_owner, special, movement_flags) +/obj/item/organ/heart/gland/on_mob_remove(mob/living/carbon/gland_owner, special, movement_flags) . = ..() active = FALSE if(initial(uses) == 1) @@ -93,7 +93,7 @@ hud.remove_atom_from_hud(gland_owner) clear_mind_control() -/obj/item/organ/heart/gland/mob_insert(mob/living/carbon/gland_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) +/obj/item/organ/heart/gland/on_mob_insert(mob/living/carbon/gland_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) . = ..() if(special != 2 && uses) // Special 2 means abductor surgery diff --git a/code/modules/antagonists/changeling/headslug_eggs.dm b/code/modules/antagonists/changeling/headslug_eggs.dm index e2238d9d7e70..c5c35923d19c 100644 --- a/code/modules/antagonists/changeling/headslug_eggs.dm +++ b/code/modules/antagonists/changeling/headslug_eggs.dm @@ -11,11 +11,11 @@ /// When this egg last got removed from a body. If -1, the egg hasn't been removed from a body. var/removal_time = -1 -/obj/item/organ/body_egg/changeling_egg/mob_insert(mob/living/carbon/egg_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) +/obj/item/organ/body_egg/changeling_egg/on_mob_insert(mob/living/carbon/egg_owner, special = FALSE, movement_flags = DELETE_IF_REPLACED) . = ..() hatch_time = world.time + (removal_time == -1 ? EGG_INCUBATION_TIME : (hatch_time - removal_time)) -/obj/item/organ/body_egg/changeling_egg/mob_remove(mob/living/carbon/egg_owner, special, movement_flags) +/obj/item/organ/body_egg/changeling_egg/on_mob_remove(mob/living/carbon/egg_owner, special, movement_flags) . = ..() removal_time = world.time diff --git a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm index 26b4e7b7729d..e5d1fd1fe5e5 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm @@ -10,7 +10,7 @@ If the scythe isn't empowered when you sheath it, you take a heap of damage and desc = "This shard seems to be directly linked to some sinister entity. It might be your god! It also gives you a really horrible rash when you hold onto it for too long." items_to_create = list(/obj/item/vorpalscythe) -/obj/item/organ/cyberimp/arm/shard/scythe/mob_insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/cyberimp/arm/shard/scythe/on_mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() if(receiver.mind) ADD_TRAIT(receiver.mind, TRAIT_MORBID, ORGAN_TRAIT) diff --git a/code/modules/mining/equipment/monster_organs/monster_organ.dm b/code/modules/mining/equipment/monster_organs/monster_organ.dm index fc786c394014..e5ba754c305e 100644 --- a/code/modules/mining/equipment/monster_organs/monster_organ.dm +++ b/code/modules/mining/equipment/monster_organs/monster_organ.dm @@ -67,17 +67,15 @@ return ..() /obj/item/organ/monster_core/mob_insert(mob/living/carbon/target_carbon, special = FALSE, movement_flags) - . = ..() - if (inert) to_chat(target_carbon, span_notice("[src] breaks down as you try to insert it.")) qdel(src) return FALSE + . = ..() if (!decay_timer) - return TRUE + return preserve(TRUE) target_carbon.visible_message(span_notice("[src] stabilizes as it's inserted.")) - return TRUE /obj/item/organ/monster_core/mob_remove(mob/living/carbon/target_carbon, special, movement_flags) if (!inert && !special) diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm index e2c076405b90..8215db033580 100644 --- a/code/modules/mob/living/carbon/alien/organs.dm +++ b/code/modules/mob/living/carbon/alien/organs.dm @@ -231,11 +231,11 @@ stomach_contents -= source UnregisterSignal(source, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_DEATH, COMSIG_QDELETING)) -/obj/item/organ/stomach/alien/mob_insert(mob/living/carbon/stomach_owner, special, movement_flags) +/obj/item/organ/stomach/alien/on_mob_insert(mob/living/carbon/stomach_owner, special, movement_flags) RegisterSignal(stomach_owner, COMSIG_ATOM_RELAYMOVE, PROC_REF(something_moved)) return ..() -/obj/item/organ/stomach/alien/mob_remove(mob/living/carbon/stomach_owner, special, movement_flags) +/obj/item/organ/stomach/alien/on_mob_remove(mob/living/carbon/stomach_owner, special, movement_flags) UnregisterSignal(stomach_owner, COMSIG_ATOM_RELAYMOVE) return ..() diff --git a/code/modules/surgery/organs/external/_visual_organs.dm b/code/modules/surgery/organs/external/_visual_organs.dm index b045516570be..d5d4fd818bd1 100644 --- a/code/modules/surgery/organs/external/_visual_organs.dm +++ b/code/modules/surgery/organs/external/_visual_organs.dm @@ -211,15 +211,13 @@ Unlike normal organs, we're actually inside a persons limbs at all times ///Store our old datum here for if our antennae are healed var/original_sprite_datum -/obj/item/organ/antennae/mob_insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/antennae/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags) . = ..() + RegisterSignal(organ_owner, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_antennae)) + RegisterSignal(organ_owner, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_antennae)) - RegisterSignal(receiver, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_antennae)) - RegisterSignal(receiver, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_antennae)) - -/obj/item/organ/antennae/mob_remove(mob/living/carbon/organ_owner, special, movement_flags) +/obj/item/organ/antennae/on_mob_remove(mob/living/carbon/organ_owner, special) . = ..() - UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL)) ///check if our antennae can burn off ;_; diff --git a/code/modules/surgery/organs/external/spines.dm b/code/modules/surgery/organs/external/spines.dm index 21a70344ca0f..7608c0769724 100644 --- a/code/modules/surgery/organs/external/spines.dm +++ b/code/modules/surgery/organs/external/spines.dm @@ -16,13 +16,13 @@ organ_flags = parent_type::organ_flags | ORGAN_EXTERNAL -/obj/item/organ/spines/mob_insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/spines/on_mob_insert(mob/living/carbon/receiver, special, movement_flags) // If we have a tail, attempt to add a tail spines overlay var/obj/item/organ/tail/our_tail = receiver.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) our_tail?.try_insert_tail_spines(our_tail.bodypart_owner) return ..() -/obj/item/organ/spines/mob_remove(mob/living/carbon/organ_owner, special, movement_flags) +/obj/item/organ/spines/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) // If we have a tail, remove any tail spines overlay var/obj/item/organ/tail/our_tail = organ_owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) our_tail?.remove_tail_spines(our_tail.bodypart_owner) diff --git a/code/modules/surgery/organs/external/wings/functional_wings.dm b/code/modules/surgery/organs/external/wings/functional_wings.dm index 7addcad0a8f7..478bde7b7f2f 100644 --- a/code/modules/surgery/organs/external/wings/functional_wings.dm +++ b/code/modules/surgery/organs/external/wings/functional_wings.dm @@ -35,14 +35,14 @@ QDEL_NULL(fly) return ..() -/obj/item/organ/wings/functional/mob_insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/wings/functional/on_mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() if(QDELETED(fly)) fly = new fly.Grant(receiver) -/obj/item/organ/wings/functional/mob_remove(mob/living/carbon/organ_owner, special, movement_flags) +/obj/item/organ/wings/functional/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() fly?.Remove(organ_owner) diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm index 3ee16ba17e01..04d1fd9c14c0 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm @@ -26,13 +26,12 @@ eye_owner.add_traits(HUD_traits, ORGAN_TRAIT) balloon_alert(eye_owner, "hud enabled") -/obj/item/organ/cyberimp/eyes/hud/mob_insert(mob/living/carbon/eye_owner, special = FALSE, movement_flags) +/obj/item/organ/cyberimp/eyes/hud/on_mob_insert(mob/living/carbon/eye_owner, special = FALSE, movement_flags) . = ..() - eye_owner.add_traits(HUD_traits, ORGAN_TRAIT) toggled_on = TRUE -/obj/item/organ/cyberimp/eyes/hud/mob_remove(mob/living/carbon/eye_owner, special, movement_flags) +/obj/item/organ/cyberimp/eyes/hud/on_mob_remove(mob/living/carbon/eye_owner, special, movement_flags) . = ..() eye_owner.remove_traits(HUD_traits, ORGAN_TRAIT) toggled_on = FALSE diff --git a/code/modules/surgery/organs/internal/eyes/_eyes.dm b/code/modules/surgery/organs/internal/eyes/_eyes.dm index 4f0a809807e4..b57b1c03007c 100644 --- a/code/modules/surgery/organs/internal/eyes/_eyes.dm +++ b/code/modules/surgery/organs/internal/eyes/_eyes.dm @@ -591,14 +591,11 @@ deactivate(close_ui = TRUE) /// Set the initial color of the eyes on insert to be the mob's previous eye color. -/obj/item/organ/eyes/robotic/glow/mob_insert(mob/living/carbon/eye_recipient, special = FALSE, movement_flags = DELETE_IF_REPLACED) +/obj/item/organ/eyes/robotic/glow/on_mob_insert(mob/living/carbon/eye_recipient, special = FALSE, movement_flags = DELETE_IF_REPLACED) . = ..() left_eye_color_string = old_eye_color_left right_eye_color_string = old_eye_color_right update_mob_eye_color(eye_recipient) - -/obj/item/organ/eyes/robotic/glow/on_mob_insert(mob/living/carbon/eye_recipient) - . = ..() deactivate(close_ui = TRUE) eye.forceMove(eye_recipient) diff --git a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm index f7f51fa174ed..1b8306a2206c 100644 --- a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm +++ b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm @@ -21,14 +21,14 @@ add_atom_colour(ethereal_color, FIXED_COLOUR_PRIORITY) update_appearance() -/obj/item/organ/heart/ethereal/mob_insert(mob/living/carbon/heart_owner, special = FALSE, movement_flags) +/obj/item/organ/heart/ethereal/on_mob_insert(mob/living/carbon/heart_owner, special = FALSE, movement_flags) . = ..() RegisterSignal(heart_owner, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_change)) RegisterSignal(heart_owner, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(on_owner_fully_heal)) RegisterSignal(heart_owner, COMSIG_QDELETING, PROC_REF(owner_deleted)) -/obj/item/organ/heart/ethereal/mob_remove(mob/living/carbon/heart_owner, special, movement_flags) +/obj/item/organ/heart/ethereal/on_mob_remove(mob/living/carbon/heart_owner, special, movement_flags) UnregisterSignal(heart_owner, list(COMSIG_MOB_STATCHANGE, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_QDELETING)) REMOVE_TRAIT(heart_owner, TRAIT_CORPSELOCKED, SPECIES_TRAIT) stop_crystalization_process(heart_owner) diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm index 4e34e729b6cb..c33b3aa34135 100644 --- a/code/modules/surgery/organs/internal/stomach/_stomach.dm +++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm @@ -245,11 +245,11 @@ disgusted.throw_alert(ALERT_DISGUST, /atom/movable/screen/alert/disgusted) disgusted.add_mood_event("disgust", /datum/mood_event/disgusted) -/obj/item/organ/stomach/mob_insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/stomach/on_mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() receiver.hud_used?.hunger?.update_hunger_bar() -/obj/item/organ/stomach/mob_remove(mob/living/carbon/stomach_owner, special, movement_flags) +/obj/item/organ/stomach/on_mob_remove(mob/living/carbon/stomach_owner, special, movement_flags) if(ishuman(stomach_owner)) var/mob/living/carbon/human/human_owner = owner human_owner.clear_alert(ALERT_DISGUST) diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm index 61bd02e8e5f6..73f46be70db7 100644 --- a/code/modules/surgery/organs/internal/tongue/_tongue.dm +++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm @@ -120,7 +120,7 @@ food_taste_reaction = FOOD_LIKED return food_taste_reaction -/obj/item/organ/tongue/mob_insert(mob/living/carbon/receiver, special, movement_flags) +/obj/item/organ/tongue/on_mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() if(modifies_speech) @@ -134,7 +134,7 @@ REMOVE_TRAIT(receiver, TRAIT_AGEUSIA, NO_TONGUE_TRAIT) apply_tongue_effects() -/obj/item/organ/tongue/mob_remove(mob/living/carbon/organ_owner, special, movement_flags) +/obj/item/organ/tongue/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() temp_say_mod = "" diff --git a/code/modules/surgery/organs/organ_movement.dm b/code/modules/surgery/organs/organ_movement.dm index 7126deda9bf7..8a94e0866e83 100644 --- a/code/modules/surgery/organs/organ_movement.dm +++ b/code/modules/surgery/organs/organ_movement.dm @@ -21,6 +21,8 @@ if(!PERFORM_ALL_TESTS(organ_sanity)) stack_trace("Tried to insert organ into non-carbon: [receiver.type]") return FALSE + 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) @@ -28,7 +30,6 @@ // to avoid double insertion, and potential bugs, we'll stop here return TRUE - mob_insert(receiver, special, movement_flags) bodypart_insert(limb_owner = receiver, movement_flags = movement_flags) if(!special) @@ -63,11 +64,11 @@ if(!iscarbon(receiver)) stack_trace("Tried to insert organ into non-carbon: [receiver.type]") - return + return FALSE if(owner == receiver) stack_trace("Organ receiver is already organ owner") - return + return FALSE var/obj/item/organ/replaced = receiver.get_organ_slot(slot) if(replaced) From d61303f7a73cf2a893649824c197fff2b8931a7c Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 29 Jan 2026 00:29:40 -0600 Subject: [PATCH 32/35] dme --- tgstation.dme | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tgstation.dme b/tgstation.dme index 1407eb57b2ab..bf21a51d4ab5 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -533,6 +533,7 @@ #include "code\_globalvars\lists\client.dm" #include "code\_globalvars\lists\color.dm" #include "code\_globalvars\lists\crafting.dm" +#include "code\_globalvars\lists\engineering.dm" #include "code\_globalvars\lists\flavor_misc.dm" #include "code\_globalvars\lists\icons.dm" #include "code\_globalvars\lists\keybindings.dm" @@ -1104,6 +1105,7 @@ #include "code\datums\components\food_storage.dm" #include "code\datums\components\force_move.dm" #include "code\datums\components\fov_handler.dm" +#include "code\datums\components\free_operation.dm" #include "code\datums\components\fullauto.dm" #include "code\datums\components\gas_leaker.dm" #include "code\datums\components\geiger_sound.dm" From 1a169a2f4e1451625e78709ed3a32d217ba970cd Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 29 Jan 2026 01:20:42 -0600 Subject: [PATCH 33/35] Fixes --- code/_onclick/hud/radial.dm | 9 ++++- code/datums/actions/action.dm | 2 ++ code/game/objects/effects/info.dm | 4 +++ code/modules/jobs/job_types/detective.dm | 3 ++ code/modules/mob/living/living_defense.dm | 2 +- .../organs/internal/stomach/_stomach.dm | 6 ++-- .../code/datums/pain/pain_implements.dm | 4 +++ .../code/modules/vending/_vending.dm | 6 ++++ .../OperatingComputer/PatientStateView.tsx | 36 +++++++++++++------ tgui/packages/tgui/interfaces/Vending.tsx | 5 ++- 10 files changed, 59 insertions(+), 18 deletions(-) diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index b2564c2fc605..062e3138cc03 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -25,6 +25,7 @@ GLOBAL_LIST_EMPTY(radial_menus) var/choice var/next_page = FALSE var/tooltips = FALSE + var/tooltip_theme /atom/movable/screen/radial/slice/set_parent(new_value) . = ..() @@ -38,7 +39,7 @@ GLOBAL_LIST_EMPTY(radial_menus) else icon_state = "[parent.radial_slice_icon]_focus" if(tooltips) - openToolTip(usr, src, params, title = name) + openToolTip(usr, src, params, title = name, theme = tooltip_theme) /atom/movable/screen/radial/slice/MouseExited(location, control, params) . = ..() @@ -245,12 +246,15 @@ GLOBAL_LIST_EMPTY(radial_menus) var/atom/movable/AM = choices_values[choice_id] //Movables only E.name = AM.name E.choice = choice_id + E.tooltip_theme = choice_datum?.tooltip_theme E.maptext = null E.next_page = FALSE if(choices_icons[choice_id]) E.add_overlay(choices_icons[choice_id]) if (choice_datum?.info) var/obj/effect/abstract/info/info_button = new(E, choice_datum.info) + info_button.name = "Info: [E.name]" + info_button.tooltip_theme = choice_datum.tooltip_theme SET_PLANE_EXPLICIT(info_button, ABOVE_HUD_PLANE, anchor) info_button.layer = RADIAL_CONTENT_LAYER E.vis_contents += info_button @@ -397,6 +401,9 @@ GLOBAL_LIST_EMPTY(radial_menus) /// If provided, will display an info button that will put this text in your chat var/info + /// If provided, changes the tooltip theme for this choice + var/tooltip_theme + /datum/radial_menu_choice/Destroy(force) . = ..() QDEL_NULL(image) diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm index bb84739f04cb..c47a2a01d0d5 100644 --- a/code/datums/actions/action.dm +++ b/code/datums/actions/action.dm @@ -217,6 +217,8 @@ if(!button) return + button.actiontooltipstyle = buttontooltipstyle + if(update_flags & UPDATE_BUTTON_NAME) update_button_name(button, force) diff --git a/code/game/objects/effects/info.dm b/code/game/objects/effects/info.dm index 8a925305d29b..a565eb46819f 100644 --- a/code/game/objects/effects/info.dm +++ b/code/game/objects/effects/info.dm @@ -7,6 +7,9 @@ /// What should the info button display when clicked? var/info_text + /// What theme should the tooltip use? + var/tooltip_theme + /obj/effect/abstract/info/Initialize(mapload, info_text) . = ..() @@ -20,6 +23,7 @@ /obj/effect/abstract/info/MouseEntered(location, control, params) . = ..() icon_state = "info_hovered" + openToolTip(usr, src, params, title = name, content = info_text, theme = tooltip_theme) /obj/effect/abstract/info/MouseExited() . = ..() diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm index 8afd613d040a..3eee6faf4913 100644 --- a/code/modules/jobs/job_types/detective.dm +++ b/code/modules/jobs/job_types/detective.dm @@ -105,3 +105,6 @@ mask = /obj/item/clothing/mask/facescarf suit = /obj/item/clothing/suit/costume/poncho/sheriff id_trim = /datum/id_trim/job/detective/sheriff + +/datum/outfit/job/detective/sheriff/get_types_to_preload() + return ..() - shoes // cowboy boots cause random ass runtimes due to spawning a snake in nullspace diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index f4a6ed238123..830cb159707a 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -396,7 +396,7 @@ if(.) return TRUE - if(!combat_mode && HAS_TRAIT(src, TRAIT_READY_TO_OPERATE) && user.perform_surgery(src)) + if(!user.combat_mode && HAS_TRAIT(src, TRAIT_READY_TO_OPERATE) && user.perform_surgery(src)) return TRUE return FALSE diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm index c33b3aa34135..00622a8278e9 100644 --- a/code/modules/surgery/organs/internal/stomach/_stomach.dm +++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm @@ -250,10 +250,8 @@ receiver.hud_used?.hunger?.update_hunger_bar() /obj/item/organ/stomach/on_mob_remove(mob/living/carbon/stomach_owner, special, movement_flags) - if(ishuman(stomach_owner)) - var/mob/living/carbon/human/human_owner = owner - human_owner.clear_alert(ALERT_DISGUST) - human_owner.clear_mood_event("disgust") + stomach_owner.clear_alert(ALERT_DISGUST) + stomach_owner.clear_mood_event("disgust") stomach_owner.hud_used?.hunger?.update_hunger_bar() return ..() diff --git a/maplestation_modules/code/datums/pain/pain_implements.dm b/maplestation_modules/code/datums/pain/pain_implements.dm index 071819562e98..7ced35481f86 100644 --- a/maplestation_modules/code/datums/pain/pain_implements.dm +++ b/maplestation_modules/code/datums/pain/pain_implements.dm @@ -492,6 +492,7 @@ // Pain implements added to various vendors. /obj/machinery/vending/drugs + theme = "operating_computer" added_categories = list( list( "name" = "Pain", @@ -501,7 +502,9 @@ ) ), ) + /obj/machinery/vending/medical + theme = "operating_computer" added_categories = list( list( "name" = "Pain", @@ -515,6 +518,7 @@ ) /obj/machinery/vending/wallmed + theme = "operating_computer" added_categories = list( list( "name" = "Pain", diff --git a/maplestation_modules/code/modules/vending/_vending.dm b/maplestation_modules/code/modules/vending/_vending.dm index dbcfdb29ff9e..fa30679c0be9 100644 --- a/maplestation_modules/code/modules/vending/_vending.dm +++ b/maplestation_modules/code/modules/vending/_vending.dm @@ -7,6 +7,8 @@ /// -- Extension of /obj/machinery/vending to add products, contraband, and premium items to vendors. -- /obj/machinery/vending + /// Window theme to use + var/theme /// Assoc lazylist of products you want to add or remove. var/list/added_products /// Assoc lazylist of contraband you want to add or remove. @@ -50,6 +52,10 @@ return ..() +/obj/machinery/vending/ui_static_data(mob/user) + . = ..() + .["theme"] = theme + /// Combines the added_categories list and the product_categories list /obj/machinery/vending/proc/combine_categories() var/list/default_stuff diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx b/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx index 3531ab3898bb..83718d86ad1c 100644 --- a/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx +++ b/tgui/packages/tgui/interfaces/OperatingComputer/PatientStateView.tsx @@ -64,9 +64,11 @@ export const PatientStateView = (props: PatientStateViewProps) => { target_zone={data.target_zone} /> + {/* NON-MODULE CHANGE START */} + {/* NON-MODULE CHANGE END */} { const { patient } = props; return ( + {/* NON-MODULE CHANGE START */} - {/* NON-MODULE CHANGE START */} {patient.stat} @@ -98,10 +106,6 @@ const PatientStateMainStateView = (props: PatientStateMainStateViewProps) => { {patient.heartrate} bpm - {/* NON-MODULE CHANGE END */} - - - {patient.blood_type || 'Unable to determine blood type'} { > `${Math.round(value)}%`} + format={(value) => `${truncateDamage(value)}%`} /> { : 'bad' } > - `${Math.round(value * 100)}%`} - /> + + + Type: {patient.blood_type || 'Unknown'} + + + `${truncateDamage(value * 100)}%`} + /> + + {damageTypes.map((type) => ( @@ -141,10 +153,11 @@ const PatientStateMainStateView = (props: PatientStateMainStateViewProps) => { value={patient[type.type] / patient.maxHealth} color="bad" > - + ))} + {/* NON-MODULE CHANGE END */} ); }; @@ -366,6 +379,7 @@ const PatientStateNextOperationsView = ( ); }; +// NON-MODULE CHANGE const PatientStateAnesthesiaView = () => { const { act, data } = useBackend(); const { anesthesia } = data; diff --git a/tgui/packages/tgui/interfaces/Vending.tsx b/tgui/packages/tgui/interfaces/Vending.tsx index ae9216a1f950..907834fef295 100644 --- a/tgui/packages/tgui/interfaces/Vending.tsx +++ b/tgui/packages/tgui/interfaces/Vending.tsx @@ -30,6 +30,8 @@ type VendingData = { access: boolean; vending_machine_input: CustomInput[]; categories: Record; + // NON-MODULE CHANGE + theme: string | null; }; type Category = { @@ -85,6 +87,7 @@ export const Vending = (props) => { coin_records = [], hidden_records = [], categories, + theme, } = data; const [selectedCategory, setSelectedCategory] = useState( @@ -129,7 +132,7 @@ export const Vending = (props) => { ); return ( - + {!!onstation && ( From 9103a2af3912885f8d29e2990ab936fe5c5223c2 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 29 Jan 2026 01:39:29 -0600 Subject: [PATCH 34/35] Fixes --- code/modules/mob/living/silicon/robot/inventory.dm | 4 ++-- code/modules/surgery/organs/_organ.dm | 4 ---- code/modules/surgery/organs/internal/liver/_liver.dm | 4 +++- code/modules/surgery/organs/organ_movement.dm | 1 - 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm index 13fee58f342a..6b2aa2612fe9 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -53,7 +53,7 @@ /mob/living/silicon/robot/proc/equip_module_to_slot(obj/item/item_module, module_num) var/storage_was_closed = FALSE //Just to be consistant and all if(!shown_robot_modules) //Tools may be invisible if the collection is hidden - hud_used.toggle_show_robot_modules() + hud_used?.toggle_show_robot_modules() storage_was_closed = TRUE switch(module_num) if(BORG_CHOOSE_MODULE_ONE) @@ -76,7 +76,7 @@ observer_screen_update(item_module, TRUE) if(storage_was_closed) - hud_used.toggle_show_robot_modules() + hud_used?.toggle_show_robot_modules() item_module.on_equipped(src, ITEM_SLOT_HANDS) return TRUE diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 1478ebda72ab..5122f5156521 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -132,10 +132,6 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) return owner.remove_status_effect(status, type) -/obj/item/organ/proc/on_owner_examine(datum/source, mob/user, list/examine_list) - SIGNAL_HANDLER - return - /obj/item/organ/proc/on_find(mob/living/finder) return diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm index 988b27bc9cfc..38e57a1499d0 100755 --- a/code/modules/surgery/organs/internal/liver/_liver.dm +++ b/code/modules/surgery/organs/internal/liver/_liver.dm @@ -64,6 +64,7 @@ /obj/item/organ/liver/on_mob_insert(mob/living/carbon/organ_owner, special) . = ..() RegisterSignal(organ_owner, COMSIG_SPECIES_HANDLE_CHEMICAL, PROC_REF(handle_chemical)) + RegisterSignal(owner, COMSIG_ATOM_EXAMINE, PROC_REF(on_owner_examine)) /// Unregisters COMSIG_SPECIES_HANDLE_CHEMICAL from owner /obj/item/organ/liver/on_mob_remove(mob/living/carbon/organ_owner, special) @@ -222,7 +223,8 @@ if(SPT_PROB(3, seconds_per_tick)) owner.emote("drool") -/obj/item/organ/liver/on_owner_examine(datum/source, mob/user, list/examine_list) +/obj/item/organ/liver/proc/on_owner_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER if(!ishuman(owner) || !(organ_flags & ORGAN_FAILING)) return diff --git a/code/modules/surgery/organs/organ_movement.dm b/code/modules/surgery/organs/organ_movement.dm index 8a94e0866e83..62fbac434973 100644 --- a/code/modules/surgery/organs/organ_movement.dm +++ b/code/modules/surgery/organs/organ_movement.dm @@ -113,7 +113,6 @@ for(var/datum/status_effect/effect as anything in organ_effects) organ_owner.apply_status_effect(effect, type) - RegisterSignal(owner, COMSIG_ATOM_EXAMINE, PROC_REF(on_owner_examine)) SEND_SIGNAL(src, COMSIG_ORGAN_IMPLANTED, organ_owner) SEND_SIGNAL(organ_owner, COMSIG_CARBON_GAIN_ORGAN, src, special) From d5243504a26f88f1747115adad46643a2180bced Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 29 Jan 2026 02:05:51 -0600 Subject: [PATCH 35/35] Fix --- code/modules/unit_tests/liver.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/unit_tests/liver.dm b/code/modules/unit_tests/liver.dm index 57a03ec1b608..cabbab9200ee 100644 --- a/code/modules/unit_tests/liver.dm +++ b/code/modules/unit_tests/liver.dm @@ -77,7 +77,7 @@ // Test plasma - r_arm.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = 100, sharpness = NONE) + r_arm.receive_damage(2 * WOUND_MINIMUM_DAMAGE, 0, wound_bonus = 100, sharpness = NONE) TEST_ASSERT(length(mrbones.all_wounds), "Plasmaman did not receive a wound on their right arm") mrbones.reagents.add_reagent(plasma, 50) @@ -91,7 +91,7 @@ // Test hot ice - l_arm.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = 100, sharpness = NONE) + l_arm.receive_damage(2 * WOUND_MINIMUM_DAMAGE, 0, wound_bonus = 100, sharpness = NONE) TEST_ASSERT(length(mrbones.all_wounds), "Plasmaman did not receive a wound on their left arm") afflicted_wound = mrbones.all_wounds[1]