mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-03-08 02:00:29 +00:00
fix(Core/Spells): Port SPELL_ATTR3_INSTANT_TARGET_PROCS cascade proc suppression from TrinityCore (#24936)
Co-authored-by: blinkysc <blinkysc@users.noreply.github.com> Co-authored-by: QAston <126822+QAston@users.noreply.github.com>
This commit is contained in:
@@ -13058,11 +13058,28 @@ void Unit::TriggerAurasProcOnEvent(std::list<AuraApplication*>* myProcAuras, std
|
||||
|
||||
void Unit::TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, AuraApplicationProcContainer& aurasTriggeringProc)
|
||||
{
|
||||
Spell const* triggeringSpell = eventInfo.GetProcSpell();
|
||||
bool const disableProcs = triggeringSpell && triggeringSpell->IsProcDisabled();
|
||||
if (disableProcs)
|
||||
SetCantProc(true);
|
||||
|
||||
for (auto const& [procEffectMask, aurApp] : aurasTriggeringProc)
|
||||
{
|
||||
if (!aurApp->GetRemoveMode())
|
||||
aurApp->GetBase()->TriggerProcOnEvent(procEffectMask, aurApp, eventInfo);
|
||||
if (aurApp->GetRemoveMode())
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = aurApp->GetBase()->GetSpellInfo();
|
||||
if (spellInfo->HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS))
|
||||
SetCantProc(true);
|
||||
|
||||
aurApp->GetBase()->TriggerProcOnEvent(procEffectMask, aurApp, eventInfo);
|
||||
|
||||
if (spellInfo->HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS))
|
||||
SetCantProc(false);
|
||||
}
|
||||
|
||||
if (disableProcs)
|
||||
SetCantProc(false);
|
||||
}
|
||||
|
||||
Player* Unit::GetSpellModOwner() const
|
||||
|
||||
@@ -565,6 +565,7 @@ public:
|
||||
bool IsNextMeleeSwingSpell() const;
|
||||
bool IsTriggered() const { return HasTriggeredCastFlag(TRIGGERED_FULL_MASK); };
|
||||
bool HasTriggeredCastFlag(TriggerCastFlags flag) const { return _triggeredCastFlags & flag; };
|
||||
[[nodiscard]] bool IsProcDisabled() const { return HasTriggeredCastFlag(TRIGGERED_DISALLOW_PROC_EVENTS); }
|
||||
bool IsChannelActive() const { return m_caster->GetUInt32Value(UNIT_CHANNEL_SPELL) != 0; }
|
||||
bool IsAutoActionResetSpell() const;
|
||||
bool IsIgnoringCooldowns() const;
|
||||
|
||||
@@ -492,6 +492,40 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Cascade Proc Suppression - simulates Unit.cpp TriggerAurasProcOnEvent
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @brief Configuration for simulating cascade proc suppression
|
||||
*
|
||||
* Models the two paths in TriggerAurasProcOnEvent that call SetCantProc():
|
||||
* 1. Outer check: triggering spell has TRIGGERED_DISALLOW_PROC_EVENTS
|
||||
* 2. Per-aura check: aura has SPELL_ATTR3_INSTANT_TARGET_PROCS (0x80000)
|
||||
*/
|
||||
struct CascadeProcConfig
|
||||
{
|
||||
bool triggeringSpellIsProcDisabled = false; // Spell::IsProcDisabled()
|
||||
bool auraHasDisableProcAttr = false; // SpellInfo::HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Returns true if cascading procs should be suppressed for this aura
|
||||
*
|
||||
* @param config Cascade proc configuration
|
||||
* @return true if SetCantProc(true) would be active during this aura's proc
|
||||
*/
|
||||
static bool ShouldSuppressCascadingProc(CascadeProcConfig const& config)
|
||||
{
|
||||
// Outer check: triggering spell disables all cascading procs
|
||||
if (config.triggeringSpellIsProcDisabled)
|
||||
return true;
|
||||
// Per-aura check: aura itself suppresses cascading
|
||||
if (config.auraHasDisableProcAttr)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Conditions System - simulates SpellAuras.cpp:2232-2236
|
||||
// =============================================================================
|
||||
|
||||
@@ -96,6 +96,12 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithAttributesEx3(uint32 attr)
|
||||
{
|
||||
_entry.AttributesEx3 = attr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithEffect(uint8 effIndex, uint32 effect, uint32 auraType = 0)
|
||||
{
|
||||
if (effIndex < MAX_SPELL_EFFECTS)
|
||||
@@ -183,6 +189,12 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithAttributesEx3(uint32 attr)
|
||||
{
|
||||
_entryHelper.WithAttributesEx3(attr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithEffect(uint8 effIndex, uint32 effect, uint32 auraType = 0)
|
||||
{
|
||||
_entryHelper.WithEffect(effIndex, effect, auraType);
|
||||
|
||||
213
src/test/server/game/Spells/CascadeProcSuppressionTest.cpp
Normal file
213
src/test/server/game/Spells/CascadeProcSuppressionTest.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file CascadeProcSuppressionTest.cpp
|
||||
* @brief Unit tests for cascade proc suppression via SPELL_ATTR3_INSTANT_TARGET_PROCS
|
||||
*
|
||||
* Tests the logic from Unit.cpp TriggerAurasProcOnEvent:
|
||||
* - Outer check: Spell::IsProcDisabled() (TRIGGERED_DISALLOW_PROC_EVENTS) suppresses all cascade procs
|
||||
* - Per-aura check: SPELL_ATTR3_INSTANT_TARGET_PROCS (0x80000) suppresses cascade for that aura
|
||||
* - Normal spells/auras without these flags allow cascading
|
||||
* - Both flags set simultaneously still suppresses correctly
|
||||
*/
|
||||
|
||||
#include "ProcChanceTestHelper.h"
|
||||
#include "SpellInfoTestHelper.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace testing;
|
||||
|
||||
class CascadeProcSuppressionTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
ProcChanceTestHelper::CascadeProcConfig MakeConfig(
|
||||
bool isProcDisabled, bool hasDisableProcAttr)
|
||||
{
|
||||
ProcChanceTestHelper::CascadeProcConfig config;
|
||||
config.triggeringSpellIsProcDisabled = isProcDisabled;
|
||||
config.auraHasDisableProcAttr = hasDisableProcAttr;
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Normal behavior (no suppression)
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(CascadeProcSuppressionTest, NormalSpellNormalAura_NotSuppressed)
|
||||
{
|
||||
auto config = MakeConfig(false, false);
|
||||
|
||||
EXPECT_FALSE(ProcChanceTestHelper::ShouldSuppressCascadingProc(config))
|
||||
<< "Normal spell + normal aura should not suppress cascading procs";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// IsProcDisabled (outer check - TRIGGERED_DISALLOW_PROC_EVENTS)
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(CascadeProcSuppressionTest, ProcDisabledSpell_NormalAura_Suppressed)
|
||||
{
|
||||
auto config = MakeConfig(true, false);
|
||||
|
||||
EXPECT_TRUE(ProcChanceTestHelper::ShouldSuppressCascadingProc(config))
|
||||
<< "Triggered spell with DISALLOW_PROC_EVENTS should suppress all cascading procs";
|
||||
}
|
||||
|
||||
TEST_F(CascadeProcSuppressionTest, ProcDisabledSpell_WithAttr_Suppressed)
|
||||
{
|
||||
auto config = MakeConfig(true, true);
|
||||
|
||||
EXPECT_TRUE(ProcChanceTestHelper::ShouldSuppressCascadingProc(config))
|
||||
<< "Both flags set should still suppress (double-suppress doesn't break)";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SPELL_ATTR3_INSTANT_TARGET_PROCS (per-aura check)
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(CascadeProcSuppressionTest, NormalSpell_AuraWithAttr_Suppressed)
|
||||
{
|
||||
auto config = MakeConfig(false, true);
|
||||
|
||||
EXPECT_TRUE(ProcChanceTestHelper::ShouldSuppressCascadingProc(config))
|
||||
<< "Aura with SPELL_ATTR3_INSTANT_TARGET_PROCS should suppress cascading procs";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SpellInfo attribute verification via SpellInfoBuilder
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(CascadeProcSuppressionTest, SpellInfo_WithAttr_HasAttributeReturnsTrue)
|
||||
{
|
||||
auto spellInfo = SpellInfoBuilder()
|
||||
.WithId(99001)
|
||||
.WithAttributesEx3(SPELL_ATTR3_INSTANT_TARGET_PROCS)
|
||||
.BuildUnique();
|
||||
|
||||
EXPECT_TRUE(spellInfo->HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS))
|
||||
<< "SpellInfo built with 0x80000 should report HasAttribute true";
|
||||
}
|
||||
|
||||
TEST_F(CascadeProcSuppressionTest, SpellInfo_WithoutAttr_HasAttributeReturnsFalse)
|
||||
{
|
||||
auto spellInfo = SpellInfoBuilder()
|
||||
.WithId(99002)
|
||||
.WithAttributesEx3(0)
|
||||
.BuildUnique();
|
||||
|
||||
EXPECT_FALSE(spellInfo->HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS))
|
||||
<< "SpellInfo built with 0 should report HasAttribute false";
|
||||
}
|
||||
|
||||
TEST_F(CascadeProcSuppressionTest, SpellInfo_WithMixedBits_HasAttributeReturnsTrue)
|
||||
{
|
||||
// 0x80001 = SPELL_ATTR3_INSTANT_TARGET_PROCS | SPELL_ATTR3_PVP_ENABLING (bit 0)
|
||||
auto spellInfo = SpellInfoBuilder()
|
||||
.WithId(99003)
|
||||
.WithAttributesEx3(0x00080001)
|
||||
.BuildUnique();
|
||||
|
||||
EXPECT_TRUE(spellInfo->HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS))
|
||||
<< "Other bits in AttributesEx3 should not interfere with attribute detection";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Real spell scenarios (data-driven)
|
||||
// These spells have SPELL_ATTR3_INSTANT_TARGET_PROCS (0x80000) in DBC
|
||||
// =============================================================================
|
||||
|
||||
struct RealSpellTestCase
|
||||
{
|
||||
const char* name;
|
||||
uint32 spellId;
|
||||
bool hasAttr; // Whether the spell has SPELL_ATTR3_INSTANT_TARGET_PROCS
|
||||
};
|
||||
|
||||
class CascadeProcRealSpellTest : public ::testing::TestWithParam<RealSpellTestCase> {};
|
||||
|
||||
TEST_P(CascadeProcRealSpellTest, VerifySuppressionForRealSpell)
|
||||
{
|
||||
auto const& tc = GetParam();
|
||||
|
||||
// Build a SpellInfo mimicking the real spell's AttributesEx3
|
||||
auto spellInfo = SpellInfoBuilder()
|
||||
.WithId(tc.spellId)
|
||||
.WithAttributesEx3(tc.hasAttr ? SPELL_ATTR3_INSTANT_TARGET_PROCS : 0)
|
||||
.BuildUnique();
|
||||
|
||||
// Verify attribute detection matches expectation
|
||||
EXPECT_EQ(spellInfo->HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS), tc.hasAttr)
|
||||
<< tc.name << " (spell " << tc.spellId << ") attribute detection mismatch";
|
||||
|
||||
// Verify cascade suppression matches attribute presence
|
||||
ProcChanceTestHelper::CascadeProcConfig config;
|
||||
config.triggeringSpellIsProcDisabled = false;
|
||||
config.auraHasDisableProcAttr = tc.hasAttr;
|
||||
|
||||
EXPECT_EQ(ProcChanceTestHelper::ShouldSuppressCascadingProc(config), tc.hasAttr)
|
||||
<< tc.name << " (spell " << tc.spellId << ") cascade suppression mismatch";
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
CascadeProcSuppression,
|
||||
CascadeProcRealSpellTest,
|
||||
::testing::Values(
|
||||
// Spells WITH SPELL_ATTR3_INSTANT_TARGET_PROCS
|
||||
RealSpellTestCase{"Seal Fate", 14195, true},
|
||||
RealSpellTestCase{"Sword Specialization", 12281, true},
|
||||
RealSpellTestCase{"Reckoning", 20178, true},
|
||||
RealSpellTestCase{"Flurry", 16257, true},
|
||||
// Counter-example: spell WITHOUT the attribute
|
||||
RealSpellTestCase{"Eviscerate", 26865, false}
|
||||
),
|
||||
[](testing::TestParamInfo<RealSpellTestCase> const& info) {
|
||||
// Generate readable test name from spell name (replace spaces)
|
||||
std::string name = info.param.name;
|
||||
std::replace(name.begin(), name.end(), ' ', '_');
|
||||
return name;
|
||||
}
|
||||
);
|
||||
|
||||
// =============================================================================
|
||||
// Nesting behavior - both flags simultaneously
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(CascadeProcSuppressionTest, BothFlagsSet_StillSuppressed)
|
||||
{
|
||||
auto config = MakeConfig(true, true);
|
||||
|
||||
EXPECT_TRUE(ProcChanceTestHelper::ShouldSuppressCascadingProc(config))
|
||||
<< "Both IsProcDisabled and INSTANT_TARGET_PROCS set should still suppress";
|
||||
}
|
||||
|
||||
TEST_F(CascadeProcSuppressionTest, OnlyOuterFlag_Suppressed)
|
||||
{
|
||||
auto config = MakeConfig(true, false);
|
||||
|
||||
EXPECT_TRUE(ProcChanceTestHelper::ShouldSuppressCascadingProc(config))
|
||||
<< "Only IsProcDisabled should be sufficient to suppress";
|
||||
}
|
||||
|
||||
TEST_F(CascadeProcSuppressionTest, OnlyPerAuraFlag_Suppressed)
|
||||
{
|
||||
auto config = MakeConfig(false, true);
|
||||
|
||||
EXPECT_TRUE(ProcChanceTestHelper::ShouldSuppressCascadingProc(config))
|
||||
<< "Only INSTANT_TARGET_PROCS should be sufficient to suppress";
|
||||
}
|
||||
Reference in New Issue
Block a user