From a4f0dcb76fec273c5a6ce8196a0b4597f3af55d6 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Wed, 31 Dec 2025 19:55:39 +0100 Subject: [PATCH] Adds flaying, human butchering and limb surgeries (#94612) ## About The Pull Request Merry Christmas! This PR expands on the more gruesome areas of chef content, such as cannibalism. Human limbs can now be individually butchered for their meat and skin with a knife or another butchering implement once the skin has been cut and retracted (forks now can be used as bootleg retractors). If the limb contains organs, those will need to be removed first, which also can be quickly performed with a butchering tool at the cost of damaging the inhards (head and chest will require the bone to be cut first, which can be done with a butcher's cleaver if you don't have a saw) You can also butcher humans whole by right clicking them with a butchering implement on combat mode, which will attempt to gut/cut apart the selected limb. Obviously this requires the limb to be prepped first, so meat hooks/kitchenspikes now allow mobs strung on them to be operated on without needing drapes. Its a pretty bad spot to be performing surgeries at though, so beware! Torsos can only be butchered after all other limbs have been. The gutting/butchering delay is based off the owner's stat, halving if the limb is detached and doubling if the owner is still alive and kicking. dreamseeker_D7QWbWKStd (The video is too large for github) https://streamable.com/459a0y After being butchered, most limbs will leave behind a nonfunctional skeletal limb (with exceptions like jelly limbs with no skeleton inside) which is always disabled. Skeleton chests husk their owner, so you'll need to replace it first. Gibbers now source their meat from the victim's limbs (and not their species) at an RNG-based percentage based on the matter bin's tier. As to allow individual limbs to be butchered, you can now perform (some) surgeries on individual limbs. There are some obvious restrictions like zombification or bioware surgeries, but most of the ones that make sense can be performed on someone's chopped off head as long as you've got the tools. Wounds also contribute to surgeries now, major slash wounds count as cut/open skin (and sealing the vessels would reduce their bleed rate), and critical blunt wounds count as sawed bones. I've refactored gibbers, butchering and meat spike code as well as fixed some backend bugs while I was at it. ## Why It's Good For The Game While rather gruesome, I feel like the freedom which these mechanics provide fits us pretty well. We already have plenty of nasty stuff and this doesn't really go that much further, and a chef serving their own leg in their dish is pretty funny. (Also you can scrape plasma off plasmamen bones) --- code/__DEFINES/bodyparts.dm | 8 + code/__DEFINES/dcs/signals/signals_medical.dm | 14 +- .../signals/signals_mob/signals_mob_carbon.dm | 3 + .../signals/signals_mob/signals_mob_living.dm | 2 +- code/__DEFINES/jobs.dm | 4 + code/__DEFINES/mobs.dm | 1 + code/__DEFINES/surgery.dm | 7 + code/__DEFINES/traits/declarations.dm | 4 +- code/__DEFINES/traits/sources.dm | 6 + code/_globalvars/traits/_traits.dm | 2 +- code/_globalvars/traits/admin_tooling.dm | 4 +- code/datums/components/bayonet_attachable.dm | 3 +- code/datums/components/butchering.dm | 518 ++++++++++++++---- code/datums/components/free_operation.dm | 33 ++ code/datums/elements/corrupted_organ.dm | 2 +- .../status_effects/buffs/bioware/cortex.dm | 12 +- code/datums/wounds/_wounds.dm | 43 +- code/datums/wounds/bones.dm | 1 + code/datums/wounds/cranial_fissure.dm | 1 + code/datums/wounds/pierce.dm | 1 + code/datums/wounds/slash.dm | 2 + code/game/objects/buckling.dm | 4 +- code/game/objects/items.dm | 1 - .../items/devices/scanners/autopsy_scanner.dm | 2 + .../items/devices/scanners/health_analyzer.dm | 2 + code/game/objects/items/knives.dm | 4 +- code/game/objects/structures/kitchen_spike.dm | 23 +- code/game/objects/structures/tables_racks.dm | 8 +- code/modules/antagonists/abductor/abductor.dm | 2 +- .../food_and_drinks/machinery/gibber.dm | 222 ++++---- .../jobs/job_types/head_of_security.dm | 15 +- .../jobs/job_types/security_officer.dm | 14 + code/modules/jobs/job_types/warden.dm | 15 + code/modules/mob/living/brain/brain_item.dm | 2 +- code/modules/mob/living/living.dm | 3 + code/modules/surgery/bodyparts/_bodyparts.dm | 152 +++-- .../modules/surgery/bodyparts/ghetto_parts.dm | 4 + code/modules/surgery/bodyparts/head.dm | 10 + code/modules/surgery/bodyparts/parts.dm | 113 ++-- .../surgery/bodyparts/robot_bodyparts.dm | 6 + .../species_parts/ghost_bodyparts.dm | 4 + .../bodyparts/species_parts/misc_bodyparts.dm | 59 ++ code/modules/surgery/operations/_operation.dm | 205 ++++--- .../surgery/operations/operation_add_limb.dm | 8 +- .../operations/operation_amputation.dm | 3 +- .../surgery/operations/operation_bioware.dm | 25 +- .../operations/operation_bone_repair.dm | 67 +-- .../surgery/operations/operation_brainwash.dm | 2 +- .../operations/operation_cavity_implant.dm | 36 +- .../surgery/operations/operation_debride.dm | 20 +- .../surgery/operations/operation_dental.dm | 38 +- .../surgery/operations/operation_filter.dm | 3 +- .../surgery/operations/operation_generic.dm | 97 ++-- .../operations/operation_generic_mechanic.dm | 48 +- .../operations/operation_implant_removal.dm | 3 +- .../surgery/operations/operation_lipo.dm | 3 +- .../surgery/operations/operation_lobotomy.dm | 40 +- .../operations/operation_organ_manip.dm | 53 +- .../operations/operation_organ_repair.dm | 189 ++++--- .../surgery/operations/operation_pacify.dm | 21 +- .../operations/operation_plastic_surgery.dm | 2 +- .../surgery/operations/operation_puncture.dm | 46 +- .../operations/operation_replace_limb.dm | 7 + .../operations/operation_wing_repair.dm | 3 +- .../surgery/operations/operation_zombie.dm | 3 +- .../internal/cyberimp/augments_internal.dm | 2 +- code/modules/surgery/organs/organ_movement.dm | 12 +- code/modules/surgery/surgery_tools.dm | 2 +- icons/mob/human/bodyparts.dmi | Bin 52823 -> 49075 bytes tgstation.dme | 1 + 70 files changed, 1515 insertions(+), 760 deletions(-) create mode 100644 code/datums/components/free_operation.dm diff --git a/code/__DEFINES/bodyparts.dm b/code/__DEFINES/bodyparts.dm index 2f396cfbabc..ffeff072f45 100644 --- a/code/__DEFINES/bodyparts.dm +++ b/code/__DEFINES/bodyparts.dm @@ -67,6 +67,9 @@ #define BODYPART_OVERLAY_CSS_SUICIDE 3 #define BODYPART_OVERLAY_VOIDWALKER_CURSE 4 +/// This limb cannot be disabled via damage thresholds +#define LIMB_NO_DISABLE -1 + // Bodypart surgery state /// An incision has been made into the skin #define SURGERY_SKIN_CUT (1<<0) @@ -174,3 +177,8 @@ DEFINE_BITFIELD(surgery_state, list( #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) + +/// How much blood is lost from unclamped vessels? +#define UNCLAMPED_VESSELS_BLEEDING 1.5 +/// How much blood is lost from clamped vessels or cut organs? +#define CLAMPED_VESSELS_BLEEDING 0.2 diff --git a/code/__DEFINES/dcs/signals/signals_medical.dm b/code/__DEFINES/dcs/signals/signals_medical.dm index de3814adb4a..4f79b79ddd7 100644 --- a/code/__DEFINES/dcs/signals/signals_medical.dm +++ b/code/__DEFINES/dcs/signals/signals_medical.dm @@ -1,11 +1,11 @@ /// From /datum/surgery_operation/try_perform(): (datum/surgery_operation/operation, atom/movable/operating_on, tool) -#define COMSIG_LIVING_SURGERY_STARTED "mob_surgery_started" +#define COMSIG_ATOM_SURGERY_STARTED "atom_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" +#define COMSIG_ATOM_SURGERY_FINISHED "atom_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" +#define COMSIG_ATOM_SURGERY_SUCCESS "atom_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" +#define COMSIG_ATOM_SURGERY_FAILED "atom_surgery_step_failed" /// From /obj/item/shockpaddles/do_help, after the defib do_after is complete, but before any effects are applied: (mob/living/defibber, obj/item/shockpaddles/source) #define COMSIG_DEFIBRILLATOR_PRE_HELP_ZAP "carbon_being_defibbed" @@ -19,7 +19,7 @@ /// From /obj/item/shockpaddles/proc/do_disarm(), sent to the shock-ee in non-revival scenarios: (obj/item/shockpaddles/source) #define COMSIG_HEARTATTACK_DEFIB "heartattack_defib" -/// Sent from /mob/living/perform_surgery: (mob/living/patient, list/possible_operations) +/// Sent from /mob/living/perform_surgery: (atom/movable/operating_on, 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" +/// Sent from /mob/living/perform_surgery: (mob/living/surgeon, list/possible_operations) +#define COMSIG_ATOM_BEING_OPERATED_ON "atom_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 03f8a044085..a463510a438 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -60,6 +60,9 @@ /// Called from bodypart being removed /obj/item/bodypart/proc/drop_limb(mob/living/carbon/old_owner, special, dismembered) #define COMSIG_BODYPART_REMOVED "bodypart_removed" +/// Sent to a limb when something *attempts* to change its surgery state (old_state, new_state, changed_states) +#define COMSIG_BODYPART_UPDATING_SURGERY_STATE "bodypart_updating_surgery_state" + ///from /item/organ/proc/Insert() (/obj/item/organ/) #define COMSIG_CARBON_GAIN_ORGAN "carbon_gain_organ" ///from /item/organ/proc/Remove() (/obj/item/organ/) 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 86bfadb0428..e6d71fefae7 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -318,7 +318,7 @@ /// From /obj/item/melee/baton/baton_effect(): (datum/source, mob/living/user, /obj/item/melee/baton) #define COMSIG_MOB_BATONED "mob_batoned" -/// From /obj/machinery/gibber/startgibbing(): (mob/living/user, /obj/machinery/gibber, list/results) +/// From /obj/machinery/gibber/start_gibbing(): (mob/living/user, /obj/machinery/gibber, list/results) #define COMSIG_LIVING_GIBBER_ACT "living_gibber_act" /// From /mob/living/get_eye_protection() (list/reflist) diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 58ffdc916ed..ad7f6bba889 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -287,3 +287,7 @@ DEFINE_BITFIELD(job_flags, list( #define HEAD_OF_STAFF_MIND_TRAITS TRAIT_FAST_TYING, TRAIT_HIGH_VALUE_RANSOM #define MEDICAL_MIND_TRAITS TRAIT_DESENSITIZED #define SECURITY_MIND_TRAITS TRAIT_DESENSITIZED + +/// Statistically average probability of a random cop or cop-adjacent person consisting of meat of sus domesticus of suidae family, +/// also known as swine or hogs, calculated by the university of random numbers +#define PIG_COP_PROBABILITY 40 diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index b2281a93e4e..c76410bb289 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -209,6 +209,7 @@ #define BODYPART_ID_PSYKER "psyker" #define BODYPART_ID_MEAT "meat" #define BODYPART_ID_PEG "peg" +#define BODYPART_ID_BONE "bone" //See: datum/species/var/digitigrade_customization diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm index dc54c139dba..1a7945b2bf7 100644 --- a/code/__DEFINES/surgery.dm +++ b/code/__DEFINES/surgery.dm @@ -117,6 +117,8 @@ #define OPERATION_MECHANIC (1<<10) /// Hides the operation from autowiki generation #define OPERATION_NO_WIKI (1<<11) +/// This operation can be performed on a detached limb +#define OPERATION_NO_PATIENT_REQUIRED (1<<12) DEFINE_BITFIELD(operation_flags, list( "AFFECTS MOOD" = OPERATION_AFFECTS_MOOD, @@ -170,3 +172,8 @@ DEFINE_BITFIELD(operation_flags, list( #define OPERATION_BURN_HEAL "burn_heal" #define OPERATION_BRUTE_MULTIPLIER "brute_multiplier" #define OPERATION_BURN_MULTIPLIER "burn_multiplier" + +/// Used in string formatting to print a limb as "John's right arm" or "the human right arm" +#define FORMAT_LIMB_OWNER(limb) (limb.owner ? "[limb.owner]'s [limb.plaintext_zone]" : limb) +/// Used in string formatting to print an organ's location as "John" or "the human chest" +#define FORMAT_ORGAN_OWNER(organ) (organ.owner || organ.loc) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index a3f7c0738b4..33510904c75 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -470,8 +470,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_PERCEPTUAL_TRAUMA_BYPASS "trauma_bypass" /// mob is immune to hallucinations #define TRAIT_HALLUCINATION_IMMUNE "hallucination_immune" -/// Increases chance of getting special traumas, makes them harder to cure -#define TRAIT_SPECIAL_TRAUMA_BOOST "special_trauma_boost" //---- Brain trauma resists /// Unable to gain any brain trauma whatsoever @@ -977,6 +975,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_DUCT_TAPE_UNREPAIRABLE "duct_tape_unrepairable" /// An item is ALWAYS considered baseline reachable and will pipe into CanBeReached(). #define TRAIT_SKIP_BASIC_REACH_CHECK "skip_basic_reach_check" +/// Increases chance of this brain getting special traumas, makes them harder to cure +#define TRAIT_SPECIAL_TRAUMA_BOOST "special_trauma_boost" //---- Heretic Traits /// Hides the heretic overlay that outs them as the heretic diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index f68a242e565..153e7c389f0 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -329,3 +329,9 @@ /// Permanent trait from an overdose effect #define OVERDOSE_TRAIT "overdose" + +/// Trait gained from bioware surgeries +#define BIOWARE_TRAIT "bioware" + +/// Trait gained from skeleton "limbs", such as husking from a butchered chest +#define SKELETON_TRAIT "skeleton" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 2bf0398d272..f099ddc3587 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -554,7 +554,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_SPARRING" = TRAIT_SPARRING, "TRAIT_SPAWNED_MOB" = TRAIT_SPAWNED_MOB, "TRAIT_SPEAKS_CLEARLY" = TRAIT_SPEAKS_CLEARLY, - "TRAIT_SPECIAL_TRAUMA_BOOST" = TRAIT_SPECIAL_TRAUMA_BOOST, "TRAIT_SPELLS_LOTTERY" = TRAIT_SPELLS_LOTTERY, "TRAIT_SPIDER_CONSUMED" = TRAIT_SPIDER_CONSUMED, "TRAIT_SPIRITUAL" = TRAIT_SPIRITUAL, @@ -781,6 +780,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( ), /obj/item/organ/brain = list( "TRAIT_BRAIN_DAMAGE_NODEATH" = TRAIT_BRAIN_DAMAGE_NODEATH, + "TRAIT_SPECIAL_TRAUMA_BOOST" = TRAIT_SPECIAL_TRAUMA_BOOST, ), /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 5fe6c87c6fa..748e64ef643 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -290,7 +290,6 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_SOOTHED_THROAT" = TRAIT_SOOTHED_THROAT, "TRAIT_SOOTHED_HEADACHE" = TRAIT_SOOTHED_HEADACHE, "TRAIT_SPACEWALK" = TRAIT_SPACEWALK, - "TRAIT_SPECIAL_TRAUMA_BOOST" = TRAIT_SPECIAL_TRAUMA_BOOST, "TRAIT_SPIRITUAL" = TRAIT_SPIRITUAL, "TRAIT_STABLEHEART" = TRAIT_STABLEHEART, "TRAIT_STABLELIVER" = TRAIT_STABLELIVER, @@ -406,6 +405,9 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_LIVING_HEART" = TRAIT_LIVING_HEART, "TRAIT_USED_ORGAN" = TRAIT_USED_ORGAN, ), + /obj/item/organ/brain = list( + "TRAIT_SPECIAL_TRAUMA_BOOST" = TRAIT_SPECIAL_TRAUMA_BOOST, + ), /obj/item/organ/liver = list( "TRAIT_BALLMER_SCIENTIST" = TRAIT_BALLMER_SCIENTIST, "TRAIT_COMEDY_METABOLISM" = TRAIT_COMEDY_METABOLISM, diff --git a/code/datums/components/bayonet_attachable.dm b/code/datums/components/bayonet_attachable.dm index 9841185ea7f..8da8d61aac3 100644 --- a/code/datums/components/bayonet_attachable.dm +++ b/code/datums/components/bayonet_attachable.dm @@ -100,7 +100,8 @@ if (isnull(bayonet) || !user.combat_mode) return NONE - INVOKE_ASYNC(bayonet, TYPE_PROC_REF(/obj/item, melee_attack_chain), user, target, modifiers) + // RMB is used for butchering, and bayonets are usually are knives + INVOKE_ASYNC(bayonet, TYPE_PROC_REF(/obj/item, melee_attack_chain), user, target, modifiers - RIGHT_CLICK) return COMPONENT_CANCEL_ATTACK_CHAIN /datum/component/bayonet_attachable/proc/on_attackby(obj/item/source, obj/item/attacking_item, mob/attacker, list/modifiers) diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm index 1f432efc734..dd14cd4dcde 100644 --- a/code/datums/components/butchering.dm +++ b/code/datums/components/butchering.dm @@ -27,72 +27,357 @@ src.effectiveness = effectiveness src.bonus_modifier = bonus_modifier src.butcher_sound = butcher_sound - if(disabled) + if (disabled) src.butchering_enabled = FALSE src.can_be_blunt = can_be_blunt src.butcher_callback = butcher_callback - if(isitem(parent)) - RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(onItemAttack)) /datum/component/butchering/Destroy(force) butcher_callback = null return ..() -/datum/component/butchering/proc/onItemAttack(obj/item/source, mob/living/M, mob/living/user) +/datum/component/butchering/RegisterWithParent() + if (!isitem(parent)) + return + var/obj/item/item_parent = parent + item_parent.item_flags |= ITEM_HAS_CONTEXTUAL_SCREENTIPS + RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(on_item_attack)) + RegisterSignal(parent, COMSIG_ITEM_INTERACTING_WITH_ATOM, PROC_REF(on_item_interaction)) + RegisterSignal(parent, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, PROC_REF(add_item_context)) + +/datum/component/butchering/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK, COMSIG_ITEM_INTERACTING_WITH_ATOM)) + +/datum/component/butchering/proc/on_item_attack(obj/item/source, mob/living/victim, mob/living/user, list/modifiers) SIGNAL_HANDLER - if(!user.combat_mode) - return - if(M.stat == DEAD && (M.butcher_results || M.guaranteed_butcher_results)) //can we butcher it? - if(butchering_enabled && (can_be_blunt || source.get_sharpness())) - INVOKE_ASYNC(src, PROC_REF(startButcher), source, M, user) - return COMPONENT_CANCEL_ATTACK_CHAIN - - if(ishuman(M) && source.force && source.get_sharpness()) - var/mob/living/carbon/human/H = M - if((user.pulling == H && user.grab_state >= GRAB_AGGRESSIVE) && user.zone_selected == BODY_ZONE_HEAD) // Only aggressive grabbed can be sliced. - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, span_warning("You don't want to harm other living beings!")) - return COMPONENT_CANCEL_ATTACK_CHAIN - - if(H.has_status_effect(/datum/status_effect/neck_slice)) - return - - INVOKE_ASYNC(src, PROC_REF(startNeckSlice), source, H, user) - return COMPONENT_CANCEL_ATTACK_CHAIN - -/datum/component/butchering/proc/startButcher(obj/item/source, mob/living/M, mob/living/user) - to_chat(user, span_notice("You begin to butcher [M]...")) - playsound(M.loc, butcher_sound, 50, TRUE, -1) - if(do_after(user, speed, M) && M.Adjacent(source)) - on_butchering(user, M) - -/datum/component/butchering/proc/startNeckSlice(obj/item/source, mob/living/carbon/human/H, mob/living/user) - if(DOING_INTERACTION_WITH_TARGET(user, H)) - to_chat(user, span_warning("You're already interacting with [H]!")) + if (!source.get_sharpness() && !can_be_blunt) return - user.visible_message(span_danger("[user] is slitting [H]'s throat!"), \ - span_danger("You start slicing [H]'s throat!"), \ - span_hear("You hear a cutting noise!"), ignored_mobs = H) - H.show_message(span_userdanger("Your throat is being slit by [user]!"), MSG_VISUAL, \ - span_userdanger("Something is cutting into your neck!"), NONE) - log_combat(user, H, "attempted throat slitting", source) + if (!user.combat_mode) + return - playsound(H.loc, butcher_sound, 50, TRUE, -1) - if(do_after(user, clamp(500 / source.force, 30, 100), H) && H.Adjacent(source)) - if(H.has_status_effect(/datum/status_effect/neck_slice)) - user.show_message(span_warning("[H]'s neck has already been already cut, you can't make the bleeding any worse!"), MSG_VISUAL, \ - span_warning("Their neck has already been already cut, you can't make the bleeding any worse!")) + // Can we butcher it? + if (victim.stat == DEAD && (victim.butcher_results || victim.guaranteed_butcher_results) && butchering_enabled) + INVOKE_ASYNC(src, PROC_REF(start_butcher), source, victim, user) + return COMPONENT_CANCEL_ATTACK_CHAIN + + if (!ishuman(victim) || !source.force) + return + + if (LAZYACCESS(modifiers, RIGHT_CLICK) && butchering_enabled) + INVOKE_ASYNC(src, PROC_REF(butcher_human), source, victim, user) + return COMPONENT_CANCEL_ATTACK_CHAIN + + // Neckslicing requires aggro grabs + if (user.pulling != victim || user.grab_state < GRAB_AGGRESSIVE || user.zone_selected != BODY_ZONE_HEAD) + return + + if (HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, span_warning("You don't want to harm other living beings!")) + return COMPONENT_CANCEL_ATTACK_CHAIN + + if (victim.has_status_effect(/datum/status_effect/neck_slice)) + return + + INVOKE_ASYNC(src, PROC_REF(start_neck_slice), source, victim, user) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/datum/component/butchering/proc/on_item_interaction(obj/item/source, mob/living/user, atom/target) + SIGNAL_HANDLER + + if (!source.get_sharpness() && !can_be_blunt) + return + + if (!user.combat_mode || !butchering_enabled) + return + + if (isbodypart(target)) + INVOKE_ASYNC(src, PROC_REF(butcher_limb), source, target, user) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/datum/component/butchering/proc/add_item_context(obj/item/source, list/context, atom/target, mob/living/user) + SIGNAL_HANDLER + + if (!source.get_sharpness() && !can_be_blunt) + return NONE + + if (!butchering_enabled) + return NONE + + if (isbodypart(target)) + context[SCREENTIP_CONTEXT_LMB] = "(Combat Mode) Butcher limb" + return CONTEXTUAL_SCREENTIP_SET + + if (!isliving(target)) + return NONE + + var/mob/living/victim = target + if (victim.stat != DEAD && (victim.butcher_results || victim.guaranteed_butcher_results)) + context[SCREENTIP_CONTEXT_LMB] = "(Combat Mode) Butcher" + return CONTEXTUAL_SCREENTIP_SET + + if (ishuman(victim)) + context[SCREENTIP_CONTEXT_RMB] = "(Combat Mode) Butcher" + return CONTEXTUAL_SCREENTIP_SET + + return NONE + +/datum/component/butchering/proc/butcher_limb(obj/item/source, obj/item/bodypart/target, mob/living/user) + target.add_fingerprint(user) + if (LIMB_HAS_SKIN(target) && !HAS_ANY_SURGERY_STATE(target.surgery_state, SURGERY_SKIN_CUT | SURGERY_SKIN_OPEN)) + to_chat(user, span_warning("[target]'s skin is still intact!")) + return + + if (LIMB_HAS_BONES(target) && !HAS_ANY_SURGERY_STATE(target.surgery_state, SURGERY_BONE_DRILLED | SURGERY_BONE_SAWED)) + // We need to gut the limb before turning it into meat, otherwise just cut around the bone I guess + if (length(target.contents)) + to_chat(user, span_warning("[target]'s bones are still intact!")) return - H.visible_message(span_danger("[user] slits [H]'s throat!"), \ - span_userdanger("[user] slits your throat...")) - log_combat(user, H, "wounded via throat slitting", source) - H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD, wound_bonus=CANT_WOUND) // easy tiger, we'll get to that in a sec - var/obj/item/bodypart/slit_throat = H.get_bodypart(BODY_ZONE_HEAD) - if (H.cause_wound_of_type_and_severity(WOUND_SLASH, slit_throat, WOUND_SEVERITY_CRITICAL)) - H.apply_status_effect(/datum/status_effect/neck_slice) + var/speed_modifier = 1 + if (!target.owner) + speed_modifier = 0.5 + else if (target.owner.stat < UNCONSCIOUS) + speed_modifier = 1.5 // yeowch + + var/limb_descriptor = (target.owner ? "[target.owner]'s [target.plaintext_zone]" : target) + // Okay *hopefully* they're already dead at this point + if (target.body_zone == BODY_ZONE_CHEST && target.owner) + // Butchering the chest gibs the victim + limb_descriptor = target.owner + + if (target.owner) + log_combat(user, target.owner, "attempted to butcher", source) + + if (length(target.contents)) + user.visible_message(span_warning("[user] begins to gut [limb_descriptor]!"), span_notice("You begin to gut [limb_descriptor]..."), ignored_mobs = target.owner) + if (target.owner) + to_chat(target.owner, span_warning("[user] begins to gut your [target.plaintext_zone]!")) + + playsound(target.loc, butcher_sound, 50, TRUE, -1) + if (!do_after(user, speed * speed_modifier, target.owner || target)) + return + target.drop_organs(user, TRUE) + return + + if (!length(target.butcher_drops)) + to_chat(user, span_warning("There is nothing left inside [limb_descriptor]!")) + return + + if (target.body_zone == BODY_ZONE_CHEST && target.owner) + // Cannot butcher the chest until we hack off all the other limbs + for (var/obj/item/bodypart/limb as anything in target.owner.bodyparts) + if (limb != target && limb.butcher_drops && limb.butcher_replacement) + to_chat(user, span_warning("You need to butcher all other limbs first!")) + return + + user.visible_message(span_warning("[user] begins to cut [limb_descriptor] apart!"), span_notice("You begin to cut [limb_descriptor] apart..."), ignored_mobs = target.owner) + if (target.owner) + to_chat(target.owner, span_warning("[user] begins to cut your [target.plaintext_zone] apart!")) + + playsound(target.loc, butcher_sound, 50, TRUE, -1) + if (!do_after(user, speed * speed_modifier, target.owner || target)) + return + + var/list/results = list() + var/turf/drop_loc = target.owner?.drop_location() || target.drop_location() + var/bonus_chance = max(0, (effectiveness - 100) + bonus_modifier) //so 125 total effectiveness = 25% extra chance + + var/list/failures = list() + var/list/bonuses = list() + + for (var/obj/item/drop_type as anything in target.butcher_drops) + var/amount = target.butcher_drops[drop_type] || 1 + var/is_stack = ispath(drop_type, /obj/item/stack) + + for (var/i in 1 to amount) + if (!prob(effectiveness)) + failures |= drop_type::name + amount -= 1 + continue + + if (prob(bonus_chance)) + if (!is_stack) + if (ispath(drop_type, /obj/item/food/meat)) + results += new drop_type(drop_loc, target.blood_dna_info?.Copy()) + else + var/obj/item/butcher_result = new drop_type(drop_loc) + if (target.blood_dna_info) + butcher_result.add_blood_DNA(target.blood_dna_info.Copy()) + results += butcher_result + amount += 1 + bonuses |= drop_type::name + + if (is_stack) + continue + + if (ispath(drop_type, /obj/item/food/meat)) + results += new drop_type(drop_loc, target.blood_dna_info?.Copy()) + continue + + var/obj/item/butcher_result = new drop_type(drop_loc) + butcher_result.add_blood_DNA(target.blood_dna_info.Copy()) + results += butcher_result + + if (is_stack && amount) + var/obj/item/stack/butcher_result = new drop_type(drop_loc, amount) + if (target.blood_dna_info) + butcher_result.add_blood_DNA(target.blood_dna_info.Copy()) + results += butcher_result + + if (target.reagents) + var/meat_produced = 0 + for (var/obj/item/food/target_meat in results) + meat_produced += 1 + + for (var/obj/item/food/target_meat in results) + target.reagents.trans_to(target_meat, target.reagents.total_volume / meat_produced, remove_blacklisted = TRUE) + + if (target.owner) + var/reagents_in_produced = 0 + for(var/obj/item/result as anything in results) + if(result.reagents) + reagents_in_produced += 1 + + var/list/diseases = target.owner.get_static_viruses() + + for(var/obj/item/result as anything in results) + if (reagents_in_produced) + if (target.owner.reagents) + target.owner.reagents.trans_to(result, target.owner.reagents.total_volume / reagents_in_produced / length(target.owner.bodyparts), remove_blacklisted = TRUE) + result.reagents?.add_reagent(/datum/reagent/consumable/nutriment/fat, target.owner.nutrition / 15 / reagents_in_produced) + + if(LAZYLEN(diseases)) + var/list/datum/disease/diseases_to_add = list() + for(var/datum/disease/disease as anything in diseases) + // Admin or special viruses that should not be reproduced + if(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS)) + continue + + diseases_to_add += disease + + if(LAZYLEN(diseases_to_add)) + result.AddComponent(/datum/component/infective, diseases_to_add) + + for (var/obj/item/food/meat/meat in results) + meat.name = "[target.owner.real_name]'s [meat.name]" + meat.set_custom_materials(list(GET_MATERIAL_REF(/datum/material/meat/mob_meat, target.owner) = 4 * SHEET_MATERIAL_AMOUNT)) + meat.subjectname = target.owner.real_name + meat.subjectjob = target.owner.job + + user.visible_message(span_warning("[user] butchers [limb_descriptor]!"), span_notice("You butcher [limb_descriptor]."), ignored_mobs = target.owner) + if (!target.owner) + target.drop_organs(violent_removal = TRUE) // Should not happen, but just in case + create_replacement_limb(target, drop_loc) + qdel(target) + return + + var/wound_type = null + if (source.damtype == BURN) + wound_type = WOUND_BURN + else + switch (source.get_sharpness()) + if (SHARP_EDGED) + wound_type = WOUND_SLASH + if (SHARP_POINTY) + wound_type = WOUND_PIERCE + else + wound_type = WOUND_BLUNT + + to_chat(target.owner, span_userdanger("[user] hacks the meat off your [target.plaintext_zone]!")) + var/mob/living/carbon/victim = target.owner + + if (!target.butcher_replacement) + target.dismember(source.damtype, wound_type) + target.drop_organs(violent_removal = TRUE) // Should not happen, but just in case + qdel(target) + return + + var/obj/item/bodypart/replacement = create_replacement_limb(target, drop_loc) + target.dismember(source.damtype, wound_type) + target.drop_organs(violent_removal = TRUE) + replacement.replace_limb(victim) + replacement.update_limb(is_creating = TRUE) + qdel(target) + +/// Creates a replacement (usually skeleton) limb for the butchered one +/datum/component/butchering/proc/create_replacement_limb(obj/item/bodypart/target, drop_loc) + var/drop_type = target.butcher_replacement + var/obj/item/bodypart/replacement = new drop_type(drop_loc) + replacement.set_initial_damage(target.brute_dam, target.burn_dam) + if (IS_ORGANIC_LIMB(replacement) && target.owner) + replacement.blood_dna_info = target.owner.get_blood_dna_list() + + for (var/datum/wound/wound as anything in target.wounds) + wound.remove_wound() + wound.apply_wound(replacement, silent = TRUE) + + return replacement + +/datum/component/butchering/proc/start_butcher(obj/item/source, mob/living/target, mob/living/user) + to_chat(user, span_notice("You begin to butcher [target]...")) + playsound(target.loc, butcher_sound, 50, TRUE, -1) + if (do_after(user, speed, target) && target.Adjacent(source)) + on_butchering(user, target) + +/datum/component/butchering/proc/butcher_human(obj/item/source, mob/living/carbon/human/victim, mob/living/user) + if (DOING_INTERACTION_WITH_TARGET(user, victim)) + to_chat(user, span_warning("You're already interacting with [victim]!")) + return + + var/static/list/butcher_spots = typecacheof(list( + /obj/structure/table, + /obj/structure/bed, + /obj/machinery/stasis, + /obj/structure/kitchenspike, + )) + + var/found_spot = FALSE + for (var/obj/thing in victim.loc) + if (is_type_in_typecache(thing, butcher_spots)) + found_spot = TRUE + break + + if (!found_spot) + to_chat(user, span_warning("You need a better spot to butcher [victim]!")) + return + + var/obj/item/bodypart/limb = victim.get_bodypart(deprecise_zone(user.zone_selected)) + if (!limb) + to_chat(user, span_warning("[victim] doesn't have a [parse_zone(deprecise_zone(user.zone_selected))]!")) + return + + butcher_limb(source, limb, user) + +/datum/component/butchering/proc/start_neck_slice(obj/item/source, mob/living/carbon/human/victim, mob/living/user) + if (DOING_INTERACTION_WITH_TARGET(user, victim)) + to_chat(user, span_warning("You're already interacting with [victim]!")) + return + + user.visible_message(span_danger("[user] is slitting [victim]'s throat!"), \ + span_danger("You start slicing [victim]'s throat!"), \ + span_hear("You hear a cutting noise!"), ignored_mobs = victim) + victim.show_message(span_userdanger("Your throat is being slit by [user]!"), MSG_VISUAL, \ + span_userdanger("Something is cutting into your neck!"), NONE) + log_combat(user, victim, "attempted throat slitting", source) + + playsound(victim.loc, butcher_sound, 50, TRUE, -1) + if (!do_after(user, clamp(500 / source.force, 30, 100), victim) && victim.Adjacent(source)) + return + + if (victim.has_status_effect(/datum/status_effect/neck_slice)) + user.show_message(span_warning("[victim]'s neck has already been already cut, you can't make the bleeding any worse!"), MSG_VISUAL, \ + span_warning("Their neck has already been already cut, you can't make the bleeding any worse!")) + return + + victim.visible_message(span_danger("[user] slits [victim]'s throat!"), \ + span_userdanger("[user] slits your throat...")) + log_combat(user, victim, "wounded via throat slitting", source) + victim.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD, wound_bonus=CANT_WOUND) // easy tiger, we'll get to that in a sec + var/obj/item/bodypart/slit_throat = victim.get_bodypart(BODY_ZONE_HEAD) + if (victim.cause_wound_of_type_and_severity(WOUND_SLASH, slit_throat, WOUND_SEVERITY_CRITICAL)) + victim.apply_status_effect(/datum/status_effect/neck_slice) /** * Handles a user butchering a target @@ -107,67 +392,85 @@ var/final_effectiveness = effectiveness - target.butcher_difficulty var/bonus_chance = max(0, (final_effectiveness - 100) + bonus_modifier) //so 125 total effectiveness = 25% extra chance - if(target.flags_1 & HOLOGRAM_1) + if (target.flags_1 & HOLOGRAM_1) butcher.visible_message(span_notice("[butcher] tries to butcher [target], but it vanishes."), \ span_notice("You try to butcher [target], but it vanishes.")) qdel(target) return - for(var/result_typepath in target.butcher_results) - var/obj/remains = result_typepath - var/amount = target.butcher_results[remains] - for(var/_i in 1 to amount) - if(!prob(final_effectiveness)) - if(butcher) - to_chat(butcher, span_warning("You fail to harvest some of the [initial(remains.name)] from [target].")) + var/list/failures = list() + var/list/bonuses = list() + for (var/obj/remains as anything in target.butcher_results) + var/amount = target.butcher_results[remains] || 1 + var/is_stack = ispath(remains, /obj/item/stack) + + for (var/i in 1 to amount) + if (!prob(final_effectiveness)) + failures |= remains::name + amount -= 1 continue - if(prob(bonus_chance)) - if(butcher) - to_chat(butcher, span_info("You harvest some extra [initial(remains.name)] from [target]!")) - results += new remains (location) - results += new remains (location) + if (prob(bonus_chance)) + if (!is_stack) + results += new remains(location) + amount += 1 + bonuses |= remains::name - target.butcher_results.Remove(remains) //in case you want to, say, have it drop its results on gib + if (!is_stack) + results += new remains(location) - for(var/guaranteed_result_typepath in target.guaranteed_butcher_results) - var/obj/guaranteed_remains = guaranteed_result_typepath + if (is_stack && amount) + results += new remains(location, amount) + + target.butcher_results?.Cut() + + if (butcher) + if (length(failures)) + to_chat(butcher, span_warning("You fail to harvest some of the [english_list(failures)] from [target].")) + if (length(bonuses)) + to_chat(butcher, span_info("You harvest some extra [english_list(bonuses)] from [target]!")) + + for (var/obj/guaranteed_remains as anything in target.guaranteed_butcher_results) var/amount = target.guaranteed_butcher_results[guaranteed_remains] - for(var/i in 1 to amount) - results += new guaranteed_remains (location) - target.guaranteed_butcher_results.Remove(guaranteed_remains) + if (ispath(guaranteed_remains, /obj/item/stack)) + results += new guaranteed_remains(location, amount) + continue - for(var/obj/item/carrion in results) + for (var/i in 1 to amount) + results += new guaranteed_remains(location) + + target.guaranteed_butcher_results?.Cut() + + for (var/obj/item/carrion in results) var/list/meat_mats = carrion.has_material_type(/datum/material/meat) - if(!length(meat_mats)) + if (!length(meat_mats)) continue carrion.set_custom_materials((carrion.custom_materials - meat_mats) + list(GET_MATERIAL_REF(/datum/material/meat/mob_meat, target) = counterlist_sum(meat_mats))) - // transfer delicious reagents to meat - if(target.reagents) + // Transfer delicious reagents to meat + if (target.reagents) var/meat_produced = 0 - for(var/obj/item/food/meat/slab/target_meat in results) + for (var/obj/item/food/target_meat in results) meat_produced += 1 - for(var/obj/item/food/meat/slab/target_meat in results) + for (var/obj/item/food/target_meat in results) target.reagents.trans_to(target_meat, target.reagents.total_volume / meat_produced, remove_blacklisted = TRUE) - // dont forget yummy diseases either! - if(iscarbon(target)) + // Don't forget yummy diseases either! + if (iscarbon(target)) var/mob/living/carbon/host_target = target var/list/diseases = host_target.get_static_viruses() - if(LAZYLEN(diseases)) + if (LAZYLEN(diseases)) var/list/datum/disease/diseases_to_add = list() - for(var/datum/disease/disease as anything in diseases) + for (var/datum/disease/disease as anything in diseases) // admin or special viruses that should not be reproduced - if(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS)) - continue + if (!(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))) + diseases_to_add += disease - diseases_to_add += disease - if(LAZYLEN(diseases_to_add)) - for(var/obj/diseased_remains in results) + if (LAZYLEN(diseases_to_add)) + for (var/obj/diseased_remains in results) diseased_remains.AddComponent(/datum/component/infective, diseases_to_add) - if(butcher) + if (butcher) butcher.visible_message(span_notice("[butcher] butchers [target]."), \ span_notice("You butcher [target].")) butcher_callback?.Invoke(butcher, target) @@ -175,20 +478,9 @@ target.log_message("has been butchered by [key_name(butcher)]", LOG_ATTACK) target.gib(DROP_BRAIN|DROP_ORGANS) -///Enables the butchering mechanic for the mob who has equipped us. -/datum/component/butchering/proc/enable_butchering(datum/source) - SIGNAL_HANDLER - butchering_enabled = TRUE - -///Disables the butchering mechanic for the mob who has dropped us. -/datum/component/butchering/proc/disable_butchering(datum/source) - SIGNAL_HANDLER - butchering_enabled = FALSE - -///Special snowflake component only used for the recycler. +/// Special snowflake component only used for the recycler. /datum/component/butchering/recycler - /datum/component/butchering/recycler/Initialize( speed, effectiveness, @@ -198,10 +490,10 @@ can_be_blunt, butcher_callback, ) - if(!istype(parent, /obj/machinery/recycler)) //EWWW + if (!istype(parent, /obj/machinery/recycler)) //EWWW return COMPONENT_INCOMPATIBLE . = ..() - if(. == COMPONENT_INCOMPATIBLE) + if (. == COMPONENT_INCOMPATIBLE) return var/static/list/loc_connections = list( @@ -212,13 +504,13 @@ /datum/component/butchering/recycler/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) SIGNAL_HANDLER - if(!isliving(arrived)) + if (!isliving(arrived)) return var/mob/living/victim = arrived var/obj/machinery/recycler/eater = parent - if(eater.safety_mode || (eater.machine_stat & (BROKEN|NOPOWER))) //I'm so sorry. + if (eater.safety_mode || (eater.machine_stat & (BROKEN|NOPOWER))) //I'm so sorry. return - if(victim.stat == DEAD && (victim.butcher_results || victim.guaranteed_butcher_results)) + if (victim.stat == DEAD && (victim.butcher_results || victim.guaranteed_butcher_results)) on_butchering(parent, victim) /datum/component/butchering/mecha @@ -237,6 +529,16 @@ COMSIG_MECHA_EQUIPMENT_DETACHED, )) +/// Enables the butchering mechanic for the mecha who has equipped us. +/datum/component/butchering/mecha/proc/enable_butchering(datum/source) + SIGNAL_HANDLER + butchering_enabled = TRUE + +/// Disables the butchering mechanic for the mecha who has dropped us. +/datum/component/butchering/mecha/proc/disable_butchering(datum/source) + SIGNAL_HANDLER + butchering_enabled = FALSE + ///When we are ready to drill through a mob /datum/component/butchering/mecha/proc/on_drill(datum/source, obj/vehicle/sealed/mecha/chassis, mob/living/target) SIGNAL_HANDLER @@ -256,16 +558,16 @@ COMSIG_ITEM_DROPPED, )) -///Same as enable_butchering but for worn items +/// Same as enable_butchering but for worn items /datum/component/butchering/wearable/proc/worn_enable_butchering(obj/item/source, mob/user, slot) SIGNAL_HANDLER //check if the item is being not worn - if(!(slot & source.slot_flags)) + if (!(slot & source.slot_flags)) return butchering_enabled = TRUE RegisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(butcher_target)) -///Same as disable_butchering but for worn items +/// Same as disable_butchering but for worn items /datum/component/butchering/wearable/proc/worn_disable_butchering(obj/item/source, mob/user) SIGNAL_HANDLER butchering_enabled = FALSE @@ -273,6 +575,6 @@ /datum/component/butchering/wearable/proc/butcher_target(mob/user, atom/target, proximity) SIGNAL_HANDLER - if(!isliving(target)) + if (!isliving(target)) return NONE - return onItemAttack(parent, target, user) + return on_item_attack(parent, target, user) diff --git a/code/datums/components/free_operation.dm b/code/datums/components/free_operation.dm new file mode 100644 index 00000000000..f1bddc05c65 --- /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/elements/corrupted_organ.dm b/code/datums/elements/corrupted_organ.dm index ccf56b17c30..7ba5b3ae8b6 100644 --- a/code/datums/elements/corrupted_organ.dm +++ b/code/datums/elements/corrupted_organ.dm @@ -30,7 +30,7 @@ /// When we're taken out of someone, do something spooky /datum/element/corrupted_organ/proc/on_removed(atom/organ, mob/living/remover, mob/living/carbon/loser) SIGNAL_HANDLER - if (loser.has_reagent(/datum/reagent/water/holywater) || loser.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY) || prob(20)) + if (loser?.has_reagent(/datum/reagent/water/holywater) || loser?.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY) || prob(20)) return if (prob(75)) organ.AddComponent(\ diff --git a/code/datums/status_effects/buffs/bioware/cortex.dm b/code/datums/status_effects/buffs/bioware/cortex.dm index 6134b9d7f6b..91c5fee9e9f 100644 --- a/code/datums/status_effects/buffs/bioware/cortex.dm +++ b/code/datums/status_effects/buffs/bioware/cortex.dm @@ -2,19 +2,15 @@ /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)) +// All cortex bioware removes the trauma boost as it is not granted via a status +/datum/status_effect/bioware/cortex/bioware_gained() + REMOVE_TRAIT(owner, TRAIT_SPECIAL_TRAUMA_BOOST, BIOWARE_TRAIT) // 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)) diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm index 31ca19429f7..8eda251d1ae 100644 --- a/code/datums/wounds/_wounds.dm +++ b/code/datums/wounds/_wounds.dm @@ -78,6 +78,8 @@ var/limp_chance /// How much we're contributing to this limb's bleed_rate var/blood_flow + /// Surgical states we're applying to our limb + var/surgery_states = NONE /// How much having this wound will add to all future check_wounding() rolls on this limb, to allow progression to worse injuries with repeated damage var/threshold_penalty @@ -112,6 +114,14 @@ /// The actionspeed modifier we will use in case we are on the arms and have a interaction penalty. Qdelled on destroy. var/datum/actionspeed_modifier/wound_interaction_inefficiency/actionspeed_mod + /// List of states -> other states that override them and prevent them from being added by wounds + var/static/list/exclusive_surgery_states = list( + "[SURGERY_SKIN_CUT]" = SURGERY_SKIN_OPEN, + "[SURGERY_VESSELS_UNCLAMPED]" = SURGERY_VESSELS_CLAMPED, + "[SURGERY_BONE_SAWED]" = SURGERY_BONE_DRILLED, + "[SURGERY_BONE_DRILLED]" = SURGERY_BONE_SAWED, + ) + /datum/wound/New() . = ..() @@ -284,16 +294,28 @@ /datum/wound/proc/set_limb(obj/item/bodypart/new_value, replaced = FALSE, destroying = FALSE) if(limb == new_value) return FALSE //Limb can either be a reference to something or `null`. Returning the number variable makes it clear no change was made. + . = limb + if(limb) // if we're nulling limb, we're basically detaching from it, so we should remove ourselves in that case - UnregisterSignal(limb, COMSIG_QDELETING) - UnregisterSignal(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_UNGAUZED)) + UnregisterSignal(limb, list(COMSIG_QDELETING, COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_UNGAUZED, COMSIG_BODYPART_UPDATING_SURGERY_STATE)) LAZYREMOVE(limb.wounds, src) if (!destroying) limb.update_wounds(replaced) if (disabling) limb.remove_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) + if(surgery_states) + for (var/state in exclusive_surgery_states) + if (!(limb.surgery_state & exclusive_surgery_states[state])) + continue + var/actual_state = text2num(state) + if (actual_state & surgery_states) + surgery_states &= ~actual_state + + if (surgery_states) + limb.remove_surgical_state(surgery_states) + limb = new_value // POST-CHANGE @@ -301,15 +323,32 @@ if (limb) RegisterSignal(limb, COMSIG_QDELETING, PROC_REF(source_died)) RegisterSignals(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_UNGAUZED), PROC_REF(gauze_state_changed)) + RegisterSignal(limb, COMSIG_BODYPART_UPDATING_SURGERY_STATE, PROC_REF(on_surgery_state_change)) if (disabling) limb.add_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) + if(surgery_states) + for (var/state in exclusive_surgery_states) + if (!(limb.surgery_state & exclusive_surgery_states[state])) + continue + var/actual_state = text2num(state) + if (actual_state & surgery_states) + surgery_states &= ~actual_state + + if (surgery_states) + limb.add_surgical_state(surgery_states) + if (victim) start_limping_if_we_should() // the status effect already handles removing itself add_or_remove_actionspeed_mod() update_inefficiencies(replaced) +/// Used to remove states applied or removed by operations from ourselves as to not remove them if we heal mid-surgery +/datum/wound/proc/on_surgery_state_change(old_state, surgery_state, changed_states) + SIGNAL_HANDLER + surgery_states &= ~changed_states + /datum/wound/proc/add_or_remove_actionspeed_mod() update_actionspeed_modifier() if (actionspeed_mod) diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 12a47139473..a2307a927d5 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -430,6 +430,7 @@ internal_bleeding_chance = 60 wound_flags = (ACCEPTS_GAUZE | MANGLES_INTERIOR) regen_ticks_needed = 240 // ticks every 2 seconds, 480 seconds, so roughly 8 minutes default + surgery_states = SURGERY_SKIN_CUT | SURGERY_BONE_SAWED // Bad enough to count as a busted skull/ribcage simple_desc = "Patient's bones have effectively shattered completely, causing total immobilization of the limb." simple_treat_text = "Bandaging the wound will slightly reduce its impact until surgically treated with bone gel and surgical tape." diff --git a/code/datums/wounds/cranial_fissure.dm b/code/datums/wounds/cranial_fissure.dm index f61e5386fb5..e26e3a97d9d 100644 --- a/code/datums/wounds/cranial_fissure.dm +++ b/code/datums/wounds/cranial_fissure.dm @@ -39,6 +39,7 @@ severity = WOUND_SEVERITY_CRITICAL sound_effect = 'sound/effects/dismember.ogg' + surgery_states = SURGERY_SKIN_OPEN | SURGERY_BONE_SAWED // Literally a cracked open skull, no vessels as it... doesn't bleed? /// If TRUE we have been prepped for surgery (to repair) VAR_FINAL/prepped = FALSE diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index 1379b1bbc0d..953dfcdac2f 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -404,6 +404,7 @@ threshold_penalty = 15 status_effect_type = /datum/status_effect/wound/pierce/critical scar_keyword = "piercecritical" + surgery_states = SURGERY_SKIN_CUT | SURGERY_VESSELS_UNCLAMPED // Bad enough to count wound_flags = (ACCEPTS_GAUZE | MANGLES_EXTERIOR | CAN_BE_GRASPED) simple_treat_text = "Bandaging the wound is of utmost importance, as is seeking direct medical attention - Death will ensue if treatment is delayed whatsoever, with lack of oxygen killing the patient, thus Food, Iron, and saline solution is always recommended after treatment. This wound will not naturally seal itself." diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm index a4e66f0188b..3e43a1081d0 100644 --- a/code/datums/wounds/slash.dm +++ b/code/datums/wounds/slash.dm @@ -342,6 +342,7 @@ demotes_to = /datum/wound/slash/flesh/moderate status_effect_type = /datum/status_effect/wound/slash/flesh/severe scar_keyword = "slashsevere" + surgery_states = SURGERY_SKIN_CUT | SURGERY_VESSELS_UNCLAMPED simple_treat_text = "Bandaging the wound is essential, and will reduce blood loss. Afterwards, the wound can be sutured shut, preferably while the patient is resting and/or grasping their wound." homemade_treat_text = "Bed sheets can be ripped up to make makeshift gauze. Flour, table salt, or salt mixed with water can be applied directly to stem the flow, though unmixed salt will irritate the skin and worsen natural healing. Resting and grabbing your wound will also reduce bleeding." @@ -375,6 +376,7 @@ demotes_to = /datum/wound/slash/flesh/severe status_effect_type = /datum/status_effect/wound/slash/flesh/critical scar_keyword = "slashcritical" + surgery_states = SURGERY_SKIN_OPEN | SURGERY_VESSELS_UNCLAMPED wound_flags = (ACCEPTS_GAUZE | MANGLES_EXTERIOR | CAN_BE_GRASPED) simple_treat_text = "Bandaging the wound is of utmost importance, as is seeking direct medical attention - Death will ensue if treatment is delayed whatsoever, with lack of oxygen killing the patient, thus Food, Iron, and saline solution is always recommended after treatment. This wound will not naturally seal itself." homemade_treat_text = "Bed sheets can be ripped up to make makeshift gauze. Flour, salt, and saltwater topically applied will help. Dropping to the ground and grabbing your wound will reduce blood flow." diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index ad6b4259f41..567f0933d12 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -9,6 +9,8 @@ var/buckle_requires_restraints = FALSE /// The mobs currently buckled to this atom var/list/mob/living/buckled_mobs = null //list() + /// How much time it takes to buckle a mob to us? + var/buckle_delay = 2 SECONDS /// The maximum number of mob/livings allowed to be buckled to this atom at once var/max_buckled_mobs = 1 /// Whether things buckled to this atom can be pulled while they're buckled @@ -321,7 +323,7 @@ M.visible_message(span_warning("[user] starts buckling [M] to [src]!"),\ span_userdanger("[user] starts buckling you to [src]!"),\ span_hear("You hear metal clanking.")) - if(!do_after(user, 2 SECONDS, M)) + if(!do_after(user, buckle_delay, M)) return FALSE // Sanity check before we attempt to buckle. Is everything still in a kosher state for buckling after the 3 seconds have elapsed? diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index f8382c876eb..446dc45ab66 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -879,7 +879,6 @@ . = ..() if(!isliving(hit_atom)) //Living mobs handle hit sounds differently. - throw_drop_sound_chain(YEET_SOUND_VOLUME) return diff --git a/code/game/objects/items/devices/scanners/autopsy_scanner.dm b/code/game/objects/items/devices/scanners/autopsy_scanner.dm index 61bc6c4d262..c541e513a0d 100644 --- a/code/game/objects/items/devices/scanners/autopsy_scanner.dm +++ b/code/game/objects/items/devices/scanners/autopsy_scanner.dm @@ -190,6 +190,8 @@ autopsy_information += "Subject is husked by: " if(HAS_TRAIT_FROM(scanned, TRAIT_HUSK, CHANGELING_DRAIN)) autopsy_information += "Desiccation, commonly caused by Changelings.
" + else if(HAS_TRAIT_FROM(scanned, TRAIT_HUSK, SKELETON_TRAIT)) + autopsy_information += "Stripped flesh.
" else if(!HAS_TRAIT_FROM(scanned, TRAIT_HUSK, BURN)) // prioritize showing unknown causes over burns autopsy_information += "Unknown causes.
" else diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index 56fab997cf7..e1f4bc6d792 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -180,6 +180,8 @@ if(advanced) if(HAS_TRAIT_FROM(target, TRAIT_HUSK, CHANGELING_DRAIN)) render_list += "Subject has been husked by [conditional_tooltip("desiccation", "Irreparable. Under normal circumstances, revival can only proceed via brain transplant.", tochat)].
" + else if(HAS_TRAIT_FROM(target, TRAIT_HUSK, SKELETON_TRAIT)) + render_list += "Subject has been husked due to severe flesh loss.
" else if(!HAS_TRAIT_FROM(target, TRAIT_HUSK, BURN)) // prioritize showing unknown causes over burns render_list += "Subject has been husked by mysterious causes.
" else diff --git a/code/game/objects/items/knives.dm b/code/game/objects/items/knives.dm index fcda07ed897..b94a91e3456 100644 --- a/code/game/objects/items/knives.dm +++ b/code/game/objects/items/knives.dm @@ -47,7 +47,7 @@ ///Adds the butchering component, used to override stats for special cases /obj/item/knife/proc/set_butchering() AddComponent(/datum/component/butchering, \ - speed = 8 SECONDS - force, \ + speed = 9 SECONDS - force * 2, \ effectiveness = 100, \ bonus_modifier = force - 10, \ ) @@ -134,7 +134,7 @@ /obj/item/knife/hunting/set_butchering() AddComponent(/datum/component/butchering, \ - speed = 8 SECONDS - force, \ + speed = 9 SECONDS - force * 2, \ effectiveness = 100, \ bonus_modifier = force + 10, \ ) diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm index f1492da1267..2988c564754 100644 --- a/code/game/objects/structures/kitchen_spike.dm +++ b/code/game/objects/structures/kitchen_spike.dm @@ -72,10 +72,12 @@ desc = "A spike for collecting meat from animals." density = TRUE anchored = TRUE - buckle_lying = FALSE + buckle_lying = 180 + buckle_dir = SOUTH can_buckle = TRUE max_integrity = 250 custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 7) + buckle_delay = 10 SECONDS /obj/structure/kitchenspike/Initialize(mapload) . = ..() @@ -114,8 +116,12 @@ /obj/structure/kitchenspike/user_buckle_mob(mob/living/target, mob/user, check_loc = TRUE) if(!iscarbon(target) && !isanimal_or_basicmob(target)) return + if(target != user || target.loc == loc) + return ..() if(!do_after(user, 10 SECONDS, target)) return + if(!is_user_buckle_possible(target, user, check_loc)) + return FALSE return ..() /obj/structure/kitchenspike/post_buckle_mob(mob/living/target) @@ -123,12 +129,11 @@ target.emote("scream") target.add_splatter_floor() target.adjust_brute_loss(30) - target.setDir(2) - var/matrix/m180 = matrix(target.transform) - m180.Turn(180) - animate(target, transform = m180, time = 3) - target.add_offsets(type, y_add = -6, animate = FALSE) + target.add_offsets(type, x_add = -1) + target.set_lying_angle(buckle_lying) ADD_TRAIT(target, TRAIT_MOVE_UPSIDE_DOWN, REF(src)) + // So you can butcher people too + target.AddComponentFrom(REF(src), /datum/component/free_operation) /obj/structure/kitchenspike/user_unbuckle_mob(mob/living/buckled_mob, mob/user) if(buckled_mob != user) @@ -156,11 +161,9 @@ buckled_mob.adjust_brute_loss(30) INVOKE_ASYNC(buckled_mob, TYPE_PROC_REF(/mob, emote), "scream") buckled_mob.AdjustParalyzed(20) - var/matrix/m180 = matrix(buckled_mob.transform) - m180.Turn(180) - animate(buckled_mob, transform = m180, time = 3) - buckled_mob.remove_offsets(type, animate = FALSE) + buckled_mob.remove_offsets(type) REMOVE_TRAIT(buckled_mob, TRAIT_MOVE_UPSIDE_DOWN, REF(src)) + buckled_mob.RemoveComponentSource(REF(src), /datum/component/free_operation) /obj/structure/kitchenspike/atom_deconstruct(disassembled = TRUE) if(disassembled) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index d171da22326..21d2fa893cc 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -1086,8 +1086,8 @@ 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_ATOM_BEING_OPERATED_ON, + COMSIG_ATOM_SURGERY_FINISHED, COMSIG_LIVING_UPDATING_SURGERY_STATE, )) if (patient.external && patient.external == air_tank) @@ -1101,10 +1101,10 @@ RegisterSignals(patient, list( SIGNAL_ADDTRAIT(TRAIT_READY_TO_OPERATE), SIGNAL_REMOVETRAIT(TRAIT_READY_TO_OPERATE), - COMSIG_LIVING_SURGERY_FINISHED, + COMSIG_ATOM_SURGERY_FINISHED, COMSIG_LIVING_UPDATING_SURGERY_STATE, ), PROC_REF(on_surgery_change)) - RegisterSignal(patient, COMSIG_LIVING_BEING_OPERATED_ON, PROC_REF(get_surgeries)) + RegisterSignal(patient, COMSIG_ATOM_BEING_OPERATED_ON, PROC_REF(get_surgeries)) /obj/structure/table/optable/proc/on_surgery_change(datum/source) SIGNAL_HANDLER diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm index 42d3645300b..a1cf2f26502 100644 --- a/code/modules/antagonists/abductor/abductor.dm +++ b/code/modules/antagonists/abductor/abductor.dm @@ -129,7 +129,7 @@ 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) +/datum/antagonist/abductor/scientist/proc/add_surgery(datum/source, atom/movable/operating_on, list/possible_operations) SIGNAL_HANDLER var/static/list/ayy_operations diff --git a/code/modules/food_and_drinks/machinery/gibber.dm b/code/modules/food_and_drinks/machinery/gibber.dm index d2a1e299cad..4900924da36 100644 --- a/code/modules/food_and_drinks/machinery/gibber.dm +++ b/code/modules/food_and_drinks/machinery/gibber.dm @@ -13,8 +13,8 @@ var/dirty = FALSE /// Time from starting until meat appears var/gibtime = 40 - /// How much meat we meet when we meat the meat - var/meat_produced = 2 + /// Average efficiency multiplier for how much meat we meet when we meat the meat per meat of each of meat's meat limbs + var/efficiency = 0.25 /// If the gibber should give the 'Subject may not have abiotic items on' message var/ignore_clothing = FALSE /// The DNA info of the last gibbed mob @@ -35,9 +35,9 @@ /obj/machinery/gibber/RefreshParts() . = ..() gibtime = 40 - meat_produced = initial(meat_produced) + efficiency = initial(efficiency) for(var/datum/stock_part/matter_bin/matter_bin in component_parts) - meat_produced += matter_bin.tier + efficiency += matter_bin.tier * 0.25 for(var/datum/stock_part/servo/servo in component_parts) gibtime -= 5 * servo.tier if(servo.tier >= 2) @@ -46,7 +46,7 @@ /obj/machinery/gibber/examine(mob/user) . = ..() if(in_range(user, src) || isobserver(user)) - . += span_notice("The status display reads: Outputting [meat_produced] meat slab(s) after [gibtime*0.1] seconds of processing.") + . += span_notice("The status display reads: Outputting [efficiency]% of meat after [gibtime*0.1] seconds of processing.") for(var/datum/stock_part/servo/servo in component_parts) if(servo.tier >= 2) . += span_notice("[src] has been upgraded to process inorganic materials.") @@ -127,7 +127,7 @@ set_occupant(C) update_appearance() else - startgibbing(user) + start_gibbing(user) /obj/machinery/gibber/wrench_act(mob/living/user, obj/item/tool) . = ..() @@ -162,12 +162,14 @@ dump_inventory_contents() update_appearance() -/obj/machinery/gibber/proc/startgibbing(mob/user) +/obj/machinery/gibber/proc/start_gibbing(mob/user) if(operating) return + if(!occupant) audible_message(span_hear("You hear a loud metallic grinding sound.")) return + if(occupant.flags_1 & HOLOGRAM_1) audible_message(span_hear("You hear a very short metallic grinding sound.")) playsound(loc, 'sound/machines/hiss.ogg', 20, TRUE) @@ -180,123 +182,153 @@ playsound(loc, 'sound/machines/juicer.ogg', 50, TRUE) operating = TRUE update_appearance() - Shake(pixelshiftx = 1, pixelshifty = 0, duration = gibtime) - var/mob/living/mob_occupant = occupant - var/sourcename = mob_occupant.real_name - var/sourcejob - if(ishuman(occupant)) - var/mob/living/carbon/human/gibee = occupant - sourcejob = gibee.job - var/sourcenutriment = mob_occupant.nutrition / 15 - var/gibtypes = null - var/typeofmeat = /obj/item/food/meat/slab/human - var/typeofskin + var/mob/living/victim = occupant var/list/results = list() - var/obj/item/stack/sheet/animalhide/skin - var/list/datum/disease/diseases = mob_occupant.get_static_viruses() + var/list/datum/disease/diseases = list() + for(var/datum/disease/disease as anything in victim.get_static_viruses()) + // admin or special viruses that should not be reproduced + if(!(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))) + diseases += disease - // We cannot initial() lists on types, so we need to create in nullspace, take the list and then delete our mob's gibspawner - var/spawner_type = mob_occupant.get_gibs_type() - if (spawner_type) - // No need to handle cleanup as it returns INITIALIZE_HINT_QDEL by default - var/obj/effect/gibspawner/spawner = new spawner_type() - gibtypes = spawner.gibtypes + blood_dna_info = victim.get_blood_dna_list() - if(ishuman(occupant)) - var/mob/living/carbon/human/gibee = occupant - if(prob(40) && (sourcejob in list(JOB_SECURITY_OFFICER,JOB_WARDEN,JOB_HEAD_OF_SECURITY))) - typeofmeat = /obj/item/food/meat/slab/pig - else if(gibee.dna && gibee.dna.species) - typeofmeat = gibee.dna.species.meat - typeofskin = gibee.dna.species.skinned_type - blood_dna_info = gibee.get_blood_dna_list() + if (!ishuman(victim)) + var/meat_type = /obj/item/food/meat/slab + var/skin_type = null + var/meat_produced = 2 + var/skin_produced = 1 + if (victim.butcher_results || victim.guaranteed_butcher_results) + for (var/possible_drop in victim.butcher_results | victim.guaranteed_butcher_results) + if (ispath(possible_drop, /obj/item/food/meat/slab)) + meat_type = possible_drop + meat_produced = victim.butcher_results[possible_drop] + victim.guaranteed_butcher_results[possible_drop] + else if (ispath(possible_drop, /obj/item/stack/sheet/animalhide)) + skin_type = possible_drop + skin_produced = victim.butcher_results[possible_drop] + victim.guaranteed_butcher_results[possible_drop] - else if(iscarbon(occupant)) - var/mob/living/carbon/carbon_occupant = occupant - typeofmeat = carbon_occupant.type_of_meat - if(isalien(carbon_occupant)) - typeofskin = /obj/item/stack/sheet/animalhide/xeno - blood_dna_info = carbon_occupant.get_blood_dna_list() + else if (iscarbon(victim)) + var/mob/living/carbon/as_carbon = victim + meat_type = as_carbon.type_of_meat + if (isalien(victim)) + skin_type = /obj/item/stack/sheet/animalhide/xeno - if (typeofmeat) - for (var/i in 1 to meat_produced) - var/obj/item/food/meat/slab/newmeat = new typeofmeat(null, blood_dna_info) - newmeat.name = "[sourcename] [newmeat.name]" - newmeat.set_custom_materials(list(GET_MATERIAL_REF(/datum/material/meat/mob_meat, occupant) = 4 * SHEET_MATERIAL_AMOUNT)) - if(!istype(newmeat)) + for (var/i in 1 to rand(floor(meat_produced * efficiency / initial(efficiency)), ceil(meat_produced * efficiency / initial(efficiency)))) + results += spawn_meat(victim, meat_type, diseases) + + if (skin_type && skin_produced) + for (var/i in 1 to rand(floor(skin_produced * efficiency / initial(efficiency)), ceil(skin_produced * efficiency / initial(efficiency)))) + results += new skin_type(src) + + SEND_SIGNAL(victim, COMSIG_LIVING_GIBBER_ACT, user, src, results) + process_results(victim, results) + addtimer(CALLBACK(src, PROC_REF(finish_gibbing), results, victim.get_gibs_type(), diseases), gibtime) + log_combat(user, victim, "gibbed") + victim.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS) + victim.death(TRUE) + victim.ghostize() + set_occupant(null) + qdel(victim) + return + + var/mob/living/carbon/human/agent_whiskey = victim + var/drop_chance = 0 + for (var/obj/item/bodypart/limb as anything in agent_whiskey.bodyparts) + if (!limb.butcher_drops) + continue + + for (var/obj/item/drop_type as anything in limb.butcher_drops) + var/amount = limb.butcher_drops[drop_type] || 1 + drop_chance += amount * efficiency + if (drop_chance > 1) + amount = floor(drop_chance) + if (prob(drop_chance * 100)) + amount += 1 + else + amount = 1 + if (!prob(drop_chance * 100)) + continue + drop_chance = 0 + + if (ispath(drop_type, /obj/item/food/meat)) + for (var/i in 1 to amount) + results += spawn_meat(victim, drop_type, diseases) continue - newmeat.subjectname = sourcename - if(sourcejob) - newmeat.subjectjob = sourcejob - results += newmeat + for (var/i in 1 to amount) + results += new drop_type(src) - SEND_SIGNAL(occupant, COMSIG_LIVING_GIBBER_ACT, user, src, results) + SEND_SIGNAL(victim, COMSIG_LIVING_GIBBER_ACT, user, src, results) + process_results(victim, results) + addtimer(CALLBACK(src, PROC_REF(finish_gibbing), results, victim.get_gibs_type(), diseases), gibtime) + log_combat(user, victim, "gibbed") + victim.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS) + victim.death(TRUE) + victim.ghostize() + set_occupant(null) + qdel(victim) +/obj/machinery/gibber/proc/spawn_meat(mob/living/victim, meat_type = /obj/item/food/meat/slab, list/datum/disease/diseases) + var/obj/item/food/meat/meat = new meat_type(src, blood_dna_info) + meat.name = "[victim.real_name]'s [meat.name]" + meat.set_custom_materials(list(GET_MATERIAL_REF(/datum/material/meat/mob_meat, victim) = 4 * SHEET_MATERIAL_AMOUNT)) + if (!istype(meat)) + return + meat.subjectname = victim.real_name + meat.subjectjob = victim.job + if (length(diseases)) + meat.AddComponent(/datum/component/infective, diseases) + return meat + +/obj/machinery/gibber/proc/process_results(mob/living/victim, list/obj/item/results) var/reagents_in_produced = 0 for(var/obj/item/result as anything in results) if(result.reagents) - reagents_in_produced++ + reagents_in_produced += 1 for(var/obj/item/result as anything in results) - occupant.reagents.trans_to(result, occupant.reagents.total_volume / reagents_in_produced, remove_blacklisted = TRUE) - result.reagents?.add_reagent(/datum/reagent/consumable/nutriment/fat, sourcenutriment / reagents_in_produced) // Thehehe. Fat guys go first + if (victim.reagents) + victim.reagents.trans_to(result, victim.reagents.total_volume / reagents_in_produced, remove_blacklisted = TRUE) + result.reagents?.add_reagent(/datum/reagent/consumable/nutriment/fat, victim.nutrition / 15 / reagents_in_produced) - - if(typeofskin) - skin = new typeofskin - - log_combat(user, occupant, "gibbed") - mob_occupant.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS) - mob_occupant.death(TRUE) - mob_occupant.ghostize() - set_occupant(null) - qdel(mob_occupant) - addtimer(CALLBACK(src, PROC_REF(make_meat), skin, results, meat_produced, gibtypes, diseases, blood_dna_info), gibtime) - -/obj/machinery/gibber/proc/make_meat(obj/item/stack/sheet/animalhide/skin, list/results, meat_produced, list/gibtypes, list/datum/disease/diseases, blood_dna_info) +/obj/machinery/gibber/proc/finish_gibbing(list/obj/item/results, gibs_type, list/datum/disease/diseases) playsound(src.loc, 'sound/effects/splat.ogg', 50, TRUE) operating = FALSE if (!dirty && prob(50)) dirty = TRUE + if(blood_dna_info) add_blood_DNA(blood_dna_info) + var/turf/our_turf = get_turf(src) var/list/turf/nearby_turfs = RANGE_TURFS(3, our_turf) - our_turf - if(skin) - skin.forceMove(loc) - skin.throw_at(pick(nearby_turfs), meat_produced, 3) + for (var/obj/item/result as anything in results) + result.forceMove(our_turf) + result.throw_at(pick(nearby_turfs), rand(1, length(results)), 1) - var/iteration = 1 - for (var/obj/item/meatslab in results) - if(LAZYLEN(diseases)) - var/list/datum/disease/diseases_to_add = list() - for(var/datum/disease/disease as anything in diseases) - // admin or special viruses that should not be reproduced - if(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS)) - continue + if (!gibs_type) + pixel_x = base_pixel_x + update_appearance() + return - diseases_to_add += disease - if(LAZYLEN(diseases_to_add)) - meatslab.AddComponent(/datum/component/infective, diseases_to_add) - meatslab.forceMove(loc) - meatslab.throw_at(pick(nearby_turfs), iteration, 3) + var/obj/effect/gibspawner/spawner = new gibs_type() + var/list/gibs = spawner.gibtypes + if (!length(gibs)) + pixel_x = base_pixel_x + update_appearance() + return - iteration++ + for (var/i in 1 to length(results)) + var/gib_dir = pick(GLOB.alldirs) + var/turf/gib_turf = get_step(src, gib_dir) + if (gib_turf.is_blocked_turf(TRUE)) + continue + var/gib_type = pick(gibs) + var/obj/effect/decal/cleanable/blood/gibs/gib = new gib_type(gib_turf, diseases, blood_dna_info) + gib.streak(gib_dir) - if (length(gibtypes)) - for (var/i in 1 to meat_produced**2) //2 slabs: 4 giblets, 3 slabs: 9, etc. - var/gibdir = pick(GLOB.alldirs) - var/turf/gibturf = get_step(src, gibdir) - if (!gibturf.is_blocked_turf(exclude_mobs = TRUE)) - var/list/gibtype = pick(gibtypes) - var/obj/effect/decal/cleanable/blood/gibs/gib = new gibtype(gibturf, diseases, blood_dna_info) - gib.streak(gibdir) - - pixel_x = base_pixel_x //return to its spot after shaking - operating = FALSE + pixel_x = base_pixel_x update_appearance() //auto-gibs anything that bumps into it diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index 64fcb70c9fe..4fcad1b97af 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -40,10 +40,23 @@ voice_of_god_power = 1.4 //Command staff has authority - /datum/job/head_of_security/get_captaincy_announcement(mob/living/captain) return "Due to staffing shortages, newly promoted Acting Captain [captain.real_name] on deck!" +/datum/job/head_of_security/after_spawn(mob/living/spawned, client/player_client) + . = ..() + if(!ishuman(spawned) || !prob(PIG_COP_PROBABILITY)) + return + var/mob/living/carbon/human/piggy = spawned + for (var/obj/item/bodypart/ham as anything in piggy.bodyparts) + // These are string lists + ham.butcher_drops = ham.butcher_drops.Copy() + for (var/meat_type in ham.butcher_drops) + if (!ispath(meat_type, /obj/item/food/meat/slab)) + continue + ham.butcher_drops[/obj/item/food/meat/slab/pig] = ham.butcher_drops[meat_type] + ham.butcher_drops -= meat_type + ham.butcher_drops = string_list(ham.butcher_drops) /datum/outfit/job/hos name = "Head of Security" diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index bc8dcfe16a1..983d7a56c5a 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -57,6 +57,20 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S */ GLOBAL_LIST_EMPTY(security_officer_distribution) +/datum/job/security_officer/after_spawn(mob/living/spawned, client/player_client) + . = ..() + if(!ishuman(spawned) || !prob(PIG_COP_PROBABILITY)) + return + var/mob/living/carbon/human/piggy = spawned + for (var/obj/item/bodypart/ham as anything in piggy.bodyparts) + // These are string lists + ham.butcher_drops = ham.butcher_drops.Copy() + for (var/meat_type in ham.butcher_drops) + if (!ispath(meat_type, /obj/item/food/meat/slab)) + continue + ham.butcher_drops[/obj/item/food/meat/slab/pig] = ham.butcher_drops[meat_type] + ham.butcher_drops -= meat_type + ham.butcher_drops = string_list(ham.butcher_drops) /datum/job/security_officer/after_roundstart_spawn(mob/living/spawning, client/player_client) . = ..() diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm index d71857d0502..c759552c049 100644 --- a/code/modules/jobs/job_types/warden.dm +++ b/code/modules/jobs/job_types/warden.dm @@ -42,6 +42,21 @@ rpg_title = "Jailor" job_flags = STATION_JOB_FLAGS | JOB_BOLD_SELECT_TEXT | JOB_ANTAG_PROTECTED +/datum/job/warden/after_spawn(mob/living/spawned, client/player_client) + . = ..() + if(!ishuman(spawned) || !prob(PIG_COP_PROBABILITY)) + return + var/mob/living/carbon/human/piggy = spawned + for (var/obj/item/bodypart/ham as anything in piggy.bodyparts) + // These are string lists + ham.butcher_drops = ham.butcher_drops.Copy() + for (var/meat_type in ham.butcher_drops) + if (!ispath(meat_type, /obj/item/food/meat/slab)) + continue + ham.butcher_drops[/obj/item/food/meat/slab/pig] = ham.butcher_drops[meat_type] + ham.butcher_drops -= meat_type + ham.butcher_drops = string_list(ham.butcher_drops) + /datum/outfit/job/warden name = "Warden" jobtype = /datum/job/warden diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index 45e3364fd35..466b8d066de 100644 --- a/code/modules/mob/living/brain/brain_item.dm +++ b/code/modules/mob/living/brain/brain_item.dm @@ -362,7 +362,7 @@ if(prob(delta_dam * (1 + max(0, (damage - BRAIN_DAMAGE_MILD)/100)))) //Base chance is the hit damage; for every point of damage past the threshold the chance is increased by 1% //learn how to do your bloody math properly goddamnit gain_trauma_type(BRAIN_TRAUMA_MILD, natural_gain = TRUE) - var/is_boosted = (owner && HAS_TRAIT(owner, TRAIT_SPECIAL_TRAUMA_BOOST)) + var/is_boosted = (owner && HAS_TRAIT(src, TRAIT_SPECIAL_TRAUMA_BOOST)) if(damage < BRAIN_DAMAGE_SEVERE) return if(prob(delta_dam * (1 + max(0, (damage - BRAIN_DAMAGE_SEVERE)/100)))) //Base chance is the hit damage; for every point of damage past the threshold the chance is increased by 1% diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index fb155e0e419..4adc765d3a9 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -2575,6 +2575,9 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/proc/set_usable_hands(new_value) if(usable_hands == new_value) return + if(new_value < 0) // Sanity check + stack_trace("[src] had set_usable_hands() called on them with a negative value!") + new_value = 0 . = usable_hands usable_hands = new_value diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 512aecdc28b..04005ff4eff 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -78,8 +78,8 @@ var/can_be_disabled = FALSE //Defaults to FALSE, as only human limbs can be disabled, and only the appendages. ///Controls if the limb is disabled. TRUE means it is disabled (similar to being removed, but still present for the sake of targeted interactions). var/bodypart_disabled = FALSE - ///Handles limb disabling by damage. If 0 (0%), a limb can't be disabled via damage. If 1 (100%), it is disabled at max limb damage. Anything between is the percentage of damage against maximum limb damage needed to disable the limb. - var/disabling_threshold_percentage = 0 + ///Handles limb disabling by damage. If LIMB_NO_DISABLE (-1), a limb can't be disabled via damage. If 1 (100%), it is disabled at max limb damage. Anything between is the percentage of damage against maximum limb damage needed to disable the limb. + var/disabling_threshold_percentage = LIMB_NO_DISABLE // Damage variables ///A mutiplication of the burn and brute damage that the limb's stored damage contributes to its attached mob's overall wellbeing. @@ -222,7 +222,15 @@ var/list/bodypart_effects /// The cached info about the blood this organ belongs to, set during on_removal() var/list/blood_dna_info - + /// What items we drop whenever we're butchered + /// If unset, the bodyparot cannot be butchered + var/list/butcher_drops = null + /// What skeleton limb, if any, we replace ourselves with when butchered? + var/obj/item/bodypart/butcher_replacement = null + /// How much meat do we add to butcher_drops when automatically generating them from our species datum? + var/base_meat_amount = 1 + /// Init cache for our butcher drops for sanic speed + var/static/list/butcher_drop_cache = list() /// What state is the bodypart in for determining surgery availability VAR_FINAL/surgery_state = NONE @@ -268,6 +276,11 @@ add_surgical_state(innate_state) name = "[limb_id] [parse_zone(body_zone)]" + // There's a lot of bodyparts in the world, and we don't need to have separate drops on each and every one of them + var/list/drop_results = get_butcher_drops() + if (length(drop_results)) + butcher_drops = string_list(drop_results) + butcher_drop_cache[type] = butcher_drops update_icon_dropped() refresh_bleed_rate() @@ -301,6 +314,16 @@ return FALSE return ..() +/obj/item/bodypart/proc/get_butcher_drops() + if(!isnull(butcher_drops)) + return butcher_drops + if (butcher_drop_cache[type]) + return butcher_drop_cache[type] + var/datum/species/species = GLOB.species_list[limb_id] + if (!species || !species.meat || !base_meat_amount) + return null + return list(species.meat = base_meat_amount) + /obj/item/bodypart/proc/on_forced_removal(atom/old_loc, dir, forced, list/old_locs) SIGNAL_HANDLER @@ -326,6 +349,10 @@ if(wound_desc) . += wound_desc + var/surgery_examine = get_surgery_examine() + if(surgery_examine) + . += surgery_examine + /** * Called when a bodypart is checked for injuries. */ @@ -475,8 +502,8 @@ /// 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/lowercase_zone = owner ? "[owner.p_their()] [plaintext_zone]" : "[src]" + var/capital_zone = owner ? "[owner.p_Their()] [plaintext_zone]" : capitalize("[src]") var/single_message = "" var/list/sub_messages = list() var/reported_state = surgery_state @@ -489,42 +516,42 @@ 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." + single_message = "The skin on [lowercase_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." + single_message = "The skin on [lowercase_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!" : "."]" + single_message = "The blood vessels in [lowercase_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." + single_message = "The blood vessels in [lowercase_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." + single_message = "The organs in [lowercase_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." + single_message = "The bones in [lowercase_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." + single_message = "The bones in [lowercase_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!" + single_message = "[capital_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]." + single_message = "A layer of plastic has been applied to [lowercase_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!" + single_message = "[owner?.p_Their() || "The"] chest cavity is wide open!" if(length(sub_messages) >= 2) - return span_danger("[t_His] [plaintext_zone]'s [english_list(sub_messages)].") + return span_danger("[capital_zone]'s [english_list(sub_messages)].") if(single_message) return span_danger(single_message) return "" @@ -555,31 +582,34 @@ return return ..() -/obj/item/bodypart/attackby(obj/item/weapon, mob/user, list/modifiers, list/attack_modifiers) - SHOULD_CALL_PARENT(TRUE) - - if(weapon.get_sharpness()) - add_fingerprint(user) - if(!contents.len) - to_chat(user, span_warning("There is nothing left inside [src]!")) - return - playsound(loc, 'sound/items/weapons/slice.ogg', 50, TRUE, -1) - user.visible_message(span_warning("[user] begins to cut open [src]."),\ - span_notice("You begin to cut open [src]...")) - if(do_after(user, 5.4 SECONDS, target = src)) - drop_organs(user, TRUE) - else - return ..() - /obj/item/bodypart/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) SHOULD_CALL_PARENT(TRUE) - ..() + . = ..() if(IS_ORGANIC_LIMB(src)) playsound(get_turf(src), 'sound/misc/splort.ogg', 50, TRUE, -1) pixel_x = rand(-3, 3) pixel_y = rand(-3, 3) +/obj/item/bodypart/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(user.combat_mode) + return NONE + + var/operation_zone = user.zone_selected + if (deprecise_zone(operation_zone) != body_zone) + operation_zone = body_zone + return user.perform_surgery(src, tool, LAZYACCESS(modifiers, RIGHT_CLICK), operation_zone) + +/obj/item/bodypart/examine_more(mob/user) + . = ..() + var/list/operations = GLOB.operations.get_instances_from(GLOB.operations.unlocked) + var/operation_zone = user.zone_selected + if (deprecise_zone(operation_zone) != body_zone) + operation_zone = body_zone + for(var/datum/surgery_operation/operation as anything in operations) + if ((operation.operation_flags & OPERATION_NO_PATIENT_REQUIRED) && operation.show_as_next_step(src, operation_zone)) + . += span_notice("You could perform [operation] on [src] with \a [operation.get_recommended_tool()]...") + //empties the bodypart from its organs and other things inside it /obj/item/bodypart/proc/drop_organs(mob/user, violent_removal) SHOULD_CALL_PARENT(TRUE) @@ -593,12 +623,18 @@ for(var/obj/item/organ/bodypart_organ in contents) if(bodypart_organ.organ_flags & ORGAN_UNREMOVABLE) continue + + if(violent_removal) + bodypart_organ.apply_organ_damage(bodypart_organ.maxHealth * 0.5) + if(owner) - bodypart_organ.Remove(bodypart_organ.owner) - else - if(bodypart_organ.bodypart_remove(src)) - if(drop_loc) //can be null if being deleted - bodypart_organ.forceMove(get_turf(drop_loc)) + if(!bodypart_organ.Remove(bodypart_organ.owner)) + continue + else if(!bodypart_organ.bodypart_remove(src)) + continue + + if(drop_loc) //can be null if being deleted + bodypart_organ.forceMove(get_turf(drop_loc)) if(drop_loc) //can be null during deletion for(var/atom/movable/movable as anything in src) @@ -870,31 +906,31 @@ return brute_dam + burn_dam //Checks disabled status thresholds -/obj/item/bodypart/proc/update_disabled() +/obj/item/bodypart/proc/update_disabled(update_limbs = TRUE) SHOULD_CALL_PARENT(TRUE) if(!owner) return if(!can_be_disabled) - set_disabled(FALSE) + set_disabled(FALSE, update_limbs) CRASH("update_disabled called with can_be_disabled false") if(HAS_TRAIT(src, TRAIT_PARALYSIS)) - set_disabled(TRUE) + set_disabled(TRUE, update_limbs) return var/total_damage = brute_dam + burn_dam // this block of checks is for limbs that can be disabled, but not through pure damage (AKA limbs that suffer wounds, human/monkey parts and such) - if(!disabling_threshold_percentage) + if(disabling_threshold_percentage == LIMB_NO_DISABLE) if(total_damage < max_damage) last_maxed = FALSE else if(!last_maxed && owner.stat < UNCONSCIOUS) INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "scream") last_maxed = TRUE - set_disabled(FALSE) // we only care about the paralysis trait + set_disabled(FALSE, update_limbs) // we only care about the paralysis trait return // we're now dealing solely with limbs that can be disabled through pure damage, AKA robot parts @@ -903,15 +939,15 @@ if(owner.stat < UNCONSCIOUS) INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "scream") last_maxed = TRUE - set_disabled(TRUE) + set_disabled(TRUE, update_limbs) return - if(bodypart_disabled && total_damage <= max_damage * 0.5) // reenable the limb at 50% health + if(bodypart_disabled && total_damage < max_damage * disabling_threshold_percentage * 0.5) // reenable the limb at 50% of the threshold last_maxed = FALSE - set_disabled(FALSE) + set_disabled(FALSE, update_limbs) ///Proc to change the value of the `disabled` variable and react to the event of its change. -/obj/item/bodypart/proc/set_disabled(new_disabled) +/obj/item/bodypart/proc/set_disabled(new_disabled, update_limbs = TRUE) SHOULD_CALL_PARENT(TRUE) PROTECTED_PROC(TRUE) @@ -990,7 +1026,7 @@ RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOBLOOD), PROC_REF(on_owner_nobleed_gain)) if(can_be_disabled) - update_disabled() + update_disabled(FALSE) RegisterSignal(owner, COMSIG_ATOM_RESTYLE, PROC_REF(on_attempt_feature_restyle_mob)) RegisterSignal(owner, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_owner_clean)) @@ -1458,14 +1494,14 @@ // 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 + var/surgery_bloodloss = 0 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 + surgery_bloodloss += UNCLAMPED_VESSELS_BLEEDING // better, but still not exactly ideal else if(HAS_ANY_SURGERY_STATE(surgery_state, SURGERY_VESSELS_CLAMPED|SURGERY_ORGANS_CUT)) - surgery_bloodloss += 0.2 + surgery_bloodloss += CLAMPED_VESSELS_BLEEDING // modify rate so cutting everything open won't nuke people if(body_zone == BODY_ZONE_HEAD) @@ -1484,6 +1520,12 @@ for(var/datum/wound/iter_wound as anything in wounds) cached_bleed_rate += iter_wound.blood_flow + if (!(iter_wound.surgery_states & SURGERY_VESSELS_UNCLAMPED) || !surgery_bloodloss) + continue + // Consider it contirubuted by the wound itself + // Not -surgery_bloodloss as this way clamping the vessels reduces the overall bleeding + cached_bleed_rate -= UNCLAMPED_VESSELS_BLEEDING + surgery_bloodloss = 0 if(owner.body_position == LYING_DOWN) cached_bleed_rate *= 0.75 @@ -1700,9 +1742,6 @@ /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) @@ -1730,11 +1769,16 @@ /// Called when surgical state changes so we can react to it /obj/item/bodypart/proc/update_surgical_state(old_state, changed_states) + SEND_SIGNAL(src, COMSIG_BODYPART_UPDATING_SURGERY_STATE, old_state, surgery_state, changed_states) + if((surgery_state & changed_states) == changed_states) + return + 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 diff --git a/code/modules/surgery/bodyparts/ghetto_parts.dm b/code/modules/surgery/bodyparts/ghetto_parts.dm index 5e8858c68c2..e22386b62e0 100644 --- a/code/modules/surgery/bodyparts/ghetto_parts.dm +++ b/code/modules/surgery/bodyparts/ghetto_parts.dm @@ -18,6 +18,7 @@ bodypart_flags = BODYPART_UNHUSKABLE biological_state = (BIO_WOOD|BIO_JOINTED) custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 2) + butcher_replacement = null /obj/item/bodypart/arm/left/ghetto/Initialize(mapload, ...) . = ..() @@ -43,6 +44,7 @@ bodypart_flags = BODYPART_UNHUSKABLE biological_state = (BIO_WOOD|BIO_JOINTED) custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 2) + butcher_replacement = null /obj/item/bodypart/arm/right/ghetto/Initialize(mapload, ...) . = ..() @@ -66,6 +68,7 @@ bodypart_flags = BODYPART_UNHUSKABLE biological_state = (BIO_WOOD|BIO_JOINTED) custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 2) + butcher_replacement = null /obj/item/bodypart/leg/left/ghetto/Initialize(mapload, ...) . = ..() @@ -89,6 +92,7 @@ bodypart_flags = BODYPART_UNHUSKABLE biological_state = (BIO_WOOD|BIO_JOINTED) custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 2) + butcher_replacement = null /obj/item/bodypart/leg/right/ghetto/Initialize(mapload, ...) . = ..() diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 3b85fe54345..9e59cb744ec 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -25,6 +25,8 @@ unarmed_damage_high = 3 unarmed_effectiveness = 0 bodypart_trait_source = HEAD_TRAIT + butcher_replacement = /obj/item/bodypart/head/skeleton/nonfunctional + base_meat_amount = 0 /// Do we show the information about missing organs upon being examined? Defaults to TRUE, useful for Dullahan heads. var/show_organs_on_examine = TRUE @@ -104,6 +106,14 @@ QDEL_NULL(worn_face_offset) return ..() +/obj/item/bodypart/head/get_butcher_drops() + if(butcher_drops) + return butcher_drops + var/datum/species/species = GLOB.species_list[limb_id] + if (!species || !species.skinned_type) + return null + return list(species.skinned_type = 1) + /obj/item/bodypart/head/grind_results() return null diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index 23e1056d716..026fb0b268b 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -12,6 +12,8 @@ px_y = 0 wound_resistance = 10 bodypart_trait_source = CHEST_TRAIT + base_meat_amount = 2 + butcher_replacement = /obj/item/bodypart/chest/skeleton/nonfunctional ///The bodyshape(s) allowed to attach to this chest. var/acceptable_bodyshape = BODYSHAPE_HUMANOID ///The bodytype(s) allowed to attach to this chest. @@ -36,6 +38,17 @@ /// Which functional (i.e. flightpotion) wing types (if any) does this bodypart support? If count is >1 a radial menu is used to choose between all icons in list var/list/wing_types = list(/obj/item/organ/wings/functional/angel) +/obj/item/bodypart/chest/get_butcher_drops() + . = ..() + if(butcher_drops) + return + var/datum/species/species = GLOB.species_list[limb_id] + if (!species || !species.skinned_type) + return + if (!islist(.)) + . = list() + .[species.skinned_type] = 1 + /obj/item/bodypart/chest/grind_results() return null @@ -201,6 +214,25 @@ var/atom/movable/screen/inventory/hand/hand = new_owner.hud_used.hand_slots["[held_index]"] hand?.update_appearance() +/obj/item/bodypart/arm/set_disabled(new_disabled) + . = ..() + if(isnull(.) || !owner) + return + + if(!.) + if(bodypart_disabled) + owner.set_usable_hands(owner.usable_hands - 1) + if(owner.stat < UNCONSCIOUS) + to_chat(owner, span_userdanger("You lose control of your [plaintext_zone]!")) + if(held_index) + owner.dropItemToGround(owner.get_item_for_held_index(held_index)) + else if(!bodypart_disabled) + owner.set_usable_hands(owner.usable_hands + 1) + + if(owner.hud_used) + var/atom/movable/screen/inventory/hand/hand_screen_object = owner.hud_used.hand_slots["[held_index]"] + hand_screen_object?.update_appearance() + /obj/item/bodypart/arm/left name = "left arm" desc = "Did you know that the word 'sinister' stems originally from the \ @@ -216,6 +248,7 @@ px_x = -6 px_y = 0 bodypart_trait_source = LEFT_ARM_TRAIT + butcher_replacement = /obj/item/bodypart/arm/left/skeleton/nonfunctional /obj/item/bodypart/arm/left/apply_ownership(mob/living/carbon/new_owner) if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_L_ARM)) @@ -248,25 +281,6 @@ UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_ARM)) RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_ARM), PROC_REF(on_owner_paralysis_gain)) -/obj/item/bodypart/arm/left/set_disabled(new_disabled) - . = ..() - if(isnull(.) || !owner) - return - - if(!.) - if(bodypart_disabled) - owner.set_usable_hands(owner.usable_hands - 1) - if(owner.stat < UNCONSCIOUS) - to_chat(owner, span_userdanger("You lose control of your [plaintext_zone]!")) - if(held_index) - owner.dropItemToGround(owner.get_item_for_held_index(held_index)) - else if(!bodypart_disabled) - owner.set_usable_hands(owner.usable_hands + 1) - - if(owner.hud_used) - var/atom/movable/screen/inventory/hand/hand_screen_object = owner.hud_used.hand_slots["[held_index]"] - hand_screen_object?.update_appearance() - /obj/item/bodypart/arm/left/monkey icon = 'icons/mob/human/species/monkey/bodyparts.dmi' icon_static = 'icons/mob/human/species/monkey/bodyparts.dmi' @@ -316,6 +330,7 @@ px_x = 6 px_y = 0 bodypart_trait_source = RIGHT_ARM_TRAIT + butcher_replacement = /obj/item/bodypart/arm/right/skeleton/nonfunctional /obj/item/bodypart/arm/right/apply_ownership(mob/living/carbon/new_owner) if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_R_ARM)) @@ -348,25 +363,6 @@ UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_ARM)) RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_ARM), PROC_REF(on_owner_paralysis_gain)) -/obj/item/bodypart/arm/right/set_disabled(new_disabled) - . = ..() - if(isnull(.) || !owner) - return - - if(!.) - if(bodypart_disabled) - owner.set_usable_hands(owner.usable_hands - 1) - if(owner.stat < UNCONSCIOUS) - to_chat(owner, span_userdanger("You lose control of your [plaintext_zone]!")) - if(held_index) - owner.dropItemToGround(owner.get_item_for_held_index(held_index)) - else if(!bodypart_disabled) - owner.set_usable_hands(owner.usable_hands + 1) - - if(owner.hud_used) - var/atom/movable/screen/inventory/hand/hand_screen_object = owner.hud_used.hand_slots["[held_index]"] - hand_screen_object?.update_appearance() - /obj/item/bodypart/arm/right/monkey icon = 'icons/mob/human/species/monkey/bodyparts.dmi' icon_static = 'icons/mob/human/species/monkey/bodyparts.dmi' @@ -449,6 +445,19 @@ QDEL_NULL(worn_foot_offset) return ..() +/obj/item/bodypart/leg/set_disabled(new_disabled, update_limbs = TRUE) + . = ..() + if(isnull(.) || !owner || !update_limbs) + return + + if(!.) + if(bodypart_disabled) + owner.set_usable_legs(owner.usable_legs - 1) + if(owner.stat < UNCONSCIOUS) + to_chat(owner, span_userdanger("You lose control of your [plaintext_zone]!")) + else if(!bodypart_disabled) + owner.set_usable_legs(owner.usable_legs + 1) + /obj/item/bodypart/leg/left name = "left leg" desc = "Some athletes prefer to tie their left shoelaces first for good \ @@ -461,6 +470,7 @@ px_y = 12 can_be_disabled = TRUE bodypart_trait_source = LEFT_LEG_TRAIT + butcher_replacement = /obj/item/bodypart/leg/left/skeleton/nonfunctional /obj/item/bodypart/leg/left/apply_ownership(mob/living/carbon/new_owner) if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_L_LEG)) @@ -493,19 +503,6 @@ UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_LEG)) RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_LEG), PROC_REF(on_owner_paralysis_gain)) -/obj/item/bodypart/leg/left/set_disabled(new_disabled) - . = ..() - if(isnull(.) || !owner) - return - - if(!.) - if(bodypart_disabled) - owner.set_usable_legs(owner.usable_legs - 1) - if(owner.stat < UNCONSCIOUS) - to_chat(owner, span_userdanger("You lose control of your [plaintext_zone]!")) - else if(!bodypart_disabled) - owner.set_usable_legs(owner.usable_legs + 1) - /obj/item/bodypart/leg/left/monkey icon = 'icons/mob/human/species/monkey/bodyparts.dmi' icon_static = 'icons/mob/human/species/monkey/bodyparts.dmi' @@ -552,6 +549,7 @@ px_x = 2 px_y = 12 bodypart_trait_source = RIGHT_LEG_TRAIT + butcher_replacement = /obj/item/bodypart/leg/right/skeleton/nonfunctional /obj/item/bodypart/leg/right/apply_ownership(mob/living/carbon/new_owner) if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_R_LEG)) @@ -585,19 +583,6 @@ RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_LEG), PROC_REF(on_owner_paralysis_gain)) -/obj/item/bodypart/leg/right/set_disabled(new_disabled) - . = ..() - if(isnull(.) || !owner) - return - - if(!.) - if(bodypart_disabled) - owner.set_usable_legs(owner.usable_legs - 1) - if(owner.stat < UNCONSCIOUS) - to_chat(owner, span_userdanger("You lose control of your [plaintext_zone]!")) - else if(!bodypart_disabled) - owner.set_usable_legs(owner.usable_legs + 1) - /obj/item/bodypart/leg/right/monkey icon = 'icons/mob/human/species/monkey/bodyparts.dmi' icon_static = 'icons/mob/human/species/monkey/bodyparts.dmi' diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm index 7b8ed90c238..5548d063461 100644 --- a/code/modules/surgery/bodyparts/robot_bodyparts.dm +++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm @@ -44,6 +44,7 @@ damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) disabling_threshold_percentage = 1 bodypart_flags = BODYPART_UNHUSKABLE + butcher_replacement = null /obj/item/bodypart/arm/right/robot name = "cyborg right arm" @@ -79,6 +80,7 @@ damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) bodypart_flags = BODYPART_UNHUSKABLE + butcher_replacement = null /obj/item/bodypart/leg/left/robot name = "cyborg left leg" @@ -114,6 +116,7 @@ damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) bodypart_flags = BODYPART_UNHUSKABLE + butcher_replacement = null /obj/item/bodypart/leg/left/robot/emp_effect(severity, protection) . = ..() @@ -163,6 +166,7 @@ damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) bodypart_flags = BODYPART_UNHUSKABLE + butcher_replacement = null /obj/item/bodypart/leg/right/robot/emp_effect(severity, protection) . = ..() @@ -209,6 +213,7 @@ damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) bodypart_flags = BODYPART_UNHUSKABLE + butcher_replacement = null robotic_emp_paralyze_damage_percent_threshold = 0.6 @@ -387,6 +392,7 @@ head_flags = HEAD_EYESPRITES bodypart_flags = BODYPART_UNHUSKABLE + butcher_replacement = null var/obj/item/assembly/flash/handheld/flash1 = null var/obj/item/assembly/flash/handheld/flash2 = null diff --git a/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm index 765dd8d465b..f3247ab84dc 100644 --- a/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm @@ -11,6 +11,7 @@ head_flags = HEAD_HAIR | HEAD_FACIAL_HAIR | HEAD_DEBRAIN teeth_count = 0 + butcher_replacement = null /obj/item/bodypart/chest/ghost icon = 'icons/mob/human/species/ghost.dmi' @@ -24,6 +25,7 @@ should_draw_greyscale = FALSE dmg_overlay_type = null wing_types = null + butcher_replacement = null /obj/item/bodypart/arm/left/ghost icon = 'icons/mob/human/species/ghost.dmi' @@ -34,6 +36,7 @@ limb_id = SPECIES_GHOST should_draw_greyscale = FALSE dmg_overlay_type = null + butcher_replacement = null /obj/item/bodypart/arm/right/ghost icon = 'icons/mob/human/species/ghost.dmi' @@ -44,3 +47,4 @@ limb_id = SPECIES_GHOST should_draw_greyscale = FALSE dmg_overlay_type = null + butcher_replacement = null diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index 5ded9a2e6e5..113f5f3e936 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -91,6 +91,7 @@ dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage head_flags = HEAD_EYECOLOR | HEAD_EYESPRITES | HEAD_HAIR | HEAD_FACIAL_HAIR + butcher_replacement = null /obj/item/bodypart/chest/jelly biological_state = (BIO_FLESH|BIO_BLOODED) @@ -99,6 +100,7 @@ dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage wing_types = list(/obj/item/organ/wings/functional/slime) + butcher_replacement = null /obj/item/bodypart/chest/jelly/get_butt_sprite() return icon('icons/mob/butts.dmi', BUTT_SPRITE_SLIME) @@ -108,24 +110,28 @@ limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage + butcher_replacement = null /obj/item/bodypart/arm/right/jelly biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage + butcher_replacement = null /obj/item/bodypart/leg/left/jelly biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage + butcher_replacement = null /obj/item/bodypart/leg/right/jelly biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage + butcher_replacement = null ///SLIME /obj/item/bodypart/head/jelly/slime @@ -377,6 +383,8 @@ dmg_overlay_type = null head_flags = NONE bodypart_flags = BODYPART_UNHUSKABLE + scarrable = FALSE + butcher_replacement = null /obj/item/bodypart/chest/skeleton biological_state = BIO_BONE @@ -386,6 +394,8 @@ dmg_overlay_type = null bodypart_flags = BODYPART_UNHUSKABLE wing_types = list(/obj/item/organ/wings/functional/skeleton) + scarrable = FALSE + butcher_replacement = null /obj/item/bodypart/arm/left/skeleton biological_state = (BIO_BONE|BIO_JOINTED) @@ -393,6 +403,8 @@ should_draw_greyscale = FALSE dmg_overlay_type = null bodypart_flags = BODYPART_UNHUSKABLE + scarrable = FALSE + butcher_replacement = null /obj/item/bodypart/arm/right/skeleton biological_state = (BIO_BONE|BIO_JOINTED) @@ -400,6 +412,8 @@ should_draw_greyscale = FALSE dmg_overlay_type = null bodypart_flags = BODYPART_UNHUSKABLE + scarrable = FALSE + butcher_replacement = null /obj/item/bodypart/leg/left/skeleton biological_state = (BIO_BONE|BIO_JOINTED) @@ -407,6 +421,8 @@ should_draw_greyscale = FALSE dmg_overlay_type = null bodypart_flags = BODYPART_UNHUSKABLE + scarrable = FALSE + butcher_replacement = null /obj/item/bodypart/leg/right/skeleton biological_state = (BIO_BONE|BIO_JOINTED) @@ -414,6 +430,44 @@ should_draw_greyscale = FALSE dmg_overlay_type = null bodypart_flags = BODYPART_UNHUSKABLE + scarrable = FALSE + butcher_replacement = null + +/// Degloved bone "limbs" +/obj/item/bodypart/head/skeleton/nonfunctional + limb_id = BODYPART_ID_BONE + // These are always disabled + disabling_threshold_percentage = 0 + +/obj/item/bodypart/chest/skeleton/nonfunctional + limb_id = BODYPART_ID_BONE + disabling_threshold_percentage = 0 + +/obj/item/bodypart/chest/skeleton/nonfunctional/on_adding(mob/living/carbon/new_owner) + . = ..() + // Treat people with bone chests as husks for all purposes for now + // Ideally husking should be per-bodypart but this simplifies a lot of behaviors + new_owner.become_husk(SKELETON_TRAIT) + +/obj/item/bodypart/chest/skeleton/nonfunctional/on_removal(mob/living/carbon/old_owner) + . = ..() + old_owner.cure_husk(SKELETON_TRAIT) + +/obj/item/bodypart/arm/left/skeleton/nonfunctional + limb_id = BODYPART_ID_BONE + disabling_threshold_percentage = 0 + +/obj/item/bodypart/arm/right/skeleton/nonfunctional + limb_id = BODYPART_ID_BONE + disabling_threshold_percentage = 0 + +/obj/item/bodypart/leg/left/skeleton/nonfunctional + limb_id = BODYPART_ID_BONE + disabling_threshold_percentage = 0 + +/obj/item/bodypart/leg/right/skeleton/nonfunctional + limb_id = BODYPART_ID_BONE + disabling_threshold_percentage = 0 ///MUSHROOM /obj/item/bodypart/head/mushroom @@ -496,6 +550,7 @@ teeth_count = 0 brute_modifier = 0.5 burn_modifier = 0.5 + butcher_replacement = null /obj/item/bodypart/head/golem/Initialize(mapload) worn_ears_offset = new( @@ -538,6 +593,7 @@ wing_types = null brute_modifier = 0.5 burn_modifier = 0.5 + butcher_replacement = null /obj/item/bodypart/chest/golem/Initialize(mapload) worn_belt_offset = new( @@ -563,6 +619,7 @@ unarmed_effectiveness = 20 brute_modifier = 0.5 burn_modifier = 0.5 + butcher_replacement = null /obj/item/bodypart/arm/left/golem/Initialize(mapload) held_hand_offset = new( @@ -599,6 +656,7 @@ unarmed_effectiveness = 20 brute_modifier = 0.5 burn_modifier = 0.5 + butcher_replacement = null /obj/item/bodypart/arm/right/golem/Initialize(mapload) held_hand_offset = new( @@ -634,6 +692,7 @@ unarmed_effectiveness = 25 brute_modifier = 0.5 burn_modifier = 0.5 + butcher_replacement = null /obj/item/bodypart/leg/right/golem icon = 'icons/mob/human/species/golems.dmi' diff --git a/code/modules/surgery/operations/_operation.dm b/code/modules/surgery/operations/_operation.dm index aa785b95713..ab8989494fb 100644 --- a/code/modules/surgery/operations/_operation.dm +++ b/code/modules/surgery/operations/_operation.dm @@ -2,23 +2,22 @@ * Attempts to perform a surgery with whatever tool is passed * * * src - the surgeon - * * patient - the mob being operated on + * * operating_on - the main thing being operated on, usually a mob, but can be a detached limb * * 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!") +/mob/living/proc/perform_surgery(atom/movable/operating_on, potential_tool = IMPLEMENT_HAND, intentionally_fail = FALSE, operating_zone = zone_selected) + if(DOING_INTERACTION(src, (HAS_TRAIT(src, TRAIT_HIPPOCRATIC_OATH) ? operating_on : DOAFTER_SOURCE_SURGERY))) + operating_on.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) + var/list/operations = get_available_operations(operating_on, potential_tool, operating_zone) // we failed to undertake any operations? if(!length(operations)) @@ -26,7 +25,7 @@ return NONE var/obj/item/realtool = potential_tool // try self-cauterization if applicable - if(src == patient) + if(src == operating_on) var/manual_cauterization = try_manual_cauterize(realtool) if(manual_cauterization & ITEM_INTERACT_ANY_BLOCKER) return manual_cauterization @@ -34,16 +33,25 @@ 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) + var/obj/item/bodypart/operating = null + if (isbodypart(operating_on)) + operating = operating_on + else if (isliving(operating_on)) + var/mob/living/patient = operating_on + 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!") + if (isliving(operating_on)) + var/mob/living/patient = operating_on + 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]!") else - patient.balloon_alert(src, "nothing to do with [realtool.name]!") + operating.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 @@ -55,29 +63,29 @@ var/picked = show_radial_menu( user = src, - anchor = patient, + anchor = operating_on, choices = radial_operations, require_near = TRUE, autopick_single_option = TRUE, radius = 56, - custom_check = CALLBACK(src, PROC_REF(surgery_check), potential_tool, patient), + custom_check = CALLBACK(src, PROC_REF(surgery_check), potential_tool, operating_on), ) 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/atom/movable/operating = 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) + return picked_op.try_perform(operating, src, potential_tool, op_info) /** - * Returns a list of all surgery operations the mob can currently perform on the patient with the potential tool + * Returns a list of all surgery operations the mob can currently perform on the target with the potential tool * * * src - the surgeon - * * patient - the mob being operated on + * * operating_on - the mob being operated on, or possibly a detached limb * * potential_tool - the tool being used for the operation (can be null / IMPLEMENT_HAND) * * operating_zone - the body zone being operated on * @@ -86,17 +94,17 @@ * * [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) +/mob/living/proc/get_available_operations(atom/movable/operating_on, 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) + SEND_SIGNAL(src, COMSIG_LIVING_OPERATING_ON, operating_on, possible_operations) + SEND_SIGNAL(operating_on, COMSIG_ATOM_BEING_OPERATED_ON, src, 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)) + var/atom/movable/operate_on = operation.get_operation_target(operating_on, operating_zone) + if(!operation.check_availability(isliving(operating_on) ? operating_on : null, 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)) @@ -111,15 +119,16 @@ return operations /// Callback for checking if the surgery radial can be kept open -/mob/living/proc/surgery_check(obj/item/tool, mob/living/patient) +/mob/living/proc/surgery_check(obj/item/tool, atom/movable/operating_on) 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 + return tool == holding.get_proxy_attacker_for(operating_on, 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) @@ -463,13 +472,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) 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)) + if(isnull(operating_on)) return FALSE if(get_tool_quality(tool) <= 0) @@ -478,6 +481,17 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) if(!is_available(operating_on, operated_zone)) return FALSE + if(isnull(patient)) + if(operation_flags & OPERATION_NO_PATIENT_REQUIRED) + return snowflake_check_availability(operating_on, surgeon, tool, operated_zone) + 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 + return snowflake_check_availability(operating_on, surgeon, tool, operated_zone) /** @@ -537,10 +551,15 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) if(any_surgery_states_blocked && has_any_surgery_state(operating_on, any_surgery_states_blocked)) return FALSE + var/mob/living/patient = get_patient(operating_on) + if(isnull(patient)) + if (!(operation_flags & OPERATION_NO_PATIENT_REQUIRED)) + return FALSE + return state_check(operating_on) + 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 @@ -723,11 +742,12 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) // Ignore alllll the penalties (but also all the bonuses) if(!HAS_TRAIT(surgeon, TRAIT_IGNORE_SURGERY_MODIFIERS)) var/mob/living/patient = get_patient(operating_on) - total_mod *= get_location_modifier(get_turf(patient)) total_mod *= get_morbid_modifier(surgeon, tool) - total_mod *= get_mob_surgery_speed_mod(patient) + if(!isnull(patient)) // Some surgeries can lack patients + total_mod *= get_location_modifier(get_turf(patient)) + 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)) + if(operating_on == surgeon && HAS_TRAIT(surgeon, TRAIT_SELF_SURGERY) && !(operation_flags & OPERATION_SELF_OPERABLE)) total_mod *= 1.5 return round(total_mod, 0.01) @@ -766,6 +786,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) /obj/structure/table/optable/abductor = 0.85, /obj/machinery/stasis = 1.15, /obj/structure/bed = 1.5, + /obj/structure/kitchenspike = 1.5, )) var/mod = 2.0 for(var/obj/thingy in operation_turf) @@ -777,19 +798,19 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) * Determines what gets passed into the try_perform() proc * If null is returned, the operation cannot be performed * - * * patient - The mob being operated on + * * operating_on - The mob or detached bodypart 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 +/datum/surgery_operation/proc/get_operation_target(atom/movable/operating_on, body_zone) + return operating_on /** * 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) +/datum/surgery_operation/proc/show_as_next_step(atom/movable/potential_target, operated_zone) + var/atom/movable/operate_on = get_operation_target(potential_target, operated_zone) return !isnull(operate_on) && is_available(operate_on, operated_zone) @@ -809,14 +830,17 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) 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/was_sleeping = FALSE var/result = NONE - update_surgery_mood(patient, SURGERY_STATE_STARTED) - SEND_SIGNAL(patient, COMSIG_LIVING_SURGERY_STARTED, src, operating_on, tool) + if(patient) + was_sleeping = (patient.stat != DEAD && HAS_TRAIT(patient, TRAIT_KNOCKEDOUT)) + update_surgery_mood(patient, SURGERY_STATE_STARTED) + SEND_SIGNAL(patient, COMSIG_ATOM_SURGERY_STARTED, src, operating_on, tool) do operation_args[OPERATION_SPEED] = get_time_modifiers(operating_on, surgeon, tool) @@ -825,20 +849,23 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) 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, + target = patient || operating_on, 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, + interaction_key = HAS_TRAIT(surgeon, TRAIT_HIPPOCRATIC_OATH) ? (patient || operating_on) : DOAFTER_SOURCE_SURGERY, )) result |= ITEM_INTERACT_BLOCKING - update_surgery_mood(patient, SURGERY_STATE_FAILURE) + if (patient) + 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) + var/list/patient_dna = get_patient_blood_dna(patient, operating_on) + if (patient_dna) + if (ishuman(surgeon)) + var/mob/living/carbon/human/surgeon_human = surgeon + surgeon_human.add_blood_DNA_to_items(patient_dna, ITEM_SLOT_GLOVES) + else + surgeon.add_blood_DNA(patient_dna) // This will annoy doctors immensely // if(isitem(tool)) @@ -860,11 +887,13 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) if(operation_args[OPERATION_FORCE_FAIL] || prob(clamp(GET_FAILURE_CHANCE(time, operation_args[OPERATION_SPEED]), 0, 99))) failure(operating_on, surgeon, tool, operation_args) result |= ITEM_INTERACT_FAILURE - update_surgery_mood(patient, SURGERY_STATE_FAILURE) + if (patient) + 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 (patient) + update_surgery_mood(patient, SURGERY_STATE_SUCCESS) if(isstack(tool)) var/obj/item/stack/tool_stack = tool @@ -872,13 +901,27 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) 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) + return result + SEND_SIGNAL(patient, COMSIG_ATOM_SURGERY_FINISHED, src, operating_on, tool) if(patient.stat == DEAD && was_sleeping) surgeon.client?.give_award(/datum/award/achievement/jobs/sandman, surgeon) return result +/datum/surgery_operation/proc/get_patient_blood_dna(mob/living/patient, atom/movable/operating_on) + if (patient) + return patient.get_blood_dna_list() + + if (isbodypart(operating_on)) + var/obj/item/bodypart/limb = operating_on + return limb.blood_dna_info?.Copy() + + if (isorgan(operating_on)) + var/obj/item/organ/guts = operating_on + return guts.blood_dna_info?.Copy() + /// 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) @@ -893,7 +936,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) if(tool_stack.amount <= 0) return FALSE - if(!surgeon.surgery_check(tool, patient)) + if(!surgeon.surgery_check(tool, (operation_flags & OPERATION_NO_PATIENT_REQUIRED) ? patient : (patient || operating_on))) return FALSE if(!check_availability(patient, operating_on, surgeon, tool, operation_args[OPERATION_TARGET_ZONE])) @@ -929,9 +972,15 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) vision_distance = 1, ignored_mobs = target_detailed ? null : target ) + if(target_detailed) return + if (!target) + if (operation_flags & OPERATION_NO_PATIENT_REQUIRED) + return + CRASH("[type] operation attempted to call display_results without a patient, despite requiring one!") + 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."), @@ -947,6 +996,11 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) if(!pain_message) return + if (!target) + if (operation_flags & OPERATION_NO_PATIENT_REQUIRED) + return + CRASH("[type] operation attempted to call display_pain without a patient, despite requiring one!") + // Determine how drunk our patient is var/drunken_patient = target.get_drunk_amount() // Create a probability to ignore the pain based on drunkenness level @@ -1043,7 +1097,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) 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))) + if(preop_time != world.time && (patient != get_patient(operating_on) || !surgeon.Adjacent(patient || operating_on) || !surgeon.is_holding(tool) || !operate_check(patient, operating_on, surgeon, tool, operation_args))) return FALSE play_operation_sound(operating_on, surgeon, tool, preop_sound) @@ -1056,13 +1110,13 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) /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) - + var/atom/movable/display_target = 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]."), + span_notice("You begin to operate on [display_target]..."), + span_notice("[surgeon] begins to operate on [display_target]."), + span_notice("[surgeon] begins to operate on [display_target]."), ) /** @@ -1078,7 +1132,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) 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) + SEND_SIGNAL(surgeon, COMSIG_ATOM_SURGERY_SUCCESS, src, operating_on, tool) play_operation_sound(operating_on, surgeon, tool, success_sound) on_success(operating_on, surgeon, tool, operation_args) @@ -1109,7 +1163,7 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) if(operation_flags & OPERATION_NOTABLE) SSblackbox.record_feedback("tally", "surgeries_failed", 1, type) - SEND_SIGNAL(surgeon, COMSIG_LIVING_SURGERY_FAILED, src, operating_on, tool) + SEND_SIGNAL(surgeon, COMSIG_ATOM_SURGERY_FAILED, src, operating_on, tool) play_operation_sound(operating_on, surgeon, tool, failure_sound) on_failure(operating_on, surgeon, tool, operation_args) @@ -1239,8 +1293,13 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) 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/get_operation_target(atom/movable/operating_on, body_zone) + if (isliving(operating_on)) + var/mob/living/patient = operating_on + return patient.get_bodypart(deprecise_zone(body_zone)) + if (!isbodypart(operating_on)) + return null + return operating_on /datum/surgery_operation/limb/is_available(obj/item/bodypart/limb, operated_zone) SHOULD_NOT_OVERRIDE(TRUE) @@ -1248,10 +1307,9 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) // 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 ..() @@ -1292,8 +1350,15 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) /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_operation_target(atom/movable/operating_on, body_zone) + if (isliving(operating_on)) + var/mob/living/patient = operating_on + return patient.get_organ_by_type(target_type) + + if (!isbodypart(operating_on)) + return null + + return locate(target_type) in operating_on /datum/surgery_operation/organ/get_patient(obj/item/organ/organ) return organ.owner diff --git a/code/modules/surgery/operations/operation_add_limb.dm b/code/modules/surgery/operations/operation_add_limb.dm index 744fdbef880..018e32d1067 100644 --- a/code/modules/surgery/operations/operation_add_limb.dm +++ b/code/modules/surgery/operations/operation_add_limb.dm @@ -10,7 +10,8 @@ ) 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 + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /// 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 @@ -49,7 +50,10 @@ return option -/datum/surgery_operation/prosthetic_replacement/get_operation_target(mob/living/patient, body_zone) +/datum/surgery_operation/prosthetic_replacement/get_operation_target(atom/movable/operating_on, body_zone) + if (!isliving(operating_on)) + return null + var/mob/living/patient = operating_on // We always operate on the chest even if we're targeting left leg or w/e return patient.get_bodypart(BODY_ZONE_CHEST) diff --git a/code/modules/surgery/operations/operation_amputation.dm b/code/modules/surgery/operations/operation_amputation.dm index 48b7962c9ed..100f9f42862 100644 --- a/code/modules/surgery/operations/operation_amputation.dm +++ b/code/modules/surgery/operations/operation_amputation.dm @@ -20,7 +20,8 @@ /obj/item = 'sound/items/handling/surgery/scalpel1.ogg', ) success_sound = 'sound/items/handling/surgery/organ2.ogg' - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED|SURGERY_VESSELS_CLAMPED + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/limb/amputate/get_recommended_tool() return TOOL_SAW diff --git a/code/modules/surgery/operations/operation_bioware.dm b/code/modules/surgery/operations/operation_bioware.dm index 1ea6f7ea820..084ba2903c6 100644 --- a/code/modules/surgery/operations/operation_bioware.dm +++ b/code/modules/surgery/operations/operation_bioware.dm @@ -247,9 +247,25 @@ 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 + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_NOTABLE | OPERATION_MORBID | OPERATION_LOCKED | OPERATION_NO_PATIENT_REQUIRED + status_effect_gained = /datum/status_effect/bioware/cortex // Not actually applied, simply for compatibility checks required_zone = BODY_ZONE_HEAD +/datum/surgery_operation/limb/bioware/cortex_folding/state_check(obj/item/bodypart/limb) + . = ..() + if (!.) + return + var/obj/item/organ/brain/brain = locate() in limb + if(isnull(brain)) + return FALSE + return !HAS_TRAIT_FROM(brain, TRAIT_SPECIAL_TRAUMA_BOOST, BIOWARE_TRAIT) + +/datum/surgery_operation/limb/bioware/cortex_folding/on_success(obj/item/bodypart/limb, mob/living/surgeon, tool, list/operation_args) + . = ..() + var/obj/item/organ/brain/brain = locate() in limb + if(!isnull(brain)) + ADD_TRAIT(brain, TRAIT_SPECIAL_TRAUMA_BOOST, BIOWARE_TRAIT) + /datum/surgery_operation/limb/bioware/cortex_folding/on_preop(obj/item/bodypart/limb, mob/living/surgeon, tool) display_results( surgeon, @@ -272,7 +288,8 @@ display_pain(limb.owner, "Your brain feels stronger... and more flexible!") /datum/surgery_operation/limb/bioware/cortex_folding/on_failure(obj/item/bodypart/limb, mob/living/surgeon, tool) - if(!limb.owner.get_organ_slot(ORGAN_SLOT_BRAIN)) + var/obj/item/organ/brain/brain = locate() in limb + if(isnull(brain)) return ..() display_results( surgeon, @@ -282,8 +299,8 @@ span_notice("[surgeon] completes the surgery on [limb.owner]'s brain."), ) display_pain(limb.owner, "Your head throbs with excruciating pain!") - limb.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 60) - limb.owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) + brain.apply_organ_damage(60) + brain.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)" diff --git a/code/modules/surgery/operations/operation_bone_repair.dm b/code/modules/surgery/operations/operation_bone_repair.dm index 47ec6dbf44f..2129d1b969e 100644 --- a/code/modules/surgery/operations/operation_bone_repair.dm +++ b/code/modules/surgery/operations/operation_bone_repair.dm @@ -1,7 +1,7 @@ /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 + operation_flags = OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED implements = list( TOOL_BONESET = 1, /obj/item/stack/medical/bone_gel = 1, @@ -27,9 +27,9 @@ 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]."), + span_notice("You begin to repair the fracture in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to repair the fracture in [FORMAT_LIMB_OWNER(limb)] with [tool]."), + span_notice("[surgeon] begins to repair the fracture in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "Your [limb.plaintext_zone] aches with pain!") @@ -40,15 +40,15 @@ 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]!"), + span_notice("You successfully repair the fracture in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully repairs the fracture in [FORMAT_LIMB_OWNER(limb)]!"), + span_notice("[surgeon] successfully repairs the fracture in [FORMAT_LIMB_OWNER(limb)]!"), ) /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 + operation_flags = OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED implements = list( TOOL_BONESET = 1, /obj/item/stack/sticky_tape/surgical = 1.66, @@ -56,7 +56,8 @@ /obj/item/stack/sticky_tape = 5, ) time = 6 SECONDS - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/limb/reset_compound/get_default_radial_image() return image(/obj/item/bonesetter) @@ -74,9 +75,9 @@ 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]."), + span_notice("You begin to reset the bone in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to reset the bone in [FORMAT_LIMB_OWNER(limb)] with [tool]."), + span_notice("[surgeon] begins to reset the bone in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "The aching pain in your [limb.plaintext_zone] is overwhelming!") @@ -87,15 +88,15 @@ 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]!"), + span_notice("You successfully reset the bone in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully resets the bone in [FORMAT_LIMB_OWNER(limb)] with [tool]!"), + span_notice("[surgeon] successfully resets the bone in [FORMAT_LIMB_OWNER(limb)]!"), ) /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 + operation_flags = OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED implements = list( /obj/item/stack/medical/bone_gel = 1, /obj/item/stack/sticky_tape/surgical = 1, @@ -121,9 +122,9 @@ 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]."), + span_notice("You begin to repair the fracture in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to repair the fracture in [FORMAT_LIMB_OWNER(limb)] with [tool]."), + span_notice("[surgeon] begins to repair the fracture in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "The aching pain in your [limb.plaintext_zone] is overwhelming!") @@ -133,15 +134,15 @@ 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]!"), + span_notice("You successfully repair the fracture in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully repairs the fracture in [FORMAT_LIMB_OWNER(limb)] with [tool]!"), + span_notice("[surgeon] successfully repairs the fracture in [FORMAT_LIMB_OWNER(limb)]!"), ) /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 + operation_flags = OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED implements = list( TOOL_HEMOSTAT = 1, TOOL_WIRECUTTER = 2.5, @@ -166,9 +167,9 @@ 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]..."), + span_notice("You begin to discard the smaller skull debris in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to discard the smaller skull debris in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to poke around in [FORMAT_LIMB_OWNER(limb)]..."), ) display_pain(limb.owner, "Your brain feels like it's getting stabbed by little shards of glass!") @@ -180,7 +181,7 @@ /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 + operation_flags = OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED implements = list( /obj/item/stack/medical/bone_gel = 1, /obj/item/stack/sticky_tape/surgical = 1, @@ -205,9 +206,9 @@ 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."), + span_notice("You begin to repair [limb.owner || limb]'s skull as best you can..."), + span_notice("[surgeon] begins to repair [limb.owner || limb]'s skull with [tool]."), + span_notice("[surgeon] begins to repair [limb.owner || limb]'s skull."), ) display_pain(limb.owner, "You can feel pieces of your skull rubbing against your brain!") @@ -219,7 +220,7 @@ 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.") + span_notice("You successfully repair [limb.owner || limb]'s skull."), + span_notice("[surgeon] successfully repairs [limb.owner || limb]'s skull with [tool]."), + span_notice("[surgeon] successfully repairs [limb.owner || limb]'s skull.") ) diff --git a/code/modules/surgery/operations/operation_brainwash.dm b/code/modules/surgery/operations/operation_brainwash.dm index eece83b3142..2f74ce14602 100644 --- a/code/modules/surgery/operations/operation_brainwash.dm +++ b/code/modules/surgery/operations/operation_brainwash.dm @@ -73,7 +73,7 @@ span_notice("[surgeon] completes the surgery on [organ.owner]'s brain."), ) display_pain(organ.owner, "Your head throbs with horrible pain!") - organ.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 40) + organ.apply_organ_damage(40) /datum/surgery_operation/organ/brainwash/mechanic name = "reprogram" diff --git a/code/modules/surgery/operations/operation_cavity_implant.dm b/code/modules/surgery/operations/operation_cavity_implant.dm index 8c54a19eed1..c6032e93403 100644 --- a/code/modules/surgery/operations/operation_cavity_implant.dm +++ b/code/modules/surgery/operations/operation_cavity_implant.dm @@ -24,9 +24,9 @@ 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."), + span_notice("You begin to open [FORMAT_LIMB_OWNER(limb)] cavity wide..."), + span_notice("[surgeon] begins to open [FORMAT_LIMB_OWNER(limb)] cavity wide."), + span_notice("[surgeon] begins to open [FORMAT_LIMB_OWNER(limb)] cavity wide."), ) display_pain(limb.owner, "You can feel pressure as your [limb.plaintext_zone] is being opened wide!") @@ -91,9 +91,9 @@ 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]."), + span_notice("You begin to insert [tool] into [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to insert [tool] into [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to insert [tool.w_class > WEIGHT_CLASS_SMALL ? tool : "something"] into [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You can feel something being inserted into your [limb.plaintext_zone], it hurts like hell!") @@ -113,9 +113,9 @@ 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]."), + span_notice("You stuff [tool] into [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] stuffs [tool] into [FORMAT_LIMB_OWNER(limb)]!"), + span_notice("[surgeon] stuffs [tool.w_class > WEIGHT_CLASS_SMALL ? tool : "something"] into [FORMAT_LIMB_OWNER(limb)]."), ) @@ -163,9 +163,9 @@ 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]."), + span_notice("You begin to extract [limb.cavity_item] from [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to extract [limb.cavity_item] from [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to extract something from [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a serious pain in your [limb.plaintext_zone]!") @@ -174,9 +174,9 @@ 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]."), + span_warning("You find nothing to remove from [FORMAT_LIMB_OWNER(limb)]."), + span_warning("[surgeon] finds nothing to remove from [FORMAT_LIMB_OWNER(limb)]."), + span_warning("[surgeon] finds nothing to remove from [FORMAT_LIMB_OWNER(limb)]."), ) return @@ -186,9 +186,9 @@ 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]!"), + span_notice("You pull [implant] out of [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] pulls [implant] out of [FORMAT_LIMB_OWNER(limb)]!"), + span_notice("[surgeon] pulls something out of [FORMAT_LIMB_OWNER(limb)]!"), ) display_pain(limb.owner, "You can feel [implant.name] being pulled out of you!") surgeon.put_in_hands(implant) diff --git a/code/modules/surgery/operations/operation_debride.dm b/code/modules/surgery/operations/operation_debride.dm index 5283a836af8..de58e19a7c3 100644 --- a/code/modules/surgery/operations/operation_debride.dm +++ b/code/modules/surgery/operations/operation_debride.dm @@ -9,7 +9,7 @@ TOOL_WIRECUTTER = 2.5, ) time = 3 SECONDS - operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_LOOPING | OPERATION_PRIORITY_NEXT_STEP + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_LOOPING | OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED preop_sound = list( TOOL_SCALPEL = 'sound/items/handling/surgery/scalpel1.ogg', TOOL_HEMOSTAT = 'sound/items/handling/surgery/hemostat1.ogg', @@ -56,9 +56,9 @@ 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]."), + span_notice("You begin to excise infected flesh from [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to excise infected flesh from [FORMAT_LIMB_OWNER(limb)] with [tool]."), + span_notice("[surgeon] begins to excise infected flesh from [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "The infection in your [limb.plaintext_zone] stings like hell! It feels like you're being stabbed!") @@ -70,17 +70,17 @@ 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]!"), + span_notice("You successfully excise some of the infected flesh from [FORMAT_LIMB_OWNER(limb)][get_progress(wound)]."), + span_notice("[surgeon] successfully excises some of the infected flesh from [FORMAT_LIMB_OWNER(limb)] with [tool]!"), + span_notice("[surgeon] successfully excises some of the infected flesh from [FORMAT_LIMB_OWNER(limb)]!"), ) /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]!"), + span_notice("You carve away some of the healthy flesh from [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] carves away some of the healthy flesh from [FORMAT_LIMB_OWNER(limb)] with [tool]!"), + span_notice("[surgeon] carves away some of the healthy flesh from [FORMAT_LIMB_OWNER(limb)]!"), ) 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 index a572520a6ba..413711caa2c 100644 --- a/code/modules/surgery/operations/operation_dental.dm +++ b/code/modules/surgery/operations/operation_dental.dm @@ -1,11 +1,13 @@ /datum/surgery_operation/limb/add_dental_implant name = "add dental implant" desc = "Implant a pill into a patient's teeth." + operation_flags = OPERATION_NO_PATIENT_REQUIRED implements = list( /obj/item/reagent_containers/applicator/pill = 1, ) time = 1.6 SECONDS - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_DRILLED + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_DRILLED + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/limb/add_dental_implant/all_required_strings() . = list() @@ -36,9 +38,9 @@ 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]."), + span_notice("You begin to wedge [tool] in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to wedge \the [tool] in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to wedge something in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "Something's being jammed into your [limb.plaintext_zone]!") @@ -54,20 +56,22 @@ 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]!"), + span_notice("You wedge [tool] into [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] wedges [tool] into [FORMAT_LIMB_OWNER(limb)]!"), + span_notice("[surgeon] wedges something into [FORMAT_LIMB_OWNER(limb)]!"), ) /datum/surgery_operation/limb/remove_dental_implant name = "remove dental implant" desc = "Remove a dental implant from a patient's teeth." + operation_flags = OPERATION_NO_PATIENT_REQUIRED 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 + all_surgery_states_required = SURGERY_BONE_DRILLED|SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/limb/remove_dental_implant/get_default_radial_image() return image(/obj/item/reagent_containers/applicator/pill) @@ -85,9 +89,9 @@ 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."), + span_notice("You begin looking in [limb.owner || limb]'s mouth for dental implants..."), + span_notice("[surgeon] begins to look in [limb.owner || limb]'s mouth."), + span_notice("[surgeon] begins to examine [limb.owner || limb]'s teeth."), ) display_pain(limb.owner, "You feel fingers poke around at your teeth.") @@ -99,9 +103,9 @@ 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]."), + span_notice("You don't find any dental implants in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] doesn't find any dental implants in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] finishes examining [FORMAT_LIMB_OWNER(limb)]."), ) return @@ -114,9 +118,9 @@ 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]."), + span_notice("You carefully remove [yoinked] from [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] carefully removes [yoinked] from [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] carefully removes something from [FORMAT_LIMB_OWNER(limb)]."), ) // Teeth pill code diff --git a/code/modules/surgery/operations/operation_filter.dm b/code/modules/surgery/operations/operation_filter.dm index 539f43b8755..44fe4be1dfd 100644 --- a/code/modules/surgery/operations/operation_filter.dm +++ b/code/modules/surgery/operations/operation_filter.dm @@ -7,7 +7,8 @@ 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 + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/limb/filter_blood/all_required_strings() . = list() diff --git a/code/modules/surgery/operations/operation_generic.dm b/code/modules/surgery/operations/operation_generic.dm index 762761ab7c6..c41a64e4b4b 100644 --- a/code/modules/surgery/operations/operation_generic.dm +++ b/code/modules/surgery/operations/operation_generic.dm @@ -19,7 +19,7 @@ time = 1.6 SECONDS preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' success_sound = 'sound/items/handling/surgery/scalpel2.ogg' - operation_flags = OPERATION_AFFECTS_MOOD + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_NO_PATIENT_REQUIRED any_surgery_states_blocked = ALL_SURGERY_SKIN_STATES /// We can't cut mobs with this biostate var/biostate_blacklist = BIO_CHITIN @@ -46,9 +46,9 @@ 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]."), + span_notice("You begin to make an incision in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to make an incision in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to make an incision in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a stabbing in your [limb.plaintext_zone].") @@ -58,13 +58,19 @@ if(!limb.can_bleed()) return - var/blood_name = limb.owner.get_bloodtype()?.get_blood_name() || "Blood" + var/blood_name = limb.owner?.get_bloodtype()?.get_blood_name() + if(!blood_name && length(limb.blood_dna_info)) + var/datum/blood_type/blood_type = limb.blood_dna_info[limb.blood_dna_info[1]] + blood_name = blood_type?.get_blood_name() + if(!blood_name) + blood_name = "Blood" + display_results( surgeon, limb.owner, - span_notice("[blood_name] pools around the incision in [limb.owner]'s [limb.plaintext_zone]."), - span_notice("[blood_name] pools around the incision in [limb.owner]'s [limb.plaintext_zone]."), - span_notice("[blood_name] pools around the incision in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("[blood_name] pools around the incision in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[blood_name] pools around the incision in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[blood_name] pools around the incision in [FORMAT_LIMB_OWNER(limb)]."), ) /// Subtype for thick skinned creatures (Xenomorphs) @@ -95,6 +101,7 @@ name = "retract skin" desc = "Retract the patient's skin to access their internal organs. \ Causes \"skin open\" surgical state." + operation_flags = OPERATION_NO_PATIENT_REQUIRED required_bodytype = ~BODYTYPE_ROBOTIC replaced_by = /datum/surgery_operation/limb/retract_skin/abductor implements = list( @@ -102,6 +109,7 @@ TOOL_SCREWDRIVER = 2.25, TOOL_WIRECUTTER = 2.85, /obj/item/stack/rods = 2.85, + /obj/item/kitchen/fork = 2.85, ) time = 2.4 SECONDS preop_sound = 'sound/items/handling/surgery/retractor1.ogg' @@ -115,9 +123,9 @@ 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]."), + span_notice("You begin to retract the skin in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to retract the skin in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to retract the skin in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a severe stinging pain spreading across your [limb.plaintext_zone] as the skin is pulled back.") @@ -136,6 +144,7 @@ desc = "Mend the incision in the patient's skin, closing it up. \ Clears most surgical states." required_bodytype = ~BODYTYPE_ROBOTIC + operation_flags = OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED replaced_by = /datum/surgery_operation/limb/close_skin/abductor implements = list( TOOL_CAUTERY = 1, @@ -181,9 +190,9 @@ 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]."), + span_notice("You begin to mend the incision in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to mend the incision in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to mend the incision in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "Your [limb.plaintext_zone] is being [istype(tool, /obj/item/stack/medical/suture) ? "pinched" : "burned"]!") @@ -222,9 +231,9 @@ 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]."), + span_notice("You begin to clamp bleeders in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to clamp bleeders in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to clamp bleeders in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a pinch as the bleeding in your [limb.plaintext_zone] is slowed.") @@ -246,6 +255,7 @@ desc = "Unclamp blood vessels in the patient's body to allow blood flow again. \ Clears \"vessels clamped\" surgical state." required_bodytype = ~BODYTYPE_ROBOTIC + operation_flags = OPERATION_PRIORITY_NEXT_STEP replaced_by = /datum/surgery_operation/limb/unclamp_bleeders/abductor implements = list( TOOL_HEMOSTAT = 1, @@ -270,9 +280,9 @@ 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]."), + span_notice("You begin to unclamp bleeders in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to unclamp bleeders in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to unclamp bleeders in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a pressure release as blood starts flowing in your [limb.plaintext_zone] again.") @@ -310,7 +320,7 @@ /obj/item = 'sound/items/handling/surgery/scalpel1.ogg', ) success_sound = 'sound/items/handling/surgery/organ2.ogg' - operation_flags = OPERATION_AFFECTS_MOOD + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_NO_PATIENT_REQUIRED all_surgery_states_required = SURGERY_SKIN_OPEN any_surgery_states_blocked = SURGERY_BONE_SAWED|SURGERY_BONE_DRILLED @@ -328,9 +338,9 @@ 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]."), + span_notice("You begin to saw through the bone in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to saw through the bone in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to saw through the bone in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a horrid ache spread through the inside of your [limb.plaintext_zone]!") @@ -341,9 +351,9 @@ 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!"), + span_notice("You saw [FORMAT_LIMB_OWNER(limb)] open."), + span_notice("[surgeon] saws [FORMAT_LIMB_OWNER(limb)] open!"), + span_notice("[surgeon] saws [FORMAT_LIMB_OWNER(limb)] open!"), ) display_pain(limb.owner, "It feels like something just broke in your [limb.plaintext_zone]!") @@ -353,6 +363,7 @@ desc = "Repair a patient's cut or broken bones. \ Clears \"bone sawed\" and \"bone drilled\" surgical states." required_bodytype = ~BODYTYPE_ROBOTIC + operation_flags = OPERATION_PRIORITY_NEXT_STEP implements = list( /obj/item/stack/medical/bone_gel = 1, /obj/item/stack/sticky_tape/surgical = 1, @@ -382,9 +393,9 @@ 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]."), + span_notice("You begin to fix the bones in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to fix the bones in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to fix the bones in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a grinding sensation in your [limb.plaintext_zone] as the bones are set back in place.") @@ -398,6 +409,7 @@ desc = "Drill through a patient's bones. \ Causes \"bone drilled\" surgical state." required_bodytype = ~BODYTYPE_ROBOTIC + operation_flags = OPERATION_PRIORITY_NEXT_STEP implements = list( TOOL_DRILL = 1, /obj/item/screwdriver/power = 1.25, @@ -426,9 +438,9 @@ 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]."), + span_notice("You begin to drill into the bone in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to drill into the bone in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to drill into the bone in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a horrible piercing pain in your [limb.plaintext_zone]!") @@ -438,9 +450,9 @@ 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]!"), + span_notice("You drill into [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] drills into [FORMAT_LIMB_OWNER(limb)]!"), + span_notice("[surgeon] drills into [FORMAT_LIMB_OWNER(limb)]!"), ) /datum/surgery_operation/limb/incise_organs @@ -448,6 +460,7 @@ 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 + operation_flags = OPERATION_PRIORITY_NEXT_STEP replaced_by = /datum/surgery_operation/limb/incise_organs/abductor implements = list( TOOL_SCALPEL = 1, @@ -477,9 +490,9 @@ 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]."), + span_notice("You begin to make an incision in the organs within [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to make an incision in the organs within [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to make an incision in the organs within [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a stabbing in your [limb.plaintext_zone].") @@ -490,9 +503,9 @@ 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]!"), + span_notice("You make an incision in the organs within [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] makes an incision in the organs within [FORMAT_LIMB_OWNER(limb)]!"), + span_notice("[surgeon] makes an incision in the organs within [FORMAT_LIMB_OWNER(limb)]!"), ) display_pain(limb.owner, "You feel a sharp pain from inside your [limb.plaintext_zone]!") diff --git a/code/modules/surgery/operations/operation_generic_mechanic.dm b/code/modules/surgery/operations/operation_generic_mechanic.dm index f8e4ec33b9a..bbcdfd55fd2 100644 --- a/code/modules/surgery/operations/operation_generic_mechanic.dm +++ b/code/modules/surgery/operations/operation_generic_mechanic.dm @@ -10,7 +10,7 @@ /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 + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC | OPERATION_NO_PATIENT_REQUIRED required_bodytype = BODYTYPE_ROBOTIC time = 2.4 SECONDS preop_sound = 'sound/items/tools/screwdriver.ogg' @@ -31,9 +31,9 @@ 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]."), + span_notice("You begin to unscrew the shell of [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to unscrew the shell of [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to unscrew the shell of [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel your [limb.plaintext_zone] grow numb as the shell is unscrewed.", TRUE) @@ -51,7 +51,7 @@ IMPLEMENT_HAND = 1, TOOL_CROWBAR = 1, ) - operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC | OPERATION_NO_PATIENT_REQUIRED time = 1 SECONDS preop_sound = 'sound/items/tools/ratchet.ogg' success_sound = 'sound/machines/airlock/doorclick.ogg' @@ -64,9 +64,9 @@ 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]."), + span_notice("You begin to open the hatch holders in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to open the hatch holders in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to open the hatch holders in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "The last faint pricks of tactile sensation fade from your [limb.plaintext_zone] as the hatch is opened.", TRUE) @@ -88,7 +88,7 @@ /obj/item/knife = 2, /obj/item = 10, ) - operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC | OPERATION_NO_PATIENT_REQUIRED time = 2.4 SECONDS preop_sound = 'sound/items/tools/screwdriver.ogg' success_sound = 'sound/items/tools/screwdriver2.ogg' @@ -111,9 +111,9 @@ 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]."), + span_notice("You begin to screw the shell of [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to screw the shell of [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to screw the shell of [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel the faint pricks of sensation return as your [limb.plaintext_zone]'s shell is screwed in.", TRUE) @@ -131,7 +131,7 @@ TOOL_MULTITOOL = 1, TOOL_HEMOSTAT = 1.33, ) - operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC | OPERATION_NO_PATIENT_REQUIRED time = 2.4 SECONDS preop_sound = 'sound/items/taperecorder/tape_flip.ogg' success_sound = 'sound/items/taperecorder/taperecorder_close.ogg' @@ -145,9 +145,9 @@ 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]."), + span_notice("You begin to prepare electronics in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to prepare electronics in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to prepare electronics in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You can feel a faint buzz in your [limb.plaintext_zone] as the electronics reboot.", TRUE) @@ -165,7 +165,7 @@ TOOL_WRENCH = 1, TOOL_RETRACTOR = 1.33, ) - operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC + operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC | OPERATION_NO_PATIENT_REQUIRED time = 2.4 SECONDS preop_sound = 'sound/items/tools/ratchet.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN @@ -178,9 +178,9 @@ 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]."), + span_notice("You begin to unwrench some bolts in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to unwrench some bolts in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to unwrench some bolts in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to loosen.", TRUE) @@ -201,7 +201,7 @@ operation_flags = OPERATION_SELF_OPERABLE | OPERATION_MECHANIC time = 2.4 SECONDS preop_sound = 'sound/items/tools/ratchet.ogg' - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED | OPERATION_NO_PATIENT_REQUIRED /datum/surgery_operation/limb/mechanic_wrench/state_check(obj/item/bodypart/limb) return LIMB_HAS_BONES(limb) @@ -216,9 +216,9 @@ 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]."), + span_notice("You begin to wrench some bolts in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to wrench some bolts in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to wrench some bolts in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a jostle in your [limb.plaintext_zone] as the bolts begin to tighten.", TRUE) diff --git a/code/modules/surgery/operations/operation_implant_removal.dm b/code/modules/surgery/operations/operation_implant_removal.dm index 3b4dc93b632..48b5ab9c91e 100644 --- a/code/modules/surgery/operations/operation_implant_removal.dm +++ b/code/modules/surgery/operations/operation_implant_removal.dm @@ -10,7 +10,8 @@ ) time = 6.4 SECONDS success_sound = 'sound/items/handling/surgery/hemostat1.ogg' - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/basic/implant_removal/get_default_radial_image() return image('icons/obj/medical/syringe.dmi', "implantcase-b") diff --git a/code/modules/surgery/operations/operation_lipo.dm b/code/modules/surgery/operations/operation_lipo.dm index 05dff033868..b04e856057f 100644 --- a/code/modules/surgery/operations/operation_lipo.dm +++ b/code/modules/surgery/operations/operation_lipo.dm @@ -19,7 +19,8 @@ /obj/item = 'sound/items/handling/surgery/scalpel1.ogg', ) success_sound = 'sound/items/handling/surgery/organ2.ogg' - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/limb/lipoplasty/get_any_tool() return "Any sharp edged item" diff --git a/code/modules/surgery/operations/operation_lobotomy.dm b/code/modules/surgery/operations/operation_lobotomy.dm index 66d64e1cd4b..903f7e29958 100644 --- a/code/modules/surgery/operations/operation_lobotomy.dm +++ b/code/modules/surgery/operations/operation_lobotomy.dm @@ -3,7 +3,7 @@ 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 + operation_flags = OPERATION_MORBID | OPERATION_AFFECTS_MOOD | OPERATION_LOCKED | OPERATION_NOTABLE | OPERATION_NO_PATIENT_REQUIRED implements = list( TOOL_SCALPEL = 1.15, /obj/item/melee/energy/sword = 0.55, @@ -16,7 +16,8 @@ preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' success_sound = 'sound/items/handling/surgery/scalpel2.ogg' failure_sound = 'sound/items/handling/surgery/organ2.ogg' - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_SAWED + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/organ/lobotomy/get_any_tool() return "Any sharp edged item" @@ -29,9 +30,9 @@ 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."), + span_notice("You begin to perform a lobotomy on [FORMAT_ORGAN_OWNER(organ)]'s brain..."), + span_notice("[surgeon] begins to perform a lobotomy on [FORMAT_ORGAN_OWNER(organ)]'s brain."), + span_notice("[surgeon] begins to perform surgery on [FORMAT_ORGAN_OWNER(organ)]'s brain."), ) display_pain(organ.owner, "Your head pounds with unimaginable pain!") @@ -39,21 +40,26 @@ 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."), + span_notice("You successfully perform a lobotomy on [FORMAT_ORGAN_OWNER(organ)]!"), + span_notice("[surgeon] successfully lobotomizes [FORMAT_ORGAN_OWNER(organ)]!"), + span_notice("[surgeon] finishes performing surgery on [FORMAT_ORGAN_OWNER(organ)]'s brain."), ) display_pain(organ.owner, "Your head goes totally numb for a moment, the pain is overwhelming!") organ.cure_all_traumas(TRAUMA_RESILIENCE_LOBOTOMY) - organ.owner.mind?.remove_antag_datum(/datum/antagonist/brainwashed) + if (organ.owner) + organ.owner.mind?.remove_antag_datum(/datum/antagonist/brainwashed) + else if (organ.brainmob) + organ.brainmob.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)) + if(HAS_TRAIT(organ, 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) @@ -65,21 +71,21 @@ 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."), + span_notice("[surgeon] unsuccessfully attempts to lobotomize [FORMAT_ORGAN_OWNER(organ)]!"), + span_notice("[surgeon] completes the surgery on [FORMAT_ORGAN_OWNER(organ)]'s brain."), ) display_pain(organ.owner, "The pain in your head only seems to get worse!") organ.apply_organ_damage(80) switch(rand(1, 3)) if(1) - organ.owner.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_MAGIC) + organ.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) + if(HAS_TRAIT(organ, TRAIT_SPECIAL_TRAUMA_BOOST) && prob(50)) + organ.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) else - organ.owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_MAGIC) + organ.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_MAGIC) if(3) - organ.owner.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) + organ.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, TRAUMA_RESILIENCE_MAGIC) /datum/surgery_operation/organ/lobotomy/mechanic name = "execute neural defragging" diff --git a/code/modules/surgery/operations/operation_organ_manip.dm b/code/modules/surgery/operations/operation_organ_manip.dm index 2c72e659a19..fb2879b714d 100644 --- a/code/modules/surgery/operations/operation_organ_manip.dm +++ b/code/modules/surgery/operations/operation_organ_manip.dm @@ -4,7 +4,7 @@ /datum/surgery_operation/limb/organ_manipulation name = "organ manipulation" abstract_type = /datum/surgery_operation/limb/organ_manipulation - operation_flags = OPERATION_MORBID | OPERATION_NOTABLE + operation_flags = OPERATION_MORBID | OPERATION_NOTABLE | OPERATION_NO_PATIENT_REQUIRED required_bodytype = ~BODYTYPE_ROBOTIC /// Radial slice datums for every organ type we can manipulate VAR_PRIVATE/list/cached_organ_manipulation_options @@ -109,7 +109,7 @@ 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." + option.info = "Remove [initial(organ.name)] from the [limb.owner ? "patient" : "limb"]." LAZYSET(cached_organ_manipulation_options, "[organ.type]_remove", option) options[option] = list("[OPERATION_ACTION]" = "remove", "[OPERATION_REMOVED_ORGAN]" = organ) @@ -123,7 +123,7 @@ 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." + option.info = "insert [initial(organ.name)] into the [limb.owner ? "patient" : "limb"]." LAZYSET(cached_organ_manipulation_options, "[organ.type]_insert", option) var/list/result = list() @@ -155,9 +155,9 @@ 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]."), + span_notice("You begin to remove [organ.name] from [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to remove [organ.name] from [limb.owner || limb]."), + span_notice("[surgeon] begins to remove something from [limb.owner || limb]."), ) display_pain(limb.owner, "You feel a tugging sensation in your [limb.plaintext_zone]!") if("insert") @@ -165,9 +165,9 @@ 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]."), + span_notice("You begin to insert [tool.name] into [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to insert [tool.name] into [limb.owner || limb]."), + span_notice("[surgeon] begins to insert something into [limb.owner || limb]."), ) display_pain(limb.owner, "You can feel something being placed in your [limb.plaintext_zone]!") @@ -186,27 +186,33 @@ 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]!"), + span_notice("You successfully extract [organ.name] from [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully extracts [organ.name] from [FORMAT_LIMB_OWNER(limb)]!"), + span_notice("[surgeon] successfully extracts something from [FORMAT_LIMB_OWNER(limb)]!"), ) display_pain(limb.owner, "Your [limb.plaintext_zone] throbs with pain, you can't feel your [organ.name] anymore!") - log_combat(surgeon, limb.owner, "surgically removed [organ.name] from") - organ.Remove(limb.owner) - organ.forceMove(limb.owner.drop_location()) + log_combat(surgeon, limb.owner || limb, "surgically removed [organ.name] from") + if (limb.owner) + organ.Remove(limb.owner) + else + organ.bodypart_remove(limb) + organ.forceMove(limb.owner ? limb.owner.drop_location() : limb.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) + if (limb.owner) + organ.Insert(limb.owner) + else + organ.bodypart_insert(limb) 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]."), + span_notice("You successfully insert [organ.name] into [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully inserts [organ.name] into [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully inserts something into [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "Your [limb.plaintext_zone] throbs with pain as your new [organ.name] comes to life!") @@ -246,7 +252,8 @@ /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 + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED 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) @@ -257,7 +264,8 @@ 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 + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/limb/organ_manipulation/external/organ_check(obj/item/bodypart/limb, obj/item/organ/organ) return (organ.organ_flags & ORGAN_EXTERNAL) @@ -277,6 +285,7 @@ /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 + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED #undef OPERATION_REMOVED_ORGAN diff --git a/code/modules/surgery/operations/operation_organ_repair.dm b/code/modules/surgery/operations/operation_organ_repair.dm index 2fa6a8d6862..b4444ba1c4a 100644 --- a/code/modules/surgery/operations/operation_organ_repair.dm +++ b/code/modules/surgery/operations/operation_organ_repair.dm @@ -4,7 +4,7 @@ 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 + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_NOTABLE | OPERATION_NO_PATIENT_REQUIRED 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 @@ -65,9 +65,9 @@ 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]."), + span_notice("You begin to make an incision in [FORMAT_ORGAN_OWNER(organ)]'s lungs..."), + span_notice("[surgeon] begins to make an incision in [FORMAT_ORGAN_OWNER(organ)]."), + span_notice("[surgeon] begins to make an incision in [FORMAT_ORGAN_OWNER(organ)]."), ) display_pain(organ.owner, "You feel a stabbing pain in your chest!") @@ -76,18 +76,18 @@ 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."), + span_notice("You successfully excise [FORMAT_ORGAN_OWNER(organ)]'s most damaged lobe."), + span_notice("[surgeon] successfully excises [FORMAT_ORGAN_OWNER(organ)]'s most damaged lobe."), + span_notice("[surgeon] successfully excises [FORMAT_ORGAN_OWNER(organ)]'s most damaged lobe."), ) /datum/surgery_operation/organ/repair/lobectomy/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) . = ..() - organ.owner.losebreath += 4 + organ.owner?.losebreath += 4 display_results( surgeon, organ.owner, - span_warning("You screw up, failing to excise [organ.owner]'s damaged lobe!"), + span_warning("You screw up, failing to excise [FORMAT_ORGAN_OWNER(organ)]'s damaged lobe!"), span_warning("[surgeon] screws up!"), span_warning("[surgeon] screws up!"), ) @@ -130,9 +130,9 @@ 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]."), + span_notice("You begin to cut out a damaged piece of [FORMAT_ORGAN_OWNER(organ)]'s liver..."), + span_notice("[surgeon] begins to make an incision in [FORMAT_ORGAN_OWNER(organ)]."), + span_notice("[surgeon] begins to make an incision in [FORMAT_ORGAN_OWNER(organ)]."), ) display_pain(organ.owner, "Your abdomen burns in horrific stabbing pain!") @@ -141,9 +141,9 @@ 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."), + span_notice("You successfully remove the damaged part of [FORMAT_ORGAN_OWNER(organ)]'s liver."), + span_notice("[surgeon] successfully removes the damaged part of [FORMAT_ORGAN_OWNER(organ)]'s liver."), + span_notice("[surgeon] successfully removes the damaged part of [FORMAT_ORGAN_OWNER(organ)]'s liver."), ) display_pain(organ.owner, "The pain receeds slightly!") @@ -152,9 +152,9 @@ 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!"), + span_warning("You cut the wrong part of [FORMAT_ORGAN_OWNER(organ)]'s liver!"), + span_warning("[surgeon] cuts the wrong part of [FORMAT_ORGAN_OWNER(organ)]'s liver!"), + span_warning("[surgeon] cuts the wrong part of [FORMAT_ORGAN_OWNER(organ)]'s liver!"), ) display_pain(organ.owner, "The pain in your abdomen intensifies!") @@ -193,9 +193,9 @@ 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."), + span_notice("You begin to graft a bypass onto [FORMAT_ORGAN_OWNER(organ)]'s heart..."), + span_notice("[surgeon] begins to graft a bypass onto [FORMAT_ORGAN_OWNER(organ)]'s heart."), + span_notice("[surgeon] begins to graft a bypass onto [FORMAT_ORGAN_OWNER(organ)]'s heart."), ) display_pain(organ.owner, "The pain in your chest is unbearable! You can barely take it anymore!") @@ -204,22 +204,22 @@ 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."), + span_notice("You successfully graft a bypass onto [FORMAT_ORGAN_OWNER(organ)]'s heart."), + span_notice("[surgeon] successfully grafts a bypass onto [FORMAT_ORGAN_OWNER(organ)]'s heart."), + span_notice("[surgeon] successfully grafts a bypass onto [FORMAT_ORGAN_OWNER(organ)]'s heart."), ) display_pain(organ.owner, "The pain in your chest throbs, but your heart feels better than ever!") /datum/surgery_operation/organ/repair/coronary_bypass/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) . = ..() - organ.bodypart_owner.adjustBleedStacks(30) - var/blood_name = LOWER_TEXT(organ.owner.get_bloodtype()?.get_blood_name()) || "blood" + organ.bodypart_owner?.adjustBleedStacks(30) + var/blood_name = LOWER_TEXT(organ.owner?.get_bloodtype()?.get_blood_name()) || "blood" display_results( surgeon, organ.owner, span_warning("You screw up in attaching the graft, and it tears off, tearing part of the heart!"), - span_warning("[surgeon] screws up, causing [blood_name] to spurt out of [organ.owner]'s chest profusely!"), - span_warning("[surgeon] screws up, causing [blood_name] to spurt out of [organ.owner]'s chest profusely!"), + span_warning("[surgeon] screws up, causing [blood_name] to spurt out of [FORMAT_ORGAN_OWNER(organ)]'s chest profusely!"), + span_warning("[surgeon] screws up, causing [blood_name] to spurt out of [FORMAT_ORGAN_OWNER(organ)]'s chest profusely!"), ) display_pain(organ.owner, "Your chest burns; you feel like you're going insane!") @@ -268,9 +268,9 @@ 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]."), + span_notice("You begin to cut out a damaged piece of [FORMAT_ORGAN_OWNER(organ)]'s stomach..."), + span_notice("[surgeon] begins to make an incision in [FORMAT_ORGAN_OWNER(organ)]."), + span_notice("[surgeon] begins to make an incision in [FORMAT_ORGAN_OWNER(organ)]."), ) display_pain(organ.owner, "You feel a horrible stab in your gut!") @@ -279,9 +279,9 @@ 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."), + span_notice("You successfully remove the damaged part of [FORMAT_ORGAN_OWNER(organ)]'s stomach."), + span_notice("[surgeon] successfully removes the damaged part of [FORMAT_ORGAN_OWNER(organ)]'s stomach."), + span_notice("[surgeon] successfully removes the damaged part of [FORMAT_ORGAN_OWNER(organ)]'s stomach."), ) display_pain(organ.owner, "The pain in your gut receeds slightly!") @@ -290,9 +290,9 @@ 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!"), + span_warning("You cut the wrong part of [FORMAT_ORGAN_OWNER(organ)]'s stomach!"), + span_warning("[surgeon] cuts the wrong part of [FORMAT_ORGAN_OWNER(organ)]'s stomach!"), + span_warning("[surgeon] cuts the wrong part of [FORMAT_ORGAN_OWNER(organ)]'s stomach!"), ) display_pain(organ.owner, "The pain in your gut intensifies!") @@ -326,7 +326,8 @@ time = 6.4 SECONDS heal_to_percent = 0 repeatable = TRUE - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/organ/repair/ears/all_blocked_strings() return ..() + list("if the limb has bones, they must be intact") @@ -341,9 +342,9 @@ 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."), + span_notice("You begin to fix [FORMAT_ORGAN_OWNER(organ)]'s ears..."), + span_notice("[surgeon] begins to fix [FORMAT_ORGAN_OWNER(organ)]'s ears."), + span_notice("[surgeon] begins to perform surgery on [FORMAT_ORGAN_OWNER(organ)]'s ears."), ) display_pain(organ.owner, "You feel a dizzying pain in your head!") @@ -354,32 +355,33 @@ 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."), + span_notice("You successfully fix [FORMAT_ORGAN_OWNER(organ)]'s ears."), + span_notice("[surgeon] successfully fixes [FORMAT_ORGAN_OWNER(organ)]'s ears."), + span_notice("[surgeon] successfully fixes [FORMAT_ORGAN_OWNER(organ)]'s ears."), ) display_pain(organ.owner, "Your head swims, but it seems like you can feel your hearing coming back!") /datum/surgery_operation/organ/repair/ears/on_failure(obj/item/organ/ears/organ, mob/living/surgeon, obj/item/tool, list/operation_args) var/obj/item/organ/brain/brain = locate() in organ.bodypart_owner - if(brain) + if(isnull(brain)) display_results( surgeon, organ.owner, - span_warning("You accidentally stab [organ.owner] right in the brain!"), - span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain!"), - span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain!"), - ) - display_pain(organ.owner, "You feel a visceral stabbing pain right through your head, into your brain!") - organ.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 70) - else - display_results( - surgeon, - organ.owner, - span_warning("You accidentally stab [organ.owner] right in the brain! Or would have, if [organ.owner] had a brain."), - span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain! Or would have, if [organ.owner] had a brain."), - span_warning("[surgeon] accidentally stabs [organ.owner] right in the brain!"), + span_warning("You accidentally stab [FORMAT_ORGAN_OWNER(organ)] right in the brain! Or would have, if [FORMAT_ORGAN_OWNER(organ)] had a brain."), + span_warning("[surgeon] accidentally stabs [FORMAT_ORGAN_OWNER(organ)] right in the brain! Or would have, if [FORMAT_ORGAN_OWNER(organ)] had a brain."), + span_warning("[surgeon] accidentally stabs [FORMAT_ORGAN_OWNER(organ)] right in the brain!"), ) + return + + display_results( + surgeon, + organ.owner, + span_warning("You accidentally stab [FORMAT_ORGAN_OWNER(organ)] right in the brain!"), + span_warning("[surgeon] accidentally stabs [FORMAT_ORGAN_OWNER(organ)] right in the brain!"), + span_warning("[surgeon] accidentally stabs [FORMAT_ORGAN_OWNER(organ)] right in the brain!"), + ) + display_pain(organ.owner, "You feel a visceral stabbing pain right through your head, into your brain!") + organ.apply_organ_damage(70) /datum/surgery_operation/organ/repair/eyes name = "eye surgery" @@ -395,7 +397,8 @@ target_type = /obj/item/organ/eyes heal_to_percent = 0 repeatable = TRUE - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/organ/repair/eyes/all_blocked_strings() return ..() + list("if the limb has bones, they must be intact") @@ -413,46 +416,46 @@ 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."), + span_notice("You begin to fix [FORMAT_ORGAN_OWNER(organ)]'s eyes..."), + span_notice("[surgeon] begins to fix [FORMAT_ORGAN_OWNER(organ)]'s eyes."), + span_notice("[surgeon] begins to perform surgery on [FORMAT_ORGAN_OWNER(organ)]'s eyes."), ) display_pain(organ.owner, "You feel a stabbing pain in your eyes!") /datum/surgery_operation/organ/repair/eyes/on_success(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) . = ..() - organ.owner.remove_status_effect(/datum/status_effect/temporary_blindness) - organ.owner.set_eye_blur_if_lower(70 SECONDS) //this will fix itself slowly. + 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."), + span_notice("You successfully fix [FORMAT_ORGAN_OWNER(organ)]'s eyes."), + span_notice("[surgeon] successfully fixes [FORMAT_ORGAN_OWNER(organ)]'s eyes."), + span_notice("[surgeon] successfully fixes [FORMAT_ORGAN_OWNER(organ)]'s eyes."), ) display_pain(organ.owner, "Your vision blurs, but it seems like you can see a little better now!") /datum/surgery_operation/organ/repair/eyes/on_failure(obj/item/organ/organ, mob/living/surgeon, obj/item/tool, list/operation_args) var/obj/item/organ/brain/brain = locate() in organ.bodypart_owner - if(brain) + if(isnull(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!"), + span_warning("You accidentally stab [FORMAT_ORGAN_OWNER(organ)] right in the brain! Or would have, if [FORMAT_ORGAN_OWNER(organ)] had a brain."), + span_warning("[surgeon] accidentally stabs [FORMAT_ORGAN_OWNER(organ)] right in the brain! Or would have, if [FORMAT_ORGAN_OWNER(organ)] had a brain."), + span_warning("[surgeon] accidentally stabs [FORMAT_ORGAN_OWNER(organ)] right in the brain!"), ) - display_pain(organ.owner, "You feel a visceral stabbing pain right through your head, into your brain!") - organ.owner.adjust_organ_loss(ORGAN_SLOT_BRAIN, 70) + return - 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!"), - ) + display_results( + surgeon, + organ.owner, + span_warning("You accidentally stab [FORMAT_ORGAN_OWNER(organ)] right in the brain!"), + span_warning("[surgeon] accidentally stabs [FORMAT_ORGAN_OWNER(organ)] right in the brain!"), + span_warning("[surgeon] accidentally stabs [FORMAT_ORGAN_OWNER(organ)] right in the brain!"), + ) + display_pain(organ.owner, "You feel a visceral stabbing pain right through your head, into your brain!") + organ.apply_organ_damage(70) /datum/surgery_operation/organ/repair/brain name = "brain surgery" @@ -472,7 +475,8 @@ 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 + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/organ/repair/brain/state_check(obj/item/organ/brain/organ) return TRUE // always available so you can intentionally fail it @@ -481,9 +485,9 @@ 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."), + span_notice("You begin to fix [FORMAT_ORGAN_OWNER(organ)]'s brain..."), + span_notice("[surgeon] begins to fix [FORMAT_ORGAN_OWNER(organ)]'s brain."), + span_notice("[surgeon] begins to perform surgery on [FORMAT_ORGAN_OWNER(organ)]'s brain."), ) display_pain(organ.owner, "Your head pounds with unimaginable pain!") @@ -492,15 +496,18 @@ 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."), + span_notice("You succeed in fixing [FORMAT_ORGAN_OWNER(organ)]'s brain."), + span_notice("[surgeon] successfully fixes [FORMAT_ORGAN_OWNER(organ)]'s brain!"), + span_notice("[surgeon] completes the surgery on [FORMAT_ORGAN_OWNER(organ)]'s brain."), ) display_pain(organ.owner, "The pain in your head receeds, thinking becomes a bit easier!") - organ.owner.mind?.remove_antag_datum(/datum/antagonist/brainwashed) + if (organ.owner) + organ.owner.mind?.remove_antag_datum(/datum/antagonist/brainwashed) + else if (organ.brainmob) + organ.brainmob.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.") + to_chat(surgeon, "[FORMAT_ORGAN_OWNER(organ)]'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) . = ..() @@ -509,7 +516,7 @@ 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."), + span_notice("[surgeon] completes the surgery on [FORMAT_ORGAN_OWNER(organ)]'s brain."), ) display_pain(organ.owner, "Your head throbs with horrible pain; thinking hurts!") organ.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) diff --git a/code/modules/surgery/operations/operation_pacify.dm b/code/modules/surgery/operations/operation_pacify.dm index 04f6c6c4571..06fd8cff7e5 100644 --- a/code/modules/surgery/operations/operation_pacify.dm +++ b/code/modules/surgery/operations/operation_pacify.dm @@ -3,7 +3,7 @@ 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 + operation_flags = OPERATION_MORBID | OPERATION_LOCKED | OPERATION_NOTABLE | OPERATION_NO_PATIENT_REQUIRED implements = list( TOOL_HEMOSTAT = 1, TOOL_SCREWDRIVER = 2.85, @@ -15,7 +15,8 @@ failure_sound = 'sound/items/handling/surgery/organ2.ogg' required_organ_flag = ORGAN_TYPE_FLAGS & ~ORGAN_ROBOTIC target_type = /obj/item/organ/brain - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_SAWED + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/organ/pacify/get_default_radial_image() return image(/atom/movable/screen/alert/status_effect/high::overlay_icon, /atom/movable/screen/alert/status_effect/high::overlay_state) @@ -24,9 +25,9 @@ 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."), + span_notice("You begin to pacify [FORMAT_ORGAN_OWNER(organ)]..."), + span_notice("[surgeon] begins to fix [FORMAT_ORGAN_OWNER(organ)]'s brain."), + span_notice("[surgeon] begins to perform surgery on [FORMAT_ORGAN_OWNER(organ)]'s brain."), ) display_pain(organ.owner, "Your head pounds with unimaginable pain!") @@ -34,9 +35,9 @@ 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."), + span_notice("You succeed in pacifying [FORMAT_ORGAN_OWNER(organ)]."), + span_notice("[surgeon] successfully fixes [FORMAT_ORGAN_OWNER(organ)]!"), + span_notice("[surgeon] completes the surgery on [FORMAT_ORGAN_OWNER(organ)]'s brain."), ) display_pain(organ.owner, "Your head pounds... the concept of violence flashes in your head, and nearly makes you hurl!") organ.gain_trauma(/datum/brain_trauma/severe/pacifism, TRAUMA_RESILIENCE_LOBOTOMY) @@ -45,9 +46,9 @@ display_results( surgeon, organ.owner, - span_notice("You screw up, rewiring [organ.owner]'s brain the wrong way around..."), + span_notice("You screw up, rewiring [FORMAT_ORGAN_OWNER(organ)]'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."), + span_notice("[surgeon] completes the surgery on [FORMAT_ORGAN_OWNER(organ)]'s brain."), ) display_pain(organ.owner, "Your head pounds, and it feels like it's getting worse!") organ.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) diff --git a/code/modules/surgery/operations/operation_plastic_surgery.dm b/code/modules/surgery/operations/operation_plastic_surgery.dm index c5ca9d364d5..2ef9db9d856 100644 --- a/code/modules/surgery/operations/operation_plastic_surgery.dm +++ b/code/modules/surgery/operations/operation_plastic_surgery.dm @@ -116,7 +116,7 @@ success_sound = 'sound/effects/blob/attackblob.ogg' failure_sound = 'sound/effects/blob/blobattack.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN - any_surgery_states_blocked = SURGERY_PLASTIC_APPLIED + any_surgery_states_blocked = SURGERY_PLASTIC_APPLIED|SURGERY_VESSELS_UNCLAMPED /datum/surgery_operation/limb/add_plastic/get_default_radial_image() return image(/obj/item/stack/sheet/plastic) diff --git a/code/modules/surgery/operations/operation_puncture.dm b/code/modules/surgery/operations/operation_puncture.dm index eebf3bba0c6..a95c4b76899 100644 --- a/code/modules/surgery/operations/operation_puncture.dm +++ b/code/modules/surgery/operations/operation_puncture.dm @@ -8,7 +8,7 @@ ) time = 3 SECONDS preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_PRIORITY_NEXT_STEP + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT /datum/surgery_operation/limb/repair_puncture/get_default_radial_image() @@ -27,9 +27,9 @@ 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]."), + span_notice("You begin to realign the torn blood vessels in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to realign the torn blood vessels in [FORMAT_LIMB_OWNER(limb)] with [tool]."), + span_notice("[surgeon] begins to realign the torn blood vessels in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a horrible stabbing pain in your [limb.plaintext_zone]!") @@ -42,9 +42,9 @@ 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]!"), + span_notice("You successfully realign the last of the torn blood vessels in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully realigns the last of the torn blood vessels in [FORMAT_LIMB_OWNER(limb)] with [tool]!"), + span_notice("[surgeon] successfully realigns the last of the torn blood vessels in [FORMAT_LIMB_OWNER(limb)]!"), ) return @@ -52,18 +52,18 @@ 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]!"), + span_notice("You successfully realign some of the blood vessels in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully realigns some of the blood vessels in [FORMAT_LIMB_OWNER(limb)] with [tool]!"), + span_notice("[surgeon] successfully realigns some of the blood vessels in [FORMAT_LIMB_OWNER(limb)]!"), ) /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]."), + span_notice("You jerk apart some of the blood vessels in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] jerks apart some of the blood vessels in [FORMAT_LIMB_OWNER(limb)] with [tool]!"), + span_notice("[surgeon] jerks apart some of the blood vessels in [FORMAT_LIMB_OWNER(limb)]."), ) limb.receive_damage(rand(4, 8), wound_bonus = 10, sharpness = SHARP_EDGED, damage_source = tool) @@ -79,7 +79,7 @@ ) time = 3.2 SECONDS preop_sound = 'sound/items/handling/surgery/hemostat1.ogg' - operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_PRIORITY_NEXT_STEP + operation_flags = OPERATION_AFFECTS_MOOD | OPERATION_PRIORITY_NEXT_STEP | OPERATION_NO_PATIENT_REQUIRED all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_ORGANS_CUT /datum/surgery_operation/limb/seal_veins/get_default_radial_image() @@ -108,9 +108,9 @@ 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]."), + span_notice("You begin to seal the realigned blood vessels in [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to seal the realigned blood vessels in [FORMAT_LIMB_OWNER(limb)] with [tool]."), + span_notice("[surgeon] begins to seal the realigned blood vessels in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a burning sensation in your [limb.plaintext_zone]!") @@ -122,9 +122,9 @@ 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]!"), + span_notice("You successfully seal the last of the ruptured blood vessels in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully seals the last of the ruptured blood vessels in [FORMAT_LIMB_OWNER(limb)] with [tool]!"), + span_notice("[surgeon] successfully seals the last of the ruptured blood vessels in [FORMAT_LIMB_OWNER(limb)]!"), ) return @@ -132,7 +132,7 @@ 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]!"), + span_notice("You successfully seal some of the blood vessels in [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] successfully seals some of the blood vessels in [FORMAT_LIMB_OWNER(limb)] with [tool]!"), + span_notice("[surgeon] successfully seals some of the blood vessels in [FORMAT_LIMB_OWNER(limb)]!"), ) diff --git a/code/modules/surgery/operations/operation_replace_limb.dm b/code/modules/surgery/operations/operation_replace_limb.dm index 47f15cf518e..0cb5ea14e21 100644 --- a/code/modules/surgery/operations/operation_replace_limb.dm +++ b/code/modules/surgery/operations/operation_replace_limb.dm @@ -50,6 +50,13 @@ return FALSE return TRUE +/datum/surgery_operation/limb/replace_limb/pre_preop(atom/movable/operating_on, mob/living/surgeon, obj/item/bodypart/tool, list/operation_args) + if(!length(tool.contents)) + return TRUE + // Prevents quickly filling someone with high-tier organs by augmenting them with a pre-stuffed limb + to_chat(surgeon, span_warning("[tool] needs to be empty in order to be attached!")) + return FALSE + /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( diff --git a/code/modules/surgery/operations/operation_wing_repair.dm b/code/modules/surgery/operations/operation_wing_repair.dm index 365d330afaa..b1bb24ec843 100644 --- a/code/modules/surgery/operations/operation_wing_repair.dm +++ b/code/modules/surgery/operations/operation_wing_repair.dm @@ -11,7 +11,8 @@ 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 + all_surgery_states_required = SURGERY_SKIN_OPEN + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED required_organ_flag = NONE /datum/surgery_operation/organ/fix_wings/get_default_radial_image() diff --git a/code/modules/surgery/operations/operation_zombie.dm b/code/modules/surgery/operations/operation_zombie.dm index 8e81cd6491c..2bb50cea049 100644 --- a/code/modules/surgery/operations/operation_zombie.dm +++ b/code/modules/surgery/operations/operation_zombie.dm @@ -9,7 +9,8 @@ ) time = 5 SECONDS operation_flags = OPERATION_MORBID | OPERATION_LOCKED | OPERATION_NOTABLE - all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_VESSELS_CLAMPED|SURGERY_BONE_SAWED + all_surgery_states_required = SURGERY_SKIN_OPEN|SURGERY_BONE_SAWED + any_surgery_states_blocked = SURGERY_VESSELS_UNCLAMPED var/list/zombie_chems = list( /datum/reagent/medicine/rezadone, /datum/reagent/toxin/zombiepowder, diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm index f3212a39bea..f18bd16cbcc 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm @@ -405,7 +405,7 @@ . = ..() UnregisterSignal(organ_owner, COMSIG_LIVING_OPERATING_ON) -/obj/item/organ/cyberimp/brain/surgical_processor/proc/check_surgery(datum/source, mob/living/patient, list/operations) +/obj/item/organ/cyberimp/brain/surgical_processor/proc/check_surgery(datum/source, atom/movable/operating_on, list/operations) SIGNAL_HANDLER if(organ_flags & (ORGAN_FAILING|ORGAN_EMP)) diff --git a/code/modules/surgery/organs/organ_movement.dm b/code/modules/surgery/organs/organ_movement.dm index 34d033b70e1..13d7dcd8560 100644 --- a/code/modules/surgery/organs/organ_movement.dm +++ b/code/modules/surgery/organs/organ_movement.dm @@ -305,7 +305,7 @@ /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, limb.owner, limb.body_zone, tool) - RemoveElement(/datum/element/decal/blood, _color = limb.owner.get_bloodtype()?.get_color() || BLOOD_COLOR_RED) + RemoveElement(/datum/element/decal/blood, _color = get_organ_blood_color(limb) || BLOOD_COLOR_RED) /** * Proc that gets called when the organ is surgically inserted by someone. Seem familiar? @@ -325,3 +325,13 @@ CRASH("[src]'s ([type]) swap_zone was called with invalid zone [target_zone]") zone = target_zone slot = valid_zones[zone] + +/obj/item/organ/proc/get_organ_blood_color(obj/item/bodypart/limb) + var/blood_color = owner?.get_bloodtype()?.get_color() + if (!limb) + return blood_color + if (!blood_color) + blood_color = limb.owner?.get_bloodtype()?.get_color() + if (!blood_color && length(limb.blood_dna_info)) + blood_color = get_color_from_blood_list(limb.blood_dna_info) + return blood_color diff --git a/code/modules/surgery/surgery_tools.dm b/code/modules/surgery/surgery_tools.dm index b271d70a81b..cff887ac4b2 100644 --- a/code/modules/surgery/surgery_tools.dm +++ b/code/modules/surgery/surgery_tools.dm @@ -401,7 +401,7 @@ if(downloaded) . += mutable_appearance(src.icon, "+downloaded") -/obj/item/surgical_processor/proc/check_surgery(datum/source, mob/living/patient, list/operations) +/obj/item/surgical_processor/proc/check_surgery(datum/source, atom/movable/operating_on, list/operations) SIGNAL_HANDLER operations |= loaded_surgeries diff --git a/icons/mob/human/bodyparts.dmi b/icons/mob/human/bodyparts.dmi index 78be880423c1ea7988b9dc3b75a7af1bac9e0466..7fd40bb0bf2be64d829d21969c259d271fd7ab98 100644 GIT binary patch literal 49075 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 literal 52823 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