aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOvahlord <dreadkiller@gmx.de>2024-07-27 01:16:01 +0200
committerOvahlord <dreadkiller@gmx.de>2024-07-27 11:03:38 +0200
commit03b6898518e9321fdb1b805b7757bf4801fa8d08 (patch)
treeae2f2f5d32f4e2be01d6a7dfee1f344cdfeef83d /src
parentacfba7fa1497d583a3c814db6e0161fe72bdfec9 (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')
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.cpp7
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.h3
-rw-r--r--src/server/database/Database/Implementation/HotfixDatabase.cpp7
-rw-r--r--src/server/database/Database/Implementation/HotfixDatabase.h4
-rw-r--r--src/server/game/DataStores/DB2LoadInfo.h15
-rw-r--r--src/server/game/DataStores/DB2Stores.cpp32
-rw-r--r--src/server/game/DataStores/DB2Stores.h3
-rw-r--r--src/server/game/DataStores/DB2Structure.h10
-rw-r--r--src/server/game/Entities/Pet/Pet.cpp4
-rw-r--r--src/server/game/Entities/Player/Player.cpp777
-rw-r--r--src/server/game/Entities/Player/Player.h46
-rw-r--r--src/server/game/Entities/Unit/TalentGroupInfo.h34
-rw-r--r--src/server/game/Handlers/CharacterHandler.cpp6
-rw-r--r--src/server/game/Handlers/SkillHandler.cpp23
-rw-r--r--src/server/game/Handlers/TraitHandler.cpp4
-rw-r--r--src/server/game/Server/Packets/TalentPackets.cpp7
-rw-r--r--src/server/game/Server/Packets/TalentPackets.h8
-rw-r--r--src/server/game/Spells/Spell.cpp11
-rw-r--r--src/server/game/Spells/Spell.h1
-rw-r--r--src/server/game/Spells/SpellEffects.cpp27
-rw-r--r--src/server/game/Spells/SpellMgr.cpp64
-rw-r--r--src/server/game/Spells/SpellMgr.h1
-rw-r--r--src/server/scripts/Commands/cs_learn.cpp4
-rw-r--r--src/server/scripts/Commands/cs_reset.cpp1
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)