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/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..dc2f80927a3e 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)
@@ -107,8 +109,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)
@@ -178,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/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
index 80d72ab4e39b..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"
@@ -312,3 +316,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_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/__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/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/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..2f1a6db2f7e9 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_INDEX(value) ? left : right)
// Used in ready menu anominity
/// Hide ckey
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 6c5ffa373e2e..a7cedf7fcfb2 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)
@@ -91,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.
@@ -102,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.
@@ -144,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
@@ -194,13 +206,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/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm
index 30acc2d37fd6..9d045b6a9353 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, 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
#define UPDATE_MOB_HEALTH 1
diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm
index 2f98f69b10e8..d244c25ef4d8 100644
--- a/code/__DEFINES/surgery.dm
+++ b/code/__DEFINES/surgery.dm
@@ -34,11 +34,22 @@
#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)
/// 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
@@ -76,22 +87,85 @@
/// 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"
+/// 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
+#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..688268b672a3 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"
@@ -1239,4 +1249,10 @@ 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"
+
+/// This trait lets you attach limbs to any player without surgery.
+#define TRAIT_EASY_ATTACH "easy_attach"
+
// END TRAIT DEFINES
diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm
index 434150b4ce13..1adf0ffb6426 100644
--- a/code/__DEFINES/wounds.dm
+++ b/code/__DEFINES/wounds.dm
@@ -71,12 +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<<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
@@ -85,6 +89,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 +112,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..235ffe5f7a32 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(
@@ -410,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(
@@ -418,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(
@@ -440,6 +444,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..47bd425e1270 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,
@@ -274,8 +275,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 +406,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,
@@ -423,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,
@@ -466,7 +470,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,
@@ -574,6 +577,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,
@@ -622,6 +628,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/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/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 428f6509ebaf..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
@@ -389,7 +393,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
@@ -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/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 26ebf23fb290..adbecdee6884 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
@@ -308,11 +313,11 @@
), ARMOR_MAX_BLOCK)
var/damage = attacking_item.force * user.outgoing_damage_mod
- if(mob_biotypes & MOB_ROBOTIC)
- damage *= attacking_item.demolition_mod
+ 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
- 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/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/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/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/code/datums/components/prosthetic_item.dm b/code/datums/components/prosthetic_item.dm
new file mode 100644
index 000000000000..1537535974f6
--- /dev/null
+++ b/code/datums/components/prosthetic_item.dm
@@ -0,0 +1,156 @@
+/**
+ * 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)
+ 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)
+
+ 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/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/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/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/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/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/elements/prosthetic_icon.dm b/code/datums/elements/prosthetic_icon.dm
new file mode 100644
index 000000000000..3ad2ef8ec780
--- /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/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/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/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/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..dbbfce7c5c71
--- /dev/null
+++ b/code/datums/status_effects/buffs/bioware/cortex.dm
@@ -0,0 +1,31 @@
+// 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
+
+/datum/status_effect/bioware/cortex/imprinted/bioware_gained()
+ . = ..()
+ var/mob/living/carbon/human/human_owner = owner
+ 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/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/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..30f2a0810a1d 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,13 +178,12 @@
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_limb_biostate = BIO_FLESH
required_wounding_types = list(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..5b956472e3a0 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/misc/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,51 +204,167 @@
// 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"]["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["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"]["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)
+ 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)
+ 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,
+ ))
-/obj/machinery/computer/operating/ui_act(action, params)
+ else if(!HAS_TRAIT(part, TRAIT_READY_TO_OPERATE))
+ just_drapes = TRUE
+
+ else if(!HAS_TRAIT(table.patient, TRAIT_READY_TO_OPERATE))
+ just_drapes = TRUE
+
+ if(just_drapes)
+ data["surgeries"] += list(list(
+ "name" = "Prepare for surgery",
+ "desc" = "Begin surgery by applying surgical drapes to the patient.",
+ "tool_rec" = /obj/item/surgical_drapes::name,
+ "show_as_next" = TRUE,
+ "show_in_list" = FALSE,
+ ))
+
+ return data
+
+/obj/machinery/computer/operating/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
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..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,14 +8,27 @@
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
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/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/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/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/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
index fe2a41f4cfb0..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
/**
@@ -391,7 +392,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/effects/info.dm b/code/game/objects/effects/info.dm
index adf609d50c23..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)
. = ..()
@@ -15,11 +18,12 @@
/obj/effect/abstract/info/Click()
. = ..()
- to_chat(usr, info_text)
+ to_chat(usr, examine_block("[span_boldnotice(name)] [span_info(info_text)]"))
/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/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/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/game/objects/items/chainsaw.dm b/code/game/objects/items/chainsaw.dm
index cdbe2b0934dd..73649544503d 100644
--- a/code/game/objects/items/chainsaw.dm
+++ b/code/game/objects/items/chainsaw.dm
@@ -4,12 +4,13 @@
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
force = 13
- var/force_on = 24
w_class = WEIGHT_CLASS_HUGE
throwforce = 13
throw_speed = 2
@@ -23,69 +24,28 @@
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)
. = ..()
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/weapons/chainsawhit.ogg', \
+ w_class_on = w_class, \
+ )
AddComponent(/datum/component/butchering, \
speed = 3 SECONDS, \
effectiveness = 100, \
@@ -93,15 +53,63 @@
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()
+
+ return COMPONENT_NO_DEFAULT_MESSAGE
+
+/obj/item/chainsaw/proc/disable_twohanded_comp()
+ SIGNAL_HANDLER
+
+ qdel(GetComponent(/datum/component/two_handed))
+
+/obj/item/chainsaw/proc/enable_twohanded_comp()
+ SIGNAL_HANDLER
-/obj/item/chainsaw/doomslayer/attack(mob/living/target_mob, mob/living/user, params)
+ 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
+
+/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/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!"))
+ var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD)
+ 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/attack(mob/living/target_mob, mob/living/user, list/modifiers, list/attack_modifiers)
if (target_mob.stat != DEAD)
return ..()
@@ -109,20 +117,37 @@
return ..()
var/obj/item/bodypart/head = target_mob.get_bodypart(BODY_ZONE_HEAD)
- if (isnull(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, 2 SECONDS, target_mob, extra_checks = CALLBACK(src, PROC_REF(has_same_head), target_mob, head)))
+ if (!do_after(user, behead_time, 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)
+ if (head.dismember(silent = FALSE))
+ user.put_in_hands(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
+
+/**
+ * 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
+ */
+
+/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)
owner.visible_message(span_danger("Ranged attacks just make [owner] angrier!"))
@@ -130,43 +155,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/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/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/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/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/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/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/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/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/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/robot/items/tools.dm b/code/game/objects/items/robot/items/tools.dm
index 01ef8abf03bb..28e4fa0aac15 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/change_jaws.ogg', 50, TRUE)
/obj/item/borg/cyborg_omnitool/update_icon_state()
if (reference)
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm
index bc82fedff047..bb40bbc3b581 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
@@ -267,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
@@ -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,16 @@
span_green("You bandage the wounds on [user == patient ? "your" : "[patient]'s"] [limb.plaintext_zone]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
+
+ 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/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 +564,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/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/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/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/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index e0f2d91673ad..fa0eee3d94b5 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 *= hit_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..cfc23d228866 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,19 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag)
if(!attacking_item.force)
return
- var/total_force = (attacking_item.force * attacking_item.demolition_mod)
-
- var/damage = take_damage(total_force, attacking_item.damtype, MELEE, 1, get_dir(src, user))
+ 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_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 +383,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/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/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/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 72756bad2116..0d6d2bfd8696 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)
@@ -850,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)
@@ -858,21 +873,23 @@
///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/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 +905,58 @@
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,
+ ))
+
+ 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/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/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/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/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/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
deleted file mode 100644
index baced8f03e88..000000000000
--- a/code/modules/antagonists/traitor/objectives/sleeper_protocol.dm
+++ /dev/null
@@ -1,143 +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
-/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
- progression_reward = list(8 MINUTES, 15 MINUTES)
- telecrystal_reward = 1
-
- inverted_limitation = TRUE
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 ? "
[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/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/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/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/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/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/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
index e02bc9971379..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,29 +38,40 @@
)
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/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 +401,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 +419,34 @@
/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
+ 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) || HAS_TRAIT_FROM(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT))
+ 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/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/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/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/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/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm
index f5ef92bfe23c..9c90e8f7d43f 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(SELECT_LEFT_OR_RIGHT(index, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
user.temporarilyRemoveItemFromInventory(src, TRUE)
Insert(user)
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/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm
index caa960882734..edde350dc1ec 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
@@ -213,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"]))
@@ -228,21 +235,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 +326,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 +355,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/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/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/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/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/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/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/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..0eb81e1f1ae3 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,8 +1139,8 @@
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]")
+ if(new_bp.replace_limb(src))
+ 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.")
@@ -1240,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
@@ -1251,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 25ac715ee08d..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)
@@ -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..ce8996bdba63 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))
@@ -171,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
@@ -394,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/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm
index 980de7d296fa..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
/**
@@ -1576,7 +1584,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/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/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/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/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index 672ff8c1f85d..f2d41d319c8b 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/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/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..830cb159707a 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(!user.combat_mode && HAS_TRAIT(src, TRAIT_READY_TO_OPERATE) && user.perform_surgery(src))
+ return TRUE
return FALSE
@@ -614,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)
@@ -622,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
@@ -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/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/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/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/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/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/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/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/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/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/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..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!"))
@@ -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"
@@ -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 90c634bf508b..8fb31d65d2d7 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -382,13 +382,13 @@
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!"))
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!") )
@@ -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."
@@ -1026,7 +1107,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))
@@ -1204,7 +1285,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..3302889c883f 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)
@@ -1178,7 +1178,7 @@
. = ..()
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)
burn_wound.sanitization += 0.9
@@ -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|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
@@ -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/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/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/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/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/reagents/reagent_containers/inhaler.dm b/code/modules/reagents/reagent_containers/inhaler.dm
new file mode 100644
index 000000000000..f6d572095709
--- /dev/null
+++ b/code/modules/reagents/reagent_containers/inhaler.dm
@@ -0,0 +1,315 @@
+/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 ITEM_INTERACT_BLOCKING
+
+ 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, target_mob))
+ return ITEM_INTERACT_BLOCKING
+ if (!can_puff(target_mob, user)) // sanity
+ return ITEM_INTERACT_BLOCKING
+
+ 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)
+ return ITEM_INTERACT_SUCCESS
+
+/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/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..d6dfd839843b 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."
@@ -1013,171 +1037,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 = /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
+
+/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/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/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.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/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..ec67b2cb6dba 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
@@ -156,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)
@@ -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,46 +390,154 @@
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)
+/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
+ 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)
@@ -640,7 +762,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.
@@ -763,6 +884,10 @@
))
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))
/// 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)
@@ -779,22 +904,25 @@
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)
+
/// Called on addition of a bodypart
/obj/item/bodypart/proc/on_adding(mob/living/carbon/new_owner)
SHOULD_CALL_PARENT(TRUE)
@@ -1155,7 +1283,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
@@ -1165,11 +1293,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)
@@ -1188,17 +1311,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
@@ -1214,7 +1330,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
@@ -1222,29 +1360,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
@@ -1259,7 +1389,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)
@@ -1368,6 +1498,8 @@
return "flesh"
if (biological_state & BIO_WIRED)
return "wiring"
+ if (biological_state & BIO_CHITIN)
+ return "chitin"
return "error"
@@ -1377,6 +1509,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 +1541,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..62aa98d51175 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")
@@ -127,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)
@@ -226,7 +222,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)
@@ -241,17 +237,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 +287,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)
@@ -352,7 +343,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/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/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm
index 6e9bd4b990f0..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
@@ -242,6 +245,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/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/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/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/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)
diff --git a/code/modules/surgery/bodyparts/wounds.dm b/code/modules/surgery/bodyparts/wounds.dm
index 49396f4e06ea..c243487ec405 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()
@@ -341,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/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
deleted file mode 100644
index da2d4302a989..000000000000
--- a/code/modules/surgery/internal_bleeding.dm
+++ /dev/null
@@ -1,89 +0,0 @@
-/// 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)"
- implements = list(
- TOOL_HEMOSTAT = 100,
- TOOL_BLOODFILTER = 100,
- TOOL_WIRECUTTER = 40,
- /obj/item/stack/sticky_tape/surgical = 30,
- /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
-
-/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)]"
- 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]."),
- )
- 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_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)
- 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]!"),
- )
- repeatable = FALSE
- 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]!"),
- )
- target.apply_damage(3, BRUTE, surgery.operated_bodypart, wound_bonus = CANT_WOUND, attacking_item = tool)
- return ..()
-
-/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)]"
- 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]!"),
- )
- target.apply_damage(rand(4, 8), BRUTE, surgery.operated_bodypart, wound_bonus = 10, sharpness = SHARP_EDGED, attacking_item = tool)
- return FALSE
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..fd94b433c7c3
--- /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 = -1
+ 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..6d7b39a38c3e
--- /dev/null
+++ b/code/modules/surgery/operations/_operation.dm
@@ -0,0 +1,1395 @@
+/**
+ * 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) ? '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,
+ 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) ? '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
+ 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, examine_block(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, 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")
+/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/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!")
+
+ 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
+ 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
+ /// 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)
+ // NON-MODULE CHANGE
+ // Ignore alllll the penalties (but also all the bonuses)
+ 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)
+ 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(!CAN_FEEL_PAIN(patient)) // NON-MODULE CHANGE
+ 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
+ // 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,
+ // 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."),
+ )
+
+// NON-MODULE CHANGE
+///
+/**
+ * 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,
+ affected_locations,
+ pain_message,
+ pain_amount = 0,
+ pain_type = BRUTE,
+ pain_overlay_severity = 1,
+)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ PROTECTED_PROC(TRUE)
+
+ if(!pain_message || !affected_locations)
+ return
+
+ // 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
+
+ // Mechanical = only give a feedback message
+ if(operation_flags & OPERATION_MECHANIC)
+ target.pain_message(span_danger(pain_message))
+ return
+
+ // 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))
+ return
+
+ // 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
+
+ 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))
+
+/// 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_zone
+ 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)
+ 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
+
+// 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)
+
+// 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
+ * 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..b789e64c62b3
--- /dev/null
+++ b/code/modules/surgery/operations/operation_add_limb.dm
@@ -0,0 +1,221 @@
+#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,
+ affected_locations = 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,
+ affected_locations = 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,
+ affected_locations = 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,
+ affected_locations = list(limb, chest),
+ pain_message = "[surgeon] begins to [tool.singular_name] [limb] to your body!",
+ )
+
+/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,
+ affected_locations = list(limb, chest),
+ pain_message = "You feel more secure as your prosthetic is firmly attached to your body!",
+ )
+ 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..c8b8b8ed7d66
--- /dev/null
+++ b/code/modules/surgery/operations/operation_amputation.dm
@@ -0,0 +1,129 @@
+/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/surgery/saw.ogg',
+ /obj/item = 'sound/surgery/scalpel1.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()
+ 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,
+ affected_locations = limb,
+ 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,
+ )
+
+/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,
+ affected_locations = limb,
+ 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,
+ )
+ 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/ratchet.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)
+ // 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/surgery/saw.ogg',
+ /obj/item = 'sound/weapons/bladeslice.ogg',
+ )
+ success_sound = 'sound/items/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..2d48585786b1
--- /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/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.
+ 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..623154038302
--- /dev/null
+++ b/code/modules/surgery/operations/operation_bioware.dm
@@ -0,0 +1,454 @@
+/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, list/operation_args)
+ 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,
+ affected_locations = BODY_ZONES_ALL,
+ pain_message = "Your entire body burns in agony!",
+ pain_amount = 4 * SURGERY_PAIN_HIGH,
+ pain_type = BURN,
+ pain_overlay_severity = 2,
+ )
+
+/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,
+ affected_locations = BODY_ZONES_ALL,
+ pain_message = "You can feel your blood pumping through reinforced veins!",
+ )
+
+/datum/surgery_operation/limb/bioware/vein_threading/mechanic
+ rnd_name = "Hydraulics Routing Optimization (Threaded Veins)"
+ desc = "Optimize the routing of a robotic patient's hydraulic system, reducing fluid loss from leaks."
+ required_bodytype = BODYTYPE_ROBOTIC
+ operation_flags = parent_type::operation_flags | OPERATION_MECHANIC
+
+/datum/surgery_operation/limb/bioware/muscled_veins
+ name = "muscled veins"
+ rnd_name = "Myovasculoplasty (Muscled Veins)" // "muscle vessel reshaping"
+ desc = "Add a muscled membrane to a patient's veins, allowing them to pump blood without a heart."
+ status_effect_gained = /datum/status_effect/bioware/heart/muscled_veins
+
+/datum/surgery_operation/limb/bioware/muscled_veins/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool)
+ display_results(
+ surgeon,
+ limb.owner,
+ span_notice("You start wrapping muscles around [limb.owner]'s blood vessels."),
+ span_notice("[surgeon] starts wrapping muscles around [limb.owner]'s blood vessels."),
+ span_notice("[surgeon] starts manipulating [limb.owner]'s blood vessels."),
+ )
+ // NON-MODULE CHANGE
+ display_pain(
+ target = limb.owner,
+ affected_locations = BODY_ZONES_ALL,
+ pain_message = "Your entire body burns in agony!",
+ pain_amount = 4 * SURGERY_PAIN_HIGH,
+ pain_type = BURN,
+ pain_overlay_severity = 2,
+ )
+
+/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 = limb.owner,
+ affected_locations = BODY_ZONES_ALL,
+ pain_message = "You can feel your heartbeat's powerful pulses ripple through your body!",
+ )
+
+/datum/surgery_operation/limb/bioware/muscled_veins/mechanic
+ rnd_name = "Hydraulics Redundancy Subroutine (Muscled Veins)"
+ desc = "Add redundancies to a robotic patient's hydraulic system, allowing it to pump fluids without an engine or pump."
+ required_bodytype = BODYTYPE_ROBOTIC
+ operation_flags = parent_type::operation_flags | OPERATION_MECHANIC
+
+/datum/surgery_operation/limb/bioware/nerve_splicing
+ name = "splice nerves"
+ rnd_name = "Symneurodesis (Spliced Nerves)" // "together nerve fusion"
+ desc = "Splice a patient's nerves together to make them more resistant to stuns."
+ time = 15.5 SECONDS
+ status_effect_gained = /datum/status_effect/bioware/nerves/spliced
+
+/datum/surgery_operation/limb/bioware/nerve_splicing/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool)
+ display_results(
+ surgeon,
+ limb.owner,
+ span_notice("You start splicing together [limb.owner]'s nerves."),
+ span_notice("[surgeon] starts splicing together [limb.owner]'s nerves."),
+ span_notice("[surgeon] starts manipulating [limb.owner]'s nervous system."),
+ )
+ display_pain(
+ target = limb.owner,
+ affected_locations = BODY_ZONES_ALL,
+ pain_message = "Your entire body goes numb!",
+ pain_amount = 4 * SURGERY_PAIN_HIGH,
+ pain_type = BURN,
+ pain_overlay_severity = 2,
+ )
+
+/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,
+ 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,
+ )
+
+/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,
+ affected_locations = BODY_ZONES_ALL,
+ pain_message = "Your entire body goes numb!",
+ pain_amount = 4 * SURGERY_PAIN_HIGH,
+ pain_type = BURN,
+ pain_overlay_severity = 2,
+ )
+
+/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 = limb.owner,
+ affected_locations = BODY_ZONES_ALL,
+ pain_message = "You regain feeling in your body! You feel energzed!",
+ pain_amount = -2 * SURGERY_PAIN_HIGH,
+ )
+
+/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 = limb.owner,
+ affected_locations = BODY_ZONES_LIMBS,
+ pain_message = "Your limbs burn with severe pain!",
+ pain_amount = 4 * SURGERY_PAIN_MEDIUM,
+ pain_type = BURN,
+ pain_overlay_severity = 2,
+ )
+
+/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,
+ affected_locations = BODY_ZONES_LIMBS,
+ pain_message = "Your limbs feel... strangely loose.",
+ )
+
+/datum/surgery_operation/limb/bioware/ligament_hook/mechanic
+ rnd_name = "Anchor Point Snaplocks (Ligament Hooks)"
+ desc = "Refactor a robotic patient's limb joints to allow for rapid deatchment, allowing limbs to be manually reattached if severed - \
+ at the cost of making them easier to detach as well."
+ required_bodytype = BODYTYPE_ROBOTIC
+ operation_flags = parent_type::operation_flags | OPERATION_MECHANIC
+
+/datum/surgery_operation/limb/bioware/ligament_reinforcement
+ name = "strengthen ligaments"
+ rnd_name = "Arthrorrhaphy (Ligament Reinforcement)" // "joint strengthening" / "joint stitching"
+ desc = "Strengthen a patient's ligaments to make dismemberment more difficult, at the cost of making nerve connections easier to interrupt."
+ status_effect_gained = /datum/status_effect/bioware/ligaments/reinforced
+
+/datum/surgery_operation/limb/bioware/ligament_reinforcement/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool)
+ display_results(
+ surgeon,
+ limb.owner,
+ span_notice("You start reinforcing [limb.owner]'s ligaments."),
+ span_notice("[surgeon] starts reinforcing [limb.owner]'s ligaments."),
+ span_notice("[surgeon] starts manipulating [limb.owner]'s ligaments."),
+ )
+ // NON-MODULE CHANGE
+ display_pain(
+ target = limb.owner,
+ affected_locations = BODY_ZONES_LIMBS,
+ pain_message = "Your limbs burn with severe pain!",
+ pain_amount = 4 * SURGERY_PAIN_MEDIUM,
+ pain_type = BURN,
+ pain_overlay_severity = 2,
+ )
+
+/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,
+ affected_locations = BODY_ZONES_LIMBS,
+ pain_message = "Your limbs feel more secure, but also more frail.",
+ )
+
+/datum/surgery_operation/limb/bioware/ligament_reinforcement/mechanic
+ rnd_name = "Anchor Point Reinforcement (Ligament Reinforcement)"
+ desc = "Reinforce a robotic patient's limb joints to prevent dismemberment, at the cost of making nerve connections easier to interrupt."
+ required_bodytype = BODYTYPE_ROBOTIC
+ operation_flags = parent_type::operation_flags | OPERATION_MECHANIC
+
+/datum/surgery_operation/limb/bioware/cortex_folding
+ name = "cortex folding"
+ rnd_name = "Encephalofractoplasty (Cortex Folding)" // it's a stretch - "brain fractal reshaping"
+ desc = "A biological upgrade which folds a patient's cerebral cortex into a fractal pattern, increasing neural density and flexibility."
+ status_effect_gained = /datum/status_effect/bioware/cortex/folded
+ required_zone = BODY_ZONE_HEAD
+
+/datum/surgery_operation/limb/bioware/cortex_folding/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool)
+ display_results(
+ surgeon,
+ limb.owner,
+ span_notice("You start folding [limb.owner]'s cerebral cortex."),
+ span_notice("[surgeon] starts folding [limb.owner]'s cerebral cortex."),
+ span_notice("[surgeon] starts performing surgery on [limb.owner]'s brain."),
+ )
+ // NON-MODULE CHANGE
+ display_pain(
+ target = limb.owner,
+ affected_locations = limb,
+ pain_message = "Your head throbs with gruesome pain, it's nearly too much to handle!",
+ pain_amount = SURGERY_PAIN_CRITICAL,
+ )
+
+/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,
+ affected_locations = limb,
+ pain_message = "Your brain feels stronger... more flexible!",
+ )
+
+/datum/surgery_operation/limb/bioware/cortex_folding/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool)
+ if(!limb.owner.get_organ_slot(ORGAN_SLOT_BRAIN))
+ return ..()
+ display_results(
+ surgeon,
+ limb.owner,
+ span_warning("You screw up, damaging the brain!"),
+ span_warning("[surgeon] screws up, damaging the brain!"),
+ span_notice("[surgeon] completes the surgery on [limb.owner]'s brain."),
+ )
+ // NON-MODULE CHANGE
+ display_pain(
+ target = limb.owner,
+ affected_locations = limb,
+ pain_message = "Your head throbs with excruciating pain!",
+ pain_amount = SURGERY_PAIN_CRITICAL,
+ )
+ 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
+ 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,
+ affected_locations = limb,
+ pain_message = "Your head throbs with gruesome pain, it's nearly too much to handle!",
+ pain_amount = SURGERY_PAIN_CRITICAL,
+ )
+
+/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,
+ affected_locations = limb,
+ pain_message = "Your brain feels stronger... more resillient!",
+ )
+
+/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,
+ affected_locations = limb,
+ pain_message = "Your brain throbs with intense pain; thinking hurts!",
+ pain_amount = SURGERY_PAIN_CRITICAL,
+ )
+ 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
+ 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..460a0ed466c4
--- /dev/null
+++ b/code/modules/surgery/operations/operation_bone_repair.dm
@@ -0,0 +1,347 @@
+// 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_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 [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!")
+
+/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 [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!")
+
+/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 [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)
+
+/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_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)
+
+/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,
+ affected_locations = limb,
+ pain_message = "Your [limb.plaintext_zone] aches with pain!",
+ 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)
+ 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_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)
+
+/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,
+ affected_locations = limb,
+ pain_message = "The aching pain in your [limb.plaintext_zone] is overwhelming!",
+ 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)
+ 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_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)
+
+/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,
+ affected_locations = limb,
+ pain_message = "The aching pain in your [limb.plaintext_zone] is overwhelming!",
+ 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)
+ 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/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)
+
+/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,
+ affected_locations = limb,
+ pain_message = "Your brain feels like it's getting stabbed by little shards of glass!",
+ 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)
+ . = ..()
+ 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_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)
+
+/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,
+ affected_locations = limb,
+ pain_message = "You can feel pieces of your skull rubbing against your brain!",
+ 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)
+ 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..48262f795d0f
--- /dev/null
+++ b/code/modules/surgery/operations/operation_brainwash.dm
@@ -0,0 +1,167 @@
+#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/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
+ 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) // 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)
+ 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,
+ 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/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_MINDSHIELD)) // NON-MODULE CHANGE
+ 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,
+ affected_locations = organ,
+ pain_message = "Your head throbs with horrible pain!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_SEVERE,
+ )
+ organ.owner.adjustOrganLoss(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/surgery/hemostat1.ogg'
+ success_sound = 'sound/surgery/hemostat1.ogg'
+ failure_sound = 'sound/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(
+ 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)
+ . = ..()
+ 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..0cfc256038f2
--- /dev/null
+++ b/code/modules/surgery/operations/operation_cavity_implant.dm
@@ -0,0 +1,218 @@
+/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/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
+
+/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,
+ 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,
+ )
+
+/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/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
+
+/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,
+ 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,
+ )
+
+/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/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()
+ 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,
+ 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,
+ )
+
+/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,
+ affected_locations = limb,
+ pain_message = "You can feel [implant.name] being pulled out of you!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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..52543b9abbc5
--- /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/simple_animal/slime) // NON-MODULE CHANGE
+
+/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(
+ 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(surgeon, span_warning("There aren't any cores left in [patient]!"))
diff --git a/code/modules/surgery/operations/operation_debride.dm b/code/modules/surgery/operations/operation_debride.dm
new file mode 100644
index 000000000000..33324d09e6f0
--- /dev/null
+++ b/code/modules/surgery/operations/operation_debride.dm
@@ -0,0 +1,98 @@
+/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/surgery/scalpel1.ogg',
+ TOOL_HEMOSTAT = 'sound/surgery/hemostat1.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
+ /// 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/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)
+
+/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/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/flesh/wound)
+ if(wound?.infection <= 0)
+ return null
+
+ var/estimated_remaining_steps = wound.infection / infection_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,
+ affected_locations = limb,
+ pain_message = "The infection in your [limb.plaintext_zone] stings like hell! It feels like you're being stabbed!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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/flesh/wound = locate() in limb.wounds
+ wound?.infection -= infection_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..d4bc021ff43a
--- /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/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/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,
+ affected_locations = limb,
+ pain_message = "Something's being jammed into your mouth!",
+ 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)
+ // 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/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,
+ affected_locations = limb,
+ 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/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/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..f1c1791e00e2
--- /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,
+ affected_locations = 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..603f7216a9f9
--- /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/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
+ 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,
+ affected_locations = limb,
+ pain_message = "You feel a stabbing in your [limb.plaintext_zone].",
+ 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)
+ . = ..() // 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_blood_type()?.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/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()
+ 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,
+ 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 = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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 = 'maplestation_modules/sound/items/snip.ogg',
+ /obj/item = 'sound/surgery/cautery1.ogg',
+ )
+ success_sound = list(
+ /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
+
+/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,
+ affected_locations = limb,
+ pain_message = "Your [limb.plaintext_zone] is being [istype(tool, /obj/item/stack/medical/suture) ? "pinched" : "burned"]!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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/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,
+ affected_locations = limb,
+ pain_message = "You feel a pinch as the bleeding in your [limb.plaintext_zone] is slowed.",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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/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,
+ affected_locations = limb,
+ pain_message = "You feel a pressure release as blood starts flowing in your [limb.plaintext_zone] again.",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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/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
+
+/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,
+ affected_locations = limb,
+ pain_message = "You feel a horrid ache spread through the inside of your [limb.plaintext_zone]!",
+ 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)
+ . = ..()
+ 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,
+ affected_locations = limb,
+ pain_message = "It feels like something just broke in your [limb.plaintext_zone]!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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_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
+ 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,
+ 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 = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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/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
+
+/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,
+ affected_locations = limb,
+ pain_message = "You feel a horrible piercing pain in your [limb.plaintext_zone]!",
+ 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)
+ . = ..()
+ 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/surgery/scalpel1.ogg'
+ success_sound = 'sound/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,
+ affected_locations = limb,
+ pain_message = "You feel a stabbing in your [limb.plaintext_zone].",
+ 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)
+ . = ..()
+ 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,
+ affected_locations = limb,
+ pain_message = "You feel a sharp pain from inside your [limb.plaintext_zone]!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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..3ab0b6bb57fc
--- /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/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
+
+/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/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
+ 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 = 'maplestation_modules/sound/items/snip.ogg',
+ /obj/item = 'sound/surgery/cautery1.ogg',
+ )
+ success_sound = list(
+ /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
+
+/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..668f8a9103cd
--- /dev/null
+++ b/code/modules/surgery/operations/operation_generic_mechanic.dm
@@ -0,0 +1,251 @@
+// 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/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()
+ 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,
+ affected_locations = limb,
+ pain_message = "You feel your [limb.plaintext_zone] grow numb as the shell is unscrewed.",
+ )
+
+/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/ratchet.ogg'
+ success_sound = 'sound/machines/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,
+ affected_locations = limb,
+ pain_message = "The last faint pricks of tactile sensation fade from your [limb.plaintext_zone] as the hatch is opened.",
+ )
+
+/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/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()
+ 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,
+ affected_locations = limb,
+ pain_message = "You feel the faint pricks of sensation return as your [limb.plaintext_zone]'s shell is screwed in.",
+ )
+
+/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,
+ affected_locations = limb,
+ pain_message = "You can feel a faint buzz in your [limb.plaintext_zone] as the electronics reboot.",
+ )
+
+/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/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,
+ affected_locations = limb,
+ pain_message = "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to loosen.",
+ )
+
+/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/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,
+ affected_locations = limb,
+ pain_message = "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to tighten.",
+ )
+
+/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..0ac5a92b34cb
--- /dev/null
+++ b/code/modules/surgery/operations/operation_healing.dm
@@ -0,0 +1,278 @@
+/// 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/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
+ 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.getBruteLoss() > 0 || patient.getFireLoss() > 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.getBruteLoss() > 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.getFireLoss() > 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.getBruteLoss() > 0 || patient.getFireLoss() > 0
+ else if(brute_heal)
+ return patient.getBruteLoss() > 0
+ else if(burn_heal)
+ return patient.getFireLoss() > 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,
+ affected_locations = BODY_ZONE_CHEST,
+ pain_message = "Your [woundtype] sting like hell!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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.getBruteLoss() / brute_healed))
+ if(burn_healed > 0)
+ 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.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)
+ 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/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))
+ 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.getBruteLoss() * brute_multiplier, DAMAGE_PRECISION)
+ burn_healed += round(patient.getFireLoss() * 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.getBruteLoss() * brute_multiplier, 0.1)
+ burn_dealt += round(patient.getFireLoss() * burn_multiplier, 0.1)
+
+ // 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 + "+"
+ 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..f0b7c65c055a
--- /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/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,
+ affected_locations = BODY_ZONE_CHEST,
+ pain_message = "You feel a serious pain as [surgeon] digs around inside you!",
+ 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)
+ 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,
+ affected_locations = BODY_ZONE_CHEST,
+ pain_message = "You can feel your [implant.name] pulled out of you!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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..8d9368322713
--- /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/surgery/saw.ogg',
+ /obj/item = 'sound/surgery/scalpel1.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()
+ 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,
+ affected_locations = limb,
+ pain_message = "You feel a stabbing in your [limb.plaintext_zone]!",
+ 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)
+ 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/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
new file mode 100644
index 000000000000..72295c8ac5d8
--- /dev/null
+++ b/code/modules/surgery/operations/operation_lobotomy.dm
@@ -0,0 +1,115 @@
+/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/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()
+ 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,
+ affected_locations = organ,
+ pain_message = "Your head pounds with unimaginable pain!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL,
+ )
+
+/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,
+ 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,
+ )
+
+ 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,
+ 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,
+ )
+ 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..c9ee69bb7921
--- /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/surgery/organ2.ogg'
+ /// Sound played when starting to remove an organ
+ var/remove_preop_sound = 'sound/surgery/hemostat1.ogg'
+ /// Sound played when successfully inserting an organ
+ var/insert_success_sound = 'sound/surgery/organ1.ogg'
+ /// Sound played when successfully removing an organ
+ var/remove_success_sound = 'sound/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,
+ affected_locations = limb,
+ pain_message = "You feel a tugging sensation in your [limb.plaintext_zone]!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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,
+ affected_locations = limb,
+ 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, operation_args)
+ if("insert")
+ play_operation_sound(limb, surgeon, tool, insert_success_sound)
+ 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)
+
+/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,
+ affected_locations = limb,
+ pain_message = "Your [limb.plaintext_zone] throbs with pain, you can't feel your [organ.name] anymore!",
+ pain_amount = get_tool_quality(tool) * 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,
+ 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,
+ )
+
+/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..18e1983f66a8
--- /dev/null
+++ b/code/modules/surgery/operations/operation_organ_repair.dm
@@ -0,0 +1,656 @@
+/// 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/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
+
+/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,
+ 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,
+ )
+
+/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,
+ affected_locations = organ,
+ pain_message = "Your [parse_zone(organ.zone)] hurts like hell, but breathing becomes slightly easier.",
+ )
+
+/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,
+ 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,
+ )
+
+/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/ratchet.ogg'
+ success_sound = 'sound/machines/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/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
+
+/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,
+ affected_locations = organ,
+ pain_message = "Your abdomen burns in horrific stabbing pain!",
+ 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)
+ . = ..()
+ 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,
+ affected_locations = organ,
+ pain_message = "The pain in your abdomen receeds slightly.",
+ 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)
+ . = ..()
+ 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,
+ affected_locations = organ,
+ pain_message = "The pain in your abdomen intensifies!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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/ratchet.ogg'
+ success_sound = 'sound/machines/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/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)
+ 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,
+ 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,
+ )
+
+/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,
+ 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,
+ )
+
+/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_blood_type()?.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,
+ 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,
+ 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/ratchet.ogg'
+ success_sound = 'sound/machines/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/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
+
+/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,
+ affected_locations = organ,
+ pain_message = "You feel a horrible stab in your gut!",
+ 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)
+ . = ..()
+ 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,
+ affected_locations = organ,
+ pain_message = "The pain in your gut recedes slightly!",
+ 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)
+ . = ..()
+ 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,
+ affected_locations = organ,
+ pain_message = "The pain in your gut intensifies!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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/ratchet.ogg'
+ success_sound = 'sound/machines/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,
+ affected_locations = organ,
+ pain_message = "You feel a dizzying pain in your [parse_zone(organ.zone)]!",
+ 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)
+ . = ..()
+ var/deaf_change = 40 SECONDS - organ.deaf
+ organ.adjustEarDamage(0, 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,
+ affected_locations = organ,
+ pain_message = "Your [parse_zone(organ.zone)] swims, but it seems like you can feel your hearing coming back!",
+ 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)
+ 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,
+ affected_locations = organ,
+ pain_message = "You feel a visceral stabbing pain right through your [parse_zone(organ.zone)], into your brain!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM,
+ )
+ organ.owner.adjustOrganLoss(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,
+ affected_locations = organ,
+ pain_message = "You feel a stabbing pain in your eyes!",
+ 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)
+ . = ..()
+ 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,
+ affected_locations = organ,
+ pain_message = "Your vision blurs, but it seems like you can see a little better now!",
+ 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)
+ 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,
+ affected_locations = organ,
+ pain_message = "You feel a visceral stabbing pain right through your [parse_zone(organ.zone)], into your brain!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_MEDIUM,
+ )
+ organ.owner.adjustOrganLoss(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/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
+ 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,
+ 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,
+ )
+
+/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,
+ affected_locations = organ,
+ pain_message = "The pain in your head recedes, thinking becomes a bit easier!",
+ 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)
+ 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,
+ affected_locations = organ,
+ pain_message = "Your head throbs with horrible pain; thinking hurts!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_HIGH,
+ )
+ 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..21aaa2be5b37
--- /dev/null
+++ b/code/modules/surgery/operations/operation_pacify.dm
@@ -0,0 +1,87 @@
+/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/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
+
+/datum/surgery_operation/organ/pacify/get_default_radial_image()
+ 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(
+ 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,
+ affected_locations = organ,
+ pain_message = "Your head pounds with unimaginable pain!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * SURGERY_PAIN_CRITICAL,
+ )
+
+/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,
+ 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,
+ )
+ 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,
+ 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,
+ )
+ 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..7b4dadce5483
--- /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/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()
+ 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,
+ affected_locations = limb,
+ pain_message = "You feel slicing pain across your face!",
+ 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)
+ 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,
+ affected_locations = limb,
+ pain_message = "The pain fades, your face feels normal again!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * -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,
+ affected_locations = limb,
+ pain_message = "The pain fades, your face feels new and unfamiliar!",
+ 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
+ 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,
+ affected_locations = limb,
+ pain_message = "Your face feels horribly scarred and deformed!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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/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
+
+/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,
+ affected_locations = limb,
+ 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..eb4e5f315241
--- /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) // NON-MODULE CHANGE
+
+/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,
+ 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,
+ )
+
+/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..5e83871ebb80
--- /dev/null
+++ b/code/modules/surgery/operations/operation_puncture.dm
@@ -0,0 +1,163 @@
+/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/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_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)
+
+/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,
+ affected_locations = limb,
+ pain_message = "You feel a horrible stabbing pain in your [limb.plaintext_zone]!",
+ 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)
+ 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/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_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)
+
+/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,
+ affected_locations = limb,
+ pain_message = "You feel a burning sensation in your [limb.plaintext_zone]!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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..db2ecd69eb87
--- /dev/null
+++ b/code/modules/surgery/operations/operation_replace_limb.dm
@@ -0,0 +1,100 @@
+/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,
+ 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 = (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)
+ 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,
+ affected_locations = list(limb.body_zone, BODY_ZONE_CHEST),
+ pain_message = "Your [limb.plaintext_zone] comes awash with synthetic sensation!",
+ )
+ 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..0fe4c396ac6c
--- /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_charge.ogg',
+ /obj/item = null,
+ )
+ 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
+
+/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()
+ // NON-MODULE CHANGE
+ 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.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.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(
+ 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..dc41922660e6
--- /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/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(
+ /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,
+ affected_locations = BODY_ZONE_CHEST,
+ pain_message = "You feel a searing heat spread through your chest!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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,
+ affected_locations = BODY_ZONE_CHEST,
+ pain_message = "You feel a faint throbbing in your chest.",
+ 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
+ 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..a3a8b5636326
--- /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,
+ affected_locations = organ,
+ pain_message = "Your wings sting like hell!",
+ pain_amount = (operation_args?[OPERATION_TOOL_QUALITY] || 1) * 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,
+ affected_locations = organ,
+ 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..b48c8e89c0c4
--- /dev/null
+++ b/code/modules/surgery/operations/operation_zombie.dm
@@ -0,0 +1,86 @@
+/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,
+ 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,
+ )
+
+/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,
+ 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,
+ )
+ 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..5122f5156521 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
@@ -62,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.
@@ -73,10 +79,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 = foodtype_flags, \
+ volume = reagent_vol, \
+ tastes = food_tastes, \
+ after_eat = CALLBACK(src, PROC_REF(OnEatFrom)), \
+ )
+ RegisterSignal(src, COMSIG_FOOD_ATTEMPT_EAT, PROC_REF(block_nom))
if(bodypart_overlay)
setup_bodypart_overlay()
@@ -123,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
@@ -149,7 +154,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]")
@@ -161,6 +166,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)
@@ -171,7 +186,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.
@@ -259,6 +275,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)
@@ -397,6 +414,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/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/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_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/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/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/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm
index 52d3bc50833e..f96c050afe46 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 (received_pressure_mult > initial_pressure_mult) // higher than usual
+ beginning_text = span_blue("[beginning_text]")
+ 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 (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("[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/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm
index 4e34e729b6cb..00622a8278e9 100644
--- a/code/modules/surgery/organs/internal/stomach/_stomach.dm
+++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm
@@ -245,15 +245,13 @@
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)
- if(ishuman(stomach_owner))
- var/mob/living/carbon/human/human_owner = owner
- human_owner.clear_alert(ALERT_DISGUST)
- human_owner.clear_mood_event("disgust")
+/obj/item/organ/stomach/on_mob_remove(mob/living/carbon/stomach_owner, special, movement_flags)
+ stomach_owner.clear_alert(ALERT_DISGUST)
+ stomach_owner.clear_mood_event("disgust")
stomach_owner.hud_used?.hunger?.update_hunger_bar()
return ..()
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/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 97f5a95a9332..62fbac434973 100644
--- a/code/modules/surgery/organs/organ_movement.dm
+++ b/code/modules/surgery/organs/organ_movement.dm
@@ -21,8 +21,15 @@
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)
+ // 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)
if(!special)
@@ -57,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)
@@ -106,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)
@@ -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..4a647227e068 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,26 +360,24 @@
. += 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)
. = ..()
- 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))
@@ -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/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]
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/unit_tests/omnitools.dm b/code/modules/unit_tests/omnitools.dm
index 9d974138de61..7c8951994122 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
+ TEST_ASSERT_NOTNULL(omnitool, "Could not find /obj/item/borg/cyborg_omnitool/engineering in borg inbuilt modules!")
borg.equip_module_to_slot(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/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 ..()
diff --git a/code/modules/unit_tests/surgeries.dm b/code/modules/unit_tests/surgeries.dm
index 8d2901bf77ff..fd7d9deb2b9e 100644
--- a/code/modules/unit_tests/surgeries.dm
+++ b/code/modules/unit_tests/surgeries.dm
@@ -1,13 +1,13 @@
/datum/unit_test/amputation/Run()
var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent)
var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent)
+ var/obj/item/circular_saw/saw = allocate(/obj/item/circular_saw)
TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 0, "Patient is somehow missing limbs before surgery")
- var/datum/surgery/amputation/surgery = new(patient, BODY_ZONE_R_ARM, patient.get_bodypart(BODY_ZONE_R_ARM))
+ var/datum/surgery_operation/limb/amputate/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__]
- var/datum/surgery_step/sever_limb/sever_limb = new
- sever_limb.success(user, patient, BODY_ZONE_R_ARM, null, surgery)
+ UNLINT(surgery.success(patient.get_bodypart(BODY_ZONE_R_ARM), user, saw, list()))
TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 1, "Patient did not lose any limbs")
TEST_ASSERT_EQUAL(patient.get_missing_limbs()[1], BODY_ZONE_R_ARM, "Patient is missing a limb that isn't the one we operated on")
@@ -20,9 +20,10 @@
TEST_ASSERT(patient.has_trauma_type(), "Patient does not have any traumas, despite being given one")
var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent)
+ var/obj/item/hemostat/hemostat = allocate(/obj/item/hemostat)
- var/datum/surgery_step/fix_brain/fix_brain = new
- fix_brain.success(user, patient)
+ var/datum/surgery_operation/organ/repair/brain/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__]
+ UNLINT(surgery.success(patient.get_organ_slot(ORGAN_SLOT_BRAIN), user, hemostat, list()))
TEST_ASSERT(!patient.has_trauma_type(), "Patient kept their brain trauma after brain surgery")
TEST_ASSERT(patient.get_organ_loss(ORGAN_SLOT_BRAIN) < 20, "Patient did not heal their brain damage after brain surgery")
@@ -56,9 +57,9 @@
TEST_ASSERT_EQUAL(bobs_head.real_name, "Bob", "Bob's head does not remember that it is from Bob")
// Put Bob's head onto Alice's body
- var/datum/surgery_step/add_prosthetic/add_prosthetic = new
+ var/datum/surgery_operation/prosthetic_replacement/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__]
user.put_in_active_hand(bobs_head)
- add_prosthetic.success(user, alice, BODY_ZONE_HEAD, bobs_head)
+ UNLINT(surgery.success(alice.get_bodypart(BODY_ZONE_CHEST), user, bobs_head, list()))
TEST_ASSERT(!isnull(alice.get_bodypart(BODY_ZONE_HEAD)), "Alice has no head after prosthetic replacement")
TEST_ASSERT_EQUAL(alice.get_visible_name(), "Bob", "Bob's head was transplanted onto Alice's body, but their name is not Bob")
@@ -69,56 +70,71 @@
/datum/unit_test/multiple_surgeries/Run()
var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent)
+ 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)
+ 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.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()])")
-
// Test that wearing clothing lowers heal amount
var/mob/living/carbon/human/naked_patient = allocate(/mob/living/carbon/human/consistent)
naked_patient.take_overall_damage(100)
@@ -127,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.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
+
+/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, picker)
+
+ TEST_ASSERT(HAS_TRAIT_FROM(nullrod, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT), "Chainsaw nullrod item attachment failed! Item does not have the nodrop trait")
+
+/// Checks all operations have a name and description
+/datum/unit_test/verify_surgery_setup
+
+/datum/unit_test/verify_surgery_setup/Run()
+ for(var/datum/surgery_operation/operation as anything in GLOB.operations.get_instances_from(subtypesof(/datum/surgery_operation), filter_replaced = FALSE))
+ if (isnull(operation.name))
+ TEST_FAIL("Surgery operation [operation.type] has no name set")
+ if (isnull(operation.desc))
+ TEST_FAIL("Surgery operation [operation.type] has no description set")
+
+/// Checks replaced surgeries are filtered out correctly
+/datum/unit_test/verify_surgery_replacements
+
+/datum/unit_test/verify_surgery_replacements/Run()
+ for(var/datum/surgery_operation/operation as anything in GLOB.operations.get_instances_from(subtypesof(/datum/surgery_operation), filter_replaced = TRUE))
+ if(!operation.replaced_by || operation.replaced_by.type == operation.type)
+ continue
+ TEST_FAIL("Surgery operation [operation.type] is marked as replaced by [operation.replaced_by.type], \
+ but the operation was not correctly filtered by get_instances.")
+
+/// Tests that make incision shows up when expected
+/datum/unit_test/incision_check
+
+/datum/unit_test/incision_check/Run()
+ var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/surgeon = allocate(/mob/living/carbon/human/consistent)
+ var/obj/item/scalpel/scalpel = allocate(/obj/item/scalpel)
+ var/obj/item/bodypart/chest/chest = patient.get_bodypart(BODY_ZONE_CHEST)
+ var/list/operations
+
+ ADD_TRAIT(patient, TRAIT_READY_TO_OPERATE, TRAIT_SOURCE_UNIT_TESTS)
+ ADD_TRAIT(chest, TRAIT_READY_TO_OPERATE, TRAIT_SOURCE_UNIT_TESTS)
+
+ surgeon.put_in_active_hand(scalpel)
+ operations = surgeon.get_available_operations(patient, scalpel, BODY_ZONE_CHEST)
+ TEST_ASSERT_EQUAL(length(operations), 0, "Surgery operations were available on a standing patient")
+
+ patient.set_body_position(LYING_DOWN)
+ operations = surgeon.get_available_operations(patient, scalpel, BODY_ZONE_CHEST)
+ if(length(operations) > 1)
+ TEST_FAIL("More operations than expected were available on the patient")
+ return
+
+ if(length(operations) == 1)
+ var/list/found_operation_data = operations[operations[1]]
+ var/datum/surgery_operation/operation = found_operation_data[1]
+ var/atom/movable/operating_on = found_operation_data[2]
+ TEST_ASSERT_EQUAL(operation.type, /datum/surgery_operation/limb/incise_skin, "The available surgery operation was not \"make incision\"")
+ TEST_ASSERT_EQUAL(operating_on, patient.get_bodypart(BODY_ZONE_CHEST), "The available surgery operation was not on the chest bodypart")
+ return
+
+ TEST_ASSERT_EQUAL(patient.body_position, LYING_DOWN, "Patient is not lying down as expected")
+
+ var/datum/surgery_operation/incise_operation = GLOB.operations.operations_by_typepath[/datum/surgery_operation/limb/incise_skin]
+ var/atom/movable/operate_on = incise_operation.get_operation_target(patient, BODY_ZONE_CHEST)
+ TEST_ASSERT_EQUAL(operate_on, patient.get_bodypart(BODY_ZONE_CHEST), "Incise skin operation did not return the chest bodypart as a valid operation target")
+
+ if(incise_operation.check_availability(patient, operate_on, surgeon, scalpel, BODY_ZONE_CHEST))
+ TEST_FAIL("Make incision operation was not found among available operations despite being available")
+ else
+ TEST_FAIL("Make incision operation was not available when it should have been")
+
+/// Checks is_location_accessible works as intended
+/datum/unit_test/location_accessibility
+
+/datum/unit_test/location_accessibility/Run()
+ var/mob/living/carbon/human/test_mob = allocate(/mob/living/carbon/human/consistent)
+
+ test_mob.equipOutfit(/datum/outfit/job/assistant/consistent)
+ TEST_ASSERT(!test_mob.is_location_accessible(BODY_ZONE_CHEST), "Chest should be inaccessible when wearing a jumpsuit")
+
+ var/obj/item/clothing/under/jumpsuit = test_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING)
+ jumpsuit.adjust_to_alt()
+ TEST_ASSERT(test_mob.is_location_accessible(BODY_ZONE_CHEST), "Chest should be accessible after rolling jumpsuit down")
diff --git a/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"
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 e1c5db84d22a..a7ea0dd9dbf9 100644
Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ
diff --git a/icons/mob/human/bodyparts.dmi b/icons/mob/human/bodyparts.dmi
index d6e4472973a3..78be880423c1 100644
Binary files a/icons/mob/human/bodyparts.dmi and b/icons/mob/human/bodyparts.dmi differ
diff --git a/icons/mob/human/species/ghetto.dmi b/icons/mob/human/species/ghetto.dmi
new file mode 100644
index 000000000000..e11701428ebc
Binary files /dev/null and b/icons/mob/human/species/ghetto.dmi differ
diff --git a/icons/obj/medical/chemical.dmi b/icons/obj/medical/chemical.dmi
index 84dfd01d2d45..4eb73f4ce00b 100644
Binary files a/icons/obj/medical/chemical.dmi and b/icons/obj/medical/chemical.dmi differ
diff --git a/icons/obj/weapons/chainsaw.dmi b/icons/obj/weapons/chainsaw.dmi
index 1d48b63e4594..b2a0385e70b6 100644
Binary files a/icons/obj/weapons/chainsaw.dmi and b/icons/obj/weapons/chainsaw.dmi differ
diff --git a/maplestation.dme b/maplestation.dme
index beceaf9aa32c..2d4edab51ff0 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"
@@ -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"
@@ -1173,6 +1175,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"
@@ -1232,7 +1235,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"
@@ -1323,6 +1325,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"
@@ -1501,6 +1504,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"
@@ -1520,6 +1524,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"
@@ -1705,6 +1710,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"
@@ -1838,6 +1844,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"
@@ -2989,7 +3000,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 +3288,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"
@@ -5595,6 +5604,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"
@@ -5856,57 +5866,11 @@
#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\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"
@@ -5920,6 +5884,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"
@@ -6417,7 +6414,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"
@@ -6770,6 +6766,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/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/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/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/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/antagonists/advanced_ling/changeling_rebalancing.dm b/maplestation_modules/code/modules/antagonists/advanced_ling/changeling_rebalancing.dm
index deffbc8257c5..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,25 +1,15 @@
// -- 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
- dna_cost = -1
+ dna_cost = CHANGELING_POWER_UNOBTAINABLE
// Disables transform sting.
/datum/action/changeling/sting/transformation
- dna_cost = -1
+ 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)
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..e2a5f413bd72 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,68 @@
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 = "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_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_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/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()
+
+/datum/surgery_operation/limb/neuter_ling/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 +88,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 +97,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)
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/maplestation_modules/code/modules/surgery/operations/internal_bleeding.dm b/maplestation_modules/code/modules/surgery/operations/internal_bleeding.dm
new file mode 100644
index 000000000000..b08cd499436c
--- /dev/null
+++ b/maplestation_modules/code/modules/surgery/operations/internal_bleeding.dm
@@ -0,0 +1,72 @@
+/// Repair internal bleeding
+/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_IGNORE_CLOTHES | OPERATION_NOTABLE | OPERATION_LOOPING
+ implements = list(
+ 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,
+ )
+ 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 limb.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(
+ 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 = limb.owner,
+ affected_locations = limb,
+ pain_message = "You feel a horrible stabbing pain in your [limb.plaintext_zone]!",
+ 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)
+ 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(
+ 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]!"),
+ )
+ return
+
+ display_results(
+ 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]!"),
+ )
+ 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
+ target_wound?.severity++
+ display_results(
+ 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]!"),
+ )
+ limb.receive_damage(rand(4, 8), BRUTE, wound_bonus = 10, sharpness = SHARP_EDGED, damage_source = tool)
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
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/strings/tips.txt b/strings/tips.txt
index 8012312c2acd..2da93956ab57 100644
--- a/strings/tips.txt
+++ b/strings/tips.txt
@@ -48,17 +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, 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!
+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 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 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.
@@ -177,6 +186,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 +239,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 +252,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..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"
@@ -1173,6 +1175,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"
@@ -1232,7 +1235,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"
@@ -1323,6 +1325,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"
@@ -1501,6 +1504,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"
@@ -1520,6 +1524,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"
@@ -1705,6 +1710,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"
@@ -1840,6 +1846,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"
@@ -2992,7 +3003,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 +3483,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"
@@ -5602,6 +5613,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"
@@ -5863,57 +5875,11 @@
#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\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"
@@ -5927,6 +5893,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/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/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 (
-
-