|
|
|
@@ -584,6 +584,37 @@ void RevokeBlockedSpellsForPlayer(Player* pl)
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Same migration for meta-skill cascade spells. Earlier builds
|
|
|
|
|
// (and this one until just now) revoked the rune-enchant spells
|
|
|
|
|
// (Razorice, Cinderglacier, Rune of the Fallen Crusader, ...)
|
|
|
|
|
// when a Paragon learned Runeforging via the panel, because
|
|
|
|
|
// they're active spells and the default classifier treats
|
|
|
|
|
// unknown active cascades as leaks. New policy: anything on
|
|
|
|
|
// SKILL_RUNEFORGING is part of the Runeforging meta-skill
|
|
|
|
|
// package and stays. Drop the revoked row and, if we have a
|
|
|
|
|
// still-owned parent (typically Runeforging itself, 53428),
|
|
|
|
|
// re-record as a child so refund/unlearn still cleans them up.
|
|
|
|
|
bool isMetaSkillRevoke = false;
|
|
|
|
|
{
|
|
|
|
|
auto bounds = sSpellMgr->GetSkillLineAbilityMapBounds(sid);
|
|
|
|
|
for (auto it = bounds.first; it != bounds.second; ++it)
|
|
|
|
|
{
|
|
|
|
|
if (it->second->SkillLine == SKILL_RUNEFORGING)
|
|
|
|
|
{
|
|
|
|
|
isMetaSkillRevoke = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (isMetaSkillRevoke)
|
|
|
|
|
{
|
|
|
|
|
if (parent && ownedPanelSpells.count(parent))
|
|
|
|
|
passiveMigrate.emplace_back(parent, sid);
|
|
|
|
|
passiveStaleAll.push_back(sid);
|
|
|
|
|
++migrated;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (allowed.count(sid))
|
|
|
|
|
{
|
|
|
|
|
stale.push_back(sid);
|
|
|
|
@@ -753,11 +784,57 @@ void RevokeBlockedSpellsForPlayer(Player* pl)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Allowlist for ACTIVE spells we explicitly want kept as
|
|
|
|
|
// panel_spell_children, even though the general policy is "actives in
|
|
|
|
|
// children = legacy garbage, drop them" (see
|
|
|
|
|
// PruneSkillLineCascadeChildrenFromDb).
|
|
|
|
|
//
|
|
|
|
|
// The original kAttached set was 100% passives (Frost Fever, Blood
|
|
|
|
|
// Plague, Forceful Deflection, Runic Focus). For those, "passive ==
|
|
|
|
|
// keep" was a perfect proxy. Runeforging changed that: the 8 basic
|
|
|
|
|
// rune-enchant spells (53344, 53343, 53341, 53331, 53342, 53323,
|
|
|
|
|
// 54447, 54446) are ACTIVE casts that we DO want to attach to the
|
|
|
|
|
// Runeforging panel purchase so:
|
|
|
|
|
// * The Lua-substitute Runeforge UI can cast them (HasActiveSpell).
|
|
|
|
|
// * Refunding Runeforging cleans them up via the standard
|
|
|
|
|
// panel_spell_children unlearn path.
|
|
|
|
|
//
|
|
|
|
|
// Without this allowlist, PruneSkillLineCascadeChildrenFromDb runs
|
|
|
|
|
// immediately after PanelLearnSpellChain attaches them, sees them as
|
|
|
|
|
// non-passive, drops them, and inserts panel_spell_revoked rows --
|
|
|
|
|
// stranding the player with no usable runeforging menu.
|
|
|
|
|
//
|
|
|
|
|
// Every entry here MUST also appear in PanelLearnSpellChain::kAttached
|
|
|
|
|
// AND in OnPlayerLogin's kFixup list (or a shared source if those ever
|
|
|
|
|
// get factored out). The pair ordering is (parentHead, attachedSpell),
|
|
|
|
|
// matching kAttached / kFixup.
|
|
|
|
|
struct IntentionalActiveAttached { uint32 parent; uint32 child; };
|
|
|
|
|
static IntentionalActiveAttached const kIntentionalActiveAttached[] = {
|
|
|
|
|
{ 53428, 53344 }, // Runeforging -> Rune of the Fallen Crusader
|
|
|
|
|
{ 53428, 53343 }, // Runeforging -> Rune of Razorice
|
|
|
|
|
{ 53428, 53341 }, // Runeforging -> Rune of Cinderglacier
|
|
|
|
|
{ 53428, 53331 }, // Runeforging -> Rune of Lichbane
|
|
|
|
|
{ 53428, 53342 }, // Runeforging -> Rune of Spellshattering
|
|
|
|
|
{ 53428, 53323 }, // Runeforging -> Rune of Swordshattering
|
|
|
|
|
{ 53428, 54447 }, // Runeforging -> Rune of Spellbreaking
|
|
|
|
|
{ 53428, 54446 }, // Runeforging -> Rune of Swordbreaking
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
[[nodiscard]] static bool IsIntentionalActiveAttachedChild(uint32 parent, uint32 child)
|
|
|
|
|
{
|
|
|
|
|
for (auto const& e : kIntentionalActiveAttached)
|
|
|
|
|
if (e.parent == parent && e.child == child)
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Current policy: cascade-granted passives stick as panel_spell_children;
|
|
|
|
|
// only actives get revoked. This pass exists to scrub *legacy* rows that
|
|
|
|
|
// older logic inserted incorrectly — specifically, any active spell that
|
|
|
|
|
// ended up in panel_spell_children from a build that classified things
|
|
|
|
|
// differently. Passive children are always retained.
|
|
|
|
|
// differently. Passive children are always retained, as are entries
|
|
|
|
|
// whitelisted via kIntentionalActiveAttached (Runeforging rune-enchants
|
|
|
|
|
// are active casts that we deliberately attach as children).
|
|
|
|
|
static void PruneSkillLineCascadeChildrenFromDb(Player* pl, uint32 lowGuid)
|
|
|
|
|
{
|
|
|
|
|
if (!pl)
|
|
|
|
@@ -776,6 +853,8 @@ static void PruneSkillLineCascadeChildrenFromDb(Player* pl, uint32 lowGuid)
|
|
|
|
|
SpellInfo const* info = sSpellMgr->GetSpellInfo(child);
|
|
|
|
|
if (info && info->IsPassive())
|
|
|
|
|
continue; // passives always stay
|
|
|
|
|
if (IsIntentionalActiveAttachedChild(parent, child))
|
|
|
|
|
continue; // intentional active attachment
|
|
|
|
|
// Active in children -> legacy garbage. Drop the row, revoke the
|
|
|
|
|
// spell, and persist into panel_spell_revoked so the login sweep
|
|
|
|
|
// catches future cascade re-fires.
|
|
|
|
@@ -1228,13 +1307,45 @@ void PanelLearnSpellChain(Player* pl, uint32 baseSpellId)
|
|
|
|
|
if (!dep)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (dep->IsPassive())
|
|
|
|
|
// Meta-skill cascade carve-out. Runeforging (776) is a
|
|
|
|
|
// CLASS-category skill that, once granted, is supposed to
|
|
|
|
|
// cascade ALL its rune-enchant spells (Rune of the Fallen
|
|
|
|
|
// Crusader, Razorice, Cinderglacier, Lichbane, Spell-/
|
|
|
|
|
// Sword-shattering, Spell-/Sword-breaking, Stoneskin
|
|
|
|
|
// Gargoyle, Nerubian Carapace) for the player to choose
|
|
|
|
|
// from at a runeforge anvil. Those rune-enchants are
|
|
|
|
|
// ACTIVE spells, so the default policy below would
|
|
|
|
|
// revoke them and the player would learn Runeforging
|
|
|
|
|
// for nothing. Treat the whole cluster the same way we
|
|
|
|
|
// treat passive deps: persist as children of the panel
|
|
|
|
|
// purchase so refund/unlearn drops them too, but do NOT
|
|
|
|
|
// revoke them.
|
|
|
|
|
//
|
|
|
|
|
// Detection: walk the dep's own SkillLineAbility entries
|
|
|
|
|
// and check for SKILL_RUNEFORGING. This auto-handles all
|
|
|
|
|
// 10 rune-enchant spells without an ID-by-ID allowlist.
|
|
|
|
|
bool isMetaSkillCascade = false;
|
|
|
|
|
{
|
|
|
|
|
auto bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);
|
|
|
|
|
for (auto it = bounds.first; it != bounds.second; ++it)
|
|
|
|
|
{
|
|
|
|
|
if (it->second->SkillLine == SKILL_RUNEFORGING)
|
|
|
|
|
{
|
|
|
|
|
isMetaSkillCascade = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dep->IsPassive() || isMetaSkillCascade)
|
|
|
|
|
{
|
|
|
|
|
DbInsertPanelSpellChild(lowGuid, trackId, spellId);
|
|
|
|
|
if (diag)
|
|
|
|
|
LOG_INFO("module",
|
|
|
|
|
"[paragon-diag] +{} (passive dep, kept as child of {})",
|
|
|
|
|
spellId, trackId);
|
|
|
|
|
"[paragon-diag] +{} ({} dep, kept as child of {})",
|
|
|
|
|
spellId,
|
|
|
|
|
isMetaSkillCascade ? "meta-skill" : "passive",
|
|
|
|
|
trackId);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
@@ -1301,6 +1412,34 @@ void PanelLearnSpellChain(Player* pl, uint32 baseSpellId)
|
|
|
|
|
{ 45477, 59921 }, // Icy Touch -> Frost Fever (passive entry)
|
|
|
|
|
{ 45477, 61455 }, // Icy Touch -> Runic Focus (parry from spell power)
|
|
|
|
|
{ 45902, 49410 }, // Blood Strike -> Forceful Deflection (parry from strength)
|
|
|
|
|
|
|
|
|
|
// Runeforging -> 8 basic rune-enchants. The
|
|
|
|
|
// SkillLineAbility rows for these (skill 776) all ship
|
|
|
|
|
// with AcquireMethod = 0 in the DBC (i.e. NOT auto-learn-
|
|
|
|
|
// on-skill-grant). For stock DKs the engine's hardcoded
|
|
|
|
|
// runeforging UI hand-rolls the cast for whichever rune
|
|
|
|
|
// the player picks, but for our Lua-substitute UI the
|
|
|
|
|
// server's HandleCastSpellOpcode / HasActiveSpell gate
|
|
|
|
|
// rejects the cast unless the spell is in the spellbook.
|
|
|
|
|
// Force-attach them as panel children so:
|
|
|
|
|
// 1. The player actually owns the spells (cast works).
|
|
|
|
|
// 2. Refunding Runeforging cleans them up via the
|
|
|
|
|
// standard panel_spell_children unlearn path.
|
|
|
|
|
// The two ADVANCED runes (Stoneskin Gargoyle 62158 and
|
|
|
|
|
// Nerubian Carapace 70164) are intentionally NOT listed:
|
|
|
|
|
// retail gates them behind item drops from heroic
|
|
|
|
|
// dungeons / Naxx / ICC, and our SkillLineAbility rows
|
|
|
|
|
// for them already use AcquireMethod=0 so the player
|
|
|
|
|
// gets them when they pick up the appropriate item, not
|
|
|
|
|
// for free with Runeforging itself.
|
|
|
|
|
{ 53428, 53344 }, // Runeforging -> Rune of the Fallen Crusader
|
|
|
|
|
{ 53428, 53343 }, // Runeforging -> Rune of Razorice
|
|
|
|
|
{ 53428, 53341 }, // Runeforging -> Rune of Cinderglacier
|
|
|
|
|
{ 53428, 53331 }, // Runeforging -> Rune of Lichbane
|
|
|
|
|
{ 53428, 53342 }, // Runeforging -> Rune of Spellshattering
|
|
|
|
|
{ 53428, 53323 }, // Runeforging -> Rune of Swordshattering
|
|
|
|
|
{ 53428, 54447 }, // Runeforging -> Rune of Spellbreaking
|
|
|
|
|
{ 53428, 54446 }, // Runeforging -> Rune of Swordbreaking
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Self-heal: a previous build of mod-paragon (briefly shipped)
|
|
|
|
@@ -2256,9 +2395,21 @@ void PushSpellSnapshot(Player* pl)
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SpellInfo const* info = sSpellMgr->GetSpellInfo(sid);
|
|
|
|
|
if (info && info->HasAttribute(SPELL_ATTR0_DO_NOT_DISPLAY))
|
|
|
|
|
continue;
|
|
|
|
|
// Note: we deliberately do NOT filter SPELL_ATTR0_DO_NOT_DISPLAY
|
|
|
|
|
// here. Earlier builds did, on the theory that hidden spells
|
|
|
|
|
// shouldn't appear in the spellbook-style Overview tab. That
|
|
|
|
|
// turned out to be wrong: cascade-granted hidden passives
|
|
|
|
|
// (Forceful Deflection, Frost Fever, ...) live in
|
|
|
|
|
// panel_spell_children, not in panel_spells -- so the only
|
|
|
|
|
// entries that ever land in this query are the chain heads
|
|
|
|
|
// the player explicitly purchased. Those MUST appear in the
|
|
|
|
|
// Overview even if their DBC entry is hidden, because they
|
|
|
|
|
// are the player's actual purchases (e.g. Runeforging 53428
|
|
|
|
|
// is hidden in the DBC but is the entire Runeforging panel
|
|
|
|
|
// purchase). Filtering them out left chars whose only buy
|
|
|
|
|
// was Runeforging with an empty Overview tab -- looked like
|
|
|
|
|
// a regression but was actually the existing snapshot logic
|
|
|
|
|
// mismatching the panel's user-facing semantics.
|
|
|
|
|
|
|
|
|
|
std::string token = (first ? "" : ",") + std::to_string(sid);
|
|
|
|
|
if (buf.size() + token.size() > kSnapshotChunkBudget)
|
|
|
|
@@ -4018,6 +4169,24 @@ public:
|
|
|
|
|
{ 45477, 59921 }, // Icy Touch -> Frost Fever (passive)
|
|
|
|
|
{ 45477, 61455 }, // Icy Touch -> Runic Focus
|
|
|
|
|
{ 45902, 49410 }, // Blood Strike -> Forceful Deflection
|
|
|
|
|
|
|
|
|
|
// Runeforging -> 8 basic rune-enchants. Mirror of
|
|
|
|
|
// PanelLearnSpellChain::kAttached: the SLA rows for
|
|
|
|
|
// these (skill 776) ship with AcquireMethod=0 so the
|
|
|
|
|
// engine's normal cascade never grants them, and for
|
|
|
|
|
// the substitute Lua runeforging UI to actually be
|
|
|
|
|
// able to cast them HasActiveSpell needs to return
|
|
|
|
|
// true. Existing Paragon characters that bought
|
|
|
|
|
// Runeforging before this fix landed get them
|
|
|
|
|
// retro-granted on their next login.
|
|
|
|
|
{ 53428, 53344 }, // Runeforging -> Fallen Crusader
|
|
|
|
|
{ 53428, 53343 }, // Runeforging -> Razorice
|
|
|
|
|
{ 53428, 53341 }, // Runeforging -> Cinderglacier
|
|
|
|
|
{ 53428, 53331 }, // Runeforging -> Lichbane
|
|
|
|
|
{ 53428, 53342 }, // Runeforging -> Spellshattering
|
|
|
|
|
{ 53428, 53323 }, // Runeforging -> Swordshattering
|
|
|
|
|
{ 53428, 54447 }, // Runeforging -> Spellbreaking
|
|
|
|
|
{ 53428, 54446 }, // Runeforging -> Swordbreaking
|
|
|
|
|
};
|
|
|
|
|
for (auto const& lf : kFixup)
|
|
|
|
|
{
|
|
|
|
|