From c09cfa780efd0330904de93cacef5b2f1a960686 Mon Sep 17 00:00:00 2001 From: absol89 Date: Wed, 4 Nov 2026 21:55:02 +0100 Subject: [PATCH] Show other players with Detect Life and keep it to the caster A remote player's Detect Life replayed on every client and synced to nearby players, so everyone saw living targets. It now only applies on the caster's own client. With PvP on, the caster's Detect Life also shows other players, which HookAddTarget used to refuse. Needs an in-game test. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018fvQxQEu7yvi9eRZyKsEur --- Code/client/Games/Skyrim/Magic/EffectItem.cpp | 5 +++++ Code/client/Games/Skyrim/Magic/EffectItem.h | 1 + Code/client/Games/Skyrim/Magic/MagicTarget.cpp | 16 ++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/Code/client/Games/Skyrim/Magic/EffectItem.cpp b/Code/client/Games/Skyrim/Magic/EffectItem.cpp index e74998c46c..0cbb06156c 100644 --- a/Code/client/Games/Skyrim/Magic/EffectItem.cpp +++ b/Code/client/Games/Skyrim/Magic/EffectItem.cpp @@ -33,6 +33,11 @@ bool EffectItem::IsVampireLordEffect() const noexcept return pEffectSetting->eArchetype == EffectArchetypes::ArchetypeID::kVampireLord; } +bool EffectItem::IsDetectLifeEffect() const noexcept +{ + return pEffectSetting->eArchetype == EffectArchetypes::ArchetypeID::kDetectLife; +} + bool EffectItem::IsNightVisionEffect() const noexcept { BGSKeyword* pMagicNightEye = Cast(TESForm::GetById(0xad7c6)); diff --git a/Code/client/Games/Skyrim/Magic/EffectItem.h b/Code/client/Games/Skyrim/Magic/EffectItem.h index c4ee9a5d4f..9c673828f7 100644 --- a/Code/client/Games/Skyrim/Magic/EffectItem.h +++ b/Code/client/Games/Skyrim/Magic/EffectItem.h @@ -20,6 +20,7 @@ struct EffectItem bool IsWerewolfEffect() const noexcept; bool IsVampireLordEffect() const noexcept; bool IsNightVisionEffect() const noexcept; + bool IsDetectLifeEffect() const noexcept; EffectItemData data; uint32_t padC; diff --git a/Code/client/Games/Skyrim/Magic/MagicTarget.cpp b/Code/client/Games/Skyrim/Magic/MagicTarget.cpp index fe6a5ac5b8..3151a7ad77 100644 --- a/Code/client/Games/Skyrim/Magic/MagicTarget.cpp +++ b/Code/client/Games/Skyrim/Magic/MagicTarget.cpp @@ -60,6 +60,10 @@ bool MagicTarget::AddTarget(AddTargetData& arData, bool aApplyHealPerkBonus, boo bool MagicTarget::AddTargetData::ShouldSync() { + // Detect life is an area effect on every actor around the caster, syncing it would show them to other players too. + if (pEffectItem->IsDetectLifeEffect()) + return false; + // Constant effect slow time is an ability each client's scripts add and remove on their own player, so a synced // copy would stay on the other players for good. Fire and forget slow time (shouts) still syncs. if (pEffectItem->IsSlowEffect()) @@ -102,6 +106,18 @@ bool TP_MAKE_THISCALL(HookAddTarget, MagicTarget, MagicTarget::AddTargetData& ar if (arData.pEffectItem->IsVampireLordEffect()) pTargetActorEx->GraphDescriptorHash = AnimationGraphDescriptor_VampireLordBehavior::m_key; + if (arData.pEffectItem->IsDetectLifeEffect() && arData.pCaster) + { + const ActorExtension* pCasterExtension = arData.pCaster->GetExtension(); + + // The caster's copy repeats the cast on this client, but only the caster gets to see living targets. + if (pCasterExtension->IsRemotePlayer()) + return false; + + if (pCasterExtension->IsLocalPlayer() && pTargetActorEx->IsRemotePlayer() && World::Get().GetServerSettings().PvpEnabled) + return TiltedPhoques::ThisCall(RealAddTarget, apThis, arData); + } + if (ScopedSpellCastOverride::IsOverriden()) return TiltedPhoques::ThisCall(RealAddTarget, apThis, arData);