diff options
author | Shauren <shauren.trinity@gmail.com> | 2024-08-10 12:01:47 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2024-08-10 12:01:47 +0200 |
commit | d1ffe61727f53505a3e2b94cae32f2ce3d28b07b (patch) | |
tree | 73077cb334bf8d67324ca8eca4e1bee9690a5bb1 | |
parent | 5912f4dd7ae311a0e8315f4d75b63c465ad9a472 (diff) |
Core/Players: Implemented hero talents
-rw-r--r-- | sql/updates/hotfixes/master/2024_08_10_00_hotfixes.sql | 37 | ||||
-rw-r--r-- | src/server/database/Database/Implementation/HotfixDatabase.cpp | 7 | ||||
-rw-r--r-- | src/server/database/Database/Implementation/HotfixDatabase.h | 4 | ||||
-rw-r--r-- | src/server/game/DataStores/DB2LoadInfo.h | 14 | ||||
-rw-r--r-- | src/server/game/DataStores/DB2Stores.cpp | 2 | ||||
-rw-r--r-- | src/server/game/DataStores/DB2Stores.h | 1 | ||||
-rw-r--r-- | src/server/game/DataStores/DB2Structure.h | 9 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 68 | ||||
-rw-r--r-- | src/server/game/Handlers/TraitHandler.cpp | 31 | ||||
-rw-r--r-- | src/server/game/Server/Packets/TraitPacketsCommon.cpp | 2 | ||||
-rw-r--r-- | src/server/game/Spells/TraitMgr.cpp | 161 | ||||
-rw-r--r-- | src/server/game/Spells/TraitMgr.h | 2 |
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); } |