diff options
author | Ovahlord <dreadkiller@gmx.de> | 2024-07-27 01:16:01 +0200 |
---|---|---|
committer | Ovahlord <dreadkiller@gmx.de> | 2024-07-27 11:03:38 +0200 |
commit | 03b6898518e9321fdb1b805b7757bf4801fa8d08 (patch) | |
tree | ae2f2f5d32f4e2be01d6a7dfee1f344cdfeef83d /src | |
parent | acfba7fa1497d583a3c814db6e0161fe72bdfec9 (diff) |
Core/Player: initial work on implementing talents for Cataclysm
- use a new structure to hold talent group data which will eventually replace the old master branch implementation
- added support for unlocking dual talent specialization and switching specs
- added basic support for learning and resetting talents
Diffstat (limited to 'src')
24 files changed, 514 insertions, 585 deletions
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index d01d9d7add8..56792e2b8f9 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -150,7 +150,8 @@ 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 talentGroup, talentId, `rank` FROM character_talent WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_TALENT_GROUPS, "SELECT id, talentTabId FROM character_talent_group WHERE guid = ? ORDER BY id ASC", 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); @@ -580,6 +581,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER, "DELETE FROM guild_bank_eventlog WHERE PlayerGuid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_GLYPHS, "DELETE FROM character_glyphs WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_TALENT, "DELETE FROM character_talent WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_TALENT_GROUP, "DELETE FROM character_talent_group WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_SKILLS, "DELETE FROM character_skills WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_MONEY, "UPDATE characters SET money = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_CHAR_ACTION, "INSERT INTO character_action (guid, spec, traitConfigId, button, action, type) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); @@ -615,7 +617,8 @@ 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, talentGroup, talentId, `rank`) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_TALENT_GROUP, "INSERT INTO character_talent_group (guid, id, talentTabId) 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/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index f098dd65898..932bc244281 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -109,6 +109,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_CHARACTER_BGDATA, CHAR_SEL_CHARACTER_GLYPHS, CHAR_SEL_CHARACTER_TALENTS, + CHAR_SEL_CHARACTER_TALENT_GROUPS, CHAR_SEL_CHARACTER_SKILLS, CHAR_SEL_CHARACTER_RANDOMBG, CHAR_SEL_CHARACTER_BANNED, @@ -466,6 +467,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER, CHAR_DEL_CHAR_GLYPHS, CHAR_DEL_CHAR_TALENT, + CHAR_DEL_CHAR_TALENT_GROUP, CHAR_DEL_CHAR_SKILLS, CHAR_UPD_CHAR_MONEY, CHAR_INS_CHAR_ACTION, @@ -500,6 +502,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_PETITION_SIGNATURE_BY_OWNER, CHAR_INS_CHAR_GLYPHS, CHAR_INS_CHAR_TALENT, + CHAR_INS_CHAR_TALENT_GROUP, CHAR_UPD_CHAR_LIST_SLOT, CHAR_INS_CHAR_FISHINGSTEPS, CHAR_DEL_CHAR_FISHINGSTEPS, diff --git a/src/server/database/Database/Implementation/HotfixDatabase.cpp b/src/server/database/Database/Implementation/HotfixDatabase.cpp index 9302f0d6cb4..12d7043f343 100644 --- a/src/server/database/Database/Implementation/HotfixDatabase.cpp +++ b/src/server/database/Database/Implementation/HotfixDatabase.cpp @@ -1172,13 +1172,6 @@ void HotfixDatabaseConnection::DoPrepareStatements() "SoundMixGroupID FROM sound_kit WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); PREPARE_MAX_ID_STMT(HOTFIX_SEL_SOUND_KIT, "SELECT MAX(ID) + 1 FROM sound_kit", CONNECTION_SYNCH); - // SpecializationSpells.db2 - PrepareStatement(HOTFIX_SEL_SPECIALIZATION_SPELLS, "SELECT Description, ID, SpecID, SpellID, OverridesSpellID, DisplayOrder" - " FROM specialization_spells WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); - PREPARE_MAX_ID_STMT(HOTFIX_SEL_SPECIALIZATION_SPELLS, "SELECT MAX(ID) + 1 FROM specialization_spells", CONNECTION_SYNCH); - PREPARE_LOCALE_STMT(HOTFIX_SEL_SPECIALIZATION_SPELLS, "SELECT ID, Description_lang FROM specialization_spells_locale" - " WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH); - // SpecSetMember.db2 PrepareStatement(HOTFIX_SEL_SPEC_SET_MEMBER, "SELECT ID, ChrSpecializationID, SpecSetID FROM spec_set_member WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); PREPARE_MAX_ID_STMT(HOTFIX_SEL_SPEC_SET_MEMBER, "SELECT MAX(ID) + 1 FROM spec_set_member", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/HotfixDatabase.h b/src/server/database/Database/Implementation/HotfixDatabase.h index aab81744a5e..6522333411d 100644 --- a/src/server/database/Database/Implementation/HotfixDatabase.h +++ b/src/server/database/Database/Implementation/HotfixDatabase.h @@ -679,10 +679,6 @@ enum HotfixDatabaseStatements : uint32 HOTFIX_SEL_SOUND_KIT, HOTFIX_SEL_SOUND_KIT_MAX_ID, - HOTFIX_SEL_SPECIALIZATION_SPELLS, - HOTFIX_SEL_SPECIALIZATION_SPELLS_MAX_ID, - HOTFIX_SEL_SPECIALIZATION_SPELLS_LOCALE, - HOTFIX_SEL_SPEC_SET_MEMBER, HOTFIX_SEL_SPEC_SET_MEMBER_MAX_ID, diff --git a/src/server/game/DataStores/DB2LoadInfo.h b/src/server/game/DataStores/DB2LoadInfo.h index 844e7d0b736..81caa13ca9b 100644 --- a/src/server/game/DataStores/DB2LoadInfo.h +++ b/src/server/game/DataStores/DB2LoadInfo.h @@ -3935,21 +3935,6 @@ struct SoundKitLoadInfo static constexpr DB2LoadInfo Instance{ Fields, 17, &SoundKitMeta::Instance, HOTFIX_SEL_SOUND_KIT }; }; -struct SpecializationSpellsLoadInfo -{ - static constexpr DB2FieldMeta Fields[6] = - { - { false, FT_STRING, "Description" }, - { false, FT_INT, "ID" }, - { false, FT_SHORT, "SpecID" }, - { true, FT_INT, "SpellID" }, - { true, FT_INT, "OverridesSpellID" }, - { false, FT_BYTE, "DisplayOrder" }, - }; - - static constexpr DB2LoadInfo Instance{ Fields, 6, &SpecializationSpellsMeta::Instance, HOTFIX_SEL_SPECIALIZATION_SPELLS }; -}; - struct SpecSetMemberLoadInfo { static constexpr DB2FieldMeta Fields[3] = diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp index 2659e069424..a85d7c7a851 100644 --- a/src/server/game/DataStores/DB2Stores.cpp +++ b/src/server/game/DataStores/DB2Stores.cpp @@ -237,7 +237,6 @@ DB2Storage<SkillLineAbilityEntry> sSkillLineAbilityStore("SkillLin DB2Storage<SkillLineXTraitTreeEntry> sSkillLineXTraitTreeStore("SkillLineXTraitTree.db2", &SkillLineXTraitTreeLoadInfo::Instance); DB2Storage<SkillRaceClassInfoEntry> sSkillRaceClassInfoStore("SkillRaceClassInfo.db2", &SkillRaceClassInfoLoadInfo::Instance); DB2Storage<SoundKitEntry> sSoundKitStore("SoundKit.db2", &SoundKitLoadInfo::Instance); -DB2Storage<SpecializationSpellsEntry> sSpecializationSpellsStore("SpecializationSpells.db2", &SpecializationSpellsLoadInfo::Instance); DB2Storage<SpecSetMemberEntry> sSpecSetMemberStore("SpecSetMember.db2", &SpecSetMemberLoadInfo::Instance); DB2Storage<SpellAuraOptionsEntry> sSpellAuraOptionsStore("SpellAuraOptions.db2", &SpellAuraOptionsLoadInfo::Instance); DB2Storage<SpellAuraRestrictionsEntry> sSpellAuraRestrictionsStore("SpellAuraRestrictions.db2", &SpellAuraRestrictionsLoadInfo::Instance); @@ -363,7 +362,6 @@ typedef std::unordered_map<uint32, std::vector<uint32>> PhaseGroupContainer; typedef std::array<PowerTypeEntry const*, MAX_POWERS> PowerTypesContainer; typedef std::unordered_map<uint32, std::pair<std::vector<QuestPackageItemEntry const*>, std::vector<QuestPackageItemEntry const*>>> QuestPackageItemContainer; typedef std::unordered_multimap<uint32, SkillRaceClassInfoEntry const*> SkillRaceClassInfoContainer; -typedef std::unordered_map<uint32, std::vector<SpecializationSpellsEntry const*>> SpecializationSpellsContainer; typedef std::unordered_map<uint32, std::vector<SpellPowerEntry const*>> SpellPowerContainer; typedef std::unordered_map<uint32, std::unordered_map<uint32, std::vector<SpellPowerEntry const*>>> SpellPowerDifficultyContainer; typedef std::unordered_map<uint32, std::vector<SpellProcsPerMinuteModEntry const*>> SpellProcsPerMinuteModContainer; @@ -438,7 +436,6 @@ namespace std::unordered_map<uint32, std::vector<SkillLineEntry const*>> _skillLinesByParentSkillLine; std::unordered_map<uint32, std::vector<SkillLineAbilityEntry const*>> _skillLineAbilitiesBySkillupSkill; SkillRaceClassInfoContainer _skillRaceClassInfoBySkill; - SpecializationSpellsContainer _specializationSpellsBySpec; std::unordered_set<std::pair<int32, uint32>> _specsBySpecSet; std::unordered_set<uint8> _spellFamilyNames; SpellProcsPerMinuteModContainer _spellProcsPerMinuteMods; @@ -455,6 +452,8 @@ namespace std::unordered_multimap<int32, UiMapAssignmentEntry const*> _uiMapAssignmentByWmoGroup[MAX_UI_MAP_SYSTEM]; std::unordered_set<int32> _uiMapPhases; WMOAreaTableLookupContainer _wmoAreaTableLookup; + std::array<std::unordered_map<int32, TalentTabEntry const*>, MAX_CLASSES> _talentTabsByIndex; + std::unordered_map<uint32, std::vector<uint32>> _primaryTalentTreeSpellsByTalentTab; } void LoadDB2(std::bitset<TOTAL_LOCALES>& availableDb2Locales, std::vector<std::string>& errlist, StorageMap& stores, DB2StorageBase* storage, std::string const& db2Path, @@ -774,7 +773,6 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul LOAD_DB2(sSkillLineXTraitTreeStore); LOAD_DB2(sSkillRaceClassInfoStore); LOAD_DB2(sSoundKitStore); - LOAD_DB2(sSpecializationSpellsStore); LOAD_DB2(sSpecSetMemberStore); LOAD_DB2(sSpellAuraOptionsStore); LOAD_DB2(sSpellAuraRestrictionsStore); @@ -1285,9 +1283,6 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul if (sSkillLineStore.LookupEntry(entry->SkillID)) _skillRaceClassInfoBySkill.insert(SkillRaceClassInfoContainer::value_type(entry->SkillID, entry)); - for (SpecializationSpellsEntry const* specSpells : sSpecializationSpellsStore) - _specializationSpellsBySpec[specSpells->SpecID].push_back(specSpells); - for (SpecSetMemberEntry const* specSetMember : sSpecSetMemberStore) _specsBySpecSet.insert(std::make_pair(specSetMember->SpecSetID, uint32(specSetMember->ChrSpecializationID))); @@ -1309,6 +1304,14 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul _talentsByPosition[talentInfo->ClassID][talentInfo->TierID][talentInfo->ColumnIndex].push_back(talentInfo); } + for (TalentTabEntry const* talentTab : sTalentTabStore) + for (uint8 i = CLASS_WARRIOR; i < MAX_CLASSES; ++i) + if (talentTab->ClassMask & (1 << (i - 1))) + _talentTabsByIndex[i][talentTab->OrderIndex] = talentTab; + + for (TalentTreePrimarySpellsEntry const* talentPrimarySpell : sTalentTreePrimarySpellsStore) + _primaryTalentTreeSpellsByTalentTab[talentPrimarySpell->TalentTabID].push_back(talentPrimarySpell->SpellID); + for (TaxiPathEntry const* entry : sTaxiPathStore) _taxiPaths[{ entry->FromTaxiNode, entry->ToTaxiNode }] = entry; @@ -2493,11 +2496,6 @@ std::vector<SkillRaceClassInfoEntry const*> DB2Manager::GetSkillRaceClassInfo(ui return result; } -std::vector<SpecializationSpellsEntry const*> const* DB2Manager::GetSpecializationSpells(uint32 specId) const -{ - return Trinity::Containers::MapGetValuePtr(_specializationSpellsBySpec, specId); -} - bool DB2Manager::IsSpecSetMember(int32 specSetId, uint32 specId) const { return _specsBySpecSet.count(std::make_pair(specSetId, specId)) > 0; @@ -2527,6 +2525,16 @@ std::vector<TalentEntry const*> const& DB2Manager::GetTalentsByPosition(uint32 c return _talentsByPosition[class_][tier][column]; } +TalentTabEntry const* DB2Manager::GetTalentTabByIndex(uint32 class_, int32 tabIndex) const +{ + return Trinity::Containers::MapGetValuePtr(_talentTabsByIndex[class_], tabIndex); +} + +std::vector<uint32> const* DB2Manager::GetPrimaryTalentTreeSpells(uint32 talentTabId) const +{ + return Trinity::Containers::MapGetValuePtr(_primaryTalentTreeSpellsByTalentTab, talentTabId); +} + TaxiPathEntry const* DB2Manager::GetTaxiPath(uint32 from, uint32 to) const { return Trinity::Containers::MapGetValuePtr(_taxiPaths, { from, to }); diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h index edb5625773c..fa1b5a6d807 100644 --- a/src/server/game/DataStores/DB2Stores.h +++ b/src/server/game/DataStores/DB2Stores.h @@ -469,12 +469,13 @@ public: std::vector<SkillLineAbilityEntry const*> const* GetSkillLineAbilitiesBySkill(uint32 skillId) const; SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, uint8 class_) const; std::vector<SkillRaceClassInfoEntry const*> GetSkillRaceClassInfo(uint32 skill) const; - std::vector<SpecializationSpellsEntry const*> const* GetSpecializationSpells(uint32 specId) const; bool IsSpecSetMember(int32 specSetId, uint32 specId) const; static bool IsValidSpellFamiliyName(SpellFamilyNames family); std::vector<SpellProcsPerMinuteModEntry const*> GetSpellProcsPerMinuteMods(uint32 spellprocsPerMinuteId) const; std::vector<SpellVisualMissileEntry const*> const* GetSpellVisualMissiles(int32 spellVisualMissileSetId) const; std::vector<TalentEntry const*> const& GetTalentsByPosition(uint32 class_, uint32 tier, uint32 column) const; + TalentTabEntry const* GetTalentTabByIndex(uint32 class_, int32 tabIndex) const; + std::vector<uint32> const* GetPrimaryTalentTreeSpells(uint32 talentTabId) const; TaxiPathEntry const* GetTaxiPath(uint32 from, uint32 to) const; static bool IsTotemCategoryCompatibleWith(uint32 itemTotemCategoryId, uint32 requiredTotemCategoryId, bool requireAllTotems = true); bool IsToyItem(uint32 toy) const; diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h index f2dc052d4dd..fbe56c5899e 100644 --- a/src/server/game/DataStores/DB2Structure.h +++ b/src/server/game/DataStores/DB2Structure.h @@ -2874,16 +2874,6 @@ struct SoundKitEntry uint32 SoundMixGroupID; }; -struct SpecializationSpellsEntry -{ - LocalizedString Description; - uint32 ID; - uint16 SpecID; - int32 SpellID; - int32 OverridesSpellID; - uint8 DisplayOrder; -}; - struct SpecSetMemberEntry { uint32 ID; diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 84bc7d2a160..3c0f5e7a1e8 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -1817,6 +1817,7 @@ void Pet::LearnSpecializationSpells() { std::vector<uint32> learnedSpells; + /* if (std::vector<SpecializationSpellsEntry const*> const* specSpells = sDB2Manager.GetSpecializationSpells(m_petSpecialization)) { for (size_t j = 0; j < specSpells->size(); ++j) @@ -1829,6 +1830,7 @@ void Pet::LearnSpecializationSpells() learnedSpells.push_back(specSpell->SpellID); } } + */ learnSpells(learnedSpells); } @@ -1837,6 +1839,7 @@ void Pet::RemoveSpecializationSpells(bool clearActionBar) { std::vector<uint32> unlearnedSpells; + /* for (uint32 i = 0; i < MAX_SPECIALIZATIONS; ++i) { if (ChrSpecializationEntry const* specialization = sDB2Manager.GetChrSpecializationByIndex(0, i)) @@ -1863,6 +1866,7 @@ void Pet::RemoveSpecializationSpells(bool clearActionBar) } } } + */ unlearnSpells(unlearnedSpells, true, clearActionBar); } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 8d7a19a9789..8bea06e4332 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -116,6 +116,7 @@ #include "SpellMgr.h" #include "SpellPackets.h" #include "StringConvert.h" +#include "TalentGroupInfo.h" #include "TalentPackets.h" #include "TerrainMgr.h" #include "ToyPackets.h" @@ -277,6 +278,8 @@ Player::Player(WorldSession* session) : Unit(true), m_sceneMgr(this) m_lastPotionId = 0; + _activeTalentGroup = 0; + m_auraBaseFlatMod.fill(0.0f); m_auraBasePctMod.fill(1.0f); m_baseRatingValue = { }; @@ -548,12 +551,6 @@ bool Player::Create(ObjectGuid::LowType guidlow, WorldPackets::Character::Charac } // all item positions resolved - if (ChrSpecializationEntry const* defaultSpec = sDB2Manager.GetDefaultChrSpecializationForClass(GetClass())) - { - SetActiveTalentGroup(defaultSpec->OrderIndex); - SetPrimarySpecialization(defaultSpec->ID); - } - GetThreatManager().Initialize(); return true; @@ -2122,7 +2119,6 @@ void Player::GiveLevel(uint8 level) UpdateSkillsForLevel(); LearnDefaultSkills(); - LearnSpecializationSpells(); // save base values (bonuses already included in stored stats for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i) @@ -2175,36 +2171,7 @@ bool Player::IsMaxLevel() const void Player::InitTalentForLevel() { - /* - uint8 level = GetLevel(); - // talents base at level diff (talents = level - 9 but some can be used already) - if (level < MIN_SPECIALIZATION_LEVEL) - ResetTalentSpecialization(); - - int32 talentTiers = DB2Manager::GetNumTalentsAtLevel(level, Classes(GetClass())); - if (level < 15) - { - // Remove all talent points - ResetTalents(true); - } - else - { - if (!GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_MORE_TALENTS_THAN_ALLOWED)) - for (int32 t = talentTiers; t < MAX_TALENT_TIERS; ++t) - for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c) - for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), t, c)) - RemoveTalent(talent); - } - - SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::MaxTalentTiers), talentTiers); - - if (!GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_MORE_TALENTS_THAN_ALLOWED)) - for (uint8 spec = 0; spec < MAX_SPECIALIZATIONS; ++spec) - for (size_t slot = sDB2Manager.GetPvpTalentNumSlotsAtLevel(level, Classes(GetClass())); slot < MAX_PVP_TALENT_SLOTS; ++slot) - if (PvpTalentEntry const* pvpTalent = sPvpTalentStore.LookupEntry(GetPvpTalentMap(spec)[slot])) - RemovePvpTalent(pvpTalent, spec); - - */ + UpdateAvailableTalentPoints(); if (!GetSession()->PlayerLoading()) SendTalentsInfoData(); // update at client @@ -2489,62 +2456,6 @@ void DeleteSpellFromAllPlayers(uint32 spellId) CharacterDatabase.Execute(stmt); } -bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning) -{ - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE); - if (!spellInfo) - { - TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist.", talent->SpellID); - return false; - } - - if (!SpellMgr::IsSpellValid(spellInfo, this, false)) - { - TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid", talent->SpellID); - 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()) - { - LearnSpell(talent->SpellID, true); - if (talent->OverridesSpellID) - AddOverrideSpell(talent->OverridesSpellID, talent->SpellID); - } - - if (learning) - RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChangeTalent); - - return true; -} - -void Player::RemoveTalent(TalentEntry const* talent) -{ - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE); - if (!spellInfo) - return; - - RemoveSpell(talent->SpellID, 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 (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; -} - void Player::AddStoredAuraTeleportLocation(uint32 spellId) { StoredAuraTeleportLocation& storedLocation = m_storedAuraTeleportLocations[spellId]; @@ -3353,25 +3264,24 @@ bool Player::ResetTalents(bool noCost) RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true); - for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) + for (auto const& talentPair : _talentGroups[_activeTalentGroup].Talents) { - TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); + TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentPair.first); 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; - - // skip non-existent talent ranks - if (talentInfo->SpellID == 0) - continue; + for (uint32 spellId : talentInfo->SpellRank) + { + if (!spellId) + continue; - RemoveTalent(talentInfo); + RemoveSpell(spellId, false, false); + } } + _talentGroups[_activeTalentGroup].Talents.clear(); + SetPrimaryTalentTree(0); + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); _SaveTalents(trans); _SaveSpells(trans); @@ -3395,6 +3305,8 @@ bool Player::ResetTalents(bool noCost) } */ + SendTalentsInfoData(); + return true; } @@ -3592,12 +3504,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); @@ -3976,6 +3882,10 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe stmt->setUInt64(0, guid); trans->Append(stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT_GROUP); + stmt->setUInt64(0, guid); + trans->Append(stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SKILLS); stmt->setUInt64(0, guid); trans->Append(stmt); @@ -17446,10 +17356,6 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol SetNumRespecs(fields.numRespecs); SetPrimarySpecialization(fields.primarySpecialization); - SetActiveTalentGroup(fields.activeTalentGroup); - ChrSpecializationEntry const* primarySpec = GetPrimarySpecializationEntry(); - if (!primarySpec || primarySpec->ClassID != GetClass() || GetActiveTalentGroup() >= MAX_SPECIALIZATIONS) - ResetTalentSpecialization(); uint32 lootSpecId = fields.lootSpecId; if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(lootSpecId)) @@ -17458,7 +17364,10 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol RegisterPowerTypes(); UpdateDisplayPower(); - _LoadTalents(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TALENTS)); + + _LoadTalents(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TALENT_GROUPS), holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TALENTS)); + SetActiveTalentGroup(HasTalentGroupUnlocked(fields.activeTalentGroup) ? fields.activeTalentGroup : 0, false); + _LoadSpells(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELLS), holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELL_FAVORITES)); GetSession()->GetCollectionMgr()->LoadToys(); GetSession()->GetCollectionMgr()->LoadHeirlooms(); @@ -17466,8 +17375,6 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol GetSession()->GetCollectionMgr()->LoadItemAppearances(); GetSession()->GetCollectionMgr()->LoadTransmogIllusions(); - LearnSpecializationSpells(); - _LoadGlyphs(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_GLYPHS)); _LoadAuras(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AURAS), holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AURA_EFFECTS), time_diff); _LoadGlyphAuras(); @@ -23910,6 +23817,22 @@ void Player::LearnSkillRewardedSpells(uint32 skillId, uint32 skillValue, Races r } } +void Player::LearnTalentTreePrimarySpells() +{ + if (std::vector<uint32> const* primaryTalentTreeSpells = sDB2Manager.GetPrimaryTalentTreeSpells(GetPrimaryTalentTree())) + for (uint32 spellId : *primaryTalentTreeSpells) + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE)) + LearnSpell(spellId, true); +} + +void Player::UnlearnTalentTreePrimarySpells() +{ + if (std::vector<uint32> const* primaryTalentTreeSpells = sDB2Manager.GetPrimaryTalentTreeSpells(GetPrimaryTalentTree())) + for (uint32 spellId : *primaryTalentTreeSpells) + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE)) + RemoveSpell(spellId, true); +} + int32 Player::GetProfessionSlotFor(uint32 skillId) const { int32 const* professionsBegin = m_activePlayerData->ProfessionSkillLine.begin(); @@ -26011,124 +25934,6 @@ 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; - - if (isDead()) - return TALENT_FAILED_CANT_DO_THAT_RIGHT_NOW; - - if (GetPrimarySpecialization() == ChrSpecialization::None) - return TALENT_FAILED_NO_PRIMARY_TREE_SELECTED; - - TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); - if (!talentInfo) - return TALENT_FAILED_UNKNOWN; - - if (talentInfo->SpecID && ChrSpecialization(talentInfo->SpecID) != GetPrimarySpecialization()) - return TALENT_FAILED_UNKNOWN; - - // 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; - - // TODO: prevent changing talents that are on cooldown - - // 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)) - { - if (!talent->SpecID) - bestSlotMatch = talent; - else if (ChrSpecialization(talent->SpecID) == GetPrimarySpecialization()) - { - bestSlotMatch = talent; - break; - } - } - - if (talentInfo != bestSlotMatch) - return TALENT_FAILED_UNKNOWN; - - // Check if player doesn't have any talent in current tier - for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c) - { - for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), talentInfo->TierID, c)) - { - if (!HasTalent(talent->ID, GetActiveTalentGroup())) - continue; - - if (!HasPlayerFlag(PLAYER_FLAGS_RESTING) && !HasUnitFlag2(UNIT_FLAG2_ALLOW_CHANGING_TALENTS)) - return TALENT_FAILED_REST_AREA; - - if (GetSpellHistory()->HasCooldown(talent->SpellID)) - { - *spellOnCooldown = talent->SpellID; - return TALENT_FAILED_CANT_REMOVE_TALENT; - } - - RemoveTalent(talent); - } - } - - // spell not set in talent.dbc - uint32 spellid = talentInfo->SpellID; - if (!spellid) - { - TC_LOG_ERROR("entities.player", "Player::LearnTalent: Talent.dbc has no spellInfo for talent: {} (spell id = 0)", talentId); - return TALENT_FAILED_UNKNOWN; - } - - // already known - if (HasTalent(talentId, GetActiveTalentGroup()) || HasSpell(spellid)) - return TALENT_FAILED_UNKNOWN; - - if (!AddTalent(talentInfo, GetActiveTalentGroup(), true)) - return TALENT_FAILED_UNKNOWN; - - TC_LOG_DEBUG("misc", "Player::LearnTalent: TalentID: {} Spell: {} Group: {}\n", talentId, spellid, GetActiveTalentGroup()); - - return TALENT_LEARN_OK; -} - -void Player::ResetTalentSpecialization() -{ - SendTalentsInfoData(); - - /* - // Reset only talents that have different spells for each spec - uint32 class_ = GetClass(); - for (uint32 t = 0; t < MAX_TALENT_TIERS; ++t) - for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c) - if (sDB2Manager.GetTalentsByPosition(class_, t, c).size() > 1) - for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(class_, t, c)) - RemoveTalent(talent); - - ResetPvpTalents(); - - RemoveSpecializationSpells(); - - ChrSpecializationEntry const* defaultSpec = ASSERT_NOTNULL(sDB2Manager.GetDefaultChrSpecializationForClass(GetClass())); - SetPrimarySpecialization(defaultSpec->ID); - SetActiveTalentGroup(defaultSpec->OrderIndex); - - LearnSpecializationSpells(); - - SendTalentsInfoData(); - UpdateItemSetAuras(false); - */ -} - void Player::InitGlyphsForLevel() { uint8 slotIndex = 0; @@ -26331,10 +26136,22 @@ void Player::SendTalentsInfoData() { WorldPackets::Talent::UpdateTalentData packet; packet.Info.IsPetTalents = false; - packet.Info.UnspentTalentPoints = DB2Manager::GetNumTalentsAtLevel(GetLevel(), static_cast<Classes>(GetClass())); - WorldPackets::Talent::TalentGroupInfo& talentGroup = packet.Info.TalentGroups.emplace_back(); - for (uint8 i = 0; i < m_activePlayerData->Glyphs.size(); ++i) - talentGroup.Glyphs.push_back(m_activePlayerData->Glyphs[i]); + packet.Info.UnspentTalentPoints = m_activePlayerData->CharacterPoints; + packet.Info.ActiveGroup = GetActiveTalentGroup(); + + for (TalentGroupInfo const& talentGroup : _talentGroups) + { + WorldPackets::Talent::TalentGroupInfo& talentGroupInfo = packet.Info.TalentGroups.emplace_back(); + talentGroupInfo.PrimarySpecialization = talentGroup.PrimaryTalentTabID; + if (_talentGroups.size() > 1) + talentGroupInfo.SpecID = _talentGroups.size(); + + for (auto const& talentPair : talentGroup.Talents) + talentGroupInfo.Talents.push_back({ talentPair.first, talentPair.second }); + + for (uint16 glyph : talentGroup.Glyphs) + talentGroupInfo.Glyphs.push_back(glyph); + } SendDirectMessage(packet.Write()); } @@ -26596,15 +26413,46 @@ void Player::_SaveGlyphs(CharacterDatabaseTransaction trans) const } } -void Player::_LoadTalents(PreparedQueryResult result) +void Player::_LoadTalents(PreparedQueryResult talentGroupResult, PreparedQueryResult talentResult) { - // "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?" - if (result) + // SELECT id, talentTabId FROM character_talent_group WHERE guid = ? ORDER BY id ASC + if (talentGroupResult) { do - if (TalentEntry const* talent = sTalentStore.LookupEntry((*result)[0].GetUInt32())) - AddTalent(talent, (*result)[1].GetUInt8(), false); - while (result->NextRow()); + { + Field* fields = talentGroupResult->Fetch(); + + TalentGroupInfo& talentGroup = _talentGroups.emplace_back(); + uint32 talentTabId = fields[1].GetUInt32(); + if (sTalentTabStore.HasRecord(talentTabId)) + talentGroup.PrimaryTalentTabID = talentTabId; + + } while (talentGroupResult->NextRow()); + } + else // If the character has no saved talent groups, we will create an empty talent group which will then get saved later + _talentGroups.emplace_back(); + + // SELECT talentGroup, talentId, `rank` FROM character_talent WHERE guid = ? + if (talentResult) + { + do + { + Field* fields = talentResult->Fetch(); + + uint8 talentGroupId = fields[0].GetUInt8(); + if (_talentGroups.size() < (talentGroupId + 1)) + continue; + + uint32 talentId = fields[1].GetUInt32(); + uint8 talentRank = fields[2].GetUInt8(); + TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); + if (!talentInfo || talentInfo->SpellRank.size() <= talentRank || !sSpellMgr->GetSpellInfo(talentInfo->SpellRank[talentRank], DIFFICULTY_NONE)) + continue; + + _talentGroups[talentGroupId].Talents.try_emplace(talentId, talentRank); + LearnSpell(talentInfo->SpellRank[talentRank], true); + + } while (talentResult->NextRow()); } } @@ -26754,24 +26602,30 @@ void Player::_SaveTalents(CharacterDatabaseTransaction trans) stmt->setUInt64(0, GetGUID().GetCounter()); trans->Append(stmt); - for (uint8 group = 0; group < MAX_SPECIALIZATIONS; ++group) + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT_GROUP); + stmt->setUInt64(0, GetGUID().GetCounter()); + trans->Append(stmt); + + uint8 talentGroupIndex = 0; + for (TalentGroupInfo const& talentGroup : _talentGroups) { - PlayerTalentMap* talents = GetTalentMap(group); - for (auto itr = talents->begin(); itr != talents->end();) - { - if (itr->second == PLAYERSPELL_REMOVED) - { - itr = talents->erase(itr); - continue; - } + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TALENT_GROUP); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setUInt8(1, talentGroupIndex); + stmt->setUInt32(2, talentGroup.PrimaryTalentTabID); + trans->Append(stmt); + for (auto const& talentPair : talentGroup.Talents) + { stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TALENT); stmt->setUInt64(0, GetGUID().GetCounter()); - stmt->setUInt32(1, itr->first); - stmt->setUInt8(2, group); + stmt->setUInt8(1, talentGroupIndex); + stmt->setUInt32(2, talentPair.first); + stmt->setUInt8(3, talentPair.second); trans->Append(stmt); - ++itr; } + + ++talentGroupIndex; } } @@ -26866,172 +26720,6 @@ void Player::_SaveTraits(CharacterDatabaseTransaction trans) m_traitConfigStates.clear(); } -void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) -{ - if (GetActiveTalentGroup() == spec->OrderIndex) - return; - - if (IsNonMeleeSpellCast(false)) - InterruptNonMeleeSpells(false); - - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - _SaveActions(trans); - CharacterDatabase.CommitTransaction(trans); - - // TO-DO: We need more research to know what happens with warlock's reagent - if (Pet* pet = GetPet()) - RemovePet(pet, PET_SAVE_NOT_IN_SLOT); - - ClearAllReactives(); - UnsummonAllTotems(); - ExitVehicle(); - RemoveAllControlled(); - - RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChangeSpec); - - // remove single target auras at other targets - AuraList& scAuras = GetSingleCastAuras(); - for (AuraList::iterator iter = scAuras.begin(); iter != scAuras.end();) - { - Aura* aura = *iter; - if (aura->GetUnitOwner() != this) - { - aura->Remove(); - iter = scAuras.begin(); - } - else - ++iter; - } - /*RemoveAllAurasOnDeath(); - if (GetPet()) - GetPet()->RemoveAllAurasOnDeath();*/ - - //RemoveAllAuras(GetGUID(), nullptr, false, true); // removes too many auras - //ExitVehicle(); // should be impossible to switch specs from inside a vehicle.. - - // 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) - continue; - - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE); - if (!spellInfo) - continue; - - RemoveSpell(talentInfo->SpellID, 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); - } - - ApplyTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID, false); - - // Remove spec specific spells - RemoveSpecializationSpells(); - - for (uint32 glyphId : GetGlyphs(GetActiveTalentGroup())) - RemoveAurasDueToSpell(sGlyphPropertiesStore.AssertEntry(glyphId)->SpellID); - - SetActiveTalentGroup(spec->OrderIndex); - SetPrimarySpecialization(spec->ID); - int32 specTraitConfigIndex = m_activePlayerData->TraitConfigs.FindIndexIf([spec](UF::TraitConfig const& traitConfig) - { - return static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat - && traitConfig.ChrSpecializationID == int32(spec->ID) - && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) != TraitCombatConfigFlags::None; - }); - if (specTraitConfigIndex >= 0) - SetActiveCombatTraitConfigID(m_activePlayerData->TraitConfigs[specTraitConfigIndex].ID); - else - SetActiveCombatTraitConfigID(0); - - for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) - { - TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); - if (!talentInfo) - continue; - - // learn only talents for character class - if (talentInfo->ClassID != GetClass()) - continue; - - if (!talentInfo->SpellID) - 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); - } - } - - LearnSpecializationSpells(); - - if (CanUseMastery()) - for (uint32 i = 0; i < MAX_MASTERY_SPELLS; ++i) - if (uint32 mastery = spec->MasterySpellID[i]) - LearnSpell(mastery, true); - - ApplyTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID, true); - - InitTalentForLevel(); - - StartLoadingActionButtons(); - - UpdateDisplayPower(); - Powers pw = GetPowerType(); - if (pw != POWER_MANA) - SetPower(POWER_MANA, 0); // Mana must be 0 even if it isn't the active power type. - - SetPower(pw, 0); - UpdateItemSetAuras(false); - // update visible transmog - for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) - if (Item* equippedItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) - SetVisibleItemSlot(i, equippedItem); - - for (uint32 glyphId : GetGlyphs(spec->OrderIndex)) - CastSpell(this, sGlyphPropertiesStore.AssertEntry(glyphId)->SpellID, true); - - WorldPackets::Talent::ActiveGlyphs activeGlyphs; - activeGlyphs.Glyphs.reserve(GetGlyphs(spec->OrderIndex).size()); - for (uint32 glyphId : GetGlyphs(spec->OrderIndex)) - if (std::vector<uint32> const* bindableSpells = sDB2Manager.GetGlyphBindableSpells(glyphId)) - for (uint32 bindableSpell : *bindableSpells) - if (HasSpell(bindableSpell) && m_overrideSpells.find(bindableSpell) == m_overrideSpells.end()) - activeGlyphs.Glyphs.emplace_back(uint32(bindableSpell), uint16(glyphId)); - - activeGlyphs.IsFullUpdate = true; - SendDirectMessage(activeGlyphs.Write()); - - Unit::AuraEffectList const& shapeshiftAuras = GetAuraEffectsByType(SPELL_AURA_MOD_SHAPESHIFT); - for (AuraEffect* aurEff : shapeshiftAuras) - { - aurEff->HandleShapeshiftBoosts(this, false); - aurEff->HandleShapeshiftBoosts(this, true); - } -} - void Player::StartLoadingActionButtons(std::function<void()>&& callback /*= nullptr*/) { int32 traitConfigId = [&]() -> int32 @@ -28592,48 +28280,6 @@ void Player::RemoveOverrideSpell(uint32 overridenSpellId, uint32 newSpellId) m_overrideSpells.erase(overrides); } -void Player::LearnSpecializationSpells() -{ - if (std::vector<SpecializationSpellsEntry const*> const* specSpells = sDB2Manager.GetSpecializationSpells(AsUnderlyingType(GetPrimarySpecialization()))) - { - for (size_t j = 0; j < specSpells->size(); ++j) - { - SpecializationSpellsEntry const* specSpell = (*specSpells)[j]; - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(specSpell->SpellID, DIFFICULTY_NONE); - if (!spellInfo || spellInfo->SpellLevel > GetLevel()) - continue; - - LearnSpell(specSpell->SpellID, true); - if (specSpell->OverridesSpellID) - AddOverrideSpell(specSpell->OverridesSpellID, specSpell->SpellID); - } - } -} - -void Player::RemoveSpecializationSpells() -{ - for (uint32 i = 0; i < MAX_SPECIALIZATIONS; ++i) - { - if (ChrSpecializationEntry const* specialization = sDB2Manager.GetChrSpecializationByIndex(GetClass(), i)) - { - if (std::vector<SpecializationSpellsEntry const*> const* specSpells = sDB2Manager.GetSpecializationSpells(specialization->ID)) - { - for (size_t j = 0; j < specSpells->size(); ++j) - { - SpecializationSpellsEntry const* specSpell = (*specSpells)[j]; - RemoveSpell(specSpell->SpellID, true); - if (specSpell->OverridesSpellID) - RemoveOverrideSpell(specSpell->OverridesSpellID, specSpell->SpellID); - } - } - - for (uint32 j = 0; j < MAX_MASTERY_SPELLS; ++j) - if (uint32 mastery = specialization->MasterySpellID[j]) - RemoveAurasDueToSpell(mastery); - } - } -} - void Player::AddSpellCategoryCooldownMod(int32 spellCategoryId, int32 mod) { int32 categoryIndex = m_activePlayerData->CategoryCooldownMods.FindIndexIf([spellCategoryId](UF::CategoryCooldownMod const& mod) @@ -28700,6 +28346,205 @@ ChrSpecializationEntry const* Player::GetPrimarySpecializationEntry() const return sChrSpecializationStore.LookupEntry(AsUnderlyingType(GetPrimarySpecialization())); } +bool Player::HasTalentGroupUnlocked(uint8 group) const +{ + return _talentGroups.size() >= (group + 1); +} + +void Player::SetTalentGroupCount(uint8 count) +{ + if (_talentGroups.size() == count || count == 0) + return; + + // If we are using one of the talent groups that are about to get removed, switch to our first talent group + if ((_activeTalentGroup + 1) > count) + SetActiveTalentGroup(0, false); + + _talentGroups.resize(count); + SendTalentsInfoData(); +} + +void Player::SetActiveTalentGroup(uint8 group, bool withUpdate /*= true*/) +{ + uint32 oldTalentTabId = _talentGroups[_activeTalentGroup].PrimaryTalentTabID; + uint32 newTalentTabId = _talentGroups[group].PrimaryTalentTabID; + + // Unlearn previously known talent tree related spells if the new talent tab Id is different + if (oldTalentTabId != newTalentTabId) + UnlearnTalentTreePrimarySpells(); + + { + // Perform cleanup actions on switching talent groups + + if (IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + _SaveActions(trans); + CharacterDatabase.CommitTransaction(trans); + + // TO-DO: We need more research to know what happens with warlock's reagent + if (Pet* pet = GetPet()) + RemovePet(pet, PET_SAVE_NOT_IN_SLOT); + + ClearAllReactives(); + UnsummonAllTotems(); + ExitVehicle(); + RemoveAllControlled(); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChangeSpec); + + // remove single target auras at other targets + AuraList& scAuras = GetSingleCastAuras(); + for (AuraList::iterator iter = scAuras.begin(); iter != scAuras.end();) + { + Aura* aura = *iter; + if (aura->GetUnitOwner() != this) + { + aura->Remove(); + iter = scAuras.begin(); + } + else + ++iter; + } + /*RemoveAllAurasOnDeath(); + if (GetPet()) + GetPet()->RemoveAllAurasOnDeath();*/ + + //RemoveAllAuras(GetGUID(), nullptr, false, true); // removes too many auras + //ExitVehicle(); // should be impossible to switch specs from inside a vehicle.. + + // Let client clear his current Actions + SendActionButtons(2); + // m_actionButtons.clear() is called in the next _LoadActionButtons + + for (uint32 glyphId : GetGlyphs(GetActiveTalentGroup())) + RemoveAurasDueToSpell(sGlyphPropertiesStore.AssertEntry(glyphId)->SpellID); + } + + _activeTalentGroup = group; + + SetPrimaryTalentTree(newTalentTabId); + + { + // Perform post switch actions - resetting powers, loading action bars, updating shapeshifting auras + StartLoadingActionButtons(); + + UpdateDisplayPower(); + Powers pw = GetPowerType(); + if (pw != POWER_MANA) + SetPower(POWER_MANA, 0); // Mana must be 0 even if it isn't the active power type. + + SetPower(pw, 0); + UpdateItemSetAuras(false); + // update visible transmog + for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + if (Item* equippedItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + SetVisibleItemSlot(i, equippedItem); + + for (uint32 glyphId : GetGlyphs(GetActiveTalentGroup())) + CastSpell(this, sGlyphPropertiesStore.AssertEntry(glyphId)->SpellID, true); + + Unit::AuraEffectList const& shapeshiftAuras = GetAuraEffectsByType(SPELL_AURA_MOD_SHAPESHIFT); + for (AuraEffect* aurEff : shapeshiftAuras) + { + aurEff->HandleShapeshiftBoosts(this, false); + aurEff->HandleShapeshiftBoosts(this, true); + } + } + + if (withUpdate) + SendTalentsInfoData(); +} + +void Player::SetPrimaryTalentTree(uint32 talentTabId, bool withUpdate /*= false*/) +{ + if (!talentTabId || GetPrimaryTalentTree() != talentTabId) + UnlearnTalentTreePrimarySpells(); + + _talentGroups[_activeTalentGroup].PrimaryTalentTabID = talentTabId; + + if (talentTabId) + LearnTalentTreePrimarySpells(); + + if (withUpdate) + SendTalentsInfoData(); +} + +uint32 Player::GetPrimaryTalentTree() const +{ + return _talentGroups[_activeTalentGroup].PrimaryTalentTabID; +} + +bool Player::LearnTalent(uint32 talentId, uint8 rank) +{ + TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); + if (!talentInfo) + return false; + + // Ensure that the talent belongs to the player's class + TalentTabEntry const* talentTab = sTalentTabStore.LookupEntry(talentInfo->TabID); + if (!talentTab || !(talentTab->ClassMask & GetClassMask())) + return false; + + // Cheating protection + if (rank >= talentInfo->SpellRank.size()) + return false; + + uint32 spellId = talentInfo->SpellRank[rank]; + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE); + if (!spellInfo || !SpellMgr::IsSpellValid(spellInfo, this, false)) + return false; + + uint32 requiredTalentPoints = 0; + + TalentGroupInfo& talentGroup = _talentGroups[_activeTalentGroup]; + auto itr = talentGroup.Talents.find(talentInfo->ID); + if (itr != talentGroup.Talents.end()) + { + // The talent is known already. Check if we are attempting to learn a higher rank + if (itr->second >= rank) + return false; + + requiredTalentPoints += rank - itr->second + 1; + } + else + requiredTalentPoints += rank + 1; + + // Not enough talent points to learn the talent at this rank + if (requiredTalentPoints > static_cast<uint32>(m_activePlayerData->CharacterPoints)) + return false; + + if (itr != talentGroup.Talents.end()) + itr->second = rank; + else + talentGroup.Talents.try_emplace(talentInfo->ID, rank); + + LearnSpell(spellId, true); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChangeTalent); + + UpdateAvailableTalentPoints(); + return true; +} + +void Player::UpdateAvailableTalentPoints() +{ + uint32 points = sDB2Manager.GetNumTalentsAtLevel(GetLevel(), Classes(GetClass())); + uint32 spentPoints = 0; + for (auto const& pair : _talentGroups[_activeTalentGroup].Talents) + spentPoints += pair.second + 1; + + // If we have spent more points than we should have at this point, reset talents to prevent exploits. This can happen when a player reduces his level or db data got corrupted + if (points < spentPoints) + { + if (GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_MORE_TALENTS_THAN_ALLOWED)) + ResetTalents(true); + spentPoints = 0; + } + + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CharacterPoints), points - spentPoints); +} + void Player::SendRaidGroupOnlyMessage(RaidGroupReason reason, int32 delay) const { WorldPackets::Instance::RaidGroupOnly raidGroupOnly; diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 4b659afd9ae..f725ed05c1d 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -58,6 +58,7 @@ struct RewardPackEntry; struct SkillRaceClassInfoEntry; struct SpellCastRequest; struct TalentEntry; +struct TalentGroupInfo; struct TrainerSpell; struct TransferAbortParams; struct VendorItem; @@ -895,6 +896,7 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_BG_DATA, PLAYER_LOGIN_QUERY_LOAD_GLYPHS, PLAYER_LOGIN_QUERY_LOAD_TALENTS, + PLAYER_LOGIN_QUERY_LOAD_TALENT_GROUPS, PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_DATA, PLAYER_LOGIN_QUERY_LOAD_SKILLS, PLAYER_LOGIN_QUERY_LOAD_WEEKLY_QUEST_STATUS, @@ -1080,21 +1082,6 @@ 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) @@ -1831,13 +1818,13 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player> void LearnDefaultSkill(SkillRaceClassInfoEntry const* rcInfo); void LearnQuestRewardedSpells(); void LearnQuestRewardedSpells(Quest const* quest); + void LearnTalentTreePrimarySpells(); + void UnlearnTalentTreePrimarySpells(); void AddTemporarySpell(uint32 spellId); void RemoveTemporarySpell(uint32 spellId); void SetOverrideSpellsId(int32 overrideSpellsId) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::OverrideSpellsID), overrideSpellsId); } void AddOverrideSpell(uint32 overridenSpellId, uint32 newSpellId); void RemoveOverrideSpell(uint32 overridenSpellId, uint32 newSpellId); - void LearnSpecializationSpells(); - void RemoveSpecializationSpells(); void AddSpellCategoryCooldownMod(int32 spellCategoryId, int32 mod); void RemoveSpellCategoryCooldownMod(int32 spellCategoryId, int32 mod); void SetSpellFavorite(uint32 spellId, bool favorite); @@ -1864,29 +1851,28 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player> void SetTalentResetTime(time_t time_) { _specializationInfo.ResetTalentsTime = time_; } 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; } - void SetActiveTalentGroup(uint8 group){ _specializationInfo.ActiveGroup = group; } uint32 GetDefaultSpecId() const; ChrSpecializationEntry const* GetPrimarySpecializationEntry() const; + uint8 GetActiveTalentGroup() const { return _activeTalentGroup; } + bool HasTalentGroupUnlocked(uint8 group) const; + void SetTalentGroupCount(uint8 count); + void SetActiveTalentGroup(uint8 group, bool withUpdate = true); + void SetPrimaryTalentTree(uint32 talentTabId, bool withUpdate = false); + uint32 GetPrimaryTalentTree() const; + bool LearnTalent(uint32 talentId, uint8 rank); + void UpdateAvailableTalentPoints(); + bool ResetTalents(bool noCost = false); uint32 GetNextResetTalentsCost() const; void InitTalentForLevel(); void SendTalentsInfoData(); - TalentLearnResult LearnTalent(uint32 talentId, int32* spellOnCooldown); - bool AddTalent(TalentEntry const* talent, uint8 spec, bool learning); - bool HasTalent(uint32 spell_id, uint8 spec) const; - void RemoveTalent(TalentEntry const* talent); - void ResetTalentSpecialization(); void InitGlyphsForLevel(); void SetGlyph(uint8 index, uint32 glyphRecId) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::Glyphs, index), glyphRecId); } void SetGlyphSlot(uint8 index, uint32 glyphSlotRecId) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::GlyphSlots, index), glyphSlotRecId); } void SetGlyphsEnabled(uint32 slotMask) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::GlyphsEnabled), slotMask); } - // Dual Spec - void ActivateTalentGroup(ChrSpecializationEntry const* spec); - 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]; } @@ -2947,7 +2933,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player> void _LoadTransmogOutfits(PreparedQueryResult result); void _LoadBGData(PreparedQueryResult result); void _LoadGlyphs(PreparedQueryResult result); - void _LoadTalents(PreparedQueryResult result); + void _LoadTalents(PreparedQueryResult talentGroupResult, PreparedQueryResult talentResult); void _LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult entriesResult); void _LoadInstanceTimeRestrictions(PreparedQueryResult result); void _LoadPetStable(uint32 summonedPetNumber, PreparedQueryResult result); @@ -3040,6 +3026,10 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player> SpecializationInfo _specializationInfo; + // Talents + std::vector<TalentGroupInfo> _talentGroups; + uint8 _activeTalentGroup; + std::unordered_map<int32, PlayerSpellState> m_traitConfigStates; ActionButtonList m_actionButtons; diff --git a/src/server/game/Entities/Unit/TalentGroupInfo.h b/src/server/game/Entities/Unit/TalentGroupInfo.h new file mode 100644 index 00000000000..7017fa9bd33 --- /dev/null +++ b/src/server/game/Entities/Unit/TalentGroupInfo.h @@ -0,0 +1,34 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _TalentGroupInfo_h__ +#define _TalentGroupInfo_h__ + +#include "Define.h" +#include <array> +#include <unordered_map> + +constexpr uint8 MAX_GLYPHS = 9; + +struct TalentGroupInfo +{ + uint32 PrimaryTalentTabID = 0; + std::unordered_map<uint32, uint8> Talents; + std::array<uint16, MAX_GLYPHS> Glyphs = { }; +}; + +#endif // _TalentGroupInfo_h__ diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 4203742e627..55be2545110 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -232,6 +232,10 @@ bool LoginQueryHolder::Initialize() stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TALENTS, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_TALENT_GROUPS); + stmt->setUInt64(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TALENT_GROUPS, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PLAYER_ACCOUNT_DATA); stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_DATA, stmt); @@ -1275,8 +1279,6 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder const& holder) if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_TALENTS)) { pCurrChar->ResetTalents(true); - pCurrChar->ResetTalentSpecialization(); - pCurrChar->SendTalentsInfoData(); // original talents send already in to SendInitialPacketsBeforeAddToMap, resend reset state SendNotification(LANG_RESET_TALENTS); } diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp index 9892f0e3c3d..758bab9f1fd 100644 --- a/src/server/game/Handlers/SkillHandler.cpp +++ b/src/server/game/Handlers/SkillHandler.cpp @@ -26,16 +26,32 @@ #include "SpellPackets.h" #include "TalentPackets.h" -void WorldSession::HandleLearnTalentOpcode(WorldPackets::Talent::LearnTalent& /*packet*/) +void WorldSession::HandleLearnTalentOpcode(WorldPackets::Talent::LearnTalent& packet) { + if (_player->LearnTalent(packet.TalentID, packet.Rank)) + _player->SendTalentsInfoData(); } -void WorldSession::HandleLearnPreviewTalentsOpcode(WorldPackets::Talent::LearnPreviewTalents& /*packet*/) +void WorldSession::HandleLearnPreviewTalentsOpcode(WorldPackets::Talent::LearnPreviewTalents& packet) { + if (!_player->GetPrimaryTalentTree() && packet.TabIndex >= 0) + if (TalentTabEntry const* talentTab = sDB2Manager.GetTalentTabByIndex(_player->GetClassMask(), packet.TabIndex)) + _player->SetPrimaryTalentTree(talentTab->ID, true); + + for (auto const& talentInfo : packet.Talents) + if (!_player->LearnTalent(talentInfo.TalentID, talentInfo.Rank)) + break; + + _player->SendTalentsInfoData(); } -void WorldSession::HandleSetPrimaryTalentTreeOpcode(WorldPackets::Talent::SetPrimaryTalentTree& /*packet*/) +void WorldSession::HandleSetPrimaryTalentTreeOpcode(WorldPackets::Talent::SetPrimaryTalentTree& packet) { + if (_player->GetPrimaryTalentTree() != 0 || packet.TabIndex < 0) + return; + + if (TalentTabEntry const* talentTab = sDB2Manager.GetTalentTabByIndex(_player->GetClassMask(), packet.TabIndex)) + _player->SetPrimaryTalentTree(talentTab->ID, true); } void WorldSession::HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRespecWipe& confirmRespecWipe) @@ -63,7 +79,6 @@ void WorldSession::HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRe if (!_player->ResetTalents()) return; - _player->SendTalentsInfoData(); unit->CastSpell(_player, 14867, true); //spell: "Untalent Visual Effect" } diff --git a/src/server/game/Handlers/TraitHandler.cpp b/src/server/game/Handlers/TraitHandler.cpp index 1392ecf8ba9..4b458f576d1 100644 --- a/src/server/game/Handlers/TraitHandler.cpp +++ b/src/server/game/Handlers/TraitHandler.cpp @@ -23,8 +23,9 @@ #include "TraitMgr.h" #include "TraitPackets.h" -void WorldSession::HandleTraitsCommitConfig(WorldPackets::Traits::TraitsCommitConfig const& traitsCommitConfig) +void WorldSession::HandleTraitsCommitConfig(WorldPackets::Traits::TraitsCommitConfig const& /*traitsCommitConfig*/) { + /* int32 configId = traitsCommitConfig.Config.ID; UF::TraitConfig const* existingConfig = _player->GetTraitConfig(configId); if (!existingConfig) @@ -139,6 +140,7 @@ void WorldSession::HandleTraitsCommitConfig(WorldPackets::Traits::TraitsCommitCo 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 1991f944b5e..301ed235b5f 100644 --- a/src/server/game/Server/Packets/TalentPackets.cpp +++ b/src/server/game/Server/Packets/TalentPackets.cpp @@ -56,6 +56,7 @@ WorldPacket const* WorldPackets::Talent::UpdateTalentData::Write() } _worldPacket.WriteBit(Info.IsPetTalents); + _worldPacket.FlushBits(); return &_worldPacket; } @@ -63,7 +64,7 @@ WorldPacket const* WorldPackets::Talent::UpdateTalentData::Write() void WorldPackets::Talent::LearnTalent::Read() { _worldPacket >> TalentID; - _worldPacket >> TalentTab; + _worldPacket >> Rank; } WorldPacket const* WorldPackets::Talent::RespecWipeConfirm::Write() @@ -113,7 +114,7 @@ WorldPacket const* WorldPackets::Talent::ActiveGlyphs::Write() void WorldPackets::Talent::LearnPreviewTalents::Read() { Talents.resize(_worldPacket.read<uint32>()); - _worldPacket >> TalentTab; + _worldPacket >> TabIndex; for (TalentInfo& talent : Talents) _worldPacket >> talent; @@ -121,5 +122,5 @@ void WorldPackets::Talent::LearnPreviewTalents::Read() void WorldPackets::Talent::SetPrimaryTalentTree::Read() { - _worldPacket >> TalentTab; + _worldPacket >> TabIndex; } diff --git a/src/server/game/Server/Packets/TalentPackets.h b/src/server/game/Server/Packets/TalentPackets.h index d506be27a20..9a42ed42d02 100644 --- a/src/server/game/Server/Packets/TalentPackets.h +++ b/src/server/game/Server/Packets/TalentPackets.h @@ -43,7 +43,7 @@ namespace WorldPackets struct TalentInfoUpdate { - uint32 UnspentTalentPoints; + uint32 UnspentTalentPoints = 0; uint8 ActiveGroup = 0; bool IsPetTalents = false; @@ -68,7 +68,7 @@ namespace WorldPackets void Read() override; uint32 TalentID = 0; - uint16 TalentTab = 0; + uint16 Rank = 0; }; class LearnPreviewTalents final : public ClientPacket @@ -78,7 +78,7 @@ namespace WorldPackets void Read() override; - uint32 TalentTab = 0; + int32 TabIndex = 0; Array<TalentInfo, 100> Talents; }; @@ -89,7 +89,7 @@ namespace WorldPackets void Read() override; - uint32 TalentTab = 0; + int32 TabIndex = 0; }; class RespecWipeConfirm final : public ServerPacket diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 5fe1f4a00d1..be00aaa4bf9 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -6593,21 +6593,10 @@ SpellCastResult Spell::CheckCast(bool strict, int32* param1 /*= nullptr*/, int32 } case SPELL_EFFECT_TALENT_SPEC_SELECT: { - ChrSpecializationEntry const* spec = sChrSpecializationStore.LookupEntry(m_misc.SpecializationId); Player* player = m_caster->ToPlayer(); if (!player) return SPELL_FAILED_TARGET_NOT_PLAYER; - if (!spec || (spec->ClassID != player->GetClass() && !spec->IsPetSpecialization())) - return SPELL_FAILED_NO_SPEC; - - if (spec->IsPetSpecialization()) - { - Pet* pet = player->GetPet(); - if (!pet || pet->getPetType() != HUNTER_PET || !pet->GetCharmInfo()) - return SPELL_FAILED_NO_PET; - } - // can't change during already started arena/battleground if (Battleground const* bg = player->GetBattleground()) if (bg->GetStatus() == STATUS_IN_PROGRESS) diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index aea53ad3a1f..dd2df2c068c 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -379,6 +379,7 @@ class TC_GAME_API Spell void EffectTitanGrip(); void EffectEnchantItemPrismatic(); void EffectPlayMusic(); + void EffectTalentSpecCount(); void EffectActivateSpec(); void EffectPlaySound(); void EffectRemoveAura(); diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index cbc2000d8f4..c91b83e5156 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -246,7 +246,7 @@ NonDefaultConstructible<SpellEffectHandlerFn> SpellEffectHandlers[TOTAL_SPELL_EF &Spell::EffectMilling, //158 SPELL_EFFECT_MILLING milling &Spell::EffectRenamePet, //159 SPELL_EFFECT_ALLOW_RENAME_PET allow rename pet once again &Spell::EffectForceCast, //160 SPELL_EFFECT_FORCE_CAST_2 - &Spell::EffectNULL, //161 SPELL_EFFECT_TALENT_SPEC_COUNT second talent spec (learn/revert) + &Spell::EffectTalentSpecCount, //161 SPELL_EFFECT_TALENT_SPEC_COUNT second talent spec (learn/revert) &Spell::EffectActivateSpec, //162 SPELL_EFFECT_TALENT_SPEC_SELECT activate primary/secondary spec &Spell::EffectNULL, //163 SPELL_EFFECT_OBLITERATE_ITEM &Spell::EffectRemoveAura, //164 SPELL_EFFECT_REMOVE_AURA @@ -4983,23 +4983,28 @@ void Spell::EffectPlayMusic() unitTarget->ToPlayer()->SendDirectMessage(WorldPackets::Misc::PlayMusic(soundid).Write()); } +void Spell::EffectTalentSpecCount() +{ + if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) + return; + + if (!unitTarget || !unitTarget->IsPlayer() || damage <= 0) + return; + + unitTarget->ToPlayer()->SetTalentGroupCount(damage); +} + void Spell::EffectActivateSpec() { if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) return; - if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + if (!unitTarget || !unitTarget->IsPlayer() || damage <= 0) return; Player* player = unitTarget->ToPlayer(); - uint32 specID = m_misc.SpecializationId; - ChrSpecializationEntry const* spec = sChrSpecializationStore.AssertEntry(specID); - - // Safety checks done in Spell::CheckCast - if (!spec->IsPetSpecialization()) - player->ActivateTalentGroup(spec); - else - player->GetPet()->SetSpecialization(specID); + if (player->HasTalentGroupUnlocked(damage - 1)) + player->SetActiveTalentGroup(damage - 1); } void Spell::EffectPlaySound() @@ -5321,7 +5326,7 @@ void Spell::EffectRemoveTalent() if (!player) return; - player->RemoveTalent(talent); + //player->RemoveTalent(talent); player->SendTalentsInfoData(); } diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index d79ae4627df..fa13dadd5a6 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -810,8 +810,69 @@ void SpellMgr::UnloadSpellInfoChains() mSpellChains.clear(); } +void SpellMgr::LoadSpellTalentRanks() +{ + // cleanup core data before reload - remove reference to ChainNode from SpellInfo + UnloadSpellInfoChains(); + + for (TalentEntry const* talentInfo : sTalentStore) + { + SpellInfo const* lastSpell = nullptr; + for (size_t rank = talentInfo->SpellRank.size() - 1; rank > 0; --rank) + { + if (talentInfo->SpellRank[rank]) + { + lastSpell = GetSpellInfo(talentInfo->SpellRank[rank], DIFFICULTY_NONE); + break; + } + } + + if (!lastSpell) + continue; + + SpellInfo const* firstSpell = GetSpellInfo(talentInfo->SpellRank[0], DIFFICULTY_NONE); + if (!firstSpell) + { + TC_LOG_ERROR("spells", "SpellMgr::LoadSpellTalentRanks: First Rank Spell {} for TalentEntry {} does not exist.", talentInfo->SpellRank[0], talentInfo->ID); + continue; + } + + SpellInfo const* prevSpell = nullptr; + for (uint8 rank = 0; rank < talentInfo->SpellRank.size(); ++rank) + { + uint32 spellId = talentInfo->SpellRank[rank]; + if (!spellId) + break; + + SpellInfo const* currentSpell = GetSpellInfo(spellId, DIFFICULTY_NONE); + if (!currentSpell) + { + TC_LOG_ERROR("spells", "SpellMgr::LoadSpellTalentRanks: Spell {} (Rank: {}) for TalentEntry {} does not exist.", spellId, rank + 1, talentInfo->ID); + break; + } + + SpellChainNode node; + node.first = firstSpell; + node.last = lastSpell; + node.rank = rank + 1; + + node.prev = prevSpell; + node.next = node.rank < talentInfo->SpellRank.size() ? GetSpellInfo(talentInfo->SpellRank[node.rank], DIFFICULTY_NONE) : nullptr; + + mSpellChains[spellId] = node; + for (SpellInfo const& difficultyInfo : _GetSpellInfo(spellId)) + const_cast<SpellInfo&>(difficultyInfo).ChainEntry = &mSpellChains[spellId]; + + prevSpell = currentSpell; + } + } +} + void SpellMgr::LoadSpellRanks() { + // cleanup data and load spell ranks for talents from db2 + LoadSpellTalentRanks(); + uint32 oldMSTime = getMSTime(); std::map<uint32 /*spell*/, uint32 /*next*/> chains; @@ -3003,7 +3064,8 @@ void SpellMgr::LoadSpellInfoCustomAttributes() std::set<uint32> talentSpells; for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i) if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(i)) - talentSpells.insert(talentInfo->SpellID); + for (uint32 talentSpell : talentInfo->SpellRank) + talentSpells.insert(talentSpell); for (SpellInfo const& spellInfo : mSpellInfoMap) { diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h index 1771e04676b..91470991901 100644 --- a/src/server/game/Spells/SpellMgr.h +++ b/src/server/game/Spells/SpellMgr.h @@ -792,6 +792,7 @@ class TC_GAME_API SpellMgr // Loading data at server startup void UnloadSpellInfoChains(); + void LoadSpellTalentRanks(); void LoadSpellRanks(); void LoadSpellRequired(); void LoadSpellLearnSkills(); diff --git a/src/server/scripts/Commands/cs_learn.cpp b/src/server/scripts/Commands/cs_learn.cpp index 2bbcf492c4a..b8b43344dff 100644 --- a/src/server/scripts/Commands/cs_learn.cpp +++ b/src/server/scripts/Commands/cs_learn.cpp @@ -202,8 +202,8 @@ public: if (!spellInfo || !SpellMgr::IsSpellValid(spellInfo, handler->GetSession()->GetPlayer(), false)) continue; - player->AddTalent(talentInfo, player->GetActiveTalentGroup(), true); - player->LearnSpell(talentInfo->SpellID, false); + //player->AddTalent(talentInfo, player->GetActiveTalentGroup(), true); + //player->LearnSpell(talentInfo->SpellID, false); } player->SendTalentsInfoData(); diff --git a/src/server/scripts/Commands/cs_reset.cpp b/src/server/scripts/Commands/cs_reset.cpp index 56c5f8b0ef8..6870f822ebd 100644 --- a/src/server/scripts/Commands/cs_reset.cpp +++ b/src/server/scripts/Commands/cs_reset.cpp @@ -237,7 +237,6 @@ public: if (target) { target->ResetTalents(true); - target->ResetTalentSpecialization(); target->SendTalentsInfoData(); ChatHandler(target->GetSession()).SendSysMessage(LANG_RESET_TALENTS); if (!handler->GetSession() || handler->GetSession()->GetPlayer() != target) |