From 8e63bdd337306969d662f4c91e3029e35456ae7c Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:25:25 +0100 Subject: [PATCH] Moves disfiguration to be handled by the head instead of the mob itself (#94948) ## About The Pull Request TRAIT_DISFIGURED and physical disfiguration are now handled by the head bodypart rather than being on the mob itself. This is mostly for internal purposes, but it does mean that: - Players can now perform plastic surgery on torn off heads. - Disfigured mobs' heads keep their face hidden, and when attached to a different body won't magically get repaired and vice versa, attaching a new head to a disfigured corpse no longer disfigures it. - Robotic (and other non-huskable) heads no longer become disfigured from husking - Also in the rare case of grafting a head torn off from a husk onto another body, they no longer permanently turn into an Unknown. (lol) ## Why It's Good For The Game Saner code, we generally want limb-specific behaviors to be on limbs themselves and not on the mob. Also yeah that one weird edge case. ~~Husks are next on the chopping block~~ ## Changelog :cl: qol: Plastic surgery can now be done on detached heads. refactor: Refactored how disfiguration is handled to be stored on head's side fix: Robotic (and other non-huskable) heads no longer become disfigured from husking fix: Attaching a head from a husk to a different body no longer permanently turns them into an Unknown. /:cl: --- code/__DEFINES/traits/declarations.dm | 1 + code/__DEFINES/traits/sources.dm | 3 + code/_globalvars/traits/_traits.dm | 8 +- code/_globalvars/traits/admin_tooling.dm | 4 +- .../advance/symptoms/disfiguration.dm | 24 +++--- code/game/objects/items/tanks/tanks.dm | 4 +- code/game/objects/items/wiki_manuals.dm | 37 +++++---- code/game/objects/structures/headpike.dm | 2 +- code/modules/mob/living/carbon/human/death.dm | 4 +- .../mob/living/carbon/human/human_defense.dm | 2 +- .../mob/living/carbon/human/human_helpers.dm | 2 +- .../mob/living/carbon/human/init_signals.dm | 1 - .../carbon/human/species_types/dullahan.dm | 1 - code/modules/mob/living/status_procs.dm | 2 - .../chemistry/reagents/medicine_reagents.dm | 55 +++++++------ code/modules/religion/burdened/psyker.dm | 6 +- code/modules/surgery/bodyparts/head.dm | 80 +++++++++++-------- .../operations/operation_plastic_surgery.dm | 67 +++++++++------- 18 files changed, 177 insertions(+), 126 deletions(-) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 4ab1fb7ef28..aa386ed0ce0 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -136,6 +136,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_STASIS "in_stasis" /// Makes the owner appear as dead to most forms of medical examination #define TRAIT_FAKEDEATH "fakedeath" +/// When applied to a mob's head, their face will be unrecognizable and get displayed as Unknown #define TRAIT_DISFIGURED "disfigured" /// "Magic" trait that blocks the mob from moving or interacting with anything. Used for transient stuff like mob transformations or incorporality in special cases. /// Will block movement, `Life()` (!!!), and other stuff based on the mob. diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index 8cbe3049d0f..5973fb25111 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -344,3 +344,6 @@ /// Trait gained by a guardian who is recalled #define GUARDIAN_RECALLED "guardian_recalled" + +/// Trait granted as a result of a mob being husked +#define HUSK_TRAIT "husk" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 5e96e3b3c55..43b2330713d 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -255,7 +255,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_DISCO_DANCER" = TRAIT_DISCO_DANCER, "TRAIT_DISEASELIKE_SEVERITY_HIGH" = TRAIT_DISEASELIKE_SEVERITY_HIGH, "TRAIT_DISEASELIKE_SEVERITY_MEDIUM" = TRAIT_DISEASELIKE_SEVERITY_MEDIUM, - "TRAIT_DISFIGURED" = TRAIT_DISFIGURED, "TRAIT_DISGUISED" = TRAIT_DISGUISED, "TRAIT_DISK_VERIFIER" = TRAIT_DISK_VERIFIER, "TRAIT_DISPLAY_JOB_IN_BINARY" = TRAIT_DISPLAY_JOB_IN_BINARY, @@ -717,13 +716,12 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_DISABLED_BY_WOUND" = TRAIT_DISABLED_BY_WOUND, "TRAIT_IGNORED_BY_LIVING_FLESH" = TRAIT_IGNORED_BY_LIVING_FLESH, "TRAIT_IMMUNE_TO_CRANIAL_FISSURE" = TRAIT_IMMUNE_TO_CRANIAL_FISSURE, - ), - /obj/item/bodypart = list( "TRAIT_PARALYSIS" = TRAIT_PARALYSIS, - ), - /obj/item/bodypart = list( "TRAIT_EASY_ATTACH" = TRAIT_EASY_ATTACH, ), + /obj/item/bodypart/head = list( + "TRAIT_DISFIGURED" = TRAIT_DISFIGURED, + ), /obj/item/card/id = list( "TRAIT_JOB_FIRST_ID_CARD" = TRAIT_JOB_FIRST_ID_CARD, "TRAIT_MAGNETIC_ID_CARD" = TRAIT_MAGNETIC_ID_CARD, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 83b83c94c8e..3bb5fc532f1 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -92,7 +92,6 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_DETECT_STORM" = TRAIT_DETECT_STORM, "TRAIT_DIAGNOSTIC_HUD" = TRAIT_DIAGNOSTIC_HUD, "TRAIT_DISCOORDINATED_TOOL_USER" = TRAIT_DISCOORDINATED_TOOL_USER, - "TRAIT_DISFIGURED" = TRAIT_DISFIGURED, "TRAIT_DISK_VERIFIER" = TRAIT_DISK_VERIFIER, "TRAIT_DISSECTED" = TRAIT_DISSECTED, "TRAIT_DRINKS_BLOOD" = TRAIT_DRINKS_BLOOD, @@ -370,6 +369,9 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( /obj/item/bodypart = list( "TRAIT_PARALYSIS" = TRAIT_PARALYSIS, ), + /obj/item/bodypart/head = list( + "TRAIT_DISFIGURED" = TRAIT_DISFIGURED, + ), /obj/item/card/id = list( "TRAIT_MAGNETIC_ID_CARD" = TRAIT_MAGNETIC_ID_CARD, ), diff --git a/code/datums/diseases/advance/symptoms/disfiguration.dm b/code/datums/diseases/advance/symptoms/disfiguration.dm index 2079e58d56e..08f6896d016 100644 --- a/code/datums/diseases/advance/symptoms/disfiguration.dm +++ b/code/datums/diseases/advance/symptoms/disfiguration.dm @@ -20,24 +20,28 @@ symptom_delay_max = 75 symptom_cure = /datum/reagent/consumable/milk -/datum/symptom/disfiguration/Activate(datum/disease/advance/A) +/datum/symptom/disfiguration/Activate(datum/disease/advance/disease) . = ..() if(!.) return - var/mob/living/M = A.affected_mob - if (HAS_TRAIT(M, TRAIT_DISFIGURED)) + var/mob/living/carbon/victim = disease.affected_mob + var/obj/item/bodypart/head = victim.get_bodypart(BODY_ZONE_HEAD) + if(!head || HAS_TRAIT_FROM(head, TRAIT_DISFIGURED, DISEASE_TRAIT)) return - switch(A.stage) + + switch(disease.stage) if(5) - ADD_TRAIT(M, TRAIT_DISFIGURED, DISEASE_TRAIT) - M.visible_message(span_warning("[M]'s face appears to cave in!"), span_notice("You feel your face crumple and cave in!")) + ADD_TRAIT(head, TRAIT_DISFIGURED, DISEASE_TRAIT) + victim.visible_message(span_warning("[victim]'s face appears to cave in!"), span_notice("You feel your face crumple and cave in!")) else - M.visible_message(span_warning("[M]'s face begins to contort..."), span_notice("Your face feels wet and malleable...")) + victim.visible_message(span_warning("[victim]'s face begins to contort..."), span_notice("Your face feels wet and malleable...")) -/datum/symptom/disfiguration/End(datum/disease/advance/A) +/datum/symptom/disfiguration/End(datum/disease/advance/disease) . = ..() if(!.) return - if(A.affected_mob) - REMOVE_TRAIT(A.affected_mob, TRAIT_DISFIGURED, DISEASE_TRAIT) + var/mob/living/carbon/victim = disease.affected_mob + var/obj/item/bodypart/head = victim?.get_bodypart(BODY_ZONE_HEAD) + if(head) + REMOVE_TRAIT(head, TRAIT_DISFIGURED, DISEASE_TRAIT) diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index 686d2f27ae3..a878e89e975 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -191,7 +191,9 @@ user.visible_message(span_suicide("[user] is putting [src]'s valve to [user.p_their()] lips! It looks like [user.p_theyre()] trying to commit suicide!")) playsound(loc, 'sound/effects/spray.ogg', 10, TRUE, -3) if(!QDELETED(human_user) && air_contents && air_contents.return_pressure() >= 1000) - ADD_TRAIT(human_user, TRAIT_DISFIGURED, TRAIT_GENERIC) + var/obj/item/bodypart/head = human_user.get_bodypart(BODY_ZONE_HEAD) + if(head) + ADD_TRAIT(head, TRAIT_DISFIGURED, TRAIT_GENERIC) human_user.inflate_gib() return MANUAL_SUICIDE to_chat(user, span_warning("There isn't enough pressure in [src] to commit suicide with...")) diff --git a/code/game/objects/items/wiki_manuals.dm b/code/game/objects/items/wiki_manuals.dm index e33f82e7ca0..5c0c0802464 100644 --- a/code/game/objects/items/wiki_manuals.dm +++ b/code/game/objects/items/wiki_manuals.dm @@ -202,23 +202,26 @@ var/mob/living/carbon/human/H = user user.visible_message(span_suicide("[user] starts dancing to the Rhumba Beat! It looks like [user.p_theyre()] trying to commit suicide!")) playsound(loc, 'sound/effects/spray.ogg', 10, TRUE, -3) - if (!QDELETED(H)) - H.emote("spin") - sleep(2 SECONDS) - for(var/obj/item/W in H) - H.dropItemToGround(W) - if(prob(50)) - step(W, pick(GLOB.alldirs)) - ADD_TRAIT(H, TRAIT_DISFIGURED, TRAIT_GENERIC) - for(var/obj/item/bodypart/part as anything in H.bodyparts) - part.adjustBleedStacks(5) - H.gib_animation() - sleep(0.3 SECONDS) - H.adjust_brute_loss(1000) //to make the body super-bloody - // if we use gib() then the body gets deleted - H.spawn_gibs() - H.spill_organs(DROP_ALL_REMAINS) - H.spread_bodyparts(DROP_BRAIN) + if(QDELETED(H)) + return + H.emote("spin") + sleep(2 SECONDS) + for(var/obj/item/W in H) + H.dropItemToGround(W) + if(prob(50)) + step(W, pick(GLOB.alldirs)) + var/obj/item/bodypart/head = H.get_bodypart(BODY_ZONE_HEAD) + if(head) + ADD_TRAIT(head, TRAIT_DISFIGURED, TRAIT_GENERIC) + for(var/obj/item/bodypart/part as anything in H.bodyparts) + part.adjustBleedStacks(5) + H.gib_animation() + sleep(0.3 SECONDS) + H.adjust_brute_loss(1000) //to make the body super-bloody + // if we use gib() then the body gets deleted + H.spawn_gibs() + H.spill_organs(DROP_ALL_REMAINS) + H.spread_bodyparts(DROP_BRAIN) return BRUTELOSS /obj/item/book/manual/wiki/plumbing diff --git a/code/game/objects/structures/headpike.dm b/code/game/objects/structures/headpike.dm index 5dcfac5f77e..f5045ca22c5 100644 --- a/code/game/objects/structures/headpike.dm +++ b/code/game/objects/structures/headpike.dm @@ -44,7 +44,7 @@ update_appearance() /obj/structure/headpike/update_name() - name = "[victim.real_name] on a [spear.name]" + name = "[victim.get_face_name()] on a [spear.name]" return ..() /obj/structure/headpike/update_overlays() diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 91e7939c194..4eb5bbb169a 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -53,8 +53,10 @@ GLOBAL_LIST_EMPTY(dead_players_during_shift) return readout /mob/living/carbon/human/proc/makeSkeleton() - ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) set_species(/datum/species/skeleton) + var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD) + if(head) + ADD_TRAIT(head, TRAIT_DISFIGURED, INNATE_TRAIT) return TRUE /mob/living/carbon/proc/Drain() diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 53e72908af0..fedc4d6e58c 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -508,7 +508,7 @@ emote("scream") set_facial_hairstyle("Shaved", update = FALSE) set_hairstyle("Bald") //This calls update_body_parts() - ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) + ADD_TRAIT(affecting, TRAIT_DISFIGURED, TRAIT_GENERIC) apply_damage(acidity * damage_mod, BRUTE, affecting) apply_damage(acidity * damage_mod * 2, BURN, affecting) diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index aa515894132..7f8ecb7a9d2 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -91,7 +91,7 @@ if(obscured_slots & HIDEFACE) return TRUE var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD) - if(isnull(head) || HAS_TRAIT(src, TRAIT_DISFIGURED) || HAS_TRAIT(src, TRAIT_INVISIBLE_MAN)) //disfigured. use id-name if possible + if(isnull(head) || HAS_TRAIT(head, TRAIT_DISFIGURED) || HAS_TRAIT(src, TRAIT_INVISIBLE_MAN)) //disfigured. use id-name if possible return TRUE return FALSE diff --git a/code/modules/mob/living/carbon/human/init_signals.dm b/code/modules/mob/living/carbon/human/init_signals.dm index 41001746d7d..55ab5d6b6b8 100644 --- a/code/modules/mob/living/carbon/human/init_signals.dm +++ b/code/modules/mob/living/carbon/human/init_signals.dm @@ -14,7 +14,6 @@ RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_HUSK), SIGNAL_REMOVETRAIT(TRAIT_HUSK)), PROC_REF(refresh_obscured)) RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_INVISIBLE_MAN), SIGNAL_REMOVETRAIT(TRAIT_INVISIBLE_MAN)), PROC_REF(invisible_man_toggle)) - RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_DISFIGURED), SIGNAL_REMOVETRAIT(TRAIT_DISFIGURED)), PROC_REF(update_visible_name)) /// Gaining or losing [TRAIT_DWARF] updates our height and grants passtable /mob/living/carbon/human/proc/on_dwarf_trait(datum/source) diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm index 90f1ecb3fb1..fb751eefe2e 100644 --- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm +++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm @@ -267,7 +267,6 @@ if (!istype(detached_head)) return // It's so over detached_head.real_name = headless.real_name - detached_head.name = headless.real_name name = headless.real_name detached_head.voice = headless.voice detached_head.pitch = pitch diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index b633494b374..14bb9471a7c 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -513,7 +513,6 @@ REMOVE_TRAIT(src, TRAIT_HUSK, source) if(HAS_TRAIT(src, TRAIT_HUSK)) return FALSE - REMOVE_TRAIT(src, TRAIT_DISFIGURED, "husk") update_body() UnregisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_UNHUSKABLE)) return TRUE @@ -525,7 +524,6 @@ ADD_TRAIT(src, TRAIT_HUSK, source) if (was_husk) return - ADD_TRAIT(src, TRAIT_DISFIGURED, "husk") update_body() RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_UNHUSKABLE), PROC_REF(became_unhuskable)) diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 2a95c8a93ce..0c57cf86b0e 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -159,7 +159,9 @@ for(var/i in affected_mob.all_wounds) var/datum/wound/iter_wound = i iter_wound.on_xadone(0.5 * power * metabolization_ratio * seconds_per_tick) - REMOVE_TRAIT(affected_mob, TRAIT_DISFIGURED, TRAIT_GENERIC) //fixes common causes for disfiguration + var/obj/item/bodypart/head = affected_mob.get_bodypart(BODY_ZONE_HEAD) + if (head) + REMOVE_TRAIT(head, TRAIT_DISFIGURED, TRAIT_GENERIC) //fixes common causes for disfiguration if(need_mob_update) return UPDATE_MOB_HEALTH @@ -178,29 +180,32 @@ /datum/reagent/medicine/pyroxadone/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, metabolization_ratio) . = ..() - if(affected_mob.bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT) - var/power = 0 - switch(affected_mob.bodytemperature) - if(BODYTEMP_HEAT_DAMAGE_LIMIT to 400) - power = 2 - if(400 to 460) - power = 3 - else - power = 5 - if(affected_mob.on_fire) - power *= 2 + if(affected_mob.bodytemperature <= BODYTEMP_HEAT_DAMAGE_LIMIT) + return + var/power = 0 + switch(affected_mob.bodytemperature) + if(BODYTEMP_HEAT_DAMAGE_LIMIT to 400) + power = 2 + if(400 to 460) + power = 3 + else + power = 5 + if(affected_mob.on_fire) + power *= 2 - var/need_mob_update - need_mob_update = affected_mob.adjust_oxy_loss(-1 * power * metabolization_ratio * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - need_mob_update += affected_mob.adjust_brute_loss(-0.5 * power * metabolization_ratio * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) - need_mob_update += affected_mob.adjust_fire_loss(-0.75 * power * metabolization_ratio * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) - need_mob_update += affected_mob.adjust_tox_loss(-0.5 * power * metabolization_ratio * seconds_per_tick, updating_health = FALSE, forced = TRUE, required_biotype = affected_biotype) - if(need_mob_update) - . = UPDATE_MOB_HEALTH - for(var/i in affected_mob.all_wounds) - var/datum/wound/iter_wound = i - iter_wound.on_xadone(0.5 * power * metabolization_ratio * seconds_per_tick) - REMOVE_TRAIT(affected_mob, TRAIT_DISFIGURED, TRAIT_GENERIC) + var/need_mob_update + need_mob_update = affected_mob.adjust_oxy_loss(-1 * power * metabolization_ratio * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjust_brute_loss(-0.5 * power * metabolization_ratio * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjust_fire_loss(-0.75 * power * metabolization_ratio * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjust_tox_loss(-0.5 * power * metabolization_ratio * seconds_per_tick, updating_health = FALSE, forced = TRUE, required_biotype = affected_biotype) + if(need_mob_update) + . = UPDATE_MOB_HEALTH + for(var/i in affected_mob.all_wounds) + var/datum/wound/iter_wound = i + iter_wound.on_xadone(0.5 * power * metabolization_ratio * seconds_per_tick) + var/obj/item/bodypart/head = affected_mob.get_bodypart(BODY_ZONE_HEAD) + if (head) + REMOVE_TRAIT(head, TRAIT_DISFIGURED, TRAIT_GENERIC) /datum/reagent/medicine/rezadone name = "Rezadone" @@ -223,7 +228,9 @@ required_bodytype = affected_biotype )) . = UPDATE_MOB_HEALTH - REMOVE_TRAIT(affected_mob, TRAIT_DISFIGURED, TRAIT_GENERIC) + var/obj/item/bodypart/head = affected_mob.get_bodypart(BODY_ZONE_HEAD) + if (head) + REMOVE_TRAIT(head, TRAIT_DISFIGURED, TRAIT_GENERIC) /datum/reagent/medicine/rezadone/overdose_process(mob/living/affected_mob, seconds_per_tick, metabolization_ratio) . = ..() diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm index 8930450f226..1e4ac046762 100644 --- a/code/modules/religion/burdened/psyker.dm +++ b/code/modules/religion/burdened/psyker.dm @@ -40,9 +40,13 @@ limb_id = BODYPART_ID_PSYKER is_dimorphic = FALSE should_draw_greyscale = FALSE - bodypart_traits = list(TRAIT_DISFIGURED, TRAIT_BALD, TRAIT_SHAVED) + bodypart_traits = list(TRAIT_BALD, TRAIT_SHAVED) head_flags = HEAD_DEBRAIN | HEAD_NO_DISFIGURE // ignore disfigurement by damage, as we're always disfigured +/obj/item/bodypart/head/psyker/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_DISFIGURED, INNATE_TRAIT) + /// flavorful variant of psykerizing that deals damage and sends messages before calling psykerize() /mob/living/carbon/human/proc/slow_psykerize(blind_them = FALSE) if(stat == DEAD || !get_bodypart(BODY_ZONE_HEAD) || istype(get_bodypart(BODY_ZONE_HEAD), /obj/item/bodypart/head/psyker)) diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 02dff71c3a9..73a526361f0 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -98,6 +98,11 @@ /// Can this head be dismembered normally? can_dismember = FALSE +/obj/item/bodypart/head/Initialize(mapload) + . = ..() + AddElement(/datum/element/toy_talk) + RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_DISFIGURED), SIGNAL_REMOVETRAIT(TRAIT_DISFIGURED)), PROC_REF(update_owner_name)) + /obj/item/bodypart/head/Destroy() QDEL_NULL(worn_ears_offset) QDEL_NULL(worn_glasses_offset) @@ -121,32 +126,39 @@ /obj/item/bodypart/head/examine(mob/user) . = ..() - if(show_organs_on_examine && IS_ORGANIC_LIMB(src)) - var/obj/item/organ/brain/brain = locate(/obj/item/organ/brain) in src - if(!brain) - . += span_info("The brain has been removed from [src].") - else if(brain.suicided || (brain.brainmob && HAS_TRAIT(brain.brainmob, TRAIT_SUICIDED))) - . += span_info("There's a miserable expression on [real_name]'s face; they must have really hated life. There's no hope of recovery.") - else if(brain.brainmob) - if(brain.brainmob?.health <= HEALTH_THRESHOLD_DEAD) - . += span_info("It's leaking some kind of... clear fluid? The brain inside must be in pretty bad shape.") - if(brain.brainmob.key || brain.brainmob.get_ghost(FALSE, TRUE)) - . += span_info("Its muscles are twitching slightly... It seems to have some life still in it.") - else - . += span_info("It's completely lifeless. Perhaps there'll be a chance for them later.") - else if(brain?.decoy_override) - . += span_info("It's completely lifeless. Perhaps there'll be a chance for them later.") + if(!show_organs_on_examine || !IS_ORGANIC_LIMB(src)) + return + var/shown_name = get_face_name() + var/obj/item/organ/brain/brain = locate(/obj/item/organ/brain) in src + if(!brain) + . += span_info("The brain has been removed from [src].") + else if(brain.suicided || (brain.brainmob && HAS_TRAIT(brain.brainmob, TRAIT_SUICIDED))) + . += span_info("There's a miserable expression on [shown_name]'s face; they must have really hated life. There's no hope of recovery.") + else if(brain.brainmob) + if(brain.brainmob?.health <= HEALTH_THRESHOLD_DEAD) + . += span_info("It's leaking some kind of... clear fluid? The brain inside must be in pretty bad shape.") + if(brain.brainmob.key || brain.brainmob.get_ghost(FALSE, TRUE)) + . += span_info("Its muscles are twitching slightly... It seems to have some life still in it.") else - . += span_info("It's completely lifeless.") + . += span_info("It's completely lifeless. Perhaps there'll be a chance for them later.") + else if(brain?.decoy_override) + . += span_info("It's completely lifeless. Perhaps there'll be a chance for them later.") + else + . += span_info("It's completely lifeless.") - if(!(locate(/obj/item/organ/eyes) in src)) - . += span_info("[real_name]'s eyes have been removed.") + if(!(locate(/obj/item/organ/eyes) in src)) + . += span_info("[shown_name]'s eyes have been removed.") - if(!(locate(/obj/item/organ/ears) in src)) - . += span_info("[real_name]'s ears have been removed.") + if(!(locate(/obj/item/organ/ears) in src)) + . += span_info("[shown_name]'s ears have been removed.") - if(!(locate(/obj/item/organ/tongue) in src)) - . += span_info("[real_name]'s tongue has been removed.") + if(!(locate(/obj/item/organ/tongue) in src)) + . += span_info("[shown_name]'s tongue has been removed.") + +/obj/item/bodypart/head/proc/get_face_name() + if (HAS_TRAIT(src, TRAIT_DISFIGURED)) + return "Unknown" + return real_name /obj/item/bodypart/head/can_dismember(obj/item/item) if (!can_dismember) @@ -171,10 +183,11 @@ /obj/item/bodypart/head/update_limb(dropping_limb, is_creating) . = ..() if(!isnull(owner)) - if(HAS_TRAIT(owner, TRAIT_HUSK)) - real_name = "Unknown" + real_name = owner.real_name + if(is_husked) + ADD_TRAIT(src, TRAIT_DISFIGURED, HUSK_TRAIT) else - real_name = owner.real_name + REMOVE_TRAIT(src, TRAIT_DISFIGURED, HUSK_TRAIT) update_hair_and_lips(dropping_limb, is_creating) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -222,12 +235,8 @@ . += eye_left . += eye_right -/obj/item/bodypart/head/Initialize(mapload) - . = ..() - AddElement(/datum/element/toy_talk) - /obj/item/bodypart/head/get_voice(add_id_name) - return "The head of [real_name]" + return "The head of [get_face_name()]" /obj/item/bodypart/head/update_bodypart_damage_state() if (head_flags & HEAD_NO_DISFIGURE) @@ -237,9 +246,16 @@ . = ..() var/new_states = brutestate + burnstate if(new_states >= HUMAN_DISFIGURATION_HEAD_DAMAGE_STATES) - add_bodypart_trait(TRAIT_DISFIGURED) + ADD_TRAIT(src, TRAIT_DISFIGURED, BODYPART_TRAIT) else if(old_states >= HUMAN_DISFIGURATION_HEAD_DAMAGE_STATES) - remove_bodypart_trait(TRAIT_DISFIGURED) + REMOVE_TRAIT(src, TRAIT_DISFIGURED, BODYPART_TRAIT) + +/obj/item/bodypart/head/proc/update_owner_name(datum/source) + SIGNAL_HANDLER + + if (ishuman(owner)) + var/mob/living/carbon/human/as_human = owner + as_human?.update_visible_name() /obj/item/bodypart/head/monkey icon = 'icons/mob/human/species/monkey/bodyparts.dmi' diff --git a/code/modules/surgery/operations/operation_plastic_surgery.dm b/code/modules/surgery/operations/operation_plastic_surgery.dm index 2ef9db9d856..1203702d1d6 100644 --- a/code/modules/surgery/operations/operation_plastic_surgery.dm +++ b/code/modules/surgery/operations/operation_plastic_surgery.dm @@ -10,7 +10,7 @@ /obj/item/pen = 5, ) time = 6.4 SECONDS - operation_flags = OPERATION_MORBID | OPERATION_AFFECTS_MOOD | OPERATION_NOTABLE + operation_flags = OPERATION_MORBID | OPERATION_AFFECTS_MOOD | OPERATION_NOTABLE | OPERATION_NO_PATIENT_REQUIRED preop_sound = 'sound/items/handling/surgery/scalpel1.ogg' success_sound = 'sound/items/handling/surgery/scalpel2.ogg' all_surgery_states_required = SURGERY_SKIN_OPEN @@ -25,15 +25,18 @@ return limb.body_zone == BODY_ZONE_HEAD /datum/surgery_operation/limb/plastic_surgery/pre_preop(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) - if(HAS_TRAIT_FROM(limb.owner, TRAIT_DISFIGURED, TRAIT_GENERIC)) + if(HAS_TRAIT_FROM(limb, TRAIT_DISFIGURED, TRAIT_GENERIC)) return TRUE //skip name selection if fixing disfigurement var/list/names = list() if(isabductor(surgeon)) for(var/j in 1 to 9) names += "Subject [limb.owner.gender == MALE ? "i" : "o"]-[pick("a", "b", "c", "d", "e")]-[rand(10000, 99999)]" - names += limb.owner.generate_random_mob_name(TRUE) //give one normal name in case they want to do regular plastic surgery + if(limb.owner) + names += limb.owner.generate_random_mob_name(TRUE) //give one normal name in case they want to do regular plastic surgery + else + names += generate_random_name_species_based(pick(MALE, FEMALE), TRUE, GLOB.species_list[limb.limb_id] || /datum/species/human) else var/advanced = LIMB_HAS_SURGERY_STATE(limb, SURGERY_PLASTIC_APPLIED) var/obj/item/offhand = surgeon.get_inactive_held_item() @@ -44,8 +47,12 @@ else if(advanced) to_chat(surgeon, span_warning("You have no picture to base the appearance on!")) + for(var/i in 1 to 10) - names += limb.owner.generate_random_mob_name(TRUE) + if(limb.owner) + names += limb.owner.generate_random_mob_name(TRUE) + else + names += generate_random_name_species_based(pick(MALE, FEMALE), TRUE, GLOB.species_list[limb.limb_id] || /datum/species/human) operation_args[OPERATION_NEW_NAME] = tgui_input_list(surgeon, "New name to assign", "Plastic Surgery", names) return !!operation_args[OPERATION_NEW_NAME] @@ -54,34 +61,40 @@ display_results( surgeon, limb.owner, - span_notice("You begin to alter [limb.owner]'s appearance..."), - span_notice("[surgeon] begins to alter [limb.owner]'s appearance."), - span_notice("[surgeon] begins to make an incision in [limb.owner]'s [limb.plaintext_zone]."), + span_notice("You begin to alter [limb.owner || limb]'s appearance..."), + span_notice("[surgeon] begins to alter [limb.owner || limb]'s appearance."), + span_notice("[surgeon] begins to make an incision in [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a slicing pain across your face!") -/datum/surgery_operation/limb/plastic_surgery/on_success(obj/item/bodypart/limb, mob/living/surgeon, obj/item/tool, list/operation_args) - if(HAS_TRAIT_FROM(limb.owner, TRAIT_DISFIGURED, TRAIT_GENERIC)) - REMOVE_TRAIT(limb.owner, TRAIT_DISFIGURED, TRAIT_GENERIC) +/datum/surgery_operation/limb/plastic_surgery/on_success(obj/item/bodypart/head/limb, mob/living/surgeon, obj/item/tool, list/operation_args) + if(!istype(limb)) + CRASH("Plastic surgery finished on a non-head limb [limb]!") + + if(HAS_TRAIT_FROM(limb, TRAIT_DISFIGURED, TRAIT_GENERIC)) + REMOVE_TRAIT(limb, TRAIT_DISFIGURED, TRAIT_GENERIC) display_results( surgeon, limb.owner, - span_notice("You successfully restore [limb.owner]'s appearance."), - span_notice("[surgeon] successfully restores [limb.owner]'s appearance!"), - span_notice("[surgeon] finishes the operation on [limb.owner]'s face."), + span_notice("You successfully restore [limb.owner || limb]'s appearance."), + span_notice("[surgeon] successfully restores [limb.owner || limb]'s appearance!"), + span_notice("[surgeon] finishes the operation on [limb.owner ? "[limb.owner]'s face." : limb]"), ) display_pain(limb.owner, "The pain fades, your face feels normal again!") return - var/oldname = limb.owner.real_name - limb.owner.real_name = operation_args[OPERATION_NEW_NAME] - var/newname = limb.owner.real_name //something about how the code handles names required that I use this instead of target.real_name + var/oldname = limb.owner?.real_name || limb.real_name + if (limb.owner) + limb.owner.real_name = operation_args[OPERATION_NEW_NAME] + else + limb.real_name = operation_args[OPERATION_NEW_NAME] + display_results( surgeon, limb.owner, - span_notice("You alter [oldname]'s appearance completely, [limb.owner.p_they()] is now [newname]."), - span_notice("[surgeon] alters [oldname]'s appearance completely, [limb.owner.p_they()] is now [newname]!"), - span_notice("[surgeon] finishes the operation on [limb.owner]'s face."), + span_notice("You alter [oldname]'s appearance completely, [limb.owner.p_they()] is now [operation_args[OPERATION_NEW_NAME]]."), + span_notice("[surgeon] alters [oldname]'s appearance completely, [limb.owner.p_they()] is now [operation_args[OPERATION_NEW_NAME]]!"), + span_notice("[surgeon] finishes the operation on [limb.owner ? "[limb.owner]'s face." : limb]."), ) display_pain(limb.owner, "The pain fades, your face feels new and unfamiliar!") if(ishuman(limb.owner)) @@ -95,12 +108,12 @@ display_results( surgeon, limb.owner, - span_warning("Your screw up, leaving [limb.owner]'s appearance disfigured!"), - span_warning("[surgeon] screws up, disfiguring [limb.owner]'s appearance!"), - span_notice("[surgeon] finishes the operation on [limb.owner]'s face."), + span_warning("Your screw up, leaving [limb.owner || limb]'s appearance disfigured!"), + span_warning("[surgeon] screws up, disfiguring [limb.owner || limb]'s appearance!"), + span_notice("[surgeon] finishes the operation on [limb.owner ? "[limb.owner]'s face." : limb]."), ) display_pain(limb.owner, "Your face feels horribly scarred and deformed!") - ADD_TRAIT(limb.owner, TRAIT_DISFIGURED, TRAIT_GENERIC) + ADD_TRAIT(limb, TRAIT_DISFIGURED, TRAIT_GENERIC) #undef OPERATION_NEW_NAME @@ -111,7 +124,7 @@ /obj/item/stack/sheet/plastic = 1, ) time = 4.8 SECONDS - operation_flags = OPERATION_MORBID | OPERATION_LOCKED + operation_flags = OPERATION_MORBID | OPERATION_LOCKED | OPERATION_NO_PATIENT_REQUIRED preop_sound = 'sound/effects/blob/blobattack.ogg' success_sound = 'sound/effects/blob/attackblob.ogg' failure_sound = 'sound/effects/blob/blobattack.ogg' @@ -128,9 +141,9 @@ display_results( surgeon, limb.owner, - span_notice("You begin to apply plastic to [limb.owner]'s [limb.plaintext_zone]..."), - span_notice("[surgeon] begins to apply plastic to [limb.owner]'s [limb.plaintext_zone]."), - span_notice("[surgeon] begins to perform surgery on [limb.owner]'s [limb.plaintext_zone]."), + span_notice("You begin to apply plastic to [FORMAT_LIMB_OWNER(limb)]..."), + span_notice("[surgeon] begins to apply plastic to [FORMAT_LIMB_OWNER(limb)]."), + span_notice("[surgeon] begins to perform surgery on [FORMAT_LIMB_OWNER(limb)]."), ) display_pain(limb.owner, "You feel a strange sensation as something is applied to your face!")