From cbd8596184f2bf32edc5a83643ec3ef7e13982df Mon Sep 17 00:00:00 2001 From: Kitzunu <24550914+Kitzunu@users.noreply.github.com> Date: Sat, 19 Jul 2025 11:35:26 +0200 Subject: [PATCH] fix(Core/Player): Unlearn invalid spells for race/class on login (#22370) Co-authored-by: Andrew <47818697+Nyeriah@users.noreply.github.com> --- src/server/game/Entities/Player/Player.cpp | 34 +++++++++++++------ src/server/game/Entities/Player/Player.h | 1 + .../game/Entities/Player/PlayerStorage.cpp | 13 +++++-- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index af8a32f24..056f8ced1 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -3101,6 +3101,28 @@ bool Player::addSpell(uint32 spellId, uint8 addSpecMask, bool updateActive, bool return true; } +bool Player::CheckSkillLearnedBySpell(uint32 spellId) +{ + SkillLineAbilityMapBounds skill_bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId); + for (SkillLineAbilityMap::const_iterator sla = skill_bounds.first; sla != skill_bounds.second; ++sla) + { + SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(sla->second->SkillLine); + if (!pSkill) + continue; + + SkillRaceClassInfoEntry const* rcEntry = GetSkillRaceClassInfo(pSkill->id, getRace(), getClass()); + if (!rcEntry) + { + LOG_ERROR("entities.player", "Player {} (GUID: {}), has spell ({}) that teach skill ({}) which is invalid for the race/class combination (Race: {}, Class: {}). Will be deleted.", + GetName(), GetGUID().GetCounter(), spellId, pSkill->id, getRace(), getClass()); + + return false; + } + } + + return true; +} + bool Player::_addSpell(uint32 spellId, uint8 addSpecMask, bool temporary, bool learnFromSkill /*= false*/) { // pussywizard: this can be called to OVERWRITE currently existing spell params! usually to set active = false for lower ranks of a spell @@ -3242,27 +3264,17 @@ bool Player::_addSpell(uint32 spellId, uint8 addSpecMask, bool temporary, bool l { SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(_spell_idx->second->SkillLine); if (!pSkill) - { continue; - } - /// @todo confirm if rogues start wth lockpicking skill at level 1 but only recieve the spell to use it at level 16 + /// @todo confirm if rogues start wth lockpicking skill at level 1 but only recieve the spell to use it at level 16 // Added for runeforging, it is confirmed via sniff that this happens when death knights learn the spell, not on character creation. if ((_spell_idx->second->AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN && !HasSkill(pSkill->id)) || ((pSkill->id == SKILL_LOCKPICKING || pSkill->id == SKILL_RUNEFORGING) && _spell_idx->second->TrivialSkillLineRankHigh == 0)) - { LearnDefaultSkill(pSkill->id, 0); - } if (pSkill->id == SKILL_MOUNTS && !Has310Flyer(false)) - { for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED && spellInfo->Effects[i].CalcValue() == 310) - { SetHas310Flyer(true); - } - } - } } } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 24af62b65..92ad5ba4b 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1720,6 +1720,7 @@ public: void learnQuestRewardedSpells(); void learnQuestRewardedSpells(Quest const* quest); void learnSpellHighRank(uint32 spellid); + bool CheckSkillLearnedBySpell(uint32 spellId); void SetReputation(uint32 factionentry, float value); [[nodiscard]] uint32 GetReputation(uint32 factionentry) const; std::string const& GetGuildName(); diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index 8de9e8a0c..43f11d14e 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -6472,9 +6472,16 @@ void Player::_LoadSpells(PreparedQueryResult result) if (result) { do - // xinef: checked - addSpell((*result)[0].Get(), (*result)[1].Get(), true); - while (result->NextRow()); + { + Field* fields = result->Fetch(); + uint32 spellId = fields[0].Get(); + uint8 specMask = fields[1].Get(); + + if (CheckSkillLearnedBySpell(spellId)) + addSpell(spellId, specMask, true); + else + removeSpell(spellId, SPEC_MASK_ALL, false); + } while (result->NextRow()); } }