fix(Core/Spells): Fully absorbed periodic damage should not break stealth (#24975)

Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
Co-authored-by: Ariel Silva <ariel-@users.noreply.github.com>
This commit is contained in:
blinkysc
2026-03-02 19:57:45 -06:00
committed by GitHub
parent e471087652
commit 1fc4781306
3 changed files with 162 additions and 4 deletions

View File

@@ -6756,7 +6756,9 @@ void AuraEffect::HandlePeriodicDamageAurasTick(Unit* target, Unit* caster) const
// Set trigger flag
uint32 procAttacker = PROC_FLAG_DONE_PERIODIC;
uint32 procVictim = PROC_FLAG_TAKEN_PERIODIC;
uint32 procEx = (crit ? PROC_EX_CRITICAL_HIT : PROC_EX_NORMAL_HIT) | PROC_EX_INTERNAL_DOT;
uint32 procEx = PROC_EX_INTERNAL_DOT;
if (damage)
procEx |= crit ? PROC_EX_CRITICAL_HIT : PROC_EX_NORMAL_HIT;
if (absorb > 0)
procEx |= PROC_EX_ABSORB;
@@ -6843,7 +6845,9 @@ void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) c
// Set trigger flag
uint32 procAttacker = PROC_FLAG_DONE_PERIODIC;
uint32 procVictim = PROC_FLAG_TAKEN_PERIODIC;
uint32 procEx = (crit ? PROC_EX_CRITICAL_HIT : PROC_EX_NORMAL_HIT) | PROC_EX_INTERNAL_DOT;
uint32 procEx = PROC_EX_INTERNAL_DOT;
if (dmgInfo.GetDamage())
procEx |= crit ? PROC_EX_CRITICAL_HIT : PROC_EX_NORMAL_HIT;
if (absorb > 0)
procEx |= PROC_EX_ABSORB;

View File

@@ -3073,9 +3073,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA
}
if (m_caster != unit && m_caster->IsHostileTo(unit) && !m_spellInfo->IsPositive() && !m_triggeredByAuraSpell && !m_spellInfo->HasAttribute(SPELL_ATTR0_CU_DONT_BREAK_STEALTH))
{
unit->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
}
if (aura_effmask)
{

View File

@@ -0,0 +1,156 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ProcEventInfoHelper.h"
#include "SpellMgr.h"
#include "WorldMock.h"
#include "gtest/gtest.h"
#include "gmock/gmock.h"
using namespace testing;
/**
* @brief Tests for fully absorbed periodic damage not triggering TAKEN procs
*
* When periodic damage (e.g. Consecration ticks) is fully absorbed by an
* absorb shield (e.g. Power Word: Shield), the hit mask should only contain
* PROC_HIT_ABSORB (no PROC_HIT_NORMAL/CRITICAL). Since TAKEN procs default
* to requiring PROC_HIT_NORMAL | PROC_HIT_CRITICAL, fully absorbed ticks
* should not trigger victim procs like stealth charge consumption.
*
* This aligns with TrinityCore behavior where hitMask only gets NORMAL/CRITICAL
* added when damage > 0 in HandlePeriodicDamageAurasTick.
*/
class PeriodicAbsorbStealthProcTest : public ::testing::Test
{
protected:
void SetUp() override
{
_originalWorld = sWorld.release();
_worldMock = new NiceMock<WorldMock>();
sWorld.reset(_worldMock);
static std::string emptyString;
ON_CALL(*_worldMock, GetDataPath()).WillByDefault(ReturnRef(emptyString));
}
void TearDown() override
{
IWorld* currentWorld = sWorld.release();
delete currentWorld;
_worldMock = nullptr;
sWorld.reset(_originalWorld);
_originalWorld = nullptr;
}
IWorld* _originalWorld = nullptr;
NiceMock<WorldMock>* _worldMock = nullptr;
};
// Stealth-like TAKEN periodic proc with default HitMask (0) should NOT
// trigger when the only hit flag is PROC_HIT_ABSORB (fully absorbed tick)
TEST_F(PeriodicAbsorbStealthProcTest, FullyAbsorbedPeriodicDoesNotTriggerTakenProc)
{
// Stealth has ProcFlags including PROC_FLAG_TAKEN_PERIODIC, HitMask=0
// Default TAKEN HitMask = PROC_HIT_NORMAL | PROC_HIT_CRITICAL (no ABSORB)
auto procEntry = SpellProcEntryBuilder()
.WithProcFlags(PROC_FLAG_TAKEN_PERIODIC)
.WithHitMask(0)
.Build();
// Fully absorbed periodic tick: hitMask = PROC_HIT_ABSORB only
// (damage=0 so PROC_EX_NORMAL_HIT is NOT set)
auto eventInfo = ProcEventInfoBuilder()
.WithTypeMask(PROC_FLAG_TAKEN_PERIODIC)
.WithHitMask(PROC_HIT_ABSORB)
.Build();
EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo));
}
// Non-absorbed periodic tick (damage > 0) SHOULD trigger TAKEN procs
TEST_F(PeriodicAbsorbStealthProcTest, NonAbsorbedPeriodicTriggersTakenProc)
{
auto procEntry = SpellProcEntryBuilder()
.WithProcFlags(PROC_FLAG_TAKEN_PERIODIC)
.WithHitMask(0)
.Build();
// Normal periodic tick: hitMask includes PROC_HIT_NORMAL
auto eventInfo = ProcEventInfoBuilder()
.WithTypeMask(PROC_FLAG_TAKEN_PERIODIC)
.WithHitMask(PROC_HIT_NORMAL)
.Build();
EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo));
}
// Critical periodic tick SHOULD trigger TAKEN procs
TEST_F(PeriodicAbsorbStealthProcTest, CriticalPeriodicTriggersTakenProc)
{
auto procEntry = SpellProcEntryBuilder()
.WithProcFlags(PROC_FLAG_TAKEN_PERIODIC)
.WithHitMask(0)
.Build();
auto eventInfo = ProcEventInfoBuilder()
.WithTypeMask(PROC_FLAG_TAKEN_PERIODIC)
.WithHitMask(PROC_HIT_CRITICAL)
.Build();
EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo));
}
// Partially absorbed periodic tick (damage > 0, some absorbed) SHOULD trigger
// because PROC_HIT_NORMAL is set alongside PROC_HIT_ABSORB
TEST_F(PeriodicAbsorbStealthProcTest, PartiallyAbsorbedPeriodicTriggersTakenProc)
{
auto procEntry = SpellProcEntryBuilder()
.WithProcFlags(PROC_FLAG_TAKEN_PERIODIC)
.WithHitMask(0)
.Build();
// Partial absorb: both NORMAL and ABSORB flags set
auto eventInfo = ProcEventInfoBuilder()
.WithTypeMask(PROC_FLAG_TAKEN_PERIODIC)
.WithHitMask(PROC_HIT_NORMAL | PROC_HIT_ABSORB)
.Build();
EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo));
}
// DONE procs (attacker side) SHOULD trigger on fully absorbed damage
// because DONE default HitMask includes PROC_HIT_ABSORB
TEST_F(PeriodicAbsorbStealthProcTest, FullyAbsorbedPeriodicTriggersDoneProc)
{
auto procEntry = SpellProcEntryBuilder()
.WithProcFlags(PROC_FLAG_DONE_PERIODIC)
.WithSpellPhaseMask(PROC_SPELL_PHASE_HIT)
.WithHitMask(0)
.Build();
// Fully absorbed: only PROC_HIT_ABSORB
auto eventInfo = ProcEventInfoBuilder()
.WithTypeMask(PROC_FLAG_DONE_PERIODIC)
.WithSpellPhaseMask(PROC_SPELL_PHASE_HIT)
.WithHitMask(PROC_HIT_ABSORB)
.Build();
// DONE default includes ABSORB, so this SHOULD trigger
EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo));
}