diff options
author | Ovahlord <dreadkiller@gmx.de> | 2023-11-20 20:06:54 +0100 |
---|---|---|
committer | Ovahlord <dreadkiller@gmx.de> | 2023-11-20 20:30:12 +0100 |
commit | 1195d7c190582f0b95f0afef7660cd108fcbd8c3 (patch) | |
tree | 43245aef52986471017058b18b59fdb56b57d62d /src | |
parent | e08019ee56415e19f3343cba88a319402348f08b (diff) |
Core/Players: re-implement talents
*todo: class restriction and talent tier checks and remove the now useless learn all talents command
Diffstat (limited to 'src')
-rw-r--r-- | src/server/database/Database/Implementation/CharacterDatabase.cpp | 4 | ||||
-rw-r--r-- | src/server/game/DataStores/DBCEnums.h | 1 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 352 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 34 | ||||
-rw-r--r-- | src/server/game/Handlers/InspectHandler.cpp | 2 | ||||
-rw-r--r-- | src/server/game/Handlers/SkillHandler.cpp | 22 | ||||
-rw-r--r-- | src/server/game/Handlers/TraitHandler.cpp | 114 | ||||
-rw-r--r-- | src/server/game/Server/Packets/TalentPackets.cpp | 6 | ||||
-rw-r--r-- | src/server/game/Server/Packets/TalentPackets.h | 13 | ||||
-rw-r--r-- | src/server/game/Server/Protocol/Opcodes.cpp | 2 | ||||
-rw-r--r-- | src/server/game/Server/WorldSession.h | 2 | ||||
-rw-r--r-- | src/server/scripts/Commands/cs_learn.cpp | 2 |
12 files changed, 234 insertions, 320 deletions
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index a47068200b9..7cd5bb2a8a7 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -150,7 +150,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() "appearance17, appearance18, mainHandEnchant, offHandEnchant FROM character_transmog_outfits WHERE guid = ? ORDER BY setindex", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_BGDATA, "SELECT instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell, queueId FROM character_battleground_data WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_GLYPHS, "SELECT talentGroup, glyphId FROM character_glyphs WHERE guid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHARACTER_TALENTS, "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_TALENTS, "SELECT talentId, talentRank, talentGroup FROM character_talent WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_SKILLS, "SELECT skill, value, max, professionSlot FROM character_skills WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_RANDOMBG, "SELECT guid FROM character_battleground_random WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_BANNED, "SELECT guid FROM character_banned WHERE guid = ? AND active = 1", CONNECTION_ASYNC); @@ -658,7 +658,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_PETITION_BY_OWNER, "DELETE FROM petition WHERE ownerguid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_OWNER, "DELETE FROM petition_sign WHERE ownerguid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_CHAR_GLYPHS, "INSERT INTO character_glyphs VALUES(?, ?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_CHAR_TALENT, "INSERT INTO character_talent (guid, talentId, talentGroup) VALUES (?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_TALENT, "INSERT INTO character_talent (guid, talentId, talentRank, talentGroup) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_LIST_SLOT, "UPDATE characters SET slot = ? WHERE guid = ? AND account = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_CHAR_FISHINGSTEPS, "INSERT INTO character_fishingsteps (guid, fishingSteps) VALUES (?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?", CONNECTION_ASYNC); diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h index 0e6d293fc68..0911216fa15 100644 --- a/src/server/game/DataStores/DBCEnums.h +++ b/src/server/game/DataStores/DBCEnums.h @@ -2129,6 +2129,7 @@ DEFINE_ENUM_FLAG(SummonPropertiesFlags); #define MAX_TALENT_TIERS 11 #define MAX_TALENT_COLUMNS 4 +#define MAX_TALENT_RANK 9 enum class TaxiNodeFlags : int32 { diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index a06f5754ca3..88cb94e4aba 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -2414,8 +2414,9 @@ bool Player::IsMaxLevel() const void Player::InitTalentForLevel() { uint8 level = GetLevel(); - int32 talentPoints = CalculateTalentsPoints(); - if (level < MIN_SPECIALIZATION_LEVEL) + uint32 talentPoints = CalculateTalentsPoints(); + uint32 spentTalentPoints = GetSpentTalentPointsCount(); + if (spentTalentPoints && level < MIN_SPECIALIZATION_LEVEL) { // Remove all talent points ResetTalents(true); @@ -2424,7 +2425,8 @@ void Player::InitTalentForLevel() if (GetSpentTalentPointsCount() > talentPoints) ResetTalents(true); - SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::MaxTalentTiers), talentPoints); + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::MaxTalentTiers), static_cast<int32>(talentPoints)); + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CharacterPoints), static_cast<int32>(talentPoints - spentTalentPoints)); if (!GetSession()->PlayerLoading()) SendTalentsInfoData(); // update at client @@ -2709,33 +2711,56 @@ void DeleteSpellFromAllPlayers(uint32 spellId) CharacterDatabase.Execute(stmt); } -bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning) +bool Player::AddTalent(TalentEntry const* talent, uint16 rank, uint8 talentGroupId, bool learning) { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellRank[rank], DIFFICULTY_NONE); if (!spellInfo) { - TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist.", talent->SpellID); + // do character spell book cleanup (all characters) + if (!IsInWorld() && !learning) // spell load case + { + TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist. Deleting for all characters in `character_spell` and `character_talent`.", talent->SpellRank[rank]); + DeleteSpellFromAllPlayers(talent->SpellRank[rank]); + } + else + TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist", talent->SpellRank[rank]); + return false; } if (!SpellMgr::IsSpellValid(spellInfo, this, false)) { - TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid", talent->SpellID); + // do character spell book cleanup (all characters) + if (!IsInWorld() && !learning) // spell load case + { + TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid. Deleting for all characters in `character_spell` and `character_talent`.", talent->SpellRank[rank]); + DeleteSpellFromAllPlayers(talent->SpellRank[rank]); + } + else + TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid", talent->SpellRank[rank]); + return false; } - PlayerTalentMap::iterator itr = GetTalentMap(spec)->find(talent->ID); - if (itr != GetTalentMap(spec)->end()) - itr->second = PLAYERSPELL_UNCHANGED; - else - (*GetTalentMap(spec))[talent->ID] = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED; - - if (spec == GetActiveTalentGroup()) + PlayerTalentMap& talentMap = GetPlayerTalentMap(talentGroupId); + auto itr = talentMap.find(talent->ID); + if (itr != talentMap.end()) { - LearnSpell(talent->SpellID, true); - if (talent->OverridesSpellID) - AddOverrideSpell(talent->OverridesSpellID, talent->SpellID); + + + itr->second.State = PLAYERSPELL_UNCHANGED; + itr->second.Rank = static_cast<uint8>(rank); } + else + talentMap[talent->ID] = { PLAYERSPELL_UNCHANGED, static_cast<uint8>(rank) }; + + // Inactive talent groups will only be initialized + if (GetActiveTalentGroup() != talentGroupId) + return true; + + LearnSpell(spellInfo->Id, true); + if (talent->OverridesSpellID) + AddOverrideSpell(talent->OverridesSpellID, talent->SpellID); if (learning) RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChangeTalent); @@ -2745,11 +2770,20 @@ bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning) void Player::RemoveTalent(TalentEntry const* talent) { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE); + PlayerTalentMap& talentMap = GetPlayerTalentMap(GetActiveTalentGroup()); + auto itr = talentMap.find(talent->ID); + if (itr == talentMap.end()) + return; + + uint32 spellId = talent->SpellRank[itr->second.Rank]; + if (!spellId) + return; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE); if (!spellInfo) return; - RemoveSpell(talent->SpellID, true); + RemoveSpell(spellId, true); // search for spells that the talent teaches and unlearn them for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects()) @@ -2759,10 +2793,9 @@ void Player::RemoveTalent(TalentEntry const* talent) if (talent->OverridesSpellID) RemoveOverrideSpell(talent->OverridesSpellID, talent->SpellID); - // if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted - PlayerTalentMap::iterator plrTalent = GetTalentMap(GetActiveTalentGroup())->find(talent->ID); - if (plrTalent != GetTalentMap(GetActiveTalentGroup())->end()) - plrTalent->second = PLAYERSPELL_REMOVED; + // Mark the talent as deleted to it will be deleted upon the next save cycle + itr->second.State = PLAYERSPELL_REMOVED; + itr->second.Rank = 0; } void Player::AddStoredAuraTeleportLocation(uint32 spellId) @@ -3573,23 +3606,25 @@ bool Player::ResetTalents(bool noCost) RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true); - for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) + PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup()); + for (auto const& pair : talentMap) { - TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); - if (!talentInfo) + TalentEntry const* talentEntry = sTalentStore.LookupEntry(pair.first); + if (!talentEntry) continue; + /* + TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TabID); + if (!talentTabInfo) + continue; // unlearn only talents for character class // some spell learned by one class as normal spells or know at creation but another class learn it as talent, // to prevent unexpected lost normal learned spell skip another class talents - if (talentInfo->ClassID != GetClass()) - continue; - - // skip non-existent talent ranks - if (talentInfo->SpellID == 0) + if ((GetClassMask() & talentTabInfo->ClassMask) == 0) continue; + */ - RemoveTalent(talentInfo); + RemoveTalent(talentEntry); } CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); @@ -3615,6 +3650,8 @@ bool Player::ResetTalents(bool noCost) } */ + InitTalentForLevel(); + return true; } @@ -3812,12 +3849,6 @@ bool Player::HasSpell(uint32 spell) const !itr->second.disabled); } -bool Player::HasTalent(uint32 talentId, uint8 group) const -{ - PlayerTalentMap::const_iterator itr = GetTalentMap(group)->find(talentId); - return (itr != GetTalentMap(group)->end() && itr->second != PLAYERSPELL_REMOVED); -} - bool Player::HasActiveSpell(uint32 spell) const { PlayerSpellMap::const_iterator itr = m_spells.find(spell); @@ -26086,94 +26117,98 @@ bool Player::ModifierTreeSatisfied(uint32 modifierTreeId) const return m_achievementMgr->ModifierTreeSatisfied(modifierTreeId); } -TalentLearnResult Player::LearnTalent(uint32 talentId, int32* spellOnCooldown) -{ - if (IsInCombat()) - return TALENT_FAILED_AFFECTING_COMBAT; +static constexpr uint8 NEEDED_TALENT_POINT_PER_TIER = 5; - if (isDead()) - return TALENT_FAILED_CANT_DO_THAT_RIGHT_NOW; +bool Player::LearnTalent(uint32 talentId, uint16 requestedRank) +{ + // No talent points left to spend, skip learn request + if (!m_activePlayerData->CharacterPoints) + return false; - if (GetPrimarySpecialization() == ChrSpecialization::None) - return TALENT_FAILED_NO_PRIMARY_TREE_SELECTED; + if (requestedRank >= MAX_TALENT_RANK) + return false; TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); if (!talentInfo) - return TALENT_FAILED_UNKNOWN; + return false; - if (talentInfo->SpecID && ChrSpecialization(talentInfo->SpecID) != GetPrimarySpecialization()) - return TALENT_FAILED_UNKNOWN; + /* + TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TabID); + if (!talentTabInfo) + return; // prevent learn talent for different class (cheating) - if (talentInfo->ClassID != GetClass()) - return TALENT_FAILED_UNKNOWN; - - // check if we have enough talent points - if (talentInfo->TierID >= m_activePlayerData->MaxTalentTiers) - return TALENT_FAILED_UNKNOWN; + if (!(GetClassMask() & talentTabInfo->ClassMask)) + return; + */ - // TODO: prevent changing talents that are on cooldown + // Check for existing talents - // Check if there is a different talent for us to learn in selected slot - // Example situation: - // Warrior talent row 2 slot 0 - // Talent.dbc has an entry for each specialization - // but only 2 out of 3 have SpecID != 0 - // We need to make sure that if player is in one of these defined specs he will not learn the other choice - TalentEntry const* bestSlotMatch = nullptr; - for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), talentInfo->TierID, talentInfo->ColumnIndex)) + uint32 neededTalentPoints = 0; + PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup()); + auto itr = talentMap.find(talentId); + if (itr != talentMap.end()) { - if (!talent->SpecID) - bestSlotMatch = talent; - else if (ChrSpecialization(talent->SpecID) == GetPrimarySpecialization()) - { - bestSlotMatch = talent; - break; - } + // We already know this or a higher rank of this talent + if (itr->second.Rank >= requestedRank) + return false; + + neededTalentPoints = (itr->second.Rank - requestedRank) + 1; } + else + neededTalentPoints = requestedRank + 1; - if (talentInfo != bestSlotMatch) - return TALENT_FAILED_UNKNOWN; + // Not enough talent points to learn the talent at this rank + if (neededTalentPoints > static_cast<uint32>(m_activePlayerData->CharacterPoints)) + return false; - // Check if player doesn't have any talent in current tier - for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c) + // Check talent dependencies + for (size_t i = 0; i < talentInfo->PrereqRank.size(); ++i) { - for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), talentInfo->TierID, c)) - { - if (!HasTalent(talent->ID, GetActiveTalentGroup())) - continue; + if (!talentInfo->PrereqTalent[i]) + continue; - if (!HasPlayerFlag(PLAYER_FLAGS_RESTING) && !HasUnitFlag2(UNIT_FLAG2_ALLOW_CHANGING_TALENTS)) - return TALENT_FAILED_REST_AREA; + itr = talentMap.find(talentInfo->PrereqTalent[i]); + if (itr == talentMap.end() || itr->second.Rank < talentInfo->PrereqRank[i]) + return false; + } - if (GetSpellHistory()->HasCooldown(talent->SpellID)) - { - *spellOnCooldown = talent->SpellID; - return TALENT_FAILED_CANT_REMOVE_TALENT; - } + // Find out how many points we have in this field + /* + uint32 spentPoints = 0; + uint32 tTab = talentInfo->TabID; + if (talentInfo->TierID > 0) + for (TalentEntry const* tmpTalent : sTalentStore) // the way talents are tracked + if (tmpTalent->TabID == tTab) + for (uint8 rank = 0; rank < MAX_TALENT_RANK; rank++) + if (tmpTalent->SpellRank[rank] != 0) + if (HasSpell(tmpTalent->SpellRank[rank])) + spentPoints += (rank + 1); - RemoveTalent(talent); - } - } + // not have required min points spent in talent tree + if (spentPoints < (talentInfo->TierID * NEEDED_TALENT_POINT_PER_TIER)) + return; + >*/ // spell not set in talent.dbc - uint32 spellid = talentInfo->SpellID; - if (!spellid) + uint32 spellId = talentInfo->SpellRank[requestedRank]; + if (spellId == 0) { TC_LOG_ERROR("entities.player", "Player::LearnTalent: Talent.dbc has no spellInfo for talent: {} (spell id = 0)", talentId); - return TALENT_FAILED_UNKNOWN; + return false; } // already known - if (HasTalent(talentId, GetActiveTalentGroup()) || HasSpell(spellid)) - return TALENT_FAILED_UNKNOWN; + if (HasSpell(spellId)) + return false; - if (!AddTalent(talentInfo, GetActiveTalentGroup(), true)) - return TALENT_FAILED_UNKNOWN; + AddTalent(talentInfo, requestedRank, GetActiveTalentGroup(), true); - TC_LOG_DEBUG("misc", "Player::LearnTalent: TalentID: {} Spell: {} Group: {}\n", talentId, spellid, GetActiveTalentGroup()); + TC_LOG_DEBUG("misc", "Player::LearnTalent: TalentID: {} Spell: {} Group: {}\n", talentId, spellId, uint32(GetActiveTalentGroup())); - return TALENT_LEARN_OK; + // update free talent points + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CharacterPoints), static_cast<int32>(CalculateTalentsPoints() - GetSpentTalentPointsCount())); + return true; } void Player::EnablePvpRules(bool dueToCombat /*= false*/) @@ -26367,39 +26402,37 @@ void Player::SendTalentsInfoData() uint8 activeGroup = GetActiveTalentGroup(); packet.ActiveGroup = activeGroup; - packet.UnspentTalentPoints = CalculateTalentsPoints() - GetSpentTalentPointsCount(); + packet.UnspentTalentPoints = m_activePlayerData->CharacterPoints; for (uint8 i = 0; i < (1 + GetBonusTalentGroupCount()); ++i) { WorldPackets::Talent::TalentGroupInfo& groupInfo = packet.TalentGroupInfos.emplace_back(); groupInfo.SpecID = MAX_SPECIALIZATIONS; - if (PlayerTalentMap const* talentMap = GetTalentMap(i)) + PlayerTalentMap const& talentMap = GetPlayerTalentMap(i); + groupInfo.Talents.reserve(talentMap.size()); + + for (auto const& pair : talentMap) { - groupInfo.Talents.reserve(talentMap->size()); + if (pair.second.State == PLAYERSPELL_REMOVED) + continue; - for (auto const& pair : *talentMap) + TalentEntry const* talentInfo = sTalentStore.LookupEntry(pair.first); + if (!talentInfo) { - if (pair.second == PLAYERSPELL_REMOVED) - continue; - - TalentEntry const* talentInfo = sTalentStore.LookupEntry(pair.first); - if (!talentInfo) - { - TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent id: {}", - GetName(), GetGUID().ToString(), pair.first); - continue; - } - - SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE); - if (!spellEntry) - { - TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent spell: {}", - GetName(), GetGUID().ToString(), talentInfo->SpellID); - continue; - } + TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent id: {}", + GetName(), GetGUID().ToString(), pair.first); + continue; + } - groupInfo.Talents.push_back(uint16(pair.first)); + SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellRank[pair.second.Rank], DIFFICULTY_NONE); + if (!spellEntry) + { + TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent spell: {}", + GetName(), GetGUID().ToString(), talentInfo->SpellID); + continue; } + + groupInfo.Talents.push_back({ pair.first, pair.second.Rank }); } std::vector<uint32> glyphs = GetGlyphs(activeGroup); @@ -26676,12 +26709,12 @@ void Player::_SaveGlyphs(CharacterDatabaseTransaction trans) const void Player::_LoadTalents(PreparedQueryResult result) { - // "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?" + // "SELECT talentId, talentRank, talentGroup FROM character_talent WHERE guid = ?" if (result) { do if (TalentEntry const* talent = sTalentStore.LookupEntry((*result)[0].GetUInt32())) - AddTalent(talent, (*result)[1].GetUInt8(), false); + AddTalent(talent, (*result)[1].GetUInt8(), (*result)[2].GetUInt8(), false); while (result->NextRow()); } } @@ -26834,19 +26867,20 @@ void Player::_SaveTalents(CharacterDatabaseTransaction trans) for (uint8 group = 0; group < MAX_SPECIALIZATIONS; ++group) { - PlayerTalentMap* talents = GetTalentMap(group); - for (auto itr = talents->begin(); itr != talents->end();) + PlayerTalentMap& talents = _specializationInfo.Talents[group]; + for (auto itr = talents.begin(); itr != talents.end();) { - if (itr->second == PLAYERSPELL_REMOVED) + if (itr->second.State == PLAYERSPELL_REMOVED) { - itr = talents->erase(itr); + itr = talents.erase(itr); continue; } stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TALENT); stmt->setUInt64(0, GetGUID().GetCounter()); stmt->setUInt32(1, itr->first); - stmt->setUInt8(2, group); + stmt->setUInt8(2, itr->second.Rank); + stmt->setUInt8(3, group); trans->Append(stmt); ++itr; } @@ -26989,37 +27023,31 @@ void Player::ActivateTalentGroup(uint8 talentGroup) // Let client clear his current Actions SendActionButtons(2); - // m_actionButtons.clear() is called in the next _LoadActionButtons - for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) - { - TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); - if (!talentInfo) - continue; - // unlearn only talents for character class - // some spell learned by one class as normal spells or know at creation but another class learn it as talent, - // to prevent unexpected lost normal learned spell skip another class talents - if (talentInfo->ClassID != GetClass()) - continue; - - if (talentInfo->SpellID == 0) + PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup()); + for (auto const& pair : talentMap) + { + TalentEntry const* talentEntry = sTalentStore.LookupEntry(pair.first); + if (!talentEntry) continue; - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentEntry->SpellRank[pair.second.Rank], DIFFICULTY_NONE); if (!spellInfo) continue; - RemoveSpell(talentInfo->SpellID, true); + RemoveSpell(spellInfo->Id, true); // search for spells that the talent teaches and unlearn them for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects()) if (spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL) && spellEffectInfo.TriggerSpell > 0) RemoveSpell(spellEffectInfo.TriggerSpell, true); - if (talentInfo->OverridesSpellID) - RemoveOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID); + if (talentEntry->OverridesSpellID) + RemoveOverrideSpell(talentEntry->OverridesSpellID, talentEntry->SpellID); } + // m_actionButtons.clear() is called in the next _LoadActionButtons + ApplyTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID, false); for (uint32 glyphId : GetGlyphs(GetActiveTalentGroup())) @@ -27028,26 +27056,21 @@ void Player::ActivateTalentGroup(uint8 talentGroup) SetActiveTalentGroup(talentGroup); SetPrimarySpecialization(0); - for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) + // if the talent can be found in the newly activated PlayerTalentMap + PlayerTalentMap const& activatedTalentMap = GetPlayerTalentMap(GetActiveTalentGroup()); + for (auto const& pair : activatedTalentMap) { - TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); - if (!talentInfo) - continue; - - // learn only talents for character class - if (talentInfo->ClassID != GetClass()) + TalentEntry const* talentEntry = sTalentStore.LookupEntry(pair.first); + if (!talentEntry) continue; - if (!talentInfo->SpellID) + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentEntry->SpellRank[pair.second.Rank], DIFFICULTY_NONE); + if (!spellInfo) continue; - // if the talent can be found in the newly activated PlayerTalentMap - if (HasTalent(talentInfo->ID, GetActiveTalentGroup())) - { - LearnSpell(talentInfo->SpellID, true); // add the talent to the PlayerSpellMap - if (talentInfo->OverridesSpellID) - AddOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID); - } + LearnSpell(spellInfo->Id, true); // add the talent to the PlayerSpellMap + if (talentEntry->OverridesSpellID) + AddOverrideSpell(talentEntry->OverridesSpellID, talentEntry->SpellID); } InitTalentForLevel(); @@ -28732,8 +28755,13 @@ void Player::SetBonusTalentGroupCount(uint8 amount) uint32 Player::GetSpentTalentPointsCount() const { - PlayerTalentMap const& talentMap = _specializationInfo.Talents[GetActiveTalentGroup()]; - return std::count_if(talentMap.begin(), talentMap.end(), [](auto const& pair) { return (pair.second != PLAYERSPELL_REMOVED); }); + PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup()); + uint32 count = 0; + for (auto const& pair : talentMap) + if (pair.second.State != PLAYERSPELL_REMOVED) + count += pair.second.Rank + 1; + + return count; } ChrSpecializationEntry const* Player::GetPrimarySpecializationEntry() const diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 55e67283755..b3c613fa794 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -264,7 +264,13 @@ struct PlayerCurrency CurrencyDbFlags Flags; }; -typedef std::unordered_map<uint32, PlayerSpellState> PlayerTalentMap; +struct PlayerTalentState +{ + PlayerSpellState State = PLAYERSPELL_UNCHANGED; + uint8 Rank = 0; +}; + +typedef std::unordered_map<uint32, PlayerTalentState> PlayerTalentMap; typedef std::unordered_map<uint32, PlayerSpell> PlayerSpellMap; typedef std::unordered_set<SpellModifier*> SpellModContainer; typedef std::unordered_map<uint32, PlayerCurrency> PlayerCurrenciesMap; @@ -1047,24 +1053,8 @@ struct GroupUpdateCounter int32 UpdateSequenceNumber; }; -enum TalentLearnResult : int32 -{ - TALENT_LEARN_OK = 0, - TALENT_FAILED_UNKNOWN = 1, - TALENT_FAILED_NOT_ENOUGH_TALENTS_IN_PRIMARY_TREE = 2, - TALENT_FAILED_NO_PRIMARY_TREE_SELECTED = 3, - TALENT_FAILED_CANT_DO_THAT_RIGHT_NOW = 4, - TALENT_FAILED_AFFECTING_COMBAT = 5, - TALENT_FAILED_CANT_REMOVE_TALENT = 6, - TALENT_FAILED_CANT_DO_THAT_CHALLENGE_MODE_ACTIVE = 7, - TALENT_FAILED_REST_AREA = 8, - TALENT_FAILED_UNSPENT_TALENT_POINTS = 9, - TALENT_FAILED_IN_PVP_MATCH = 10 -}; - struct TC_GAME_API SpecializationInfo { - SpecializationInfo() : ResetTalentsCost(0), ResetTalentsTime(0), ActiveGroup(0), BonusGroups(0) { } PlayerTalentMap Talents[MAX_SPECIALIZATIONS]; @@ -1818,6 +1808,9 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void SetTalentResetCost(uint32 cost) { _specializationInfo.ResetTalentsCost = cost; } time_t GetTalentResetTime() const { return _specializationInfo.ResetTalentsTime; } void SetTalentResetTime(time_t time_) { _specializationInfo.ResetTalentsTime = time_; } + PlayerTalentMap const& GetPlayerTalentMap(uint8 talentGroupId) const { return _specializationInfo.Talents[talentGroupId]; } + PlayerTalentMap& GetPlayerTalentMap(uint8 talentGroupId) { return _specializationInfo.Talents[talentGroupId]; } + ChrSpecialization GetPrimarySpecialization() const { return ChrSpecialization(*m_playerData->CurrentSpecID); } void SetPrimarySpecialization(uint32 spec) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::CurrentSpecID), spec); } uint8 GetActiveTalentGroup() const { return _specializationInfo.ActiveGroup; } @@ -1833,9 +1826,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void InitTalentForLevel(); void SendTalentsInfoData(); uint32 CalculateTalentsPoints() const; - TalentLearnResult LearnTalent(uint32 talentId, int32* spellOnCooldown); - bool AddTalent(TalentEntry const* talent, uint8 spec, bool learning); - bool HasTalent(uint32 spell_id, uint8 spec) const; + bool LearnTalent(uint32 talentId, uint16 requestedRank); + bool AddTalent(TalentEntry const* talent, uint16 rank, uint8 talentGroupId, bool learning); void RemoveTalent(TalentEntry const* talent); void EnablePvpRules(bool dueToCombat = false); @@ -1847,8 +1839,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> // Dual Spec void ActivateTalentGroup(uint8 talentGroup); - PlayerTalentMap const* GetTalentMap(uint8 spec) const { return &_specializationInfo.Talents[spec]; } - PlayerTalentMap* GetTalentMap(uint8 spec) { return &_specializationInfo.Talents[spec]; } std::vector<uint32> const& GetGlyphs(uint8 spec) const { return _specializationInfo.Glyphs[spec]; } std::vector<uint32>& GetGlyphs(uint8 spec) { return _specializationInfo.Glyphs[spec]; } ActionButtonList const& GetActionButtons() const { return m_actionButtons; } diff --git a/src/server/game/Handlers/InspectHandler.cpp b/src/server/game/Handlers/InspectHandler.cpp index 865dcc25152..87023704fa5 100644 --- a/src/server/game/Handlers/InspectHandler.cpp +++ b/src/server/game/Handlers/InspectHandler.cpp @@ -47,10 +47,12 @@ void WorldSession::HandleInspectOpcode(WorldPackets::Inspect::Inspect& inspect) if (GetPlayer()->CanBeGameMaster() || sWorld->getIntConfig(CONFIG_TALENTS_INSPECTING) + (GetPlayer()->GetEffectiveTeam() == player->GetEffectiveTeam()) > 1) { + /* PlayerTalentMap const* talents = player->GetTalentMap(player->GetActiveTalentGroup()); for (PlayerTalentMap::value_type const& v : *talents) if (v.second != PLAYERSPELL_REMOVED) inspectResult.Talents.push_back(v.first); + */ inspectResult.TalentTraits.Level = player->GetLevel(); inspectResult.TalentTraits.ChrSpecializationID = AsUnderlyingType(player->GetPrimarySpecialization()); diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp index f7447b6ba7a..4316db154d8 100644 --- a/src/server/game/Handlers/SkillHandler.cpp +++ b/src/server/game/Handlers/SkillHandler.cpp @@ -28,25 +28,11 @@ void WorldSession::HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents& packet) { - WorldPackets::Talent::LearnTalentFailed learnTalentFailed; - bool anythingLearned = false; - for (uint32 talentId : packet.Talents) - { - if (TalentLearnResult result = _player->LearnTalent(talentId, &learnTalentFailed.SpellID)) - { - if (!learnTalentFailed.Reason) - learnTalentFailed.Reason = result; - - learnTalentFailed.Talents.push_back(talentId); - } - else - anythingLearned = true; - } - - if (learnTalentFailed.Reason) - SendPacket(learnTalentFailed.Write()); +} - if (anythingLearned) +void WorldSession::HandleLearnTalentOpcode(WorldPackets::Talent::LearnTalent& packet) +{ + if (_player->LearnTalent(packet.TalentID, packet.RequestedRank)) _player->SendTalentsInfoData(); } diff --git a/src/server/game/Handlers/TraitHandler.cpp b/src/server/game/Handlers/TraitHandler.cpp index 1392ecf8ba9..05eb3ce4d21 100644 --- a/src/server/game/Handlers/TraitHandler.cpp +++ b/src/server/game/Handlers/TraitHandler.cpp @@ -25,120 +25,6 @@ void WorldSession::HandleTraitsCommitConfig(WorldPackets::Traits::TraitsCommitConfig const& traitsCommitConfig) { - int32 configId = traitsCommitConfig.Config.ID; - UF::TraitConfig const* existingConfig = _player->GetTraitConfig(configId); - if (!existingConfig) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write()); - return; - } - - if (_player->IsInCombat()) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_AFFECTING_COMBAT).Write()); - return; - } - - if (_player->GetBattleground() && _player->GetBattleground()->GetStatus() == STATUS_IN_PROGRESS) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_IN_PVP_MATCH).Write()); - return; - } - - auto findEntry = [](WorldPackets::Traits::TraitConfig& config, int32 traitNodeId, int32 traitNodeEntryId) -> WorldPackets::Traits::TraitEntry* - { - auto entryItr = std::find_if(config.Entries.begin(), config.Entries.end(), [=](WorldPackets::Traits::TraitEntry const& traitEntry) - { - return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId; - }); - return entryItr != config.Entries.end() ? &*entryItr : nullptr; - }; - - bool hasRemovedEntries = false; - WorldPackets::Traits::TraitConfig newConfigState(*existingConfig); - for (WorldPackets::Traits::TraitEntry const& newEntry : traitsCommitConfig.Config.Entries) - { - WorldPackets::Traits::TraitEntry* traitEntry = findEntry(newConfigState, newEntry.TraitNodeID, newEntry.TraitNodeEntryID); - if (traitEntry && traitEntry->Rank > newEntry.Rank) - { - TraitNodeEntry const* traitNode = sTraitNodeStore.LookupEntry(newEntry.TraitNodeID); - if (!traitNode) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write()); - return; - } - - TraitTreeEntry const* traitTree = sTraitTreeStore.LookupEntry(traitNode->TraitTreeID); - if (!traitTree) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write()); - return; - } - - if (traitTree->GetFlags().HasFlag(TraitTreeFlag::CannotRefund)) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_CANT_REMOVE_TALENT).Write()); - return; - } - - TraitNodeEntryEntry const* traitNodeEntry = sTraitNodeEntryStore.LookupEntry(newEntry.TraitNodeEntryID); - if (!traitNodeEntry) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write()); - return; - } - - TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(traitNodeEntry->TraitDefinitionID); - if (!traitDefinition) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write()); - return; - } - - if (traitDefinition->SpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->SpellID)) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->SpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write()); - return; - } - - if (traitDefinition->VisibleSpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->VisibleSpellID)) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->VisibleSpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write()); - return; - } - - hasRemovedEntries = true; - } - - if (traitEntry) - { - if (newEntry.Rank) - traitEntry->Rank = newEntry.Rank; - else - newConfigState.Entries.erase(std::remove_if(newConfigState.Entries.begin(), newConfigState.Entries.end(), [&newEntry](WorldPackets::Traits::TraitEntry const& traitEntry) - { - return traitEntry.TraitNodeID == newEntry.TraitNodeID && traitEntry.TraitNodeEntryID == newEntry.TraitNodeEntryID; - }), newConfigState.Entries.end()); - } - else - newConfigState.Entries.emplace_back() = newEntry; - } - - TraitMgr::LearnResult validationResult = TraitMgr::ValidateConfig(newConfigState, _player, true); - if (validationResult != TraitMgr::LearnResult::Ok) - { - SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, AsUnderlyingType(validationResult)).Write()); - return; - } - - bool needsCastTime = newConfigState.Type == TraitConfigType::Combat && hasRemovedEntries; - - if (traitsCommitConfig.SavedLocalIdentifier) - newConfigState.LocalIdentifier = traitsCommitConfig.SavedLocalIdentifier; - else if (UF::TraitConfig const* savedConfig = _player->GetTraitConfig(traitsCommitConfig.SavedLocalIdentifier)) - newConfigState.LocalIdentifier = savedConfig->LocalIdentifier; - - _player->UpdateTraitConfig(std::move(newConfigState), traitsCommitConfig.SavedConfigID, needsCastTime); } void WorldSession::HandleClassTalentsRequestNewConfig(WorldPackets::Traits::ClassTalentsRequestNewConfig& classTalentsRequestNewConfig) diff --git a/src/server/game/Server/Packets/TalentPackets.cpp b/src/server/game/Server/Packets/TalentPackets.cpp index d715348d90f..4237f45f7c7 100644 --- a/src/server/game/Server/Packets/TalentPackets.cpp +++ b/src/server/game/Server/Packets/TalentPackets.cpp @@ -78,6 +78,12 @@ void WorldPackets::Talent::LearnTalents::Read() _worldPacket >> Talents[i]; } +void WorldPackets::Talent::LearnTalent::Read() +{ + _worldPacket >> TalentID; + _worldPacket >> RequestedRank; +} + WorldPacket const* WorldPackets::Talent::RespecWipeConfirm::Write() { _worldPacket << int8(RespecType); diff --git a/src/server/game/Server/Packets/TalentPackets.h b/src/server/game/Server/Packets/TalentPackets.h index 494866575ae..9bcd2418ca9 100644 --- a/src/server/game/Server/Packets/TalentPackets.h +++ b/src/server/game/Server/Packets/TalentPackets.h @@ -42,7 +42,7 @@ namespace WorldPackets struct TalentGroupInfo { int32 SpecID = 0; - std::vector<uint16> Talents; + std::vector<TalentInfo> Talents; std::vector<uint16> GlyphIDs; }; @@ -143,6 +143,17 @@ namespace WorldPackets int32 SpellID = 0; std::vector<PvPTalent> Talents; }; + + class LearnTalent final : public ClientPacket + { + public: + LearnTalent(WorldPacket&& packet) : ClientPacket(CMSG_LEARN_TALENT, std::move(packet)) { } + + void Read() override; + + int32 TalentID = 0; + uint16 RequestedRank = 0; + }; } } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 9a1279a183d..82b7ddd7c7f 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -557,7 +557,7 @@ void OpcodeTable::Initialize() DEFINE_HANDLER(CMSG_LEARN_TALENTS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleLearnTalentsOpcode); DEFINE_HANDLER(CMSG_LEARN_PREVIEW_TALENTS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_LEARN_PREVIEW_TALENTS_PET, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_LEARN_TALENT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_LEARN_TALENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnTalentOpcode); DEFINE_HANDLER(CMSG_LEAVE_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLeaveGroupOpcode); DEFINE_HANDLER(CMSG_LEAVE_PET_BATTLE_QUEUE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_LFG_LIST_APPLY_TO_GROUP, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 3d29bc83b7e..c974768ff78 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -733,6 +733,7 @@ namespace WorldPackets namespace Talent { + class LearnTalent; class LearnTalents; class LearnPvpTalents; class ConfirmRespecWipe; @@ -1514,6 +1515,7 @@ class TC_GAME_API WorldSession void HandleLearnPvpTalentsOpcode(WorldPackets::Talent::LearnPvpTalents& packet); void HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents& packet); + void HandleLearnTalentOpcode(WorldPackets::Talent::LearnTalent& packet); void HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRespecWipe& confirmRespecWipe); void HandleUnlearnSkillOpcode(WorldPackets::Spells::UnlearnSkill& packet); void HandleTradeSkillSetFavorite(WorldPackets::Spells::TradeSkillSetFavorite const& tradeSkillSetFavorite); diff --git a/src/server/scripts/Commands/cs_learn.cpp b/src/server/scripts/Commands/cs_learn.cpp index 2986bb3ce13..239acc7505c 100644 --- a/src/server/scripts/Commands/cs_learn.cpp +++ b/src/server/scripts/Commands/cs_learn.cpp @@ -183,6 +183,7 @@ public: static bool HandleLearnAllTalentsCommand(ChatHandler* handler) { + /* Player* player = handler->GetSession()->GetPlayer(); uint32 playerClass = player->GetClass(); @@ -209,6 +210,7 @@ public: player->SendTalentsInfoData(); handler->SendSysMessage(LANG_COMMAND_LEARN_CLASS_TALENTS); + */ return true; } |