Empaths is now a component instead of a trait (#93680)

## About The Pull Request

Empath has become a component instead of a trait, letting it store
variables or whatever components are good for. Also, it works slightly
differently for empath quirk users and moon heretics alike.

Additionally:
- You can only shiver when you see an evil person once. After that it
won't happen again.
- Empaths no longer get a mood debuff from seeing someone distressed. 
- Moon heretic empaths no longer get smited by evil people. 
- Moon heretic empaths aren't scared of evil people.

The component also supports empaths being able to tell if a fake dead
body is actually dead, and being able to use empath on yourself, but
those aren't enabled for the empath quirk or moon heretics. (Self-empath
would probably powercreep self aware)

## Why It's Good For The Game

While the empath quirk is incompatible with the evil quirk, the trait
wasn't. That means you could potentially become an evil empath and still
feel bad about other people feeling bad and get smited by other evil
people. Being an empath is different from having empathy, and there's
now a personality trait (compassionate) that has a very similar feature
so the mood debuff got axed and moon heretics can't get smited by
mending touch anymore.

I think being a heretic and looking into pierced realities probably
means you have the mental fortitude to not freak out when you see an
evil person. Evil people are (probably) a lot less scary than whatever
mind destroying thing is in those holes.

As for the only-getting-scared-once bit, freaking out when you see an
evil person is flavorful but annoying after it happens multiple times.
Especially if it happens after examining the same person multiple times.
This commit is contained in:
Fghj240
2025-11-06 16:04:07 -08:00
committed by GitHub
parent 7b296c44e7
commit 6b2409065a
15 changed files with 154 additions and 45 deletions

View File

@@ -336,3 +336,23 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define EMOTE_IMPORTANT (1<<2)
/// Emote only prints to runechat, not to the chat window
#define EMOTE_RUNECHAT (1<<3)
// Flags for the empath component
/// Can the empath see if a living mob has combat mode on
#define EMPATH_SEE_COMBAT (1<<0)
/// Can the empath see if living mob has over 10 oxyloss
#define EMPATH_SEE_OXY (1<<1)
/// Can the empath see if living mob has over 10 toxloss
#define EMPATH_SEE_TOX (1<<2)
/// Can the empath see if living mob's sanity is at distressed or below
#define EMPATH_SEE_SANITY (1<<3)
/// Can the empath see if living mob is blind
#define EMPATH_SEE_BLIND (1<<4)
/// Can the empath see if living mob is deaf
#define EMPATH_SEE_DEAF (1<<5)
/// Can the empath see if living mob's body temperature is too hot for their species
#define EMPATH_SEE_HOT (1<<6)
/// Can the empath see if living mob's body temperature is too low for their species
#define EMPATH_SEE_COLD (1<<7)
/// Can the empath see if living mob has the fundamentally evil trait
#define EMPATH_SEE_EVIL (1<<8)

View File

@@ -57,3 +57,6 @@
/// From /datum/action/vehicle/ridden/wheelchair/bell/Trigger():
#define COMSIG_WHEELCHAIR_BELL_RANG "wheelchair_bell_rang"
/// From /datum/action/cooldown/spell/touch/lay_on_hands/proc/determine_if_this_hurts_instead(), sent to the /mob/living/carbon/hurtguy: (/mob/living/carbon/mendicant)
#define COMSIG_ON_LAY_ON_HANDS "mob_ability_lay_on_hands"

View File

@@ -9,6 +9,8 @@
#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON "atom_init_success_on"
///from base of atom/examine(): (/mob, list/examine_text)
#define COMSIG_ATOM_EXAMINE "atom_examine"
/// from base of atom/examine(): (/mob, list/examine_text)
#define COMSIG_CARBON_MID_EXAMINE "carbon_mid_examine"
///from base of atom/examine_tags(): (/mob, list/examine_tags)
#define COMSIG_ATOM_EXAMINE_TAGS "atom_examine_tags"
///from base of atom/get_examine_name(): (/mob, list/overrides)

View File

@@ -332,6 +332,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_WOUND_LICKER "wound_licker"
/// Mobs with this trait are allowed to use silicon emotes
#define TRAIT_SILICON_EMOTES_ALLOWED "silicon_emotes_allowed"
/// Mobs with this trait can tell when other mobs whisper even if their mouth is covered. They still can't tell what was whispered though.
#define TRAIT_SEE_MASK_WHISPER "see_mask_whisper"
/// This trait designate that the mob was originally a monkey
#define TRAIT_BORN_MONKEY "born_as_a_monkey"
@@ -1011,7 +1013,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_PHOTOGRAPHER "photographer"
#define TRAIT_MUSICIAN "musician"
#define TRAIT_LIGHT_DRINKER "light_drinker"
#define TRAIT_EMPATH "empath"
#define TRAIT_EVIL "evil"
#define TRAIT_FRIENDLY "friendly"
#define TRAIT_GRABWEAKNESS "grab_weakness"

View File

@@ -666,3 +666,15 @@ DEFINE_BITFIELD(weather_flags, list(
"WEATHER_BAROMETER" = WEATHER_BAROMETER,
"WEATHER_TEMPERATURE_BYPASS_CLOTHING" = WEATHER_TEMPERATURE_BYPASS_CLOTHING,
))
DEFINE_BITFIELD(visible_info, list(
"EMPATH_SEE_COMBAT" = EMPATH_SEE_COMBAT,
"EMPATH_SEE_OXY" = EMPATH_SEE_OXY,
"EMPATH_SEE_TOX" = EMPATH_SEE_TOX,
"EMPATH_SEE_SANITY" = EMPATH_SEE_SANITY,
"EMPATH_SEE_BLIND" = EMPATH_SEE_BLIND,
"EMPATH_SEE_DEAF" = EMPATH_SEE_DEAF,
"EMPATH_SEE_HOT" = EMPATH_SEE_HOT,
"EMPATH_SEE_COLD" = EMPATH_SEE_COLD,
"EMPATH_SEE_EVIL" = EMPATH_SEE_EVIL,
))

View File

@@ -272,7 +272,6 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_ELDRITCH_PAINTING_EXAMINE" = TRAIT_ELDRITCH_PAINTING_EXAMINE,
"TRAIT_ELITE_CHALLENGER" = TRAIT_ELITE_CHALLENGER,
"TRAIT_EMOTEMUTE" = TRAIT_EMOTEMUTE,
"TRAIT_EMPATH" = TRAIT_EMPATH,
"TRAIT_ENTRAILS_READER" = TRAIT_ENTRAILS_READER,
"TRAIT_EVIL" = TRAIT_EVIL,
"TRAIT_EXAMINE_DEEPER_FISH" = TRAIT_EXAMINE_DEEPER_FISH,
@@ -522,6 +521,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_SABRAGE_PRO" = TRAIT_SABRAGE_PRO,
"TRAIT_SECURITY_HUD" = TRAIT_SECURITY_HUD,
"TRAIT_SECURITY_HUD_ID_ONLY" = TRAIT_SECURITY_HUD_ID_ONLY,
"TRAIT_SEE_MASK_WHISPER" = TRAIT_SEE_MASK_WHISPER,
"TRAIT_SEE_WORN_COLOURS" = TRAIT_SEE_WORN_COLOURS,
"TRAIT_SELF_AWARE" = TRAIT_SELF_AWARE,
"TRAIT_SELF_SURGERY" = TRAIT_SELF_SURGERY,

View File

@@ -100,7 +100,6 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_EASILY_WOUNDED" = TRAIT_EASILY_WOUNDED,
"TRAIT_EASYDISMEMBER" = TRAIT_EASYDISMEMBER,
"TRAIT_EMOTEMUTE" = TRAIT_EMOTEMUTE,
"TRAIT_EMPATH" = TRAIT_EMPATH,
"TRAIT_ENTRAILS_READER" = TRAIT_ENTRAILS_READER,
"TRAIT_EVIL" = TRAIT_EVIL,
"TRAIT_EXAMINE_FISHING_SPOT" = TRAIT_EXAMINE_FISHING_SPOT,
@@ -270,6 +269,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_ROD_SUPLEX" = TRAIT_ROD_SUPLEX,
"TRAIT_ROUGHRIDER" = TRAIT_ROUGHRIDER,
"TRAIT_SABRAGE_PRO" = TRAIT_SABRAGE_PRO,
"TRAIT_SEE_MASK_WHISPER" = TRAIT_SEE_MASK_WHISPER,
"TRAIT_SECURITY_HUD" = TRAIT_SECURITY_HUD,
"TRAIT_SECURITY_HUD_ID_ONLY" = TRAIT_SECURITY_HUD_ID_ONLY,
"TRAIT_SELF_AWARE" = TRAIT_SELF_AWARE,

View File

@@ -0,0 +1,98 @@
// Empath quirk component, it's a component because it can be applied in ways that don't give you the quirk. (For health analyzer purposes)
/datum/component/empathy
dupe_mode = COMPONENT_DUPE_SOURCES
// Whether or not we should get scared the next time we see an evil person.
var/seen_it = FALSE
// What sort of information we can glean from examining someone
var/visible_info = ALL
// Whether or not we can use empathy on ourselves
var/self_empath = FALSE
// Whether or not empathy works on humans playing dead
var/sense_dead = FALSE
// Whether or not we can tell if people whisper under their mask from far away (We can't hear what they said, we just know they said something)
var/sense_whisper = TRUE
// Whether or not we can be smited by someoneone with the evil trait using the mending touch mutation
var/smite_target = TRUE
/datum/component/empathy/Initialize(seen_it = FALSE, visible_info = ALL, self_empath = FALSE, sense_dead = FALSE, sense_whisper = TRUE, smite_target = TRUE)
if (!isliving(parent))
return COMPONENT_INCOMPATIBLE
src.seen_it = seen_it
src.visible_info = visible_info
src.self_empath = self_empath
src.sense_dead = sense_dead
src.sense_whisper = sense_whisper
src.smite_target = smite_target
if(sense_whisper)
ADD_TRAIT(parent, TRAIT_SEE_MASK_WHISPER, REF(src))
/datum/component/empathy/RegisterWithParent()
RegisterSignal(parent, COMSIG_CARBON_MID_EXAMINE, PROC_REF(get_empath_info))
RegisterSignal(parent, COMSIG_ON_LAY_ON_HANDS, PROC_REF(on_hands_laid))
/datum/component/empathy/proc/get_empath_info(datum/source, mob/living/target, list/examine_list)
SIGNAL_HANDLER
if(target.stat == DEAD)
return
if(HAS_TRAIT(target, TRAIT_FAKEDEATH))
if(sense_dead)
examine_list += "Something about this dead body doesn't look right..."
else
return
var/mob/living/living_parent = parent
if(target == living_parent && !self_empath)
return
var/t_They = target.p_They()
var/t_their = target.p_their()
var/t_Their = target.p_Their()
var/t_are = target.p_are()
if((visible_info & EMPATH_SEE_COMBAT) && target.combat_mode)
examine_list += "[t_They] seem[p_s()] to be on guard."
if((visible_info & EMPATH_SEE_OXY) && target.getOxyLoss() >= 10)
examine_list += "[t_They] seem[p_s()] winded."
if((visible_info & EMPATH_SEE_TOX) && target.getToxLoss() >= 10)
examine_list += "[t_They] seem[p_s()] sickly."
if((visible_info & EMPATH_SEE_SANITY) && target.mob_mood.sanity <= SANITY_DISTURBED)
examine_list += "[t_They] seem[p_s()] distressed."
if((visible_info & EMPATH_SEE_BLIND) && target.is_blind())
examine_list += "[t_They] appear[p_s()] to be staring off into space."
if((visible_info & EMPATH_SEE_DEAF) && HAS_TRAIT(target, TRAIT_DEAF))
examine_list += "[t_They] appear[p_s()] to not be responding to noises."
if((visible_info & EMPATH_SEE_HOT) && target.bodytemperature > target.get_body_temp_heat_damage_limit())
examine_list += "[t_They] [t_are] flushed and wheezing."
if((visible_info & EMPATH_SEE_COLD) && target.bodytemperature < target.get_body_temp_cold_damage_limit())
examine_list += "[t_They] [t_are] shivering."
if((visible_info & EMPATH_SEE_EVIL) && HAS_TRAIT(target, TRAIT_EVIL))
examine_list += "[t_Their] eyes radiate with a unfeeling, cold detachment. There is nothing but darkness within [t_their] soul."
if(living_parent.mind?.holy_role >= HOLY_ROLE_PRIEST)
examine_list += span_warning("PERFECT FOR SMITING!!")
else if(!seen_it)
seen_it = TRUE
living_parent.add_mood_event("encountered_evil", /datum/mood_event/encountered_evil)
living_parent.set_jitter_if_lower(15 SECONDS)
/datum/component/empathy/proc/on_hands_laid(datum/source, mob/living/carbon/smiter)
SIGNAL_HANDLER
if(iscarbon(parent))
var/mob/living/carbon/carbon_parent = parent
if(carbon_parent.mob_biotypes & MOB_UNDEAD)
return FALSE
if(smite_target && HAS_TRAIT(smiter, TRAIT_EVIL))
return TRUE
return FALSE
/datum/component/empathy/UnregisterFromParent()
UnregisterSignal(parent, COMSIG_CARBON_MID_EXAMINE)
/datum/component/empathy/Destroy(force = FALSE)
REMOVE_TRAIT(parent, TRAIT_SEE_MASK_WHISPER, REF(src))
return ..()

View File

@@ -245,14 +245,6 @@
mood_change = -15
event_flags = MOOD_EVENT_PAIN
/datum/mood_event/sad_empath
description = "Someone seems upset..."
mood_change = -1
timeout = 60 SECONDS
/datum/mood_event/sad_empath/add_effects(mob/sadtarget)
description = "[sadtarget.name] seems upset..."
/datum/mood_event/sacrifice_bad
description = "Those darn savages!"
mood_change = -5

View File

@@ -356,6 +356,8 @@
/datum/action/cooldown/spell/touch/lay_on_hands/proc/determine_if_this_hurts_instead(mob/living/carbon/mendicant, mob/living/hurtguy)
var/hurtguy_smiteable = SEND_SIGNAL(hurtguy, COMSIG_ON_LAY_ON_HANDS, mendicant)
if(hurtguy.mob_biotypes & MOB_UNDEAD && mendicant.mob_biotypes & MOB_UNDEAD)
return FALSE //always return false if we're both undead //undead solidarity
@@ -365,10 +367,9 @@
if(HAS_TRAIT(hurtguy, TRAIT_EVIL) && !HAS_TRAIT(mendicant, TRAIT_EVIL)) //Is the guy evil and we're not evil? If so, hurt.
return TRUE
if(!(hurtguy.mob_biotypes & MOB_UNDEAD) && HAS_TRAIT(hurtguy, TRAIT_EMPATH) && HAS_TRAIT(mendicant, TRAIT_EVIL)) //Is the guy not undead, they're an empath and we're evil? If so, hurt.
if(hurtguy_smiteable) //Is some other property of the target (like the empath component) causing them to be smited? If so, hurt.
return TRUE
return FALSE
return (FALSE)
///If our target was undead or evil, we blast them with a firey beam rather than healing them. For, you know, 'holy' reasons. When did genes become so morally uptight?

View File

@@ -3,8 +3,13 @@
desc = "Whether it's a sixth sense or careful study of body language, it only takes you a quick glance at someone to understand how they feel."
icon = FA_ICON_SMILE_BEAM
value = 8
mob_trait = TRAIT_EMPATH
gain_text = span_notice("You feel in tune with those around you.")
lose_text = span_danger("You feel isolated from others.")
medical_record_text = "Patient is highly perceptive of and sensitive to social cues, or may possibly have ESP. Further testing needed."
mail_goodies = list(/obj/item/toy/foamfinger)
/datum/quirk/empath/add(client/client_source)
quirk_holder.AddComponentFrom(REF(src), /datum/component/empathy)
/datum/quirk/empath/remove(client/client_source)
quirk_holder.RemoveComponentSource(REF(src), /datum/component/empathy)

View File

@@ -69,7 +69,7 @@
/datum/heretic_knowledge/limited_amount/starting/base_moon/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
ADD_TRAIT(user, TRAIT_EMPATH, REF(src))
user.AddComponentFrom(REF(src), /datum/component/empathy, seen_it = TRUE, visible_info = ALL, self_empath = FALSE, sense_dead = FALSE, sense_whisper = TRUE, smite_target = FALSE)
/datum/heretic_knowledge/limited_amount/starting/base_moon/on_mansus_grasp(mob/living/source, mob/living/target)
. = ..()

View File

@@ -198,38 +198,12 @@
if(reagents.has_reagent(/datum/reagent/teslium, needs_metabolizing = TRUE))
. += span_smallnoticeital("[t_He] [t_is] emitting a gentle blue glow!") // this should be signalized
var/mob/living/living_user = user
SEND_SIGNAL(living_user, COMSIG_CARBON_MID_EXAMINE, src, .) // Adds examine text after clothing and wounds but before death and scars
if(just_sleeping)
. += span_notice("[t_He] [t_is]n't responding to anything around [t_him] and seem[p_s()] to be asleep.")
else if(!appears_dead)
var/mob/living/living_user = user
if(src != user)
if(HAS_TRAIT(user, TRAIT_EMPATH))
if (combat_mode)
. += "[t_He] seem[p_s()] to be on guard."
if (getOxyLoss() >= 10)
. += "[t_He] seem[p_s()] winded."
if (getToxLoss() >= 10)
. += "[t_He] seem[p_s()] sickly."
if(mob_mood.sanity <= SANITY_DISTURBED)
. += "[t_He] seem[p_s()] distressed."
living_user.add_mood_event("empath", /datum/mood_event/sad_empath, src)
if(is_blind())
. += "[t_He] appear[p_s()] to be staring off into space."
if (HAS_TRAIT(src, TRAIT_DEAF))
. += "[t_He] appear[p_s()] to not be responding to noises."
if (bodytemperature > dna.species.bodytemp_heat_damage_limit)
. += "[t_He] [t_is] flushed and wheezing."
if (bodytemperature < dna.species.bodytemp_cold_damage_limit)
. += "[t_He] [t_is] shivering."
if(HAS_TRAIT(src, TRAIT_EVIL))
. += "[t_His] eyes radiate with a unfeeling, cold detachment. There is nothing but darkness within [t_his] soul."
if(living_user.mind?.holy_role >= HOLY_ROLE_PRIEST)
. += span_warning("PERFECT FOR SMITING!!")
else
living_user.add_mood_event("encountered_evil", /datum/mood_event/encountered_evil)
living_user.set_jitter_if_lower(15 SECONDS)
if(HAS_TRAIT(user, TRAIT_SPIRITUAL) && mind?.holy_role && user != src)
. += "[t_He] [t_has] a holy aura about [t_him]."
living_user.add_mood_event("religious_comfort", /datum/mood_event/religiously_comforted)

View File

@@ -310,7 +310,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
if(isliving(speaker))
var/mob/living/living_speaker = speaker
var/mouth_hidden = living_speaker.is_mouth_covered() || HAS_TRAIT(living_speaker, TRAIT_FACE_COVERED)
if(!HAS_TRAIT(src, TRAIT_EMPATH) && mouth_hidden) // Can't see them speak if their mouth is covered or hidden, unless we're an empath
if(mouth_hidden && !HAS_TRAIT(src, TRAIT_SEE_MASK_WHISPER)) // Can't see them speak if their mouth is covered or hidden, unless we're an empath
return FALSE
deaf_message = "[span_name("[speaker]")] [speaker.verb_whisper] something, but you are too far away to hear [speaker.p_them()]."

View File

@@ -1159,6 +1159,7 @@
#include "code\datums\components\effect_remover.dm"
#include "code\datums\components\egg_layer.dm"
#include "code\datums\components\electrified_buckle.dm"
#include "code\datums\components\empathy.dm"
#include "code\datums\components\energized.dm"
#include "code\datums\components\engraved.dm"
#include "code\datums\components\evolutionary_leap.dm"