From c83cb5de7dcf4478b6aa0ba86b5683aabbb4b13d Mon Sep 17 00:00:00 2001 From: EricksOliveira Date: Sat, 18 Jul 2026 22:58:50 +0000 Subject: [PATCH] fix(Core/Player): Fixed some spells appearing as available at trainer when they are already known (#22678) Co-authored-by: Shauren * cherry-pick commit (https://github.com/TrinityCore/TrinityCore/commit/baebb2d602ca2f10159ea0039ec3a6d03155c3bd) --- src/server/game/Entities/Player/Player.cpp | 76 ++++++++++++++++++++-- src/server/game/Entities/Player/Player.h | 3 + 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index bd68be918..8728e5252 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -2841,6 +2841,66 @@ void Player::SendInitialSpells() SendDirectMessage(&data); } +void Player::SendUnlearnSpells() +{ + WorldPacket data(SMSG_SEND_UNLEARN_SPELLS, 4 + 4 * m_spells.size()); + + uint32 spellCount = 0; + size_t countPos = data.wpos(); + data << uint32(spellCount); + + for (auto const& itr : m_spells) + { + if (itr.second->State == PLAYERSPELL_REMOVED || itr.second->Active) + continue; + + auto skillLineAbilities = sSpellMgr->GetSkillLineAbilityMapBounds(itr.first); + if (skillLineAbilities.first == skillLineAbilities.second) + continue; + + // Client already hides ranks that have a superseding rank + bool hasSupercedingRank = false; + for (auto slaItr = skillLineAbilities.first; slaItr != skillLineAbilities.second; ++slaItr) + { + if (slaItr->second->SupercededBySpell) + { + hasSupercedingRank = true; + break; + } + } + if (hasSupercedingRank) + continue; + + uint32 nextRank = sSpellMgr->GetNextSpellInChain(itr.first); + if (!nextRank || !HasSpell(nextRank)) + continue; + + data << uint32(itr.first); + ++spellCount; + } + + data.put(countPos, spellCount); + SendDirectMessage(&data); +} + +bool Player::IsUnlearnNeededForSpell(uint32 spellId) +{ + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(spellId); + if (spellInfo->IsRanked() && !spellInfo->IsStackableWithRanks()) + { + auto skillLineAbilities = sSpellMgr->GetSkillLineAbilityMapBounds(spellId); + if (skillLineAbilities.first != skillLineAbilities.second) + { + for (auto itr = skillLineAbilities.first; itr != skillLineAbilities.second; ++itr) + if (itr->second->SupercededBySpell) + return false; + + return true; + } + } + return false; +} + void Player::RemoveMail(uint32 id) { for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) @@ -3066,6 +3126,10 @@ bool Player::addSpell(uint32 spellId, uint8 addSpecMask, bool updateActive, bool if (nextSpellInfo->GetRank() < spellInfo->GetRank()) { itr->second->Active = false; + + if (!isBeingLoaded() && IsUnlearnNeededForSpell(spellId)) + SendUnlearnSpells(); + if (IsInWorld()) { WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4); @@ -3080,6 +3144,10 @@ bool Player::addSpell(uint32 spellId, uint8 addSpecMask, bool updateActive, bool PlayerSpellMap::iterator itr2 = m_spells.find(spellInfo->Id); if (itr2 != m_spells.end()) itr2->second->Active = false; + + if (!isBeingLoaded() && IsUnlearnNeededForSpell(spellId)) + SendUnlearnSpells(); + return false; } } @@ -3087,6 +3155,9 @@ bool Player::addSpell(uint32 spellId, uint8 addSpecMask, bool updateActive, bool } } + if (!isBeingLoaded() && IsUnlearnNeededForSpell(spellId)) + SendUnlearnSpells(); + return true; } @@ -11628,10 +11699,7 @@ void Player::SendInitialPacketsBeforeAddToMap() SendDirectMessage(&data); SendInitialSpells(); - - data.Initialize(SMSG_SEND_UNLEARN_SPELLS, 4); - data << uint32(0); // count, for (count) uint32; - SendDirectMessage(&data); + SendUnlearnSpells(); SendInitialActionButtons(); m_reputationMgr->SendInitialReputations(); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index bfd7491bb..140fee652 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1664,6 +1664,9 @@ public: void UpdateNextMailTimeAndUnreads(); void AddNewMailDeliverTime(time_t deliver_time); + void SendUnlearnSpells(); + static bool IsUnlearnNeededForSpell(uint32 spellId); + void RemoveMail(uint32 id); void AddMail(Mail* mail) { m_mail.push_front(mail); }// for call from WorldSession::SendMailTo