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:
@@ -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);
|
||||
@@ -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()
|
||||
{
|
||||
new Paragon_PlayerScript();
|
||||
RegisterSpellScript(spell_paragon_arcane_torrent);
|
||||
RegisterSpellScript(spell_paragon_predatory_strikes);
|
||||
}
|
||||
|
||||
@@ -4767,6 +4767,36 @@ Respawn.DynamicEscortNPC = 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
|
||||
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
|
||||
@@ -9773,7 +9773,11 @@ bool Player::IsAffectedBySpellmod(SpellInfo const* spellInfo, SpellModifier* mod
|
||||
if (mod->op == SPELLMOD_DURATION && spellInfo->GetDuration() == -1)
|
||||
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>
|
||||
|
||||
@@ -72,11 +72,25 @@
|
||||
#include "Util.h"
|
||||
#include "Vehicle.h"
|
||||
#include "World.h"
|
||||
#include "WorldConfig.h"
|
||||
#include "WorldPacket.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#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] =
|
||||
{
|
||||
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
|
||||
// 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);
|
||||
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();
|
||||
|
||||
// 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);
|
||||
|
||||
if (mechanicMask)
|
||||
|
||||
@@ -2268,6 +2268,15 @@ private:
|
||||
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
|
||||
{
|
||||
// Binary predicate for sorting Units based on percent value of a power
|
||||
|
||||
@@ -2175,7 +2175,9 @@ uint8 Aura::GetProcEffectMask(AuraApplication* aurApp, ProcEventInfo& eventInfo,
|
||||
return 0;
|
||||
|
||||
// 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;
|
||||
|
||||
// check if spell was affected by this aura's spellmod (used by Arcane Potency and similar effects)
|
||||
|
||||
@@ -3540,6 +3540,32 @@ SpellCastResult Spell::prepare(SpellCastTargets const* targets, AuraEffect const
|
||||
if (m_caster->ToPlayer()->GetCommandStatus(CHEAT_CASTTIME))
|
||||
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
|
||||
// (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())
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "SpellAuraDefines.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "World.h"
|
||||
#include "WorldConfig.h"
|
||||
|
||||
uint32 GetTargetFlagMask(SpellTargetObjectTypes objType)
|
||||
{
|
||||
@@ -1323,11 +1325,26 @@ bool SpellInfo::HasInitialAggro() 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)
|
||||
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;
|
||||
|
||||
if (familyFlags && !(familyFlags & SpellFamilyFlags))
|
||||
@@ -1342,6 +1359,11 @@ bool SpellInfo::IsAffectedBySpellMods() 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
|
||||
if (mod->op != SPELLMOD_DURATION)
|
||||
@@ -1356,7 +1378,7 @@ bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const
|
||||
if (!sScriptMgr->OnIsAffectedBySpellModCheck(affectSpell, this, mod))
|
||||
return true;
|
||||
|
||||
return IsAffected(affectSpell->SpellFamilyName, mod->mask);
|
||||
return IsAffected(affectSpell->SpellFamilyName, mod->mask, listenerOwner);
|
||||
}
|
||||
|
||||
bool SpellInfo::CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const
|
||||
|
||||
@@ -494,9 +494,21 @@ public:
|
||||
bool HasInitialAggro() 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 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 CanDispelAura(SpellInfo const* auraSpellInfo) const;
|
||||
|
||||
@@ -842,7 +842,8 @@ SpellProcEntry const* SpellMgr::GetSpellProcEntry(uint32 spellId) const
|
||||
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
|
||||
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
|
||||
if (eventInfo.GetTypeMask() & SPELL_PROC_FLAG_MASK)
|
||||
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;
|
||||
|
||||
// check spell type mask (if set)
|
||||
|
||||
@@ -699,7 +699,12 @@ public:
|
||||
|
||||
// Spell proc table
|
||||
[[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
|
||||
[[nodiscard]] SpellBonusEntry const* GetSpellBonusData(uint32 spellId) const;
|
||||
|
||||
@@ -684,4 +684,10 @@ void WorldConfig::BuildConfigCache()
|
||||
|
||||
// Achievement
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -495,6 +495,12 @@ enum ServerConfigs
|
||||
CONFIG_NEW_CHAR_STRING,
|
||||
CONFIG_VALIDATE_SKILL_LEARNED_BY_SPELLS,
|
||||
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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user