From 065c9eb763b4403f3ba4c89cc7ca37a5cd41c11b Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:39:36 -0500 Subject: [PATCH] Revenants are always visible in mirrors (#95379) ## About The Pull Request Revenants, even while invisible, can be seen in the reflection of mirrors image They have an ethereal, ghostly effect applied to their reflection while invisible (it's an animation so you can't see it in the picture), this effect is removed if they become visible. It also doesn't apply when reflecting in a magic mirror. Additionally, "Defile" will now break mirrors, though not magic mirrors. Also, Revenants no longer elevate over tables. They're ghosts after all. ## Why It's Good For The Game It's a thematically appropriate way for a Revenant to reveal themselves to the crew without, well, outright revealing themselves. Also there's a hypothetical world where Medbay puts mirrors everywhere to catch a Revenant. ## Changelog :cl: Melbert add: Revenants are always visible in mirror reflections, even while invisible. add: Revenant "Defile" now shatters mirrors. qol: Revenants no longer elevate over tables. /:cl: --- code/__DEFINES/dcs/signals/signals_object.dm | 3 ++ code/datums/components/reflection.dm | 4 +++ code/game/objects/structures/mirror.dm | 6 ++-- .../basic/space_fauna/revenant/_revenant.dm | 30 +++++++++++++++++-- .../revenant/revenant_abilities.dm | 5 ++++ 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 62573bad430..9125770ea3c 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -654,3 +654,6 @@ /// Sent from /obj/item/mail/traitor/after_unwrap(mob/user, obj/item/mail/traitor/letter) #define COMSIG_ITEM_IN_UNWRAPPED_TRAITOR_MAIL "traitor_mail_opened" #define COMPONENT_TRAITOR_MAIL_HANDLED (1<<0) + +/// Sent from /datum/component/reflection when the reflection is updated to the mob reflecting: (atom/movable/reflecting_in, obj/effect/abstract/reflection) +#define COMSIG_REFLECTION_UPDATED "reflection_updated" diff --git a/code/datums/components/reflection.dm b/code/datums/components/reflection.dm index d5d37b72dd2..5e786b0c4ac 100644 --- a/code/datums/components/reflection.dm +++ b/code/datums/components/reflection.dm @@ -118,6 +118,8 @@ /datum/component/reflection/proc/check_can_reflect(atom/movable/target) if(target == parent || !(target in view(1, parent))) return FALSE + if(target.invisibility >= INVISIBILITY_MAXIMUM) + return FALSE var/atom/movable/mov_parent = parent if(target.loc != mov_parent.loc && get_dir(mov_parent, target) != reflected_dir) return FALSE @@ -201,6 +203,8 @@ // purely for vv reflection.name = "[target.name]'s reflection" + SEND_SIGNAL(target, COMSIG_REFLECTION_UPDATED, parent, reflection) + ///Called when the target movable changes its appearance or dir. /datum/component/reflection/proc/update_reflection(atom/movable/source) SIGNAL_HANDLER diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 0bddf5575d2..dff81692a5f 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -61,10 +61,10 @@ register_context() /obj/structure/mirror/proc/can_reflect(atom/movable/target) - ///I'm doing it this way too, because the signal is sent before the broken variable is set to TRUE. - if(atom_integrity <= integrity_failure * max_integrity) + // I'm doing it this way too, because the signal is sent before the broken variable is set to TRUE. + if(atom_integrity <= integrity_failure * max_integrity || broken) return FALSE - if(broken || !isliving(target) || HAS_TRAIT(target, TRAIT_NO_MIRROR_REFLECTION)) + if(!isliving(target) || HAS_TRAIT(target, TRAIT_NO_MIRROR_REFLECTION)) return FALSE return TRUE diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm index 4d7dd688350..4aae0ffa2d7 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm @@ -95,13 +95,22 @@ /mob/living/basic/revenant/Initialize(mapload) . = ..() AddElement(/datum/element/simple_flying) - add_traits(list(TRAIT_SPACEWALK, TRAIT_SIXTHSENSE, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_SEE_BLESSED_TILES), INNATE_TRAIT) + add_traits(list(TRAIT_SPACEWALK, TRAIT_SIXTHSENSE, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_SEE_BLESSED_TILES, TRAIT_IGNORE_ELEVATION), INNATE_TRAIT) grant_actions_by_list(abilities) RegisterSignal(src, COMSIG_LIVING_BANED, PROC_REF(on_baned)) RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move)) RegisterSignal(src, COMSIG_LIVING_LIFE, PROC_REF(on_life)) + RegisterSignal(src, COMSIG_REFLECTION_UPDATED, PROC_REF(on_reflect)) + RegisterSignals(src, list( + SIGNAL_ADDTRAIT(TRAIT_REVENANT_REVEALED), + SIGNAL_REMOVETRAIT(TRAIT_REVENANT_REVEALED), + SIGNAL_ADDTRAIT(TRAIT_REVENANT_INHIBITED), + SIGNAL_REMOVETRAIT(TRAIT_REVENANT_INHIBITED), + SIGNAL_ADDTRAIT(TRAIT_NO_TRANSFORM), + SIGNAL_REMOVETRAIT(TRAIT_NO_TRANSFORM), + ), PROC_REF(update_revenant_appearance)) name = generate_random_mob_name() GLOB.revenant_relay_mobs |= src @@ -146,9 +155,12 @@ essence = min(essence + (essence_regen_amount * change_in_time), max_essence) update_mob_action_buttons() //because we update something required by our spells in life, we need to update our buttons - update_appearance(UPDATE_ICON) update_health_hud() +/mob/living/basic/revenant/proc/update_revenant_appearance() + SIGNAL_HANDLER + update_appearance(UPDATE_ICON) + /mob/living/basic/revenant/AltClickOn(atom/target) if(CAN_I_SEE(target)) client.loot_panel.open(get_turf(target)) @@ -466,4 +478,18 @@ to_chat(src, span_revenminor("Lost [essence_to_change_by]E [source ? "from [source]":""].")) return TRUE +/mob/living/basic/revenant/proc/on_reflect(datum/source, atom/movable/reflecting_in, obj/effect/abstract/reflection) + SIGNAL_HANDLER + // powers are inhibited and we're not revealed so we can't project a reflect + if(HAS_TRAIT(src, TRAIT_REVENANT_INHIBITED) && !HAS_TRAIT(src, TRAIT_REVENANT_REVEALED)) + return + + // otherwise revenants are always visible in reflections even if otherwise invisible + reflection.clear_filters() + reflection.SetInvisibility(0) + + // but if we're (actually) invisible we look all wibbly and ghostly (unless the mirror is magic) + if(!HAS_TRAIT(src, TRAIT_REVENANT_REVEALED) && !istype(reflecting_in, /obj/structure/mirror/magic)) + apply_wibbly_filters(reflection) + #undef REVENANT_STUNNED_TRAIT diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm index 8409716a629..d456f104218 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm @@ -188,6 +188,11 @@ new /obj/effect/temp_visual/revenant/cracks(window.loc) for(var/obj/machinery/light/light in victim) light.flicker(rand(3, 5)) //spooky + for(var/obj/structure/mirror/mirror in victim) + if(istype(mirror, /obj/structure/mirror/magic)) + continue + new /obj/effect/temp_visual/revenant(mirror.loc) + mirror.atom_break("magic") //Malfunction: Makes bad stuff happen to robots and machines. /datum/action/cooldown/spell/aoe/revenant/malfunction