Paragon: weapon-class subclass bypass for proc talents (Maelstrom Weapon any weapon)

Cross-class wildcard now also relaxes the EquippedItemSubClassMask
gate on weapon-class proc talents so e.g. Maelstrom Weapon procs
from any weapon a Paragon has equipped, not just the talent's stock
melee subset (axe / mace / staff / fist / dagger / 2H sword/axe/mace).

Three independent gates run in the proc chain; all three needed the
bypass for the proc to actually fire:

- Player::HasItemFitToSpellRequirements -- talent-attach check at
  item-equip / login time. Without this the passive talent aura
  never even applies for a Paragon wielding a non-stock weapon.
- Player::CheckAttackFitToAuraRequirement -- per-swing match the
  proc engine uses to decide whether attackType + weapon is
  compatible with the aura's EquippedItemClass / SubClassMask.
- Aura::IsProcTriggeredOnEvent (SpellAuras.cpp) -- per-event proc
  evaluator that calls Item::IsFitToSpellRequirements again,
  independently of the previous two. Was the proc-killing gate
  before this commit: talent attached, swing matched, but this
  evaluator returned 0 charges for any weapon outside the stock
  subclass mask, so no stack was ever applied.

All three bypasses are gated on:
- IsParagonWildcardCaller(this) (player class 12 + config flag
  Paragon.WildcardFamilyMatching = 1).
- spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON. ARMOR-class
  gates (shield) are deliberately left alone -- shield-required
  talents (Shield Specialization, Shield Block, etc.) still need
  an actual shield equipped.

Each bypass also requires *some* weapon to be in the relevant slot
(MAINHAND / OFFHAND / RANGED). Unarmed Paragons do not auto-activate
every weapon-gated talent in the game.

Net effect for Paragon characters with Maelstrom Weapon talented:
proc fires from any melee weapon -- 1H sword / polearm / spear /
fist / dagger / staff / 2H weapons / axes / maces. Stock Shamans
and every other non-Paragon class are unchanged.

Caveat (not addressed by this commit): the talent's stock ProcFlags
(0xC00014) only fire on melee swings + melee abilities. Ranged
auto-attacks (bow / gun / crossbow / wand) fire
PROC_FLAG_DONE_RANGED_AUTO_ATTACK (0x40), which the proc engine
never matches against this talent, so the new weapon-subclass
bypass is moot for those weapons. Adding ranged-auto-attack support
would require a Paragon-only ProcFlags expansion at the proc
engine layer; deferred until requested.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Docker Build
2026-05-11 18:19:42 -04:00
parent e649402163
commit 9aeb5f5a7c
2 changed files with 50 additions and 1 deletions
@@ -7143,6 +7143,16 @@ bool Player::CheckAttackFitToAuraRequirement(WeaponAttackType attackType, AuraEf
if (spellInfo->EquippedItemClass == -1)
return true;
// Fractured / Paragon: cross-class wildcard relaxes weapon-class subclass
// gates on per-swing proc matches. A Paragon's talent list spans every
// class so a stock weapon-subclass mask (e.g. Maelstrom Weapon's
// axe/mace/staff/fist/dagger restriction) excludes weapons the player
// can legitimately wield. Accept any equipped weapon in attackType slot
// when listener is a Paragon AND the spell gates on ITEM_CLASS_WEAPON;
// ITEM_CLASS_ARMOR (shield) gates still enforce the original mask.
if (spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON && IsParagonWildcardCaller(this))
return GetWeaponForAttack(attackType, true) != nullptr;
Item* item = GetWeaponForAttack(attackType, true);
if (!item || !item->IsFitToSpellRequirements(spellInfo))
return false;
@@ -12581,6 +12591,27 @@ bool Player::HasItemFitToSpellRequirements(SpellInfo const* spellInfo, Item cons
if (spellInfo->EquippedItemClass < 0)
return true;
// Fractured / Paragon: cross-class wildcard relaxes weapon-class subclass
// gates so passive talent auras (e.g. Maelstrom Weapon talents 51528-51532)
// attach for any equipped weapon, not just the talent's restrictive
// subclass mask. Mirrors CheckAttackFitToAuraRequirement so per-swing
// proc match agrees with talent-attach time. Still requires *some* weapon
// to be equipped (otherwise unarmed Paragons would auto-activate every
// weapon-gated talent in the game). ITEM_CLASS_ARMOR (shield) is left
// alone -- shield-gated talents still need an actual shield.
if (spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON && IsParagonWildcardCaller(this))
{
for (uint8 i = EQUIPMENT_SLOT_MAINHAND; i < EQUIPMENT_SLOT_TABARD; ++i)
if (Item const* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i))
if (item != ignoreItem)
if (ItemTemplate const* proto = item->GetTemplate())
if (proto->Class == ITEM_CLASS_WEAPON)
return true;
// No weapon equipped at all -- fall through to stock logic, which
// returns false for passive talent auras (correct: an unarmed
// Paragon shouldn't have weapon talents active).
}
// scan other equipped items for same requirements (mostly 2 daggers/etc)
// for optimize check 2 used cases only
switch (spellInfo->EquippedItemClass)
+19 -1
View File
@@ -2251,8 +2251,26 @@ uint8 Aura::GetProcEffectMask(AuraApplication* aurApp, ProcEventInfo& eventInfo,
item = target->ToPlayer()->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
}
if (!item || item->IsBroken() || !item->IsFitToSpellRequirements(GetSpellInfo()))
if (!item || item->IsBroken())
return 0;
if (!item->IsFitToSpellRequirements(GetSpellInfo()))
{
// Fractured / Paragon: cross-class wildcard relaxes weapon-
// class subclass gates on per-event proc evaluation. This
// mirrors Player::CheckAttackFitToAuraRequirement and
// Player::HasItemFitToSpellRequirements -- without this
// third bypass, the talent attaches (HasItemFit lets it),
// the per-swing match accepts the weapon (CheckAttackFit
// lets it), but IsProcTriggeredOnEvent still kills the
// proc here for any weapon outside the talent's stock
// subclass mask (e.g. Maelstrom Weapon on a Paragon
// wielding a 1H sword or polearm). Restricted to
// ITEM_CLASS_WEAPON so shield-gated talents still need
// an actual shield.
if (!(GetSpellInfo()->EquippedItemClass == ITEM_CLASS_WEAPON
&& IsParagonWildcardCaller(target)))
return 0;
}
}
}