Paragon cross-class family wildcard + Predatory Strikes proc

- CONFIG_PARAGON_WILDCARD_FAMILY + Paragon.WildcardFamilyMatching (reloadable)
- SpellInfo::IsAffected / IsAffectedBySpellMod(listenerOwner) for Paragon proc/mod wildcard
- SpellMgr::CanSpellTriggerProcOnEvent(procOwner) + Aura::IsProcTriggeredOnEvent wiring
- Player::IsAffectedBySpellmod passes listener for SpellMod wildcard
- ParagonFamilyMatches helper + Nourish / Shred-Maul bleed gate usage in Unit.cpp
- Spell::prepare: Paragon consumes 69369 for Nature spells <10s base cast (non-channeled)
- spell_paragon_predatory_strikes + SQL 2026_05_11_00.sql (spell_proc + spell_script_names)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Docker Build
2026-05-11 01:24:50 -04:00
parent b408c8a95d
commit a1c9172beb
14 changed files with 337 additions and 9 deletions
@@ -0,0 +1,60 @@
-- mod-paragon: Predatory Strikes (16972 / 16974 / 16975) Cataclysm-style
-- finisher proc for CLASS_PARAGON characters.
--
-- The 3.3.5 Predatory Strikes is a passive AP / ranged-attack-power talent
-- with no proc payload. The Cataclysm redesign added "Predator's Swiftness"
-- (69369), which makes the next Nature spell <10s base cast time instant.
-- That buff already exists in the WotLK Spell.dbc (because Blizzard reused
-- the spell id), but no server-side trigger ever calls CastSpell(69369) on
-- a 3.3.5 server. We need both halves: the proc handler AND a spell_proc
-- row so the proc evaluator actually invokes our AuraScript.
--
-- AuraScript binding: spell_paragon_predatory_strikes is registered in
-- modules/mod-paragon/src/Paragon_SC.cpp. It checks
-- (a) caster is CLASS_PARAGON,
-- (b) source spell consumes combo points (NeedsComboPoints),
-- (c) source spell deals damage (DmgClass MELEE/RANGED + at least one
-- damage effect or periodic-damage aura -- filters Slice and Dice,
-- Savage Roar, Maim, Kidney Shot, Expose Armor, Recuperate),
-- then rolls a per-rank chance of (CP * 3 / 5 / 7)% to cast 69369 on
-- the caster.
--
-- spell_proc row params:
-- ProcFlags = 0x40000 = PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS
-- SpellTypeMask = 0x3 (DAMAGE | HEAL bitmask in proc engine; we
-- filter precisely in CheckProc anyway, the
-- mask just gates "spell-type events" through)
-- SpellPhaseMask = 0x2 = PROC_SPELL_PHASE_CAST -- fires DURING cast
-- so player->GetComboPoints() inside HandleProc
-- still returns the pre-_handle_finish_phase
-- value.
-- Chance = 100 (the per-CP chance is rolled inside the script)
--
-- Note: this row's SpellFamilyName / SpellFamilyMask are 0 so the proc
-- engine's IsAffected check is a wildcard at the entry-level. The
-- AuraScript's CheckProc owns all real filtering. Combined with Phase A
-- (Paragon SpellFamilyName wildcard) this is harmless on stock classes
-- because non-Paragon characters cannot learn Predatory Strikes via the
-- Character Advancement panel.
DELETE FROM `spell_script_names`
WHERE `spell_id` IN (16972, 16974, 16975)
AND `ScriptName` = 'spell_paragon_predatory_strikes';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(16972, 'spell_paragon_predatory_strikes'),
(16974, 'spell_paragon_predatory_strikes'),
(16975, 'spell_paragon_predatory_strikes');
DELETE FROM `spell_proc` WHERE `SpellId` IN (16972, 16974, 16975);
INSERT INTO `spell_proc`
(`SpellId`, `SchoolMask`, `SpellFamilyName`,
`SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`,
`ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`,
`AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`,
`Chance`, `Cooldown`, `Charges`)
VALUES
(16972, 0, 0, 0, 0, 0, 0x40000, 0x3, 0x2, 0, 0, 0, 0, 100.0, 0, 0),
(16974, 0, 0, 0, 0, 0, 0x40000, 0x3, 0x2, 0, 0, 0, 0, 100.0, 0, 0),
(16975, 0, 0, 0, 0, 0, 0x40000, 0x3, 0x2, 0, 0, 0, 0, 100.0, 0, 0);
+128
View File
@@ -514,8 +514,136 @@ class spell_paragon_arcane_torrent : public SpellScript
} }
}; };
// Predatory Strikes (16972 / 16974 / 16975) for Paragon: re-implements the
// Cataclysm-era proc behavior of the talent so a Paragon's damaging
// finishers (Eviscerate / Envenom / Ferocious Bite / Rip / Rupture) can
// roll Predator's Swiftness (69369) -- the same buff that real druids
// get from the Cata redesign of this talent. Combined with the
// Spell::prepare interception in core (Spell.cpp), 69369 makes the
// Paragon's NEXT Nature-school spell with a base cast time below 10s
// instant cast: Chain Lightning, Lightning Bolt, Healing Touch, Wrath,
// Nourish, etc. -- not just the Druid-family Nature subset that the
// stock SPELLMOD_CASTING_TIME mask on 69369 covers.
//
// Filter logic:
// - Source spell must consume combo points (NeedsComboPoints() — gates
// out non-finisher combo-point builders).
// - "Damaging finisher": SPELL_ATTR1_FINISHING_MOVE_DAMAGE (Eviscerate,
// Envenom, Ferocious Bite, ...) OR a SPELL_ATTR1_FINISHING_MOVE_DURATION
// finisher that applies periodic damage (Rip, Rupture). Duration
// finishers that only heal (Recuperate) or only buff / CC / armor shred
// (Slice and Dice, Savage Roar, Kidney Shot, Maim, Expose Armor) are
// rejected.
//
// Chance per combo point matches the Cataclysm tuning that the user's
// client tooltip text reflects: rank 1 = 3% per CP, rank 2 = 5% per CP,
// rank 3 = 7% per CP. At 5 CP that is 15% / 25% / 35%, capped at 100%.
//
// Combo-point read happens during PROC_SPELL_PHASE_CAST, which fires in
// Spell::cast → Spell::ProcReflectProcs / Unit::ProcDamageAndSpellFor
// BEFORE Spell::_handle_finish_phase clears the player's combo points
// (see Spell.cpp:_handle_finish_phase clearing combo points). So
// player->GetComboPoints() inside HandleProc returns the pre-clear value.
class spell_paragon_predatory_strikes : public AuraScript
{
PrepareAuraScript(spell_paragon_predatory_strikes);
static constexpr uint32 SPELL_PARAGON_PREDATORS_SWIFTNESS = 69369;
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_PARAGON_PREDATORS_SWIFTNESS });
}
bool CheckProc(ProcEventInfo& eventInfo)
{
SpellInfo const* spellInfo = eventInfo.GetSpellInfo();
if (!spellInfo || !spellInfo->NeedsComboPoints())
return false;
if (spellInfo->HasAttribute(SPELL_ATTR1_FINISHING_MOVE_DAMAGE))
return true;
if (spellInfo->HasAttribute(SPELL_ATTR1_FINISHING_MOVE_DURATION))
{
bool periodicHeal = false;
bool periodicDamage = false;
for (SpellEffectInfo const& eff : spellInfo->Effects)
{
if (eff.Effect != SPELL_EFFECT_APPLY_AURA && eff.Effect != SPELL_EFFECT_APPLY_AREA_AURA_PARTY
&& eff.Effect != SPELL_EFFECT_PERSISTENT_AREA_AURA)
continue;
switch (eff.ApplyAuraName)
{
case SPELL_AURA_PERIODIC_HEAL:
case SPELL_AURA_PERIODIC_HEALTH_FUNNEL:
case SPELL_AURA_OBS_MOD_HEALTH:
periodicHeal = true;
break;
case SPELL_AURA_PERIODIC_DAMAGE:
case SPELL_AURA_PERIODIC_DAMAGE_PERCENT:
case SPELL_AURA_PERIODIC_LEECH:
periodicDamage = true;
break;
default:
break;
}
}
if (periodicHeal)
return false;
return periodicDamage;
}
return false;
}
void HandleProc(ProcEventInfo& eventInfo)
{
PreventDefaultAction();
Unit* actor = eventInfo.GetActor();
Player* player = actor ? actor->ToPlayer() : nullptr;
if (!player || player->getClass() != CLASS_PARAGON)
return;
uint8 const cp = player->GetComboPoints();
if (cp == 0)
return;
SpellInfo const* talent = GetSpellInfo();
if (!talent)
return;
uint32 pctPerCP = 0;
switch (talent->Id)
{
case 16972: pctPerCP = 3; break;
case 16974: pctPerCP = 5; break;
case 16975: pctPerCP = 7; break;
default:
return;
}
uint32 const chance = std::min<uint32>(100u, pctPerCP * uint32(cp));
if (!roll_chance_i(int32(chance)))
return;
player->CastSpell(player, SPELL_PARAGON_PREDATORS_SWIFTNESS, true);
}
void Register() override
{
DoCheckProc += AuraCheckProcFn(spell_paragon_predatory_strikes::CheckProc);
OnProc += AuraProcFn(spell_paragon_predatory_strikes::HandleProc);
}
};
void AddSC_paragon() void AddSC_paragon()
{ {
new Paragon_PlayerScript(); new Paragon_PlayerScript();
RegisterSpellScript(spell_paragon_arcane_torrent); RegisterSpellScript(spell_paragon_arcane_torrent);
RegisterSpellScript(spell_paragon_predatory_strikes);
} }
@@ -4767,6 +4767,36 @@ Respawn.DynamicEscortNPC = 0
Respawn.ForceCompatibilityMode = 0 Respawn.ForceCompatibilityMode = 0
#
# Paragon.WildcardFamilyMatching
# Description: Fractured / Paragon class (CLASS_PARAGON, id 12) only.
# When enabled, the SpellFamilyName equality check is
# wildcarded for Paragon characters in proc evaluation
# (SpellMgr::CanSpellTriggerProcOnEvent), talent
# SpellMod application (Player::ApplySpellMod /
# SpellInfo::IsAffectedBySpellMod), and the
# ParagonFamilyMatches() helper used by ad-hoc
# `switch (SpellFamilyName)` listener gates in
# Unit/SpellEffects/SpellAuraEffects code.
# This makes cross-class talent procs and modifiers
# (e.g. Predator's Swiftness 69369 making Shaman
# Chain Lightning instant cast off a Rogue Eviscerate
# finisher) apply to Paragon characters even when the
# listener was authored for one specific class family.
# SpellFamilyFlags / class-mask flag-bit checks still
# run, so listener gates that explicitly opt into a
# subset of spells via flag bits are still respected.
# Stock classes (Warrior / Paladin / etc.) are NEVER
# wildcarded; this only affects players whose class
# id is CLASS_PARAGON. Set to 0 to disable the
# wildcard at runtime (no rebuild required) if a
# regression appears.
# Default: 1 - (Enabled, Paragon characters get cross-class procs/mods)
# 0 - (Disabled, Paragon characters are gated by stock family equality)
#
Paragon.WildcardFamilyMatching = 1
# #
################################################################################################### ###################################################################################################
+5 -1
View File
@@ -9773,7 +9773,11 @@ bool Player::IsAffectedBySpellmod(SpellInfo const* spellInfo, SpellModifier* mod
if (mod->op == SPELLMOD_DURATION && spellInfo->GetDuration() == -1) if (mod->op == SPELLMOD_DURATION && spellInfo->GetDuration() == -1)
return false; return false;
return spellInfo->IsAffectedBySpellMod(mod); // Fractured / Paragon: pass the player owning the modifier aura so the
// SpellFamilyName equality check can be wildcarded for CLASS_PARAGON.
// Stock classes hit the same code path with `this` as a non-Paragon
// unit, which makes IsAffected behave identically to the 2-arg form.
return spellInfo->IsAffectedBySpellMod(mod, this);
} }
template <class T> template <class T>
+16 -2
View File
@@ -72,11 +72,25 @@
#include "Util.h" #include "Util.h"
#include "Vehicle.h" #include "Vehicle.h"
#include "World.h" #include "World.h"
#include "WorldConfig.h"
#include "WorldPacket.h" #include "WorldPacket.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <limits> #include <limits>
// Fractured / Paragon: cross-class wildcard helper used by ad-hoc
// `switch (SpellFamilyName)` listener gates in Unit / SpellEffects /
// SpellAuraEffects. Returns true when the listener is a CLASS_PARAGON
// player and the wildcard config flag is enabled, otherwise falls back
// to strict family-name equality.
bool ParagonFamilyMatches(Unit const* listener, uint32 expectedFamily, uint32 actualFamily)
{
if (listener && listener->getClass() == CLASS_PARAGON
&& sWorld->getBoolConfig(CONFIG_PARAGON_WILDCARD_FAMILY))
return true;
return expectedFamily == actualFamily;
}
float baseMoveSpeed[MAX_MOVE_TYPE] = float baseMoveSpeed[MAX_MOVE_TYPE] =
{ {
2.5f, // MOVE_WALK 2.5f, // MOVE_WALK
@@ -9702,7 +9716,7 @@ uint32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, u
// Nourish cast - 20% bonus if target has Rejuvenation, Regrowth, Lifebloom, or Wild Growth from caster // Nourish cast - 20% bonus if target has Rejuvenation, Regrowth, Lifebloom, or Wild Growth from caster
// Glyph of Nourish is handled by spell_dru_nourish script // Glyph of Nourish is handled by spell_dru_nourish script
if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[1] & 0x2000000 && caster) if (ParagonFamilyMatches(caster, SPELLFAMILY_DRUID, spellProto->SpellFamilyName) && spellProto->SpellFamilyFlags[1] & 0x2000000 && caster)
{ {
AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL); AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL);
for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i) for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i)
@@ -10421,7 +10435,7 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT
uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask(); uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask();
// Shred, Maul - "Effects which increase Bleed damage also increase Shred damage" // Shred, Maul - "Effects which increase Bleed damage also increase Shred damage"
if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[0] & 0x00008800) if (ParagonFamilyMatches(attacker, SPELLFAMILY_DRUID, spellProto->SpellFamilyName) && spellProto->SpellFamilyFlags[0] & 0x00008800)
mechanicMask |= (1ULL << MECHANIC_BLEED); mechanicMask |= (1ULL << MECHANIC_BLEED);
if (mechanicMask) if (mechanicMask)
+9
View File
@@ -2268,6 +2268,15 @@ private:
ValuesUpdateCache _valuesUpdateCache; ValuesUpdateCache _valuesUpdateCache;
}; };
// Fractured / Paragon: helper for ad-hoc `switch (SpellFamilyName)` listener
// gates scattered across Unit / SpellEffects / SpellAuraEffects. When the
// listener (i.e. the unit holding the gating talent / aura) is a Paragon
// AND `Paragon.WildcardFamilyMatching` is enabled, accept any source family
// so cross-class procs / bonuses can fire. Stock classes use stock equality.
// Defined inline here so call sites do not need an extra include for World.h
// beyond what they already include via Unit.h's transitive headers.
[[nodiscard]] bool ParagonFamilyMatches(Unit const* listener, uint32 expectedFamily, uint32 actualFamily);
namespace Acore namespace Acore
{ {
// Binary predicate for sorting Units based on percent value of a power // Binary predicate for sorting Units based on percent value of a power
+3 -1
View File
@@ -2175,7 +2175,9 @@ uint8 Aura::GetProcEffectMask(AuraApplication* aurApp, ProcEventInfo& eventInfo,
return 0; return 0;
// do checks against db data // do checks against db data
if (!sSpellMgr->CanSpellTriggerProcOnEvent(*procEntry, eventInfo)) // Fractured / Paragon: the unit that owns this aura is the listener;
// pass it through so cross-family procs can match for Paragon players.
if (!sSpellMgr->CanSpellTriggerProcOnEvent(*procEntry, eventInfo, aurApp->GetTarget()))
return 0; return 0;
// check if spell was affected by this aura's spellmod (used by Arcane Potency and similar effects) // check if spell was affected by this aura's spellmod (used by Arcane Potency and similar effects)
+26
View File
@@ -3540,6 +3540,32 @@ SpellCastResult Spell::prepare(SpellCastTargets const* targets, AuraEffect const
if (m_caster->ToPlayer()->GetCommandStatus(CHEAT_CASTTIME)) if (m_caster->ToPlayer()->GetCommandStatus(CHEAT_CASTTIME))
m_casttime = 0; m_casttime = 0;
// Fractured / Paragon: cross-class Predator's Swiftness (69369).
// Stock 3.3.5 only ADD_PCT_MODIFIER's the cast time of Druid-family
// Nature spells via class mask, so a Paragon with the buff cannot
// instant-cast Shaman Chain Lightning / Lightning Bolt or any other
// non-Druid Nature spell. The tooltip ("next Nature spell with a
// base cast time below 10 sec becomes instant") expects all-Nature
// behavior; honor that here for CLASS_PARAGON. We deliberately do
// not touch the stock SpellMod path -- real Druids continue to hit
// the existing class-mask code path unchanged.
if (Player* paragonCaster = m_caster->ToPlayer())
{
constexpr uint32 SPELL_PARAGON_PREDATORY_SWIFTNESS = 69369;
if (m_casttime > 0
&& paragonCaster->getClass() == CLASS_PARAGON
&& (m_spellInfo->SchoolMask & SPELL_SCHOOL_MASK_NATURE)
&& m_spellInfo->CastTimeEntry
&& !m_spellInfo->IsChanneled()
&& !HasTriggeredCastFlag(TRIGGERED_FULL_MASK)
&& m_spellInfo->CalcCastTime() < 10 * IN_MILLISECONDS
&& paragonCaster->HasAura(SPELL_PARAGON_PREDATORY_SWIFTNESS))
{
m_casttime = 0;
paragonCaster->RemoveAurasDueToSpell(SPELL_PARAGON_PREDATORY_SWIFTNESS);
}
}
// don't allow channeled spells / spells with cast time to be casted while moving // don't allow channeled spells / spells with cast time to be casted while moving
// (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in) // (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in)
if ((m_spellInfo->IsChanneled() || m_casttime) && m_caster->IsPlayer() && m_caster->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT && !IsTriggered()) if ((m_spellInfo->IsChanneled() || m_casttime) && m_caster->IsPlayer() && m_caster->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT && !IsTriggered())
+24 -2
View File
@@ -27,6 +27,8 @@
#include "SpellAuraDefines.h" #include "SpellAuraDefines.h"
#include "SpellAuraEffects.h" #include "SpellAuraEffects.h"
#include "SpellMgr.h" #include "SpellMgr.h"
#include "World.h"
#include "WorldConfig.h"
uint32 GetTargetFlagMask(SpellTargetObjectTypes objType) uint32 GetTargetFlagMask(SpellTargetObjectTypes objType)
{ {
@@ -1323,11 +1325,26 @@ bool SpellInfo::HasInitialAggro() const
} }
bool SpellInfo::IsAffected(uint32 familyName, flag96 const& familyFlags) const bool SpellInfo::IsAffected(uint32 familyName, flag96 const& familyFlags) const
{
return IsAffected(familyName, familyFlags, nullptr);
}
bool SpellInfo::IsAffected(uint32 familyName, flag96 const& familyFlags,
Unit const* listenerOwner) const
{ {
if (!familyName) if (!familyName)
return true; return true;
if (familyName != SpellFamilyName) // Fractured / Paragon: when the unit that owns the listening proc /
// spellmod aura is a Paragon, accept any source family. The class
// mask flag-bit check below still runs, so listeners that explicitly
// opt into a subset of spells via SpellFamilyFlags / class mask are
// still respected; only the family-name equality gate is wildcarded.
bool const wildcardFamily = listenerOwner
&& listenerOwner->getClass() == CLASS_PARAGON
&& sWorld->getBoolConfig(CONFIG_PARAGON_WILDCARD_FAMILY);
if (!wildcardFamily && familyName != SpellFamilyName)
return false; return false;
if (familyFlags && !(familyFlags & SpellFamilyFlags)) if (familyFlags && !(familyFlags & SpellFamilyFlags))
@@ -1342,6 +1359,11 @@ bool SpellInfo::IsAffectedBySpellMods() const
} }
bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const
{
return IsAffectedBySpellMod(mod, nullptr);
}
bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod, Unit const* listenerOwner) const
{ {
// xinef: dont check duration mod // xinef: dont check duration mod
if (mod->op != SPELLMOD_DURATION) if (mod->op != SPELLMOD_DURATION)
@@ -1356,7 +1378,7 @@ bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const
if (!sScriptMgr->OnIsAffectedBySpellModCheck(affectSpell, this, mod)) if (!sScriptMgr->OnIsAffectedBySpellModCheck(affectSpell, this, mod))
return true; return true;
return IsAffected(affectSpell->SpellFamilyName, mod->mask); return IsAffected(affectSpell->SpellFamilyName, mod->mask, listenerOwner);
} }
bool SpellInfo::CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const bool SpellInfo::CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const
+12
View File
@@ -494,9 +494,21 @@ public:
bool HasInitialAggro() const; bool HasInitialAggro() const;
[[nodiscard]] bool IsAffected(uint32 familyName, flag96 const& familyFlags) const; [[nodiscard]] bool IsAffected(uint32 familyName, flag96 const& familyFlags) const;
// Fractured / Paragon overload. When `listenerOwner` is a CLASS_PARAGON
// unit and Paragon.WildcardFamilyMatching is enabled, the
// SpellFamilyName equality check is skipped (flag-bit check still runs)
// so cross-class procs / spellmods can react to the spell. Passing
// nullptr (or any non-Paragon unit) reproduces the stock 2-arg
// behavior; the 2-arg form forwards to this overload with nullptr.
[[nodiscard]] bool IsAffected(uint32 familyName, flag96 const& familyFlags,
Unit const* listenerOwner) const;
bool IsAffectedBySpellMods() const; bool IsAffectedBySpellMods() const;
bool IsAffectedBySpellMod(SpellModifier const* mod) const; bool IsAffectedBySpellMod(SpellModifier const* mod) const;
// Fractured / Paragon overload: pass the player who owns the modifier
// aura so wildcard-family matching can apply when that player is a
// Paragon. Stock callers may forward to this with nullptr.
bool IsAffectedBySpellMod(SpellModifier const* mod, Unit const* listenerOwner) const;
bool CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const; bool CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const;
bool CanDispelAura(SpellInfo const* auraSpellInfo) const; bool CanDispelAura(SpellInfo const* auraSpellInfo) const;
+6 -2
View File
@@ -842,7 +842,8 @@ SpellProcEntry const* SpellMgr::GetSpellProcEntry(uint32 spellId) const
return nullptr; return nullptr;
} }
bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo) const bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo,
Unit const* procOwner /*= nullptr*/) const
{ {
// proc type doesn't match // proc type doesn't match
if (!(eventInfo.GetTypeMask() & procEntry.ProcFlags)) if (!(eventInfo.GetTypeMask() & procEntry.ProcFlags))
@@ -873,7 +874,10 @@ bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcE
// check spell family name/flags (if set) for spells // check spell family name/flags (if set) for spells
if (eventInfo.GetTypeMask() & SPELL_PROC_FLAG_MASK) if (eventInfo.GetTypeMask() & SPELL_PROC_FLAG_MASK)
if (SpellInfo const* eventSpellInfo = eventInfo.GetSpellInfo()) if (SpellInfo const* eventSpellInfo = eventInfo.GetSpellInfo())
if (!eventSpellInfo->IsAffected(procEntry.SpellFamilyName, procEntry.SpellFamilyMask)) // Fractured / Paragon: thread the proc-aura owner so a Paragon
// listener accepts cross-family source spells. See
// SpellInfo::IsAffected(family, flags, listenerOwner).
if (!eventSpellInfo->IsAffected(procEntry.SpellFamilyName, procEntry.SpellFamilyMask, procOwner))
return false; return false;
// check spell type mask (if set) // check spell type mask (if set)
+6 -1
View File
@@ -699,7 +699,12 @@ public:
// Spell proc table // Spell proc table
[[nodiscard]] SpellProcEntry const* GetSpellProcEntry(uint32 spellId) const; [[nodiscard]] SpellProcEntry const* GetSpellProcEntry(uint32 spellId) const;
bool CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo) const; // Fractured / Paragon: `procOwner` is the unit that holds the listening
// proc aura. Passing it lets SpellInfo::IsAffected wildcard the family
// check when the listener is on a CLASS_PARAGON player. Non-Paragon
// owners (or nullptr) reproduce stock behavior exactly.
bool CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo,
Unit const* procOwner = nullptr) const;
// Spell bonus data table // Spell bonus data table
[[nodiscard]] SpellBonusEntry const* GetSpellBonusData(uint32 spellId) const; [[nodiscard]] SpellBonusEntry const* GetSpellBonusData(uint32 spellId) const;
+6
View File
@@ -684,4 +684,10 @@ void WorldConfig::BuildConfigCache()
// Achievement // Achievement
SetConfigValue<uint32>(CONFIG_ACHIEVEMENT_REALM_FIRST_KILL_WINDOW, "Achievement.RealmFirstKillWindow", 60); SetConfigValue<uint32>(CONFIG_ACHIEVEMENT_REALM_FIRST_KILL_WINDOW, "Achievement.RealmFirstKillWindow", 60);
// Fractured / Paragon: cross-class wildcard for SpellFamilyName gating.
// Default ON because the Paragon class is designed around it; flip to 0
// (no rebuild required) if a regression appears and stock family
// gating needs to be restored without backing out the code.
SetConfigValue<bool>(CONFIG_PARAGON_WILDCARD_FAMILY, "Paragon.WildcardFamilyMatching", true);
} }
+6
View File
@@ -495,6 +495,12 @@ enum ServerConfigs
CONFIG_NEW_CHAR_STRING, CONFIG_NEW_CHAR_STRING,
CONFIG_VALIDATE_SKILL_LEARNED_BY_SPELLS, CONFIG_VALIDATE_SKILL_LEARNED_BY_SPELLS,
CONFIG_ACHIEVEMENT_REALM_FIRST_KILL_WINDOW, CONFIG_ACHIEVEMENT_REALM_FIRST_KILL_WINDOW,
// Fractured / Paragon: when true, CLASS_PARAGON characters bypass the
// SpellFamilyName equality check in proc / spellmod / aura listener
// gates so cross-class talent procs and modifiers can interact with
// spells learned from other classes (e.g. Predator's Swiftness 69369
// making Shaman Chain Lightning instant). Stock classes are unaffected.
CONFIG_PARAGON_WILDCARD_FAMILY,
MAX_NUM_SERVER_CONFIGS MAX_NUM_SERVER_CONFIGS
}; };