aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2024-08-10 12:01:47 +0200
committerShauren <shauren.trinity@gmail.com>2024-08-10 12:01:47 +0200
commitd1ffe61727f53505a3e2b94cae32f2ce3d28b07b (patch)
tree73077cb334bf8d67324ca8eca4e1bee9690a5bb1
parent5912f4dd7ae311a0e8315f4d75b63c465ad9a472 (diff)
Core/Players: Implemented hero talents
-rw-r--r--sql/updates/hotfixes/master/2024_08_10_00_hotfixes.sql37
-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.h14
-rw-r--r--src/server/game/DataStores/DB2Stores.cpp2
-rw-r--r--src/server/game/DataStores/DB2Stores.h1
-rw-r--r--src/server/game/DataStores/DB2Structure.h9
-rw-r--r--src/server/game/Entities/Player/Player.cpp68
-rw-r--r--src/server/game/Handlers/TraitHandler.cpp31
-rw-r--r--src/server/game/Server/Packets/TraitPacketsCommon.cpp2
-rw-r--r--src/server/game/Spells/TraitMgr.cpp161
-rw-r--r--src/server/game/Spells/TraitMgr.h2
12 files changed, 314 insertions, 24 deletions
diff --git a/sql/updates/hotfixes/master/2024_08_10_00_hotfixes.sql b/sql/updates/hotfixes/master/2024_08_10_00_hotfixes.sql
new file mode 100644
index 00000000000..297325106f0
--- /dev/null
+++ b/sql/updates/hotfixes/master/2024_08_10_00_hotfixes.sql
@@ -0,0 +1,37 @@
+--
+-- Table structure for table `trait_sub_tree`
+--
+DROP TABLE IF EXISTS `trait_sub_tree`;
+CREATE TABLE `trait_sub_tree` (
+ `Name` text,
+ `Description` text,
+ `ID` int unsigned NOT NULL DEFAULT '0',
+ `UiTextureAtlasElementID` int NOT NULL DEFAULT '0',
+ `TraitTreeID` int NOT NULL DEFAULT '0',
+ `VerifiedBuild` int NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`,`VerifiedBuild`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Table structure for table `trait_sub_tree_locale`
+--
+DROP TABLE IF EXISTS `trait_sub_tree_locale`;
+CREATE TABLE `trait_sub_tree_locale` (
+ `ID` int unsigned NOT NULL DEFAULT '0',
+ `locale` varchar(4) NOT NULL,
+ `Name_lang` text,
+ `Description_lang` text,
+ `VerifiedBuild` int NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
+PARTITION BY LIST COLUMNS(locale)
+(PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB,
+ PARTITION esES VALUES IN ('esES') ENGINE = InnoDB,
+ PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB,
+ PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB,
+ PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB,
+ PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB,
+ PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB,
+ PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB,
+ PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB,
+ PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB);
diff --git a/src/server/database/Database/Implementation/HotfixDatabase.cpp b/src/server/database/Database/Implementation/HotfixDatabase.cpp
index 7dc74b23847..9f004e89e84 100644
--- a/src/server/database/Database/Implementation/HotfixDatabase.cpp
+++ b/src/server/database/Database/Implementation/HotfixDatabase.cpp
@@ -1852,6 +1852,13 @@ void HotfixDatabaseConnection::DoPrepareStatements()
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY, "SELECT MAX(ID) + 1 FROM trait_node_x_trait_node_entry", CONNECTION_SYNCH);
+ // TraitSubTree.db2
+ PrepareStatement(HOTFIX_SEL_TRAIT_SUB_TREE, "SELECT Name, Description, ID, UiTextureAtlasElementID, TraitTreeID FROM trait_sub_tree"
+ " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
+ PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_SUB_TREE, "SELECT MAX(ID) + 1 FROM trait_sub_tree", CONNECTION_SYNCH);
+ PREPARE_LOCALE_STMT(HOTFIX_SEL_TRAIT_SUB_TREE, "SELECT ID, Name_lang, Description_lang FROM trait_sub_tree_locale WHERE (`VerifiedBuild` > 0) = ?"
+ " AND locale = ?", CONNECTION_SYNCH);
+
// TraitTree.db2
PrepareStatement(HOTFIX_SEL_TRAIT_TREE, "SELECT ID, TraitSystemID, Unused1000_1, FirstTraitNodeID, PlayerConditionID, Flags, Unused1000_2, "
"Unused1000_3 FROM trait_tree WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
diff --git a/src/server/database/Database/Implementation/HotfixDatabase.h b/src/server/database/Database/Implementation/HotfixDatabase.h
index c08ea0d074a..7dc81074692 100644
--- a/src/server/database/Database/Implementation/HotfixDatabase.h
+++ b/src/server/database/Database/Implementation/HotfixDatabase.h
@@ -1076,6 +1076,10 @@ enum HotfixDatabaseStatements : uint32
HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY,
HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY_MAX_ID,
+ HOTFIX_SEL_TRAIT_SUB_TREE,
+ HOTFIX_SEL_TRAIT_SUB_TREE_MAX_ID,
+ HOTFIX_SEL_TRAIT_SUB_TREE_LOCALE,
+
HOTFIX_SEL_TRAIT_TREE,
HOTFIX_SEL_TRAIT_TREE_MAX_ID,
diff --git a/src/server/game/DataStores/DB2LoadInfo.h b/src/server/game/DataStores/DB2LoadInfo.h
index d68f101603a..27e44e80002 100644
--- a/src/server/game/DataStores/DB2LoadInfo.h
+++ b/src/server/game/DataStores/DB2LoadInfo.h
@@ -6089,6 +6089,20 @@ struct TraitNodeXTraitNodeEntryLoadInfo
static constexpr DB2LoadInfo Instance{ Fields, 4, &TraitNodeXTraitNodeEntryMeta::Instance, HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY };
};
+struct TraitSubTreeLoadInfo
+{
+ static constexpr DB2FieldMeta Fields[5] =
+ {
+ { false, FT_STRING, "Name" },
+ { false, FT_STRING, "Description" },
+ { false, FT_INT, "ID" },
+ { true, FT_INT, "UiTextureAtlasElementID" },
+ { true, FT_INT, "TraitTreeID" },
+ };
+
+ static constexpr DB2LoadInfo Instance{ Fields, 5, &TraitSubTreeMeta::Instance, HOTFIX_SEL_TRAIT_SUB_TREE };
+};
+
struct TraitTreeLoadInfo
{
static constexpr DB2FieldMeta Fields[8] =
diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp
index 84611d89d31..f2c47a79b41 100644
--- a/src/server/game/DataStores/DB2Stores.cpp
+++ b/src/server/game/DataStores/DB2Stores.cpp
@@ -359,6 +359,7 @@ DB2Storage<TraitNodeGroupXTraitNodeEntry> sTraitNodeGroupXTraitNodeStore("
DB2Storage<TraitNodeXTraitCondEntry> sTraitNodeXTraitCondStore("TraitNodeXTraitCond.db2", &TraitNodeXTraitCondLoadInfo::Instance);
DB2Storage<TraitNodeXTraitCostEntry> sTraitNodeXTraitCostStore("TraitNodeXTraitCost.db2", &TraitNodeXTraitCostLoadInfo::Instance);
DB2Storage<TraitNodeXTraitNodeEntryEntry> sTraitNodeXTraitNodeEntryStore("TraitNodeXTraitNodeEntry.db2", &TraitNodeXTraitNodeEntryLoadInfo::Instance);
+DB2Storage<TraitSubTreeEntry> sTraitSubTreeStore("TraitSubTree.db2", &TraitSubTreeLoadInfo::Instance);
DB2Storage<TraitTreeEntry> sTraitTreeStore("TraitTree.db2", &TraitTreeLoadInfo::Instance);
DB2Storage<TraitTreeLoadoutEntry> sTraitTreeLoadoutStore("TraitTreeLoadout.db2", &TraitTreeLoadoutLoadInfo::Instance);
DB2Storage<TraitTreeLoadoutEntryEntry> sTraitTreeLoadoutEntryStore("TraitTreeLoadoutEntry.db2", &TraitTreeLoadoutEntryLoadInfo::Instance);
@@ -970,6 +971,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
LOAD_DB2(sTraitNodeXTraitCondStore);
LOAD_DB2(sTraitNodeXTraitCostStore);
LOAD_DB2(sTraitNodeXTraitNodeEntryStore);
+ LOAD_DB2(sTraitSubTreeStore);
LOAD_DB2(sTraitTreeStore);
LOAD_DB2(sTraitTreeLoadoutStore);
LOAD_DB2(sTraitTreeLoadoutEntryStore);
diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h
index 210333e5ea3..2977a1d4ad7 100644
--- a/src/server/game/DataStores/DB2Stores.h
+++ b/src/server/game/DataStores/DB2Stores.h
@@ -285,6 +285,7 @@ TC_GAME_API extern DB2Storage<TraitNodeGroupXTraitNodeEntry> sTraitNodeGr
TC_GAME_API extern DB2Storage<TraitNodeXTraitCondEntry> sTraitNodeXTraitCondStore;
TC_GAME_API extern DB2Storage<TraitNodeXTraitCostEntry> sTraitNodeXTraitCostStore;
TC_GAME_API extern DB2Storage<TraitNodeXTraitNodeEntryEntry> sTraitNodeXTraitNodeEntryStore;
+TC_GAME_API extern DB2Storage<TraitSubTreeEntry> sTraitSubTreeStore;
TC_GAME_API extern DB2Storage<TraitTreeEntry> sTraitTreeStore;
TC_GAME_API extern DB2Storage<TraitTreeLoadoutEntry> sTraitTreeLoadoutStore;
TC_GAME_API extern DB2Storage<TraitTreeLoadoutEntryEntry> sTraitTreeLoadoutEntryStore;
diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h
index 64d723fb500..766f9f19dc8 100644
--- a/src/server/game/DataStores/DB2Structure.h
+++ b/src/server/game/DataStores/DB2Structure.h
@@ -4349,6 +4349,15 @@ struct TraitNodeXTraitNodeEntryEntry
int32 Index;
};
+struct TraitSubTreeEntry
+{
+ LocalizedString Name;
+ LocalizedString Description;
+ uint32 ID;
+ int32 UiTextureAtlasElementID;
+ int32 TraitTreeID; // Parent tree
+};
+
struct TraitTreeEntry
{
uint32 ID;
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index f539f903104..8bdef90b5cf 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -27854,8 +27854,12 @@ void Player::_LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult
if (TraitMgr::ValidateConfig(traitConfig, this, false, true) != TraitMgr::LearnResult::Ok)
{
traitConfig.Entries.clear();
+ traitConfig.SubTrees.clear();
for (UF::TraitEntry const& grantedEntry : TraitMgr::GetGrantedTraitEntriesForConfig(traitConfig, this))
traitConfig.Entries.emplace_back(grantedEntry);
+
+ // rebuild subtrees
+ TraitMgr::ValidateConfig(traitConfig, this, false, true);
}
AddTraitConfig(traitConfig);
@@ -28395,6 +28399,22 @@ void Player::AddTraitConfig(WorldPackets::Traits::TraitConfig const& traitConfig
newEntry.Rank = traitEntry.Rank;
newEntry.GrantedRanks = traitEntry.GrantedRanks;
}
+
+ for (WorldPackets::Traits::TraitSubTreeCache const& traitSubTree : traitConfig.SubTrees)
+ {
+ UF::TraitSubTreeCache& newSubTree = AddDynamicUpdateFieldValue(setter.ModifyValue(&UF::TraitConfig::SubTrees));
+ newSubTree.TraitSubTreeID = traitSubTree.TraitSubTreeID;
+ newSubTree.Active = traitSubTree.Active;
+
+ for (WorldPackets::Traits::TraitEntry const& traitEntry : traitSubTree.Entries)
+ {
+ UF::TraitEntry& newEntry = newSubTree.Entries.emplace_back();
+ newEntry.TraitNodeID = traitEntry.TraitNodeID;
+ newEntry.TraitNodeEntryID = traitEntry.TraitNodeEntryID;
+ newEntry.Rank = traitEntry.Rank;
+ newEntry.GrantedRanks = traitEntry.GrantedRanks;
+ }
+ }
}
UF::TraitConfig const* Player::GetTraitConfig(int32 configId) const
@@ -28478,7 +28498,7 @@ void Player::ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::
for (int32 i = 0; i < int32(editedConfig.Entries.size()); ++i)
{
UF::TraitEntry const& oldEntry = editedConfig.Entries[i];
- auto entryItr = std::find_if(newConfig.Entries.begin(), newConfig.Entries.end(), makeTraitEntryFinder(oldEntry.TraitNodeID, oldEntry.TraitNodeEntryID));
+ auto entryItr = std::ranges::find_if(newConfig.Entries, makeTraitEntryFinder(oldEntry.TraitNodeID, oldEntry.TraitNodeEntryID));
if (entryItr != newConfig.Entries.end())
continue;
@@ -28567,6 +28587,49 @@ void Player::ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::
}
}
+ for (std::size_t i = 0; i < newConfig.SubTrees.size(); ++i)
+ {
+ WorldPackets::Traits::TraitSubTreeCache const& newSubTree = newConfig.SubTrees[i];
+ int32 oldSubTreeIndex = editedConfig.SubTrees.FindIndexIf([&](UF::TraitSubTreeCache const& ufSubTree) { return ufSubTree.TraitSubTreeID == newSubTree.TraitSubTreeID; });
+ std::vector<UF::TraitEntry> subTreeEntries;
+ subTreeEntries.resize(newSubTree.Entries.size());
+ for (std::size_t j = 0; j < newSubTree.Entries.size(); ++j)
+ {
+ UF::TraitEntry& newUfEntry = subTreeEntries[j];
+ newUfEntry.TraitNodeID = newSubTree.Entries[j].TraitNodeID;
+ newUfEntry.TraitNodeEntryID = newSubTree.Entries[j].TraitNodeEntryID;
+ newUfEntry.Rank = newSubTree.Entries[j].Rank;
+ newUfEntry.GrantedRanks = newSubTree.Entries[j].GrantedRanks;
+ }
+ if (oldSubTreeIndex < 0)
+ {
+ UF::TraitSubTreeCache& newUfSubTree = AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData)
+ .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex)
+ .ModifyValue(&UF::TraitConfig::SubTrees));
+ newUfSubTree.TraitSubTreeID = newSubTree.TraitSubTreeID;
+ newUfSubTree.Active = newSubTree.Active;
+ newUfSubTree.Entries = std::move(subTreeEntries);
+ }
+ else
+ {
+ bool wasActive = m_activePlayerData->TraitConfigs[editedIndex].SubTrees[oldSubTreeIndex].Active != 0;
+
+ SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData)
+ .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex)
+ .ModifyValue(&UF::TraitConfig::SubTrees, oldSubTreeIndex)
+ .ModifyValue(&UF::TraitSubTreeCache::Active), newSubTree.Active);
+
+ SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData)
+ .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex)
+ .ModifyValue(&UF::TraitConfig::SubTrees, oldSubTreeIndex)
+ .ModifyValue(&UF::TraitSubTreeCache::Entries), std::move(subTreeEntries));
+
+ if (applyTraits && wasActive != newSubTree.Active)
+ for (WorldPackets::Traits::TraitEntry const& subTreeEntry : newSubTree.Entries)
+ ApplyTraitEntry(subTreeEntry.TraitNodeEntryID, subTreeEntry.Rank, subTreeEntry.GrantedRanks, newSubTree.Active);
+ }
+ }
+
m_traitConfigStates[editedConfigId] = PLAYERSPELL_CHANGED;
}
@@ -28612,7 +28675,8 @@ void Player::ApplyTraitConfig(int32 configId, bool apply)
return;
for (UF::TraitEntry const& traitEntry : traitConfig->Entries)
- ApplyTraitEntry(traitEntry.TraitNodeEntryID, traitEntry.Rank, traitEntry.GrantedRanks, apply);
+ if (!apply || TraitMgr::CanApplyTraitNode(*traitConfig, traitEntry))
+ ApplyTraitEntry(traitEntry.TraitNodeEntryID, traitEntry.Rank, traitEntry.GrantedRanks, apply);
}
void Player::ApplyTraitEntry(int32 traitNodeEntryId, int32 /*rank*/, int32 /*grantedRanks*/, bool apply)
diff --git a/src/server/game/Handlers/TraitHandler.cpp b/src/server/game/Handlers/TraitHandler.cpp
index 1392ecf8ba9..1e1de92a139 100644
--- a/src/server/game/Handlers/TraitHandler.cpp
+++ b/src/server/game/Handlers/TraitHandler.cpp
@@ -88,23 +88,26 @@ void WorldSession::HandleTraitsCommitConfig(WorldPackets::Traits::TraitsCommitCo
return;
}
- TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(traitNodeEntry->TraitDefinitionID);
- if (!traitDefinition)
+ if (traitNodeEntry->TraitDefinitionID)
{
- SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
- return;
- }
+ TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(traitNodeEntry->TraitDefinitionID);
+ if (!traitDefinition)
+ {
+ SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
+ return;
+ }
- if (traitDefinition->SpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->SpellID))
- {
- SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->SpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
- return;
- }
+ if (traitDefinition->SpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->SpellID))
+ {
+ SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->SpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
+ return;
+ }
- if (traitDefinition->VisibleSpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->VisibleSpellID))
- {
- SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->VisibleSpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
- return;
+ if (traitDefinition->VisibleSpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->VisibleSpellID))
+ {
+ SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->VisibleSpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
+ return;
+ }
}
hasRemovedEntries = true;
diff --git a/src/server/game/Server/Packets/TraitPacketsCommon.cpp b/src/server/game/Server/Packets/TraitPacketsCommon.cpp
index 0d3158ef4b9..632376e615f 100644
--- a/src/server/game/Server/Packets/TraitPacketsCommon.cpp
+++ b/src/server/game/Server/Packets/TraitPacketsCommon.cpp
@@ -54,6 +54,8 @@ TraitConfig::TraitConfig(UF::TraitConfig const& ufConfig)
TraitSystemID = ufConfig.TraitSystemID;
for (UF::TraitEntry const& ufEntry : ufConfig.Entries)
Entries.emplace_back(ufEntry);
+ for (UF::TraitSubTreeCache const& ufSubTree : ufConfig.SubTrees)
+ SubTrees.emplace_back(ufSubTree);
Name = ufConfig.Name;
}
diff --git a/src/server/game/Spells/TraitMgr.cpp b/src/server/game/Spells/TraitMgr.cpp
index 32b8c179545..64df1f2d0de 100644
--- a/src/server/game/Spells/TraitMgr.cpp
+++ b/src/server/game/Spells/TraitMgr.cpp
@@ -17,6 +17,7 @@
#include "TraitMgr.h"
#include "DB2Stores.h"
+#include "FlatSet.h"
#include "IteratorPair.h"
#include "MapUtils.h"
#include "TraitPacketsCommon.h"
@@ -29,6 +30,7 @@ namespace
struct NodeEntry;
struct Node;
struct NodeGroup;
+struct SubTree;
struct Tree;
struct NodeEntry
@@ -56,17 +58,26 @@ struct NodeGroup
std::vector<Node const*> Nodes;
};
+struct SubTree
+{
+ TraitSubTreeEntry const* Data = nullptr;
+ std::vector<Node const*> Nodes;
+ Trinity::Containers::FlatSet<TraitCurrencyEntry const*> Currencies;
+};
+
struct Tree
{
TraitTreeEntry const* Data = nullptr;
std::vector<Node const*> Nodes;
std::vector<TraitCostEntry const*> Costs;
std::vector<TraitCurrencyEntry const*> Currencies;
+ std::vector<SubTree const*> SubTrees;
TraitConfigType ConfigType = TraitConfigType::Invalid;
};
std::unordered_map<int32, NodeGroup> _traitGroups;
std::unordered_map<int32, Node> _traitNodes;
+std::unordered_map<int32, SubTree> _traitSubTrees;
std::unordered_map<int32, Tree> _traitTrees;
std::array<int32, MAX_CLASSES> _skillLinesByClass;
std::unordered_map<int32, std::vector<Tree const*>> _traitTreesBySkillLine;
@@ -125,10 +136,10 @@ void Load()
if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitTreeXTraitCostEntry->TraitCostID))
treeCosts[traitTreeXTraitCostEntry->TraitTreeID].push_back(traitCostEntry);
- std::unordered_map<int32, std::vector<TraitCurrencyEntry const*>> treeCurrencies;
+ std::unordered_map<int32, std::vector<TraitTreeXTraitCurrencyEntry const*>> treeCurrencies;
for (TraitTreeXTraitCurrencyEntry const* traitTreeXTraitCurrencyEntry : sTraitTreeXTraitCurrencyStore)
- if (TraitCurrencyEntry const* traitCurrencyEntry = sTraitCurrencyStore.LookupEntry(traitTreeXTraitCurrencyEntry->TraitCurrencyID))
- treeCurrencies[traitTreeXTraitCurrencyEntry->TraitTreeID].push_back(traitCurrencyEntry);
+ if (sTraitCurrencyStore.HasRecord(traitTreeXTraitCurrencyEntry->TraitCurrencyID))
+ treeCurrencies[traitTreeXTraitCurrencyEntry->TraitTreeID].push_back(traitTreeXTraitCurrencyEntry);
std::unordered_map<int32, std::vector<int32>> traitTreesIdsByTraitSystem;
@@ -140,8 +151,14 @@ void Load()
if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(treeCosts, traitTree->ID))
tree.Costs = std::move(*costs);
- if (std::vector<TraitCurrencyEntry const*>* currencies = Trinity::Containers::MapGetValuePtr(treeCurrencies, traitTree->ID))
- tree.Currencies = std::move(*currencies);
+ if (std::vector<TraitTreeXTraitCurrencyEntry const*>* currencies = Trinity::Containers::MapGetValuePtr(treeCurrencies, traitTree->ID))
+ {
+ tree.Currencies.resize(currencies->size());
+ std::ranges::sort(*currencies, {}, &TraitTreeXTraitCurrencyEntry::Index);
+ std::ranges::transform(*currencies, tree.Currencies.begin(),
+ [](uint32 traitCurrencyId) { return sTraitCurrencyStore.AssertEntry(traitCurrencyId); },
+ &TraitTreeXTraitCurrencyEntry::TraitCurrencyID);
+ }
if (traitTree->TraitSystemID)
{
@@ -150,6 +167,15 @@ void Load()
}
}
+ for (TraitSubTreeEntry const* traitSubTree : sTraitSubTreeStore)
+ {
+ SubTree& subTree = _traitSubTrees[traitSubTree->ID];
+ subTree.Data = traitSubTree;
+
+ if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitSubTree->TraitTreeID))
+ tree->SubTrees.push_back(&subTree);
+ }
+
for (TraitNodeGroupEntry const* traitNodeGroup : sTraitNodeGroupStore)
{
NodeGroup& nodeGroup = _traitGroups[traitNodeGroup->ID];
@@ -197,6 +223,30 @@ void Load()
if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeCosts, traitNode->ID))
node.Costs = std::move(*costs);
+
+ if (SubTree* subTree = Trinity::Containers::MapGetValuePtr(_traitSubTrees, traitNode->TraitSubTreeID))
+ {
+ subTree->Nodes.push_back(&node);
+
+ for (NodeEntry const& nodeEntry : node.Entries)
+ for (TraitCostEntry const* cost : nodeEntry.Costs)
+ if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
+ subTree->Currencies.insert(traitCurrency);
+
+ for (NodeGroup const* nodeGroup : node.Groups)
+ for (TraitCostEntry const* cost : nodeGroup->Costs)
+ if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
+ subTree->Currencies.insert(traitCurrency);
+
+ for (TraitCostEntry const* cost : node.Costs)
+ if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
+ subTree->Currencies.insert(traitCurrency);
+
+ if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitNode->TraitTreeID))
+ for (TraitCostEntry const* cost : tree->Costs)
+ if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
+ subTree->Currencies.insert(traitCurrency);
+ }
}
for (TraitEdgeEntry const* traitEdgeEntry : sTraitEdgeStore)
@@ -442,6 +492,30 @@ void FillSpentCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig
FillSpentCurrenciesMap(entry, cachedCurrencies);
}
+std::array<int32, 2> GetClassAndSpecTreeCurrencies(WorldPackets::Traits::TraitConfig const& traitConfig)
+{
+ std::array<int32, 2> currencies = {};
+
+ if (std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig))
+ {
+ auto dest = currencies.begin();
+ for (auto treeItr = trees->begin(); treeItr != trees->end() && dest != currencies.end(); ++treeItr)
+ for (auto currencyItr = (*treeItr)->Currencies.begin(); currencyItr != (*treeItr)->Currencies.end() && dest != currencies.end(); ++currencyItr)
+ *dest++ = (*currencyItr)->ID;
+ }
+
+ return currencies;
+}
+
+std::span<TraitCurrencyEntry const* const> GetSubTreeCurrency(int32 traitSubTreeId)
+{
+ SubTree const* subTree = Trinity::Containers::MapGetValuePtr(_traitSubTrees, traitSubTreeId);
+ if (!subTree)
+ return {};
+
+ return subTree->Currencies;
+}
+
bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player, TraitCondEntry const* condition,
Optional<std::map<int32, int32>>& cachedCurrencies)
{
@@ -616,7 +690,7 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
return LearnResult::Unknown;
for (NodeEntry const& entry : node->Entries)
- if (!meetsConditions(entry.Conditions))
+ if (int32(entry.Data->ID) == traitEntry.TraitNodeEntryID && !meetsConditions(entry.Conditions))
return LearnResult::Unknown;
if (!meetsConditions(node->Conditions))
@@ -670,6 +744,42 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
++itr;
}
+ struct SubtreeValidationData
+ {
+ std::vector<WorldPackets::Traits::TraitEntry> Entries;
+ bool IsSelected = false;
+ };
+ std::unordered_map<int32, SubtreeValidationData> subtrees;
+
+ for (WorldPackets::Traits::TraitEntry const& traitEntry : traitConfig.Entries)
+ {
+ Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
+ auto entryItr = std::ranges::find(node->Entries, traitEntry.TraitNodeEntryID, [](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID); });
+ ASSERT(entryItr != node->Entries.end());
+
+ if (node->Data->GetType() == TraitNodeType::SubTreeSelection)
+ subtrees[entryItr->Data->TraitSubTreeID].IsSelected = true;
+
+ if (node->Data->TraitSubTreeID)
+ subtrees[node->Data->TraitSubTreeID].Entries.push_back(traitEntry);
+ }
+
+ for (WorldPackets::Traits::TraitSubTreeCache& subTree : traitConfig.SubTrees)
+ subTree.Active = false;
+
+ for (auto&& [selectedSubTreeId, data] : subtrees)
+ {
+ auto subtreeDataItr = std::ranges::find(traitConfig.SubTrees, selectedSubTreeId, &WorldPackets::Traits::TraitSubTreeCache::TraitSubTreeID);
+ if (subtreeDataItr == std::ranges::end(traitConfig.SubTrees))
+ {
+ subtreeDataItr = traitConfig.SubTrees.emplace(traitConfig.SubTrees.end());
+ subtreeDataItr->TraitSubTreeID = selectedSubTreeId;
+ }
+
+ subtreeDataItr->Entries = std::move(data.Entries);
+ subtreeDataItr->Active = data.IsSelected;
+ }
+
std::map<int32, int32> grantedCurrencies;
FillOwnedCurrenciesMap(traitConfig, player, grantedCurrencies);
@@ -688,20 +798,55 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
if (requireSpendingAllCurrencies && traitConfig.Type == TraitConfigType::Combat)
{
- for (auto [traitCurrencyId, grantedAmount] : grantedCurrencies)
+ // client checks only first two currencies for trait tree
+ for (int32 traitCurrencyId : GetClassAndSpecTreeCurrencies(traitConfig))
{
+ int32* grantedAmount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, traitCurrencyId);
if (!grantedAmount)
continue;
int32* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, traitCurrencyId);
- if (!spentAmount || *spentAmount != grantedAmount)
+ if (!spentAmount || *spentAmount != *grantedAmount)
return LearnResult::UnspentTalentPoints;
}
+
+ for (auto&& [selectedTraitSubTreeId, data] : subtrees)
+ {
+ if (!data.IsSelected)
+ continue;
+
+ for (TraitCurrencyEntry const* subTreeCurrency : GetSubTreeCurrency(selectedTraitSubTreeId))
+ {
+ int32* grantedAmount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, subTreeCurrency->ID);
+ if (!grantedAmount)
+ continue;
+
+ int32* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, subTreeCurrency->ID);
+ if (!spentAmount || *spentAmount != *grantedAmount)
+ return LearnResult::UnspentTalentPoints;
+ }
+ }
}
return LearnResult::Ok;
}
+bool CanApplyTraitNode(UF::TraitConfig const& traitConfig, UF::TraitEntry const& traitEntry)
+{
+ Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
+ if (!node)
+ return false;
+
+ if (node->Data->TraitSubTreeID)
+ {
+ auto subTreeItr = std::ranges::find(traitConfig.SubTrees, node->Data->TraitSubTreeID, &UF::TraitSubTreeCache::TraitSubTreeID);
+ if (subTreeItr == std::ranges::end(traitConfig.SubTrees) || !subTreeItr->Active)
+ return false;
+ }
+
+ return true;
+}
+
std::vector<TraitDefinitionEffectPointsEntry const*> const* GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId)
{
return Trinity::Containers::MapGetValuePtr(_traitDefinitionEffectPointModifiers, traitDefinitionId);
diff --git a/src/server/game/Spells/TraitMgr.h b/src/server/game/Spells/TraitMgr.h
index 611be5b49be..5e446a56e62 100644
--- a/src/server/game/Spells/TraitMgr.h
+++ b/src/server/game/Spells/TraitMgr.h
@@ -29,6 +29,7 @@ enum TalentLearnResult : int32;
namespace UF
{
+struct TraitConfig;
struct TraitEntry;
}
@@ -80,6 +81,7 @@ void FillSpentCurrenciesMap(WorldPackets::Traits::TraitEntry const& entry, std::
std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player);
bool IsValidEntry(WorldPackets::Traits::TraitEntry const& traitEntry);
LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies = false, bool removeInvalidEntries = false);
+bool CanApplyTraitNode(UF::TraitConfig const& traitConfig, UF::TraitEntry const& traitEntry);
std::vector<TraitDefinitionEffectPointsEntry const*> const* GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId);
void InitializeStarterBuildTraitConfig(WorldPackets::Traits::TraitConfig& traitConfig, PlayerDataAccessor player);
}