fix(Core/Spells): Add proc chain guard and TAKEN auto-trigger logic (#24966)

Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
Co-authored-by: ariel- <ariel-@users.noreply.github.com>
This commit is contained in:
blinkysc
2026-03-02 13:19:29 -06:00
committed by GitHub
parent dd6f32d54d
commit 361595e6c2
3 changed files with 756 additions and 0 deletions

View File

@@ -523,6 +523,54 @@ public:
// Per-aura check: aura itself suppresses cascading
if (config.auraHasDisableProcAttr)
return true;
return false;
}
// =============================================================================
// TAKEN Auto-Trigger Logic - simulates SpellMgr.cpp:2033-2049
// =============================================================================
/**
* @brief Configuration for simulating the TAKEN auto-trigger logic
* from SpellMgr::LoadSpellProcs() auto-generation
*/
struct TakenAutoTriggerConfig
{
uint32 procFlags = 0; // SpellInfo::ProcFlags
uint32 auraName = 0; // Effect's ApplyAuraName
bool isAlwaysTriggeredAura = false; // Already set by isAlwaysTriggeredAura[]
};
/**
* @brief Simulate the TAKEN auto-trigger logic from SpellMgr::LoadSpellProcs()
*
* During auto-generation of proc entries, TAKEN-proc auras with
* SPELL_AURA_PROC_TRIGGER_SPELL or SPELL_AURA_PROC_TRIGGER_DAMAGE
* should get PROC_ATTR_TRIGGERED_CAN_PROC set automatically.
*
* @param config Configuration describing the aura
* @return true if addTriggerFlag should be set
*/
static bool ShouldAutoAddTriggeredCanProc(TakenAutoTriggerConfig const& config)
{
// If already marked as always-triggered, keep it
if (config.isAlwaysTriggeredAura)
return true;
// TAKEN auto-trigger: TAKEN proc flags + PROC_TRIGGER_SPELL/DAMAGE
if (config.procFlags & TAKEN_HIT_PROC_FLAG_MASK)
{
switch (config.auraName)
{
case SPELL_AURA_PROC_TRIGGER_SPELL:
case SPELL_AURA_PROC_TRIGGER_DAMAGE:
return true;
default:
break;
}
}
return false;
}
@@ -641,4 +689,94 @@ private:
std::unique_ptr<AuraStub> _aura;
};
/**
* @brief Simulates the proc chain guard logic from Unit::TriggerAurasProcOnEvent
*
* Tracks the m_procDeep counter to verify that:
* - TRIGGERED_DISALLOW_PROC_EVENTS on the triggering spell disables procs
* for all auras in the container
* - SPELL_ATTR3_INSTANT_TARGET_PROCS on individual auras disables procs
* only during that specific aura's TriggerProcOnEvent call
* - The counter is properly balanced (returns to 0 after function exits)
*/
class ProcChainGuardSimulator
{
public:
struct AuraConfig
{
uint32 spellId = 0;
bool hasInstantTargetProcs = false; // SPELL_ATTR3_INSTANT_TARGET_PROCS
bool isRemoved = false; // AuraApplication::GetRemoveMode()
};
struct ProcRecord
{
uint32 spellId;
bool canProcDuringTrigger; // CanProc() state when TriggerProcOnEvent fires
int32 procDeepDuringTrigger; // m_procDeep value during trigger
};
ProcChainGuardSimulator() : _procDeep(0) {}
/**
* @brief Simulate Unit::TriggerAurasProcOnEvent
*
* @param triggeringSpellHasDisallowProcEvents Whether the triggering spell
* has TRIGGERED_DISALLOW_PROC_EVENTS cast flag
* @param auras List of auras in the proc container
*/
void SimulateTriggerAurasProc(
bool triggeringSpellHasDisallowProcEvents,
std::vector<AuraConfig> const& auras)
{
_records.clear();
bool const disableProcs = triggeringSpellHasDisallowProcEvents;
if (disableProcs)
SetCantProc(true);
for (auto const& aura : auras)
{
if (aura.isRemoved)
continue;
if (aura.hasInstantTargetProcs)
SetCantProc(true);
// Record CanProc() state during TriggerProcOnEvent
_records.push_back({
aura.spellId,
CanProc(),
_procDeep
});
if (aura.hasInstantTargetProcs)
SetCantProc(false);
}
if (disableProcs)
SetCantProc(false);
}
[[nodiscard]] std::vector<ProcRecord> const& GetRecords() const
{
return _records;
}
[[nodiscard]] int32 GetProcDeep() const { return _procDeep; }
[[nodiscard]] bool CanProc() const { return _procDeep == 0; }
private:
void SetCantProc(bool apply)
{
if (apply)
++_procDeep;
else
--_procDeep;
}
int32 _procDeep;
std::vector<ProcRecord> _records;
};
#endif // AZEROTHCORE_PROC_CHANCE_TEST_HELPER_H