diff --git a/code/__DEFINES/_flags.dm b/code/__DEFINES/_flags.dm index ca65bcaf9dd..383edbe3e81 100644 --- a/code/__DEFINES/_flags.dm +++ b/code/__DEFINES/_flags.dm @@ -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) diff --git a/code/__DEFINES/dcs/signals/signals_action.dm b/code/__DEFINES/dcs/signals/signals_action.dm index 514a88b2bea..fe57adae363 100644 --- a/code/__DEFINES/dcs/signals/signals_action.dm +++ b/code/__DEFINES/dcs/signals/signals_action.dm @@ -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" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm index 9a8f4002a44..b6cc94584be 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm @@ -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) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index b61584b6f94..a226fedfc22 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -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" diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index f2a923d0d58..4179a5b4085 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -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, +)) diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 6d1c1292af9..ae43a415f20 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -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, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index eb54b2259dd..0321d4da3bf 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -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, diff --git a/code/datums/components/empathy.dm b/code/datums/components/empathy.dm new file mode 100644 index 00000000000..afcc264b001 --- /dev/null +++ b/code/datums/components/empathy.dm @@ -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 ..() diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 66a5dc8ce49..62d2f8913c1 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -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 diff --git a/code/datums/mutations/touch.dm b/code/datums/mutations/touch.dm index 6ffcc79e8d6..3020715d257 100644 --- a/code/datums/mutations/touch.dm +++ b/code/datums/mutations/touch.dm @@ -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? diff --git a/code/datums/quirks/positive_quirks/empath.dm b/code/datums/quirks/positive_quirks/empath.dm index f80ce3b5ef1..0cb5704ad4f 100644 --- a/code/datums/quirks/positive_quirks/empath.dm +++ b/code/datums/quirks/positive_quirks/empath.dm @@ -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) diff --git a/code/modules/antagonists/heretic/knowledge/moon_lore.dm b/code/modules/antagonists/heretic/knowledge/moon_lore.dm index d99712400cc..4db73da4622 100644 --- a/code/modules/antagonists/heretic/knowledge/moon_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/moon_lore.dm @@ -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) . = ..() diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index b2592aae09b..82f06048ffa 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -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) diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index 34065f7d2ed..3a28770c4c5 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -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()]." diff --git a/tgstation.dme b/tgstation.dme index 2d55c38f86d..46e9805a4db 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -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"