From bf22a388caecb037059f5b0c67f6201da95ab3ef Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:22:07 -0600 Subject: [PATCH] People become desensitized to death from exposure (#94924) ## About The Pull Request Desensitized is no longer binary yes/no, now scales from 0.1x to 1x (or beyond, I guess) Desensitized jobs start as 0.5x desensitized (which is the threshold for being considered "truly desensitized") Some antags are now 0.1x to 0.25x desensitized Witnessing death of a fellow human gives you slight desensitization (currently -0.025x). A normal crewmember, after witnessing 20 deaths, is on par with a roundstart desensitized crewmember. Likewise a desensitized crewmember has almost no reaction to death after witnessing 20 deaths. Your own deaths count towards this value. There's an achievement for managing to go from 1x to 0.1x across the course of an entire round. ## Why It's Good For The Game This is intended to contribute to the "story" people face across the length of a round. Rounds with few overall deaths results in crewmembers regularly getting shocked, but bloodbaths results in crewmembers "dehumanizing and facing the bloodshed". ## Changelog :cl: Melbert add: Desensitization to death is no longer binary - some antagonists are now more used to it than others. add: Witnessing the death of a fellow humanoid (or dying yourself) will slightly desensitize you to future deaths. add: Adds an achievement for managing to max out desensitization across a round. add: Desensitized crewmembers care less when splattered with blood. add: Holodeck mobs have a reduced death mood impact. /:cl: --- code/__DEFINES/achievements.dm | 1 + code/__DEFINES/jobs.dm | 2 - code/__DEFINES/mood.dm | 10 +++++ code/__DEFINES/traits/declarations.dm | 3 -- code/_globalvars/traits/_traits.dm | 1 - code/_globalvars/traits/admin_tooling.dm | 1 - code/datums/achievements/misc_achievements.dm | 7 +++ code/datums/brain_damage/creepy_trauma.dm | 4 +- code/datums/brain_damage/special.dm | 8 ++-- code/datums/mind/_mind.dm | 27 +++++++++++- code/datums/mood_events/death.dm | 14 +++--- .../mood_events/generic_negative_events.dm | 2 + code/datums/status_effects/buffs.dm | 41 ++++++++++++++++++ .../antagonists/_common/antag_datum.dm | 4 ++ .../antagonists/cult/datums/cult_team.dm | 1 - .../antagonists/cult/datums/cultist.dm | 3 +- code/modules/antagonists/ert/ert.dm | 9 +--- .../antagonists/heretic/heretic_knowledge.dm | 2 +- .../heretic/status_effects/ghoul.dm | 4 +- .../antagonists/highlander/highlander.dm | 2 +- .../antagonists/nukeop/datums/operative.dm | 2 +- code/modules/antagonists/pirate/pirate.dm | 3 +- .../antagonists/space_ninja/space_ninja.dm | 9 +--- code/modules/holodeck/holo_effect.dm | 2 +- code/modules/jobs/job_types/_job.dm | 5 +++ code/modules/jobs/job_types/captain.dm | 3 +- .../jobs/job_types/chaplain/chaplain.dm | 3 +- .../jobs/job_types/chief_medical_officer.dm | 3 +- code/modules/jobs/job_types/cook.dm | 2 +- code/modules/jobs/job_types/coroner.dm | 3 +- code/modules/jobs/job_types/detective.dm | 2 +- .../jobs/job_types/head_of_security.dm | 3 +- code/modules/jobs/job_types/medical_doctor.dm | 2 +- code/modules/jobs/job_types/paramedic.dm | 3 +- .../jobs/job_types/security_officer.dm | 2 +- code/modules/jobs/job_types/warden.dm | 2 +- code/modules/mob/living/death.dm | 6 ++- code/modules/unit_tests/death_moodlets.dm | 27 ++++++++++-- icons/ui/achievements/achievements.dmi | Bin 283489 -> 287353 bytes 39 files changed, 164 insertions(+), 64 deletions(-) diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm index 575efae4186..69741ef4bff 100644 --- a/code/__DEFINES/achievements.dm +++ b/code/__DEFINES/achievements.dm @@ -62,6 +62,7 @@ #define MEDAL_CIGARETTES "Cigarettes" #define MEDAL_SHARKDRAGON "Sharkdragon" #define MEDAL_THEORETICAL_LIMITS "All Within Theoretical Limits" +#define MEDAL_DESENSITIZED "In Flanders Fields" //Skill medal hub IDs #define MEDAL_LEGENDARY_MINER "Legendary Miner" diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index ad7f6bba889..148c1b4c8f7 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -285,8 +285,6 @@ DEFINE_BITFIELD(job_flags, list( /// Mind traits that should be shared by every head of staff. has to be this way cause byond lists lol #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 diff --git a/code/__DEFINES/mood.dm b/code/__DEFINES/mood.dm index 0dcdc9340d6..80994ef5907 100644 --- a/code/__DEFINES/mood.dm +++ b/code/__DEFINES/mood.dm @@ -112,3 +112,13 @@ #define BLOCK_NEW_MOOD FALSE /// Return from /be_replaced or /be_refreshed to actually go through and allow the new mood event to be added #define ALLOW_NEW_MOOD TRUE + +/// Threshold for desensitization effects to start applying +#define DESENSITIZED_THRESHOLD 0.5 +/// Minimum level for desensitization multiplier +#define DESENSITIZED_MINIMUM 0.1 +/// Reduction in desensitization level per death witnessed +#define DESENSITIZED_REDUCTION_PER_DEATH 0.025 + +/// Check if a mob is desensitized (passes the threshold) +#define IS_DESENSITIZED(mob) ((mob.mind?.desensitized_level || 1) <= DESENSITIZED_THRESHOLD) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 8980e3f5d88..4ab1fb7ef28 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1610,9 +1610,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Trait that allows an item to perform holy rites akin to a nullrod #define TRAIT_NULLROD_ITEM "nullrod_item" -/// Mob gets far less severe negative moodlets from seeing death / blood -#define TRAIT_DESENSITIZED "desensitized" - /// Trait specifying that an AI has a remote connection to an integrated circuit #define TRAIT_CONNECTED_TO_CIRCUIT "connected_to_circuit" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 0bed48f0616..5e96e3b3c55 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -247,7 +247,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_DEAF" = TRAIT_DEAF, "TRAIT_DEATHCOMA" = TRAIT_DEATHCOMA, "TRAIT_DEFIB_BLACKLISTED" = TRAIT_DEFIB_BLACKLISTED, - "TRAIT_DESENSITIZED" = TRAIT_DESENSITIZED, "TRAIT_DESIGNATED_TARGET" = TRAIT_DESIGNATED_TARGET, "TRAIT_DETECTIVES_TASTE" = TRAIT_DETECTIVES_TASTE, "TRAIT_DETECT_STORM" = TRAIT_DETECT_STORM, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index b369f8cc814..83b83c94c8e 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -89,7 +89,6 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_DEAF" = TRAIT_DEAF, "TRAIT_DEATHCOMA" = TRAIT_DEATHCOMA, "TRAIT_DEFIB_BLACKLISTED" = TRAIT_DEFIB_BLACKLISTED, - "TRAIT_DESENSITIZED" = TRAIT_DESENSITIZED, "TRAIT_DETECT_STORM" = TRAIT_DETECT_STORM, "TRAIT_DIAGNOSTIC_HUD" = TRAIT_DIAGNOSTIC_HUD, "TRAIT_DISCOORDINATED_TOOL_USER" = TRAIT_DISCOORDINATED_TOOL_USER, diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm index 224941e6ce3..a15977e7e53 100644 --- a/code/datums/achievements/misc_achievements.dm +++ b/code/datums/achievements/misc_achievements.dm @@ -250,3 +250,10 @@ desc = "Nutritionists often recommend a balanced and varied diet. However that clearly isn't the case for some creatures." database_id = MEDAL_SHARKDRAGON icon_state = "dragon_plus_fish" + +/datum/award/achievement/misc/desensitized + name = "In Flanders Fields" + desc = "You have witnessed more death and despair in one shift than most TerraGov marines have seen across an entire tour of duty. \ + Perhaps one day you'll find peace. But not today." + database_id = MEDAL_DESENSITIZED + icon_state = "desensitized" diff --git a/code/datums/brain_damage/creepy_trauma.dm b/code/datums/brain_damage/creepy_trauma.dm index 8a2e13ed9e5..8cc32018531 100644 --- a/code/datums/brain_damage/creepy_trauma.dm +++ b/code/datums/brain_damage/creepy_trauma.dm @@ -37,7 +37,7 @@ antagonist.greet() log_game("[key_name(antagonist)] has developed an obsession with [key_name(obsession)].") RegisterSignal(owner, COMSIG_CARBON_HELPED, PROC_REF(on_hug)) - ADD_TRAIT(owner, TRAIT_DESENSITIZED, REF(src)) + owner.apply_status_effect(/datum/status_effect/desensitized, REF(src), DESENSITIZED_THRESHOLD) /datum/brain_trauma/special/obsessed/on_life(seconds_per_tick) if(!obsession || obsession.stat == DEAD) @@ -75,7 +75,7 @@ if(obsession) log_game("[key_name(owner)] is no longer obsessed with [key_name(obsession)].") UnregisterSignal(obsession, COMSIG_MOB_EYECONTACT) - REMOVE_TRAIT(owner, TRAIT_DESENSITIZED, REF(src)) + owner.remove_status_effect(/datum/status_effect/desensitized, REF(src)) /datum/brain_trauma/special/obsessed/handle_speech(datum/source, list/speech_args) if(!viewing) diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm index 8928f36409a..e4e6847011f 100644 --- a/code/datums/brain_damage/special.dm +++ b/code/datums/brain_damage/special.dm @@ -483,15 +483,15 @@ /datum/brain_trauma/special/ptsd/on_gain() owner.add_mood_event("combat_ptsd", /datum/mood_event/desentized) owner.mob_mood?.mood_modifier -= 1 //Basically nothing can change your mood - owner.mob_mood?.sanity_level = SANITY_DISTURBED //Makes sanity on a unstable level unless cured - ADD_TRAIT(owner, TRAIT_DESENSITIZED, REF(src)) + owner.mob_mood?.set_sanity(SANITY_DISTURBED, override = TRUE) //Makes sanity on a unstable level unless cured + owner.apply_status_effect(/datum/status_effect/desensitized, REF(src), DESENSITIZED_THRESHOLD) . = ..() /datum/brain_trauma/special/ptsd/on_lose() owner.clear_mood_event("combat_ptsd") owner.mob_mood?.mood_modifier += 1 - owner.mob_mood?.sanity_level = SANITY_GREAT - REMOVE_TRAIT(owner, TRAIT_DESENSITIZED, REF(src)) + owner.mob_mood?.set_sanity(SANITY_GREAT, override = TRUE) + owner.remove_status_effect(/datum/status_effect/desensitized, REF(src)) return ..() /datum/brain_trauma/special/primal_instincts diff --git a/code/datums/mind/_mind.dm b/code/datums/mind/_mind.dm index 83af9390bb5..2fef2f8f571 100644 --- a/code/datums/mind/_mind.dm +++ b/code/datums/mind/_mind.dm @@ -101,6 +101,12 @@ /// A list to keep track of which books a person has read (to prevent people from reading the same book again and again for positive mood events) var/list/book_titles_read + /// How desensitized are we to death - multiplier to magnitude of death moodlet. + /// Doesn't go beneath 0.1 (but could go above 1.0 if you really wanted) + var/desensitized_level = 1 + /// Counts how many humanoid deaths we've seen + var/deaths_witnessed = 0 + /datum/mind/New(_key) key = _key init_known_skills() @@ -211,9 +217,9 @@ new_character.client.init_verbs() // re-initialize character specific verbs SEND_SIGNAL(src, COMSIG_MIND_TRANSFERRED, old_current) - SEND_SIGNAL(current, COMSIG_MOB_MIND_TRANSFERRED_INTO, old_current) + SEND_SIGNAL(current, COMSIG_MOB_MIND_TRANSFERRED_INTO, old_current, src) if(!isnull(old_current)) - SEND_SIGNAL(old_current, COMSIG_MOB_MIND_TRANSFERRED_OUT_OF, current) + SEND_SIGNAL(old_current, COMSIG_MOB_MIND_TRANSFERRED_OUT_OF, current, src) //I cannot trust you fucks to do this properly /datum/mind/proc/set_original_character(new_original_character) @@ -543,3 +549,20 @@ work_areas += dep.primary_work_area return work_areas + +/// Called when we witness the death of a humanoid mob. +/datum/mind/proc/witnessed_death(mob/living/dead_mob) + if(HAS_TRAIT(dead_mob, TRAIT_SPAWNED_MOB) || !ishuman(dead_mob) || (dead_mob.flags_1 & ADMIN_SPAWNED_1)) + return + + // every humanoid death gives us % resistance to the next one + desensitized_level = max(desensitized_level - DESENSITIZED_REDUCTION_PER_DEATH, DESENSITIZED_MINIMUM) + deaths_witnessed += 1 + + // if you manage to gain 90% resistance to death moodlets in one shift, you get an "achievement" + if(deaths_witnessed * DESENSITIZED_REDUCTION_PER_DEATH >= (1.0 - DESENSITIZED_MINIMUM)) + current.client?.give_award(/datum/award/achievement/misc/desensitized, current) + +/// Called when this mob is killed, but not gibbed or dusted +/datum/mind/proc/experienced_death() + witnessed_death(current) // you get desensitized for your own death! diff --git a/code/datums/mood_events/death.dm b/code/datums/mood_events/death.dm index e8da1c07b52..3101b3dea96 100644 --- a/code/datums/mood_events/death.dm +++ b/code/datums/mood_events/death.dm @@ -32,9 +32,12 @@ mood_change *= 0.25 timeout *= 0.2 - if(HAS_PERSONALITY(owner, /datum/personality/compassionate) && mood_change < 0) - mood_change *= 1.5 - timeout *= 1.5 + if(mood_change < 0) + mood_change = ceil(mood_change * max(DESENSITIZED_MINIMUM, owner.mind?.desensitized_level || 1.0)) + + if(HAS_PERSONALITY(owner, /datum/personality/compassionate)) + mood_change *= 1.5 + timeout *= 1.5 if(gibbed || dusted) mood_change *= 1.2 @@ -72,7 +75,7 @@ /// Checks if our mood can get worse by seeing another death (or better if we're weird like that) /datum/mood_event/conditional/see_death/proc/can_stack_effect(mob/dead_mob) // if we're desensitized, don't stack unless it's a buff - if(HAS_MIND_TRAIT(owner, TRAIT_DESENSITIZED) && mood_change > 0) + if(IS_DESENSITIZED(owner) && mood_change < 0) return FALSE // if we're seeing a spawned mob die, don't stack if(HAS_TRAIT(dead_mob, TRAIT_SPAWNED_MOB)) @@ -275,11 +278,10 @@ /// Desensitized brings up the rear /datum/mood_event/conditional/see_death/desensitized priority = DESENSITIZED_PRIORITY - mood_change = parent_type::mood_change * 0.5 timeout = parent_type::timeout * 0.5 /datum/mood_event/conditional/see_death/desensitized/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) - return HAS_MIND_TRAIT(who, TRAIT_DESENSITIZED) + return IS_DESENSITIZED(who) /datum/mood_event/conditional/see_death/desensitized/update_effect(mob/dead_mob, dusted, gibbed) if(gibbed) diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 8102d928212..665857195f1 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -638,6 +638,8 @@ mood_change = 0 description = "I just got coated in blood. Fascinating!" return + if(IS_DESENSITIZED(owner)) + mood_change *= 0.5 /datum/mood_event/teetotal_hangover description = "What a disgraceful display! This is what happens when one indulges in alcohol!" diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index 750bfba732f..cad5efca459 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -675,3 +675,44 @@ desc = "Bathed in soothing darkness, you will slowly heal yourself" use_user_hud_icon = TRUE overlay_state = "lightless" + +/// Applies desensitized mood modifier to the mob, carrying between mind transfers +/datum/status_effect/desensitized + id = "desensitized" + duration = STATUS_EFFECT_PERMANENT + status_type = STATUS_EFFECT_MULTIPLE + alert_type = null + /// How much to multiply desensitization level by + var/magnitude = 1.0 + /// Effect ID for removal purposes + var/effect_id + +/datum/status_effect/desensitized/on_creation(mob/living/new_owner, effect_id, magnitude) + src.effect_id = effect_id + src.magnitude = max(DESENSITIZED_MINIMUM, magnitude) + return ..() + +/datum/status_effect/desensitized/on_apply() + owner.mind?.desensitized_level *= magnitude + RegisterSignal(owner, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(add_magnitude)) + RegisterSignal(owner, COMSIG_MOB_MIND_TRANSFERRED_OUT_OF, PROC_REF(remove_magnitude)) + return TRUE + +/datum/status_effect/desensitized/on_remove() + owner.mind?.desensitized_level /= magnitude + UnregisterSignal(owner, list(COMSIG_MOB_MIND_TRANSFERRED_INTO, COMSIG_MOB_MIND_TRANSFERRED_OUT_OF)) + +/datum/status_effect/desensitized/before_remove(effect_id, magnitude) + if(istext(src.effect_id) && istext(effect_id)) // if an id is set, they must match + return src.effect_id == effect_id + if(isnum(magnitude)) // otherwise if a magnitude is passed, it must match + return src.magnitude == magnitude + return FALSE + +/datum/status_effect/desensitized/proc/add_magnitude(datum/source, mob/living/old_body, datum/mind/swapping) + SIGNAL_HANDLER + swapping.desensitized_level *= magnitude + +/datum/status_effect/desensitized/proc/remove_magnitude(datum/source, mob/living/old_body, datum/mind/swapping) + SIGNAL_HANDLER + swapping.desensitized_level /= magnitude diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 4e6ade226cd..ac45ba08242 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -57,6 +57,8 @@ GLOBAL_LIST_EMPTY(antagonists) var/hardcore_random_bonus = FALSE /// A path to the audio stinger that plays upon gaining this datum. var/stinger_sound + /// Multiplicative modifier to the mind's desensitized level when this antagonist is applied. Minimum is 0.1. + var/desensitized_modifier = 1.0 //ANTAG UI @@ -262,6 +264,7 @@ GLOBAL_LIST_EMPTY(antagonists) if(type_policy) to_chat(owner.current, type_policy) + owner.desensitized_level *= max(DESENSITIZED_MINIMUM, desensitized_modifier) apply_innate_effects() give_antag_moodies() RegisterSignal(owner, COMSIG_PRE_MINDSHIELD_IMPLANT, PROC_REF(pre_mindshield)) @@ -320,6 +323,7 @@ GLOBAL_LIST_EMPTY(antagonists) if(!owner) CRASH("Antag datum with no owner.") + owner.desensitized_level /= max(DESENSITIZED_MINIMUM, desensitized_modifier) if(owner.current) remove_innate_effects() clear_antag_moodies() diff --git a/code/modules/antagonists/cult/datums/cult_team.dm b/code/modules/antagonists/cult/datums/cult_team.dm index 7cbd9fe6284..130683a9046 100644 --- a/code/modules/antagonists/cult/datums/cult_team.dm +++ b/code/modules/antagonists/cult/datums/cult_team.dm @@ -58,7 +58,6 @@ SEND_SOUND(mind.current, 'sound/music/antag/bloodcult/bloodcult_eyes.ogg') to_chat(mind.current, span_cult_large(span_warning("The veil weakens as your cult grows, your eyes begin to glow..."))) mind.current.AddElement(/datum/element/cult_eyes) - ADD_TRAIT(mind.current, TRAIT_DESENSITIZED, CULT_TRAIT) cult_risen = TRUE log_game("The blood cult has risen with [cultplayers] players.") diff --git a/code/modules/antagonists/cult/datums/cultist.dm b/code/modules/antagonists/cult/datums/cultist.dm index 9ce4dc5b13a..c637b2b45cb 100644 --- a/code/modules/antagonists/cult/datums/cultist.dm +++ b/code/modules/antagonists/cult/datums/cultist.dm @@ -8,6 +8,7 @@ pref_flag = ROLE_CULTIST antag_hud_name = "cult" stinger_sound = 'sound/music/antag/bloodcult/bloodcult_gain.ogg' + desensitized_modifier = DESENSITIZED_THRESHOLD ///Boolean on whether the starting equipment should be given to their inventory. var/give_equipment = FALSE @@ -65,7 +66,6 @@ if(cult_team.cult_risen) current.AddElement(/datum/element/cult_eyes, initial_delay = 0 SECONDS) - ADD_TRAIT(current, TRAIT_DESENSITIZED, CULT_TRAIT) if(cult_team.cult_ascendent) current.AddElement(/datum/element/cult_halo, initial_delay = 0 SECONDS) @@ -88,7 +88,6 @@ if (HAS_TRAIT(current, TRAIT_CULT_HALO)) current.RemoveElement(/datum/element/cult_halo) - REMOVE_TRAIT(current, TRAIT_DESENSITIZED, CULT_TRAIT) REMOVE_TRAIT(current, TRAIT_HEALS_FROM_CULT_PYLONS, CULT_TRAIT) /datum/antagonist/cult/on_mindshield(mob/implanter) diff --git a/code/modules/antagonists/ert/ert.dm b/code/modules/antagonists/ert/ert.dm index c0bf50819e0..f7ae99d8fe0 100644 --- a/code/modules/antagonists/ert/ert.dm +++ b/code/modules/antagonists/ert/ert.dm @@ -13,6 +13,7 @@ suicide_cry = "FOR NANOTRASEN!!" // Not 'true' antags, this disables certain interactions that assume the owner is a baddie antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST + desensitized_modifier = DESENSITIZED_THRESHOLD * 0.5 var/datum/team/ert/ert_team var/leader = FALSE var/datum/outfit/outfit = /datum/outfit/centcom/ert/security @@ -36,14 +37,6 @@ equipERT() . = ..() -/datum/antagonist/ert/apply_innate_effects(mob/living/mob_override) - var/mob/living/carbon/human/officer = mob_override || owner.current - ADD_TRAIT(officer, TRAIT_DESENSITIZED, REF(src)) - -/datum/antagonist/ert/remove_innate_effects(mob/living/mob_override) - var/mob/living/carbon/human/officer = mob_override || owner.current - REMOVE_TRAIT(officer, TRAIT_DESENSITIZED, REF(src)) - /datum/antagonist/ert/get_team() return ert_team diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index af0381db3cc..a8ce3af3509 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -654,7 +654,7 @@ if(!isnull(ascension_achievement)) user.client?.give_award(ascension_achievement, user) heretic_datum.rust_strength = RUST_RESISTANCE_ORGANIC // Ascended heretics can rust whatever they want (below RUST_RESISTANCE_ABSOLUTE) - ADD_TRAIT(user, TRAIT_DESENSITIZED, type) + user.apply_status_effect(/datum/status_effect/desensitized, type, DESENSITIZED_THRESHOLD * 0.4) return TRUE /datum/heretic_knowledge/ultimate/cleanup_atoms(list/selected_atoms) diff --git a/code/modules/antagonists/heretic/status_effects/ghoul.dm b/code/modules/antagonists/heretic/status_effects/ghoul.dm index 74e60c966a4..bc2f524e756 100644 --- a/code/modules/antagonists/heretic/status_effects/ghoul.dm +++ b/code/modules/antagonists/heretic/status_effects/ghoul.dm @@ -62,9 +62,9 @@ on_made_callback?.Invoke(human_target) ADD_TRAIT(human_target, TRAIT_FAKEDEATH, TRAIT_STATUS_EFFECT(id)) ADD_TRAIT(human_target, TRAIT_HERETIC_SUMMON, TRAIT_STATUS_EFFECT(id)) - ADD_TRAIT(human_target, TRAIT_DESENSITIZED, TRAIT_STATUS_EFFECT(id)) human_target.become_husk(TRAIT_STATUS_EFFECT(id)) human_target.add_faction(FACTION_HERETIC) + human_target.apply_status_effect(/datum/status_effect/desensitized, TRAIT_STATUS_EFFECT(id), DESENSITIZED_THRESHOLD * 0.2) if(human_target.mind) var/datum/antagonist/heretic_monster/heretic_monster = human_target.mind.add_antag_datum(/datum/antagonist/heretic_monster) @@ -93,9 +93,9 @@ on_lost_callback?.Invoke(human_target) REMOVE_TRAIT(human_target, TRAIT_FAKEDEATH, TRAIT_STATUS_EFFECT(id)) REMOVE_TRAIT(human_target, TRAIT_HERETIC_SUMMON, TRAIT_STATUS_EFFECT(id)) - REMOVE_TRAIT(human_target, TRAIT_DESENSITIZED, TRAIT_STATUS_EFFECT(id)) human_target.cure_husk(TRAIT_STATUS_EFFECT(id)) human_target.remove_faction(FACTION_HERETIC) + human_target.remove_status_effect(/datum/status_effect/desensitized, TRAIT_STATUS_EFFECT(id)) human_target.mind?.remove_antag_datum(/datum/antagonist/heretic_monster) UnregisterSignal(human_target, COMSIG_LIVING_DEATH) diff --git a/code/modules/antagonists/highlander/highlander.dm b/code/modules/antagonists/highlander/highlander.dm index d197f91f573..39a6a52eef9 100644 --- a/code/modules/antagonists/highlander/highlander.dm +++ b/code/modules/antagonists/highlander/highlander.dm @@ -6,9 +6,9 @@ can_elimination_hijack = ELIMINATION_ENABLED suicide_cry = "FOR SCOTLAND!!" // If they manage to lose their no-drop stuff somehow antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST + desensitized_modifier = DESENSITIZED_THRESHOLD * 0.2 /// Traits we apply/remove to our target on-demand. var/static/list/applicable_traits = list( - TRAIT_DESENSITIZED, TRAIT_NOBREATH, TRAIT_NODISMEMBER, TRAIT_NOFIRE, diff --git a/code/modules/antagonists/nukeop/datums/operative.dm b/code/modules/antagonists/nukeop/datums/operative.dm index 59b2ca2f4af..abf38593990 100644 --- a/code/modules/antagonists/nukeop/datums/operative.dm +++ b/code/modules/antagonists/nukeop/datums/operative.dm @@ -9,6 +9,7 @@ hijack_speed = 2 //If you can't take out the station, take the shuttle instead. suicide_cry = "FOR THE SYNDICATE!!" stinger_sound = 'sound/music/antag/ops.ogg' + desensitized_modifier = DESENSITIZED_THRESHOLD * 0.5 /// Which nukie team are we on? var/datum/team/nuclear/nuke_team @@ -132,7 +133,6 @@ var/mob/living/carbon/human/operative = owner.current ADD_TRAIT(operative, TRAIT_NOFEAR_HOLDUPS, INNATE_TRAIT) - ADD_TRAIT(operative, TRAIT_DESENSITIZED, INNATE_TRAIT) if(!nukeop_outfit) // this variable is null in instances where an antagonist datum is granted via enslaving the mind (/datum/mind/proc/enslave_mind_to_creator), like in golems. return diff --git a/code/modules/antagonists/pirate/pirate.dm b/code/modules/antagonists/pirate/pirate.dm index 7b09a2540ec..e3f4d00d8eb 100644 --- a/code/modules/antagonists/pirate/pirate.dm +++ b/code/modules/antagonists/pirate/pirate.dm @@ -8,6 +8,7 @@ suicide_cry = "FOR ME MATEYS!!" stinger_sound = 'sound/music/antag/pirate/pirate_start.ogg' hijack_speed = 2 // That is without doubt the worst pirate I have ever seen. + desensitized_modifier = DESENSITIZED_THRESHOLD var/datum/team/pirate/crew /datum/antagonist/pirate/greet() @@ -46,12 +47,10 @@ var/datum/language_holder/holder = owner_mob.get_language_holder() holder.grant_language(/datum/language/piratespeak, source = LANGUAGE_PIRATE) holder.selected_language = /datum/language/piratespeak - ADD_TRAIT(owner_mob, TRAIT_DESENSITIZED, REF(src)) /datum/antagonist/pirate/remove_innate_effects(mob/living/mob_override) var/mob/living/owner_mob = mob_override || owner.current owner_mob.remove_language(/datum/language/piratespeak, source = LANGUAGE_PIRATE) - REMOVE_TRAIT(owner_mob, TRAIT_DESENSITIZED, REF(src)) /datum/team/pirate name = "\improper Pirate crew" diff --git a/code/modules/antagonists/space_ninja/space_ninja.dm b/code/modules/antagonists/space_ninja/space_ninja.dm index 9cb98f928f9..5a1bed62c7f 100644 --- a/code/modules/antagonists/space_ninja/space_ninja.dm +++ b/code/modules/antagonists/space_ninja/space_ninja.dm @@ -12,17 +12,10 @@ can_assign_self_objectives = TRUE ui_name = "AntagInfoNinja" default_custom_objective = "Destroy vital station infrastructure, without being seen." + desensitized_modifier = DESENSITIZED_THRESHOLD ///Whether or not this ninja will obtain objectives var/give_objectives = TRUE -/datum/antagonist/ninja/apply_innate_effects(mob/living/mob_override) - var/mob/ninja = mob_override || owner.current - ADD_TRAIT(ninja, TRAIT_DESENSITIZED, REF(src)) - -/datum/antagonist/ninja/remove_innate_effects(mob/living/mob_override) - var/mob/ninja = mob_override || owner.current - REMOVE_TRAIT(ninja, TRAIT_DESENSITIZED, REF(src)) - /** * Proc that equips the space ninja outfit on a given individual. By default this is the owner of the antagonist datum. * diff --git a/code/modules/holodeck/holo_effect.dm b/code/modules/holodeck/holo_effect.dm index fbff1b5a861..2f61f9e832e 100644 --- a/code/modules/holodeck/holo_effect.dm +++ b/code/modules/holodeck/holo_effect.dm @@ -63,7 +63,7 @@ our_mob.flags_1 |= HOLOGRAM_1 // these vars are not really standardized but all would theoretically create stuff on death - our_mob.add_traits(list(TRAIT_PERMANENTLY_MORTAL, TRAIT_NO_BLOOD_OVERLAY, TRAIT_NOBLOOD, TRAIT_NOHUNGER), INNATE_TRAIT) + our_mob.add_traits(list(TRAIT_PERMANENTLY_MORTAL, TRAIT_NO_BLOOD_OVERLAY, TRAIT_NOBLOOD, TRAIT_NOHUNGER, TRAIT_SPAWNED_MOB), INNATE_TRAIT) RegisterSignal(our_mob, COMSIG_QDELETING, PROC_REF(handle_mob_delete)) return our_mob diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 04ed2d91fcf..165444d106d 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -132,6 +132,9 @@ /// If set, look for a policy with this instead of the job title var/policy_override + /// How desensitized this job is to seeing death as a base - applied with the job + var/desensitized_base = 1.0 + /datum/job/New() . = ..() var/new_spawn_positions = CHECK_MAP_JOB_CHANGE(title, "spawn_positions") @@ -147,6 +150,8 @@ if(length(mind_traits)) spawned.mind.add_traits(mind_traits, JOB_TRAIT) + spawned.mind.desensitized_level = clamp(desensitized_base, DESENSITIZED_MINIMUM, spawned.mind.desensitized_level) + var/obj/item/organ/liver/liver = spawned.get_organ_slot(ORGAN_SLOT_LIVER) if(liver && length(liver_traits)) liver.add_traits(liver_traits, JOB_TRAIT) diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index 718751bf787..a3ad2d52aa8 100644 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -22,7 +22,8 @@ paycheck = PAYCHECK_COMMAND paycheck_department = ACCOUNT_SEC - mind_traits = list(HEAD_OF_STAFF_MIND_TRAITS, TRAIT_DESENSITIZED) + mind_traits = list(HEAD_OF_STAFF_MIND_TRAITS) + desensitized_base = DESENSITIZED_THRESHOLD liver_traits = list(TRAIT_ROYAL_METABOLISM) display_order = JOB_DISPLAY_ORDER_CAPTAIN diff --git a/code/modules/jobs/job_types/chaplain/chaplain.dm b/code/modules/jobs/job_types/chaplain/chaplain.dm index 75fd0a7c00a..d4c39db5bee 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain.dm @@ -12,7 +12,8 @@ outfit = /datum/outfit/job/chaplain plasmaman_outfit = /datum/outfit/plasmaman/chaplain - mind_traits = list(TRAIT_SPIRITUAL, TRAIT_DESENSITIZED) + mind_traits = list(TRAIT_SPIRITUAL) + desensitized_base = DESENSITIZED_THRESHOLD paycheck = PAYCHECK_CREW paycheck_department = ACCOUNT_SRV diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm index 4cab7531da4..3b4a856a118 100644 --- a/code/modules/jobs/job_types/chief_medical_officer.dm +++ b/code/modules/jobs/job_types/chief_medical_officer.dm @@ -26,7 +26,8 @@ paycheck = PAYCHECK_COMMAND paycheck_department = ACCOUNT_MED - mind_traits = list(HEAD_OF_STAFF_MIND_TRAITS, MEDICAL_MIND_TRAITS) + mind_traits = list(HEAD_OF_STAFF_MIND_TRAITS) + desensitized_base = DESENSITIZED_THRESHOLD liver_traits = list(TRAIT_MEDICAL_METABOLISM, TRAIT_ROYAL_METABOLISM) display_order = JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm index c4013d122ad..aebe1b9ca80 100644 --- a/code/modules/jobs/job_types/cook.dm +++ b/code/modules/jobs/job_types/cook.dm @@ -15,7 +15,7 @@ paycheck = PAYCHECK_CREW paycheck_department = ACCOUNT_SRV - mind_traits = list(TRAIT_DESENSITIZED) // butcher + desensitized_base = DESENSITIZED_THRESHOLD // butcher liver_traits = list(TRAIT_CULINARY_METABOLISM) display_order = JOB_DISPLAY_ORDER_COOK diff --git a/code/modules/jobs/job_types/coroner.dm b/code/modules/jobs/job_types/coroner.dm index ddb1ad7e83f..150d029d35c 100644 --- a/code/modules/jobs/job_types/coroner.dm +++ b/code/modules/jobs/job_types/coroner.dm @@ -12,7 +12,8 @@ outfit = /datum/outfit/job/coroner plasmaman_outfit = /datum/outfit/plasmaman/coroner - mind_traits = list(TRAIT_MORBID, MEDICAL_MIND_TRAITS) + mind_traits = list(TRAIT_MORBID) + desensitized_base = DESENSITIZED_THRESHOLD liver_traits = list(TRAIT_CORONER_METABOLISM) paycheck = PAYCHECK_CREW diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm index e56225c42a1..4926749c636 100644 --- a/code/modules/jobs/job_types/detective.dm +++ b/code/modules/jobs/job_types/detective.dm @@ -22,7 +22,7 @@ paycheck = PAYCHECK_CREW paycheck_department = ACCOUNT_SEC - mind_traits = list(SECURITY_MIND_TRAITS) + desensitized_base = DESENSITIZED_THRESHOLD liver_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM) display_order = JOB_DISPLAY_ORDER_DETECTIVE diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index 4fcad1b97af..936028f86f0 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -23,7 +23,8 @@ /datum/job_department/command, ) - mind_traits = list(HEAD_OF_STAFF_MIND_TRAITS, SECURITY_MIND_TRAITS) + mind_traits = list(HEAD_OF_STAFF_MIND_TRAITS) + desensitized_base = DESENSITIZED_THRESHOLD liver_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM, TRAIT_ROYAL_METABOLISM) paycheck = PAYCHECK_COMMAND diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm index d7963faeedf..f357bcac8ac 100644 --- a/code/modules/jobs/job_types/medical_doctor.dm +++ b/code/modules/jobs/job_types/medical_doctor.dm @@ -15,7 +15,7 @@ paycheck = PAYCHECK_CREW paycheck_department = ACCOUNT_MED - mind_traits = list(MEDICAL_MIND_TRAITS) + desensitized_base = DESENSITIZED_THRESHOLD liver_traits = list(TRAIT_MEDICAL_METABOLISM) display_order = JOB_DISPLAY_ORDER_MEDICAL_DOCTOR diff --git a/code/modules/jobs/job_types/paramedic.dm b/code/modules/jobs/job_types/paramedic.dm index 85aae2f1f94..ad9244333b8 100644 --- a/code/modules/jobs/job_types/paramedic.dm +++ b/code/modules/jobs/job_types/paramedic.dm @@ -15,7 +15,7 @@ paycheck = PAYCHECK_CREW paycheck_department = ACCOUNT_MED - mind_traits = list(MEDICAL_MIND_TRAITS) + desensitized_base = DESENSITIZED_THRESHOLD liver_traits = list(TRAIT_MEDICAL_METABOLISM) display_order = JOB_DISPLAY_ORDER_PARAMEDIC @@ -79,4 +79,3 @@ mask = /obj/item/clothing/mask/breath/medical r_pocket = /obj/item/flashlight/pen internals_slot = ITEM_SLOT_SUITSTORE - diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index 78ea30f1fc1..06c78c25f1c 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -19,7 +19,7 @@ paycheck = PAYCHECK_CREW paycheck_department = ACCOUNT_SEC - mind_traits = list(SECURITY_MIND_TRAITS) + desensitized_base = DESENSITIZED_THRESHOLD liver_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM) display_order = JOB_DISPLAY_ORDER_SECURITY_OFFICER diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm index c759552c049..bcc07dfd2d6 100644 --- a/code/modules/jobs/job_types/warden.dm +++ b/code/modules/jobs/job_types/warden.dm @@ -20,7 +20,7 @@ paycheck = PAYCHECK_CREW paycheck_department = ACCOUNT_SEC - mind_traits = list(SECURITY_MIND_TRAITS) + desensitized_base = DESENSITIZED_THRESHOLD liver_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM, TRAIT_PRETENDER_ROYAL_METABOLISM) display_order = JOB_DISPLAY_ORDER_WARDEN diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index e87b425acd7..80189ae4902 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -180,9 +180,13 @@ return for(var/mob/living/nearby in viewers(src)) - if(nearby.stat >= UNCONSCIOUS || nearby.is_blind()) + if(nearby == src || nearby.stat >= UNCONSCIOUS || nearby.is_blind()) continue nearby.add_mood_event("saw_death", /datum/mood_event/conditional/see_death, src, dusted, gibbed) + nearby.mind?.witnessed_death(src) + + if(!gibbed && !dusted) + mind?.experienced_death() /mob/living/silicon/send_death_moodlets(dusted = FALSE, gibbed = FALSE) return // You are a machine (Future todo, roboticists feel sad though) diff --git a/code/modules/unit_tests/death_moodlets.dm b/code/modules/unit_tests/death_moodlets.dm index ded468abbb1..4b1b2fc3512 100644 --- a/code/modules/unit_tests/death_moodlets.dm +++ b/code/modules/unit_tests/death_moodlets.dm @@ -6,6 +6,7 @@ /datum/unit_test/death_moodlets/Run() var/mob/living/carbon/human/consistent/dummy = allocate(__IMPLIED_TYPE__) + dummy.mind_initialize() prepare_dummy(dummy) var/mob/living/dying = get_dying_mob() @@ -47,7 +48,7 @@ desired_moodlet = /datum/mood_event/conditional/see_death/desensitized /datum/unit_test/death_moodlets/human/desensitized/prepare_dummy(mob/living/carbon/human/consistent/dummy) - ADD_TRAIT(dummy, TRAIT_DESENSITIZED, TRAIT_SOURCE_UNIT_TESTS) + dummy.mind.desensitized_level = DESENSITIZED_THRESHOLD /// Test callous moodlet /datum/unit_test/death_moodlets/human/callous @@ -61,8 +62,8 @@ desired_moodlet = /datum/mood_event/conditional/see_death/dontcare /datum/unit_test/death_moodlets/human/desensitized_and_callous/prepare_dummy(mob/living/carbon/human/consistent/dummy) - ADD_TRAIT(dummy, TRAIT_DESENSITIZED, TRAIT_SOURCE_UNIT_TESTS) dummy.add_personality(/datum/personality/callous) + dummy.mind.desensitized_level = DESENSITIZED_THRESHOLD /// Test cultist positive moodlet /datum/unit_test/death_moodlets/human/cultist @@ -87,7 +88,7 @@ desired_moodlet = /datum/mood_event/conditional/see_death/pet /datum/unit_test/death_moodlets/pet/desensitized_to_pet/prepare_dummy(mob/living/carbon/human/consistent/dummy) - ADD_TRAIT(dummy, TRAIT_DESENSITIZED, TRAIT_SOURCE_UNIT_TESTS) + dummy.mind.desensitized_level = DESENSITIZED_THRESHOLD /// Tests callous moodlet when a pet dies /datum/unit_test/death_moodlets/pet/callous_to_pet @@ -102,3 +103,23 @@ /datum/unit_test/death_moodlets/pet/animal_disliker_to_pet/prepare_dummy(mob/living/carbon/human/consistent/dummy) dummy.add_personality(/datum/personality/animal_disliker) + +// This one just tests that the death desensitized status effect works as expected +/datum/unit_test/desensitized_status_effect + +/datum/unit_test/desensitized_status_effect/Run() + var/mob/living/carbon/human/consistent/normal_dummy = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/desensitized_dummy = EASY_ALLOCATE() + desensitized_dummy.mind_initialize() + + desensitized_dummy.apply_status_effect(/datum/status_effect/desensitized, TRAIT_SOURCE_UNIT_TESTS, DESENSITIZED_THRESHOLD) + TEST_ASSERT_EQUAL(desensitized_dummy.mind.desensitized_level, DESENSITIZED_THRESHOLD, "Desensitized level not set correctly on application of desensitized status effect.") + + desensitized_dummy.mind.transfer_to(normal_dummy) + TEST_ASSERT_EQUAL(normal_dummy.mind.desensitized_level, 1.0, "Desensitized level not reset to normal on transfer of mind from desensitized mob.") + + normal_dummy.mind.transfer_to(desensitized_dummy) + TEST_ASSERT_EQUAL(desensitized_dummy.mind.desensitized_level, DESENSITIZED_THRESHOLD, "Desensitized level not restored on transfer of mind back to desensitized mob.") + + desensitized_dummy.remove_status_effect(/datum/status_effect/desensitized, TRAIT_SOURCE_UNIT_TESTS) + TEST_ASSERT_EQUAL(desensitized_dummy.mind.desensitized_level, 1.0, "Desensitized level not reset to normal on removal of desensitized status effect.") diff --git a/icons/ui/achievements/achievements.dmi b/icons/ui/achievements/achievements.dmi index 80ebc9cd2972fae9e475109279f036a59c2ac999..d5bef5c42f87f3b0f29e0b09e6574f7a8374ecf5 100644 GIT binary patch delta 9355 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 Ls?YM}4gLQI)Z=kq delta 5460 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 KMNUMnLSTY}mDrB}