Skip to content

Commit 1fc4781

Browse files
blinkyscariel-
andauthored
fix(Core/Spells): Fully absorbed periodic damage should not break stealth (azerothcore#24975)
Co-authored-by: blinkysc <blinkysc@users.noreply.github.com> Co-authored-by: Ariel Silva <ariel-@users.noreply.github.com>
1 parent e471087 commit 1fc4781

File tree

3 files changed

+162
-4
lines changed

3 files changed

+162
-4
lines changed

src/server/game/Spells/Auras/SpellAuraEffects.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6756,7 +6756,9 @@ void AuraEffect::HandlePeriodicDamageAurasTick(Unit* target, Unit* caster) const
67566756
// Set trigger flag
67576757
uint32 procAttacker = PROC_FLAG_DONE_PERIODIC;
67586758
uint32 procVictim = PROC_FLAG_TAKEN_PERIODIC;
6759-
uint32 procEx = (crit ? PROC_EX_CRITICAL_HIT : PROC_EX_NORMAL_HIT) | PROC_EX_INTERNAL_DOT;
6759+
uint32 procEx = PROC_EX_INTERNAL_DOT;
6760+
if (damage)
6761+
procEx |= crit ? PROC_EX_CRITICAL_HIT : PROC_EX_NORMAL_HIT;
67606762
if (absorb > 0)
67616763
procEx |= PROC_EX_ABSORB;
67626764

@@ -6843,7 +6845,9 @@ void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) c
68436845
// Set trigger flag
68446846
uint32 procAttacker = PROC_FLAG_DONE_PERIODIC;
68456847
uint32 procVictim = PROC_FLAG_TAKEN_PERIODIC;
6846-
uint32 procEx = (crit ? PROC_EX_CRITICAL_HIT : PROC_EX_NORMAL_HIT) | PROC_EX_INTERNAL_DOT;
6848+
uint32 procEx = PROC_EX_INTERNAL_DOT;
6849+
if (dmgInfo.GetDamage())
6850+
procEx |= crit ? PROC_EX_CRITICAL_HIT : PROC_EX_NORMAL_HIT;
68476851
if (absorb > 0)
68486852
procEx |= PROC_EX_ABSORB;
68496853

src/server/game/Spells/Spell.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3073,9 +3073,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA
30733073
}
30743074

30753075
if (m_caster != unit && m_caster->IsHostileTo(unit) && !m_spellInfo->IsPositive() && !m_triggeredByAuraSpell && !m_spellInfo->HasAttribute(SPELL_ATTR0_CU_DONT_BREAK_STEALTH))
3076-
{
30773076
unit->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
3078-
}
30793077

30803078
if (aura_effmask)
30813079
{
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12+
* more details.
13+
*
14+
* You should have received a copy of the GNU General Public License along
15+
* with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
#include "ProcEventInfoHelper.h"
19+
#include "SpellMgr.h"
20+
#include "WorldMock.h"
21+
#include "gtest/gtest.h"
22+
#include "gmock/gmock.h"
23+
24+
using namespace testing;
25+
26+
/**
27+
* @brief Tests for fully absorbed periodic damage not triggering TAKEN procs
28+
*
29+
* When periodic damage (e.g. Consecration ticks) is fully absorbed by an
30+
* absorb shield (e.g. Power Word: Shield), the hit mask should only contain
31+
* PROC_HIT_ABSORB (no PROC_HIT_NORMAL/CRITICAL). Since TAKEN procs default
32+
* to requiring PROC_HIT_NORMAL | PROC_HIT_CRITICAL, fully absorbed ticks
33+
* should not trigger victim procs like stealth charge consumption.
34+
*
35+
* This aligns with TrinityCore behavior where hitMask only gets NORMAL/CRITICAL
36+
* added when damage > 0 in HandlePeriodicDamageAurasTick.
37+
*/
38+
class PeriodicAbsorbStealthProcTest : public ::testing::Test
39+
{
40+
protected:
41+
void SetUp() override
42+
{
43+
_originalWorld = sWorld.release();
44+
_worldMock = new NiceMock<WorldMock>();
45+
sWorld.reset(_worldMock);
46+
47+
static std::string emptyString;
48+
ON_CALL(*_worldMock, GetDataPath()).WillByDefault(ReturnRef(emptyString));
49+
}
50+
51+
void TearDown() override
52+
{
53+
IWorld* currentWorld = sWorld.release();
54+
delete currentWorld;
55+
_worldMock = nullptr;
56+
57+
sWorld.reset(_originalWorld);
58+
_originalWorld = nullptr;
59+
}
60+
61+
IWorld* _originalWorld = nullptr;
62+
NiceMock<WorldMock>* _worldMock = nullptr;
63+
};
64+
65+
// Stealth-like TAKEN periodic proc with default HitMask (0) should NOT
66+
// trigger when the only hit flag is PROC_HIT_ABSORB (fully absorbed tick)
67+
TEST_F(PeriodicAbsorbStealthProcTest, FullyAbsorbedPeriodicDoesNotTriggerTakenProc)
68+
{
69+
// Stealth has ProcFlags including PROC_FLAG_TAKEN_PERIODIC, HitMask=0
70+
// Default TAKEN HitMask = PROC_HIT_NORMAL | PROC_HIT_CRITICAL (no ABSORB)
71+
auto procEntry = SpellProcEntryBuilder()
72+
.WithProcFlags(PROC_FLAG_TAKEN_PERIODIC)
73+
.WithHitMask(0)
74+
.Build();
75+
76+
// Fully absorbed periodic tick: hitMask = PROC_HIT_ABSORB only
77+
// (damage=0 so PROC_EX_NORMAL_HIT is NOT set)
78+
auto eventInfo = ProcEventInfoBuilder()
79+
.WithTypeMask(PROC_FLAG_TAKEN_PERIODIC)
80+
.WithHitMask(PROC_HIT_ABSORB)
81+
.Build();
82+
83+
EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo));
84+
}
85+
86+
// Non-absorbed periodic tick (damage > 0) SHOULD trigger TAKEN procs
87+
TEST_F(PeriodicAbsorbStealthProcTest, NonAbsorbedPeriodicTriggersTakenProc)
88+
{
89+
auto procEntry = SpellProcEntryBuilder()
90+
.WithProcFlags(PROC_FLAG_TAKEN_PERIODIC)
91+
.WithHitMask(0)
92+
.Build();
93+
94+
// Normal periodic tick: hitMask includes PROC_HIT_NORMAL
95+
auto eventInfo = ProcEventInfoBuilder()
96+
.WithTypeMask(PROC_FLAG_TAKEN_PERIODIC)
97+
.WithHitMask(PROC_HIT_NORMAL)
98+
.Build();
99+
100+
EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo));
101+
}
102+
103+
// Critical periodic tick SHOULD trigger TAKEN procs
104+
TEST_F(PeriodicAbsorbStealthProcTest, CriticalPeriodicTriggersTakenProc)
105+
{
106+
auto procEntry = SpellProcEntryBuilder()
107+
.WithProcFlags(PROC_FLAG_TAKEN_PERIODIC)
108+
.WithHitMask(0)
109+
.Build();
110+
111+
auto eventInfo = ProcEventInfoBuilder()
112+
.WithTypeMask(PROC_FLAG_TAKEN_PERIODIC)
113+
.WithHitMask(PROC_HIT_CRITICAL)
114+
.Build();
115+
116+
EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo));
117+
}
118+
119+
// Partially absorbed periodic tick (damage > 0, some absorbed) SHOULD trigger
120+
// because PROC_HIT_NORMAL is set alongside PROC_HIT_ABSORB
121+
TEST_F(PeriodicAbsorbStealthProcTest, PartiallyAbsorbedPeriodicTriggersTakenProc)
122+
{
123+
auto procEntry = SpellProcEntryBuilder()
124+
.WithProcFlags(PROC_FLAG_TAKEN_PERIODIC)
125+
.WithHitMask(0)
126+
.Build();
127+
128+
// Partial absorb: both NORMAL and ABSORB flags set
129+
auto eventInfo = ProcEventInfoBuilder()
130+
.WithTypeMask(PROC_FLAG_TAKEN_PERIODIC)
131+
.WithHitMask(PROC_HIT_NORMAL | PROC_HIT_ABSORB)
132+
.Build();
133+
134+
EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo));
135+
}
136+
137+
// DONE procs (attacker side) SHOULD trigger on fully absorbed damage
138+
// because DONE default HitMask includes PROC_HIT_ABSORB
139+
TEST_F(PeriodicAbsorbStealthProcTest, FullyAbsorbedPeriodicTriggersDoneProc)
140+
{
141+
auto procEntry = SpellProcEntryBuilder()
142+
.WithProcFlags(PROC_FLAG_DONE_PERIODIC)
143+
.WithSpellPhaseMask(PROC_SPELL_PHASE_HIT)
144+
.WithHitMask(0)
145+
.Build();
146+
147+
// Fully absorbed: only PROC_HIT_ABSORB
148+
auto eventInfo = ProcEventInfoBuilder()
149+
.WithTypeMask(PROC_FLAG_DONE_PERIODIC)
150+
.WithSpellPhaseMask(PROC_SPELL_PHASE_HIT)
151+
.WithHitMask(PROC_HIT_ABSORB)
152+
.Build();
153+
154+
// DONE default includes ABSORB, so this SHOULD trigger
155+
EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo));
156+
}

0 commit comments

Comments
 (0)