aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Spells/TraitMgr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/game/Spells/TraitMgr.cpp')
-rw-r--r--src/server/game/Spells/TraitMgr.cpp735
1 files changed, 735 insertions, 0 deletions
diff --git a/src/server/game/Spells/TraitMgr.cpp b/src/server/game/Spells/TraitMgr.cpp
new file mode 100644
index 00000000000..33d5f215f2c
--- /dev/null
+++ b/src/server/game/Spells/TraitMgr.cpp
@@ -0,0 +1,735 @@
+/*
+ * 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/>.
+ */
+
+#include "TraitMgr.h"
+#include "Containers.h"
+#include "DB2Stores.h"
+#include "IteratorPair.h"
+#include "Player.h"
+#include "TraitPacketsCommon.h"
+
+namespace TraitMgr
+{
+namespace
+{
+struct NodeEntry;
+struct Node;
+struct NodeGroup;
+struct Tree;
+
+struct NodeEntry
+{
+ TraitNodeEntryEntry const* Data = nullptr;
+ std::vector<TraitCondEntry const*> Conditions;
+ std::vector<TraitCostEntry const*> Costs;
+};
+
+struct Node
+{
+ TraitNodeEntry const* Data = nullptr;
+ std::vector<NodeEntry> Entries;
+ std::vector<NodeGroup const*> Groups;
+ std::vector<std::pair<Node const*, TraitEdgeType>> ParentNodes; // TraitEdge::LeftTraitNodeID
+ std::vector<TraitCondEntry const*> Conditions;
+ std::vector<TraitCostEntry const*> Costs;
+};
+
+struct NodeGroup
+{
+ TraitNodeGroupEntry const* Data = nullptr;
+ std::vector<TraitCondEntry const*> Conditions;
+ std::vector<TraitCostEntry const*> Costs;
+ std::vector<Node const*> Nodes;
+};
+
+struct Tree
+{
+ TraitTreeEntry const* Data = nullptr;
+ std::vector<Node const*> Nodes;
+ std::vector<TraitCostEntry const*> Costs;
+ std::vector<TraitCurrencyEntry const*> Currencies;
+ TraitConfigType ConfigType = TraitConfigType::Invalid;
+};
+
+std::unordered_map<int32, NodeGroup> _traitGroups;
+std::unordered_map<int32, Node> _traitNodes;
+std::unordered_map<int32, Tree> _traitTrees;
+std::array<int32, MAX_CLASSES> _skillLinesByClass;
+std::unordered_map<int32, std::vector<Tree const*>> _traitTreesBySkillLine;
+std::unordered_map<int32, std::vector<Tree const*>> _traitTreesByTraitSystem;
+int32 _configIdGenerator = 0;
+std::unordered_map<int32, std::vector<TraitCurrencySourceEntry const*>> _traitCurrencySourcesByCurrency;
+std::unordered_map<int32, std::vector<TraitDefinitionEffectPointsEntry const*>> _traitDefinitionEffectPointModifiers;
+std::unordered_map<int32, std::vector<TraitTreeLoadoutEntryEntry const*>> _traitTreeLoadoutsByChrSpecialization;
+}
+
+void Load()
+{
+ _configIdGenerator = time(nullptr);
+
+ std::unordered_map<int32, std::vector<TraitCondEntry const*>> nodeEntryConditions;
+ for (TraitNodeEntryXTraitCondEntry const* traitNodeEntryXTraitCondEntry : sTraitNodeEntryXTraitCondStore)
+ if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeEntryXTraitCondEntry->TraitCondID))
+ nodeEntryConditions[traitNodeEntryXTraitCondEntry->TraitNodeEntryID].push_back(traitCondEntry);
+
+ std::unordered_map<int32, std::vector<TraitCostEntry const*>> nodeEntryCosts;
+ for (TraitNodeEntryXTraitCostEntry const* traitNodeEntryXTraitCostEntry : sTraitNodeEntryXTraitCostStore)
+ if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitNodeEntryXTraitCostEntry->TraitCostID))
+ nodeEntryCosts[traitNodeEntryXTraitCostEntry->TraitNodeEntryID].push_back(traitCostEntry);
+
+ std::unordered_map<int32, std::vector<TraitCondEntry const*>> nodeGroupConditions;
+ for (TraitNodeGroupXTraitCondEntry const* traitNodeGroupXTraitCondEntry : sTraitNodeGroupXTraitCondStore)
+ if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeGroupXTraitCondEntry->TraitCondID))
+ nodeGroupConditions[traitNodeGroupXTraitCondEntry->TraitNodeGroupID].push_back(traitCondEntry);
+
+ std::unordered_map<int32, std::vector<TraitCostEntry const*>> nodeGroupCosts;
+ for (TraitNodeGroupXTraitCostEntry const* traitNodeGroupXTraitCostEntry : sTraitNodeGroupXTraitCostStore)
+ if (TraitCostEntry const* traitCondEntry = sTraitCostStore.LookupEntry(traitNodeGroupXTraitCostEntry->TraitCostID))
+ nodeGroupCosts[traitNodeGroupXTraitCostEntry->TraitNodeGroupID].push_back(traitCondEntry);
+
+ std::unordered_multimap<int32, int32> nodeGroups;
+ for (TraitNodeGroupXTraitNodeEntry const* traitNodeGroupXTraitNodeEntry : sTraitNodeGroupXTraitNodeStore)
+ nodeGroups.emplace(traitNodeGroupXTraitNodeEntry->TraitNodeID, traitNodeGroupXTraitNodeEntry->TraitNodeGroupID);
+
+ std::unordered_map<int32, std::vector<TraitCondEntry const*>> nodeConditions;
+ for (TraitNodeXTraitCondEntry const* traitNodeXTraitCondEntry : sTraitNodeXTraitCondStore)
+ if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeXTraitCondEntry->TraitCondID))
+ nodeConditions[traitNodeXTraitCondEntry->TraitNodeID].push_back(traitCondEntry);
+
+ std::unordered_map<int32, std::vector<TraitCostEntry const*>> nodeCosts;
+ for (TraitNodeXTraitCostEntry const* traitNodeXTraitCostEntry : sTraitNodeXTraitCostStore)
+ if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitNodeXTraitCostEntry->TraitCostID))
+ nodeCosts[traitNodeXTraitCostEntry->TraitNodeID].push_back(traitCostEntry);
+
+ std::unordered_multimap<int32, TraitNodeEntryEntry const*> nodeEntries;
+ for (TraitNodeXTraitNodeEntryEntry const* traitNodeXTraitNodeEntryEntry : sTraitNodeXTraitNodeEntryStore)
+ if (TraitNodeEntryEntry const* traitNodeEntryEntry = sTraitNodeEntryStore.LookupEntry(traitNodeXTraitNodeEntryEntry->TraitNodeEntryID))
+ nodeEntries.emplace(traitNodeXTraitNodeEntryEntry->TraitNodeID, traitNodeEntryEntry);
+
+ std::unordered_map<int32, std::vector<TraitCostEntry const*>> treeCosts;
+ for (TraitTreeXTraitCostEntry const* traitTreeXTraitCostEntry : sTraitTreeXTraitCostStore)
+ if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitTreeXTraitCostEntry->TraitCostID))
+ treeCosts[traitTreeXTraitCostEntry->TraitTreeID].push_back(traitCostEntry);
+
+ std::unordered_map<int32, std::vector<TraitCurrencyEntry const*>> treeCurrencies;
+ for (TraitTreeXTraitCurrencyEntry const* traitTreeXTraitCurrencyEntry : sTraitTreeXTraitCurrencyStore)
+ if (TraitCurrencyEntry const* traitCurrencyEntry = sTraitCurrencyStore.LookupEntry(traitTreeXTraitCurrencyEntry->TraitCurrencyID))
+ treeCurrencies[traitTreeXTraitCurrencyEntry->TraitTreeID].push_back(traitCurrencyEntry);
+
+ std::unordered_map<int32, std::vector<int32>> traitTreesIdsByTraitSystem;
+
+ for (TraitTreeEntry const* traitTree : sTraitTreeStore)
+ {
+ Tree& tree = _traitTrees[traitTree->ID];
+ tree.Data = traitTree;
+
+ 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 (traitTree->TraitSystemID)
+ {
+ traitTreesIdsByTraitSystem[traitTree->TraitSystemID].push_back(traitTree->ID);
+ tree.ConfigType = TraitConfigType::Generic;
+ }
+ }
+
+ for (TraitNodeGroupEntry const* traitNodeGroup : sTraitNodeGroupStore)
+ {
+ NodeGroup& nodeGroup = _traitGroups[traitNodeGroup->ID];
+ nodeGroup.Data = traitNodeGroup;
+
+ if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeGroupConditions, traitNodeGroup->ID))
+ nodeGroup.Conditions = std::move(*conditions);
+
+ if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeGroupCosts, traitNodeGroup->ID))
+ nodeGroup.Costs = std::move(*costs);
+ }
+
+ for (TraitNodeEntry const* traitNode : sTraitNodeStore)
+ {
+ Node& node = _traitNodes[traitNode->ID];
+ node.Data = traitNode;
+
+ if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitNode->TraitTreeID))
+ tree->Nodes.push_back(&node);
+
+ for (auto&& [_, traitNodeEntry] : Trinity::Containers::MapEqualRange(nodeEntries, traitNode->ID))
+ {
+ NodeEntry& entry = node.Entries.emplace_back();
+ entry.Data = traitNodeEntry;
+
+ if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeEntryConditions, traitNodeEntry->ID))
+ entry.Conditions = std::move(*conditions);
+
+ if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeEntryCosts, traitNodeEntry->ID))
+ entry.Costs = std::move(*costs);
+ }
+
+ for (auto&& [_, nodeGroupId] : Trinity::Containers::MapEqualRange(nodeGroups, traitNode->ID))
+ {
+ NodeGroup* nodeGroup = Trinity::Containers::MapGetValuePtr(_traitGroups, nodeGroupId);
+ if (!nodeGroup)
+ continue;
+
+ nodeGroup->Nodes.push_back(&node);
+ node.Groups.push_back(nodeGroup);
+ }
+
+ if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeConditions, traitNode->ID))
+ node.Conditions = std::move(*conditions);
+
+ if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeCosts, traitNode->ID))
+ node.Costs = std::move(*costs);
+ }
+
+ for (TraitEdgeEntry const* traitEdgeEntry : sTraitEdgeStore)
+ {
+ Node* left = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEdgeEntry->LeftTraitNodeID);
+ Node* right = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEdgeEntry->RightTraitNodeID);
+ if (!left || !right)
+ continue;
+
+ right->ParentNodes.emplace_back(left, static_cast<TraitEdgeType>(traitEdgeEntry->Type));
+ }
+
+ for (SkillLineXTraitTreeEntry const* skillLineXTraitTreeEntry : sSkillLineXTraitTreeStore)
+ {
+ Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, skillLineXTraitTreeEntry->TraitTreeID);
+ if (!tree)
+ continue;
+
+ SkillLineEntry const* skillLineEntry = sSkillLineStore.LookupEntry(skillLineXTraitTreeEntry->SkillLineID);
+ if (!skillLineEntry)
+ continue;
+
+ _traitTreesBySkillLine[skillLineXTraitTreeEntry->SkillLineID].push_back(tree);
+ if (skillLineEntry->CategoryID == SKILL_CATEGORY_CLASS)
+ {
+ for (SkillRaceClassInfoEntry const* skillRaceClassInfo : sDB2Manager.GetSkillRaceClassInfo(skillLineEntry->ID))
+ for (int32 i = 1; i < MAX_CLASSES; ++i)
+ if (skillRaceClassInfo->ClassMask & (1 << (i - 1)))
+ _skillLinesByClass[i] = skillLineXTraitTreeEntry->SkillLineID;
+
+ tree->ConfigType = TraitConfigType::Combat;
+ }
+ else
+ tree->ConfigType = TraitConfigType::Profession;
+ }
+
+ for (auto&& [traitSystemId, traitTreeIds] : traitTreesIdsByTraitSystem)
+ for (int32 traitTreeId : traitTreeIds)
+ if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitTreeId))
+ _traitTreesByTraitSystem[traitSystemId].push_back(tree);
+
+ for (TraitCurrencySourceEntry const* traitCurrencySource : sTraitCurrencySourceStore)
+ _traitCurrencySourcesByCurrency[traitCurrencySource->TraitCurrencyID].push_back(traitCurrencySource);
+
+ for (TraitDefinitionEffectPointsEntry const* traitDefinitionEffectPoints : sTraitDefinitionEffectPointsStore)
+ _traitDefinitionEffectPointModifiers[traitDefinitionEffectPoints->TraitDefinitionID].push_back(traitDefinitionEffectPoints);
+
+ std::unordered_map<int32, std::vector<TraitTreeLoadoutEntryEntry const*>> traitTreeLoadoutEntries;
+ for (TraitTreeLoadoutEntryEntry const* traitTreeLoadoutEntry : sTraitTreeLoadoutEntryStore)
+ traitTreeLoadoutEntries[traitTreeLoadoutEntry->TraitTreeLoadoutID].push_back(traitTreeLoadoutEntry);
+
+ for (TraitTreeLoadoutEntry const* traitTreeLoadout : sTraitTreeLoadoutStore)
+ {
+ if (std::vector<TraitTreeLoadoutEntryEntry const*>* entries = Trinity::Containers::MapGetValuePtr(traitTreeLoadoutEntries, traitTreeLoadout->ID))
+ {
+ std::sort(entries->begin(), entries->end(), [](TraitTreeLoadoutEntryEntry const* left, TraitTreeLoadoutEntryEntry const* right)
+ {
+ return left->OrderIndex < right->OrderIndex;
+ });
+ // there should be only one loadout per spec, we take last one encountered
+ _traitTreeLoadoutsByChrSpecialization[traitTreeLoadout->ChrSpecializationID] = std::move(*entries);
+ }
+ }
+}
+
+/**
+ * Generates new TraitConfig identifier.
+ * Because this only needs to be unique for each character we let it overflow
+*/
+int32 GenerateNewTraitConfigId()
+{
+ if (_configIdGenerator == std::numeric_limits<int32>::max())
+ _configIdGenerator = 0;
+
+ return ++_configIdGenerator;
+}
+
+TraitConfigType GetConfigTypeForTree(int32 traitTreeId)
+{
+ Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitTreeId);
+ if (!tree)
+ return TraitConfigType::Invalid;
+
+ return tree->ConfigType;
+}
+
+/**
+ * @brief Finds relevant TraitTree identifiers
+ * @param traitConfig config data
+ * @return Trait tree data
+*/
+std::vector<Tree const*> const* GetTreesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig)
+{
+ switch (traitConfig.Type)
+ {
+ case TraitConfigType::Combat:
+ if (ChrSpecializationEntry const* chrSpecializationEntry = sChrSpecializationStore.LookupEntry(traitConfig.ChrSpecializationID))
+ return Trinity::Containers::MapGetValuePtr(_traitTreesBySkillLine, _skillLinesByClass[chrSpecializationEntry->ClassID]);
+ break;
+ case TraitConfigType::Profession:
+ return Trinity::Containers::MapGetValuePtr(_traitTreesBySkillLine, traitConfig.SkillLineID);
+ case TraitConfigType::Generic:
+ return Trinity::Containers::MapGetValuePtr(_traitTreesByTraitSystem, traitConfig.TraitSystemID);
+ default:
+ break;
+ }
+
+ return nullptr;
+}
+
+bool HasEnoughCurrency(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32> const& currencies)
+{
+ auto getCurrencyCount = [&](int32 currencyId)
+ {
+ int32 const* count = Trinity::Containers::MapGetValuePtr(currencies, currencyId);
+ return count ? *count : 0;
+ };
+
+ Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
+ for (NodeGroup const* group : node->Groups)
+ for (TraitCostEntry const* cost : group->Costs)
+ if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
+ return false;
+
+ auto nodeEntryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; });
+ if (nodeEntryItr != node->Entries.end())
+ for (TraitCostEntry const* cost : nodeEntryItr->Costs)
+ if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
+ return false;
+
+ for (TraitCostEntry const* cost : node->Costs)
+ if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
+ return false;
+
+ if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
+ for (TraitCostEntry const* cost : tree->Costs)
+ if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
+ return false;
+
+ return true;
+}
+
+void TakeCurrencyCost(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& currencies)
+{
+ Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
+ for (NodeGroup const* group : node->Groups)
+ for (TraitCostEntry const* cost : group->Costs)
+ currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
+
+ auto nodeEntryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; });
+ if (nodeEntryItr != node->Entries.end())
+ for (TraitCostEntry const* cost : nodeEntryItr->Costs)
+ currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
+
+ for (TraitCostEntry const* cost : node->Costs)
+ currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
+
+ if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
+ for (TraitCostEntry const* cost : tree->Costs)
+ currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
+}
+
+void FillOwnedCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig, Player const* player, std::map<int32, int32>& currencies)
+{
+ std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig);
+ if (!trees)
+ return;
+
+ auto hasTraitNodeEntry = [&traitConfig](int32 traitNodeEntryId)
+ {
+ return std::find_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), [traitNodeEntryId](WorldPackets::Traits::TraitEntry const& traitEntry)
+ {
+ return traitEntry.TraitNodeEntryID == traitNodeEntryId && (traitEntry.Rank > 0 || traitEntry.GrantedRanks > 0);
+ }) != traitConfig.Entries.end();
+ };
+
+ for (Tree const* tree : *trees)
+ {
+ for (TraitCurrencyEntry const* currency : tree->Currencies)
+ {
+ switch (currency->GetType())
+ {
+ case TraitCurrencyType::Gold:
+ {
+ int32& amount = currencies[currency->ID];
+ if (player->GetMoney() > uint64(std::numeric_limits<int32>::max() - amount))
+ amount = std::numeric_limits<int32>::max();
+ else
+ amount += player->GetMoney();
+ break;
+ }
+ case TraitCurrencyType::CurrencyTypesBased:
+ currencies[currency->ID] += player->GetCurrency(currency->CurrencyTypesID);
+ break;
+ case TraitCurrencyType::TraitSourced:
+ if (std::vector<TraitCurrencySourceEntry const*>* currencySources = Trinity::Containers::MapGetValuePtr(_traitCurrencySourcesByCurrency, currency->ID))
+ {
+ for (TraitCurrencySourceEntry const* currencySource : *currencySources)
+ {
+ if (currencySource->QuestID && !player->IsQuestRewarded(currencySource->QuestID))
+ continue;
+
+ if (currencySource->AchievementID && !player->HasAchieved(currencySource->AchievementID))
+ continue;
+
+ if (currencySource->PlayerLevel && player->GetLevel() < currencySource->PlayerLevel)
+ continue;
+
+ if (currencySource->TraitNodeEntryID && !hasTraitNodeEntry(currencySource->TraitNodeEntryID))
+ continue;
+
+ currencies[currencySource->TraitCurrencyID] += currencySource->Amount;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+void FillSpentCurrenciesMap(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& cachedCurrencies)
+{
+ Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
+ for (NodeGroup const* group : node->Groups)
+ for (TraitCostEntry const* cost : group->Costs)
+ cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
+
+ auto nodeEntryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; });
+ if (nodeEntryItr != node->Entries.end())
+ for (TraitCostEntry const* cost : nodeEntryItr->Costs)
+ cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
+
+ for (TraitCostEntry const* cost : node->Costs)
+ cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
+
+ if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
+ for (TraitCostEntry const* cost : tree->Costs)
+ cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
+}
+
+void FillSpentCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig, std::map<int32, int32>& cachedCurrencies)
+{
+ for (WorldPackets::Traits::TraitEntry const& entry : traitConfig.Entries)
+ FillSpentCurrenciesMap(entry, cachedCurrencies);
+}
+
+bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const& traitConfig, Player const* player, TraitCondEntry const* condition,
+ Optional<std::map<int32, int32>>& cachedCurrencies)
+{
+ if (condition->QuestID && !player->IsQuestRewarded(condition->QuestID))
+ return false;
+
+ if (condition->AchievementID && !player->HasAchieved(condition->AchievementID))
+ return false;
+
+ if (condition->SpecSetID && !sDB2Manager.IsSpecSetMember(condition->SpecSetID, player->GetPrimarySpecialization()))
+ return false;
+
+ if (condition->TraitCurrencyID && condition->SpentAmountRequired)
+ {
+ if (!cachedCurrencies)
+ FillSpentCurrenciesMap(traitConfig, cachedCurrencies.emplace());
+
+ if (condition->TraitNodeGroupID)
+ {
+ auto itr = cachedCurrencies->try_emplace(condition->TraitCurrencyID, 0).first;
+ if (itr->second < condition->SpentAmountRequired)
+ return false;
+ }
+ else if (condition->TraitNodeID)
+ {
+ auto itr = cachedCurrencies->try_emplace(condition->TraitCurrencyID, 0).first;
+ if (itr->second < condition->SpentAmountRequired)
+ return false;
+ }
+ }
+
+ if (condition->RequiredLevel && int32(player->GetLevel()) < condition->RequiredLevel)
+ return false;
+
+ return true;
+}
+
+std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig, Player const* player)
+{
+ std::vector<UF::TraitEntry> entries;
+ std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig);
+ if (!trees)
+ return entries;
+
+ auto getOrCreateEntry = [&entries](int32 nodeId, int32 entryId)
+ {
+ auto itr = std::find_if(entries.begin(), entries.end(), [&](UF::TraitEntry const& traitEntry)
+ {
+ return traitEntry.TraitNodeID == nodeId && traitEntry.TraitNodeEntryID == entryId;
+ });
+ if (itr == entries.end())
+ {
+ itr = entries.emplace(entries.end());
+ itr->TraitNodeID = nodeId;
+ itr->TraitNodeEntryID = entryId;
+ itr->Rank = 0;
+ itr->GrantedRanks = 0;
+ }
+ return &*itr;
+ };
+
+ Optional<std::map<int32, int32>> cachedCurrencies;
+
+ for (Tree const* tree : *trees)
+ {
+ for (Node const* node : tree->Nodes)
+ {
+ for (NodeEntry const& entry : node->Entries)
+ for (TraitCondEntry const* condition : entry.Conditions)
+ if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies))
+ getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks;
+
+ for (TraitCondEntry const* condition : node->Conditions)
+ if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies))
+ for (NodeEntry const& entry : node->Entries)
+ getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks;
+
+ for (NodeGroup const* group : node->Groups)
+ for (TraitCondEntry const* condition : group->Conditions)
+ if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies))
+ for (NodeEntry const& entry : node->Entries)
+ getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks;
+ }
+ }
+
+ return entries;
+}
+
+bool IsValidEntry(WorldPackets::Traits::TraitEntry const& traitEntry)
+{
+ Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
+ if (!node)
+ return false;
+
+ auto entryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&](NodeEntry const& entry) { return entry.Data->ID == uint32(traitEntry.TraitNodeEntryID); });
+ if (entryItr == node->Entries.end())
+ return false;
+
+ if (entryItr->Data->MaxRanks < traitEntry.Rank + traitEntry.GrantedRanks)
+ return false;
+
+ return true;
+}
+
+TalentLearnResult ValidateConfig(WorldPackets::Traits::TraitConfig const& traitConfig, Player const* player, bool requireSpendingAllCurrencies /*= false*/)
+{
+ auto getNodeEntryCount = [&](int32 traitNodeId)
+ {
+ return std::count_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), [traitNodeId](WorldPackets::Traits::TraitEntry const& traitEntry)
+ {
+ return traitEntry.TraitNodeID == traitNodeId;
+ });
+ };
+
+ auto getNodeEntry = [&](int32 traitNodeId, int32 traitNodeEntryId)
+ {
+ auto entryItr = std::find_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), [=](WorldPackets::Traits::TraitEntry const& traitEntry)
+ {
+ return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId;
+ });
+ return entryItr != traitConfig.Entries.end() ? &*entryItr : nullptr;
+ };
+
+ auto isNodeFullyFilled = [&](Node const* node)
+ {
+ if (node->Data->GetType() == TraitNodeType::Selection)
+ return std::any_of(node->Entries.begin(), node->Entries.end(), [&](NodeEntry const& nodeEntry)
+ {
+ WorldPackets::Traits::TraitEntry const* traitEntry = getNodeEntry(node->Data->ID, nodeEntry.Data->ID);
+ return traitEntry && (traitEntry->Rank + traitEntry->GrantedRanks) == nodeEntry.Data->MaxRanks;
+ });
+
+ return std::all_of(node->Entries.begin(), node->Entries.end(), [&](NodeEntry const& nodeEntry)
+ {
+ WorldPackets::Traits::TraitEntry const* traitEntry = getNodeEntry(node->Data->ID, nodeEntry.Data->ID);
+ return traitEntry && (traitEntry->Rank + traitEntry->GrantedRanks) == nodeEntry.Data->MaxRanks;
+ });
+ };
+
+ Optional<std::map<int32, int32>> spentCurrencies;
+ FillSpentCurrenciesMap(traitConfig, spentCurrencies.emplace());
+
+ for (WorldPackets::Traits::TraitEntry const& traitEntry : traitConfig.Entries)
+ {
+ if (!IsValidEntry(traitEntry))
+ return TALENT_FAILED_UNKNOWN;
+
+ Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
+ if (node->Data->GetType() == TraitNodeType::Selection)
+ if (getNodeEntryCount(traitEntry.TraitNodeID) != 1)
+ return TALENT_FAILED_UNKNOWN;
+
+ for (NodeEntry const& entry : node->Entries)
+ for (TraitCondEntry const* condition : entry.Conditions)
+ if ((condition->GetCondType() == TraitConditionType::Available || condition->GetCondType() == TraitConditionType::Visible)
+ && !MeetsTraitCondition(traitConfig, player, condition, spentCurrencies))
+ return TALENT_FAILED_UNKNOWN;
+
+ for (TraitCondEntry const* condition : node->Conditions)
+ if ((condition->GetCondType() == TraitConditionType::Available || condition->GetCondType() == TraitConditionType::Visible)
+ && !MeetsTraitCondition(traitConfig, player, condition, spentCurrencies))
+ return TALENT_FAILED_UNKNOWN;
+
+ for (NodeGroup const* group : node->Groups)
+ for (TraitCondEntry const* condition : group->Conditions)
+ if ((condition->GetCondType() == TraitConditionType::Available || condition->GetCondType() == TraitConditionType::Visible)
+ && !MeetsTraitCondition(traitConfig, player, condition, spentCurrencies))
+ return TALENT_FAILED_UNKNOWN;
+
+ if (!node->ParentNodes.empty())
+ {
+ bool hasAnyParentTrait = false;
+ for (auto const& [parentNode, edgeType] : node->ParentNodes)
+ {
+ if (!isNodeFullyFilled(parentNode))
+ {
+ if (edgeType == TraitEdgeType::RequiredForAvailability)
+ return TALENT_FAILED_NOT_ENOUGH_TALENTS_IN_PRIMARY_TREE;
+
+ continue;
+ }
+
+ hasAnyParentTrait = true;
+ }
+
+ if (!hasAnyParentTrait)
+ return TALENT_FAILED_NOT_ENOUGH_TALENTS_IN_PRIMARY_TREE;
+ }
+ }
+
+ std::map<int32, int32> grantedCurrencies;
+ FillOwnedCurrenciesMap(traitConfig, player, grantedCurrencies);
+
+ for (auto [traitCurrencyId, spentAmount] : *spentCurrencies)
+ {
+ if (sTraitCurrencyStore.AssertEntry(traitCurrencyId)->GetType() != TraitCurrencyType::TraitSourced)
+ continue;
+
+ if (!spentAmount)
+ continue;
+
+ int32* grantedCount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, traitCurrencyId);
+ if (!grantedCount || *grantedCount < spentAmount)
+ return TALENT_FAILED_NOT_ENOUGH_TALENTS_IN_PRIMARY_TREE;
+
+ }
+
+ if (requireSpendingAllCurrencies && traitConfig.Type == TraitConfigType::Combat)
+ {
+ for (auto [traitCurrencyId, grantedAmount] : grantedCurrencies)
+ {
+ if (!grantedAmount)
+ continue;
+
+ int32* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, traitCurrencyId);
+ if (!spentAmount || *spentAmount != grantedAmount)
+ return TALENT_FAILED_UNSPENT_TALENT_POINTS;
+ }
+ }
+
+ return TALENT_LEARN_OK;
+}
+
+std::vector<TraitDefinitionEffectPointsEntry const*> const* GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId)
+{
+ return Trinity::Containers::MapGetValuePtr(_traitDefinitionEffectPointModifiers, traitDefinitionId);
+}
+
+void InitializeStarterBuildTraitConfig(WorldPackets::Traits::TraitConfig& traitConfig, Player const* player)
+{
+ traitConfig.Entries.clear();
+ std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig);
+ if (!trees)
+ return;
+
+ for (UF::TraitEntry const& grant : GetGrantedTraitEntriesForConfig(traitConfig, player))
+ {
+ WorldPackets::Traits::TraitEntry& newEntry = traitConfig.Entries.emplace_back();
+ newEntry.TraitNodeID = grant.TraitNodeID;
+ newEntry.TraitNodeEntryID = grant.TraitNodeEntryID;
+ newEntry.GrantedRanks = grant.GrantedRanks;
+ }
+
+ std::map<int32, int32> currencies;
+ FillOwnedCurrenciesMap(traitConfig, player, currencies);
+
+ if (std::vector<TraitTreeLoadoutEntryEntry const*> const* loadoutEntries = Trinity::Containers::MapGetValuePtr(_traitTreeLoadoutsByChrSpecialization, traitConfig.ChrSpecializationID))
+ {
+ auto findEntry = [](WorldPackets::Traits::TraitConfig& config, int32 traitNodeId, int32 traitNodeEntryId) -> WorldPackets::Traits::TraitEntry*
+ {
+ auto entryItr = std::find_if(config.Entries.begin(), config.Entries.end(), [=](WorldPackets::Traits::TraitEntry const& traitEntry)
+ {
+ return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId;
+ });
+ return entryItr != config.Entries.end() ? &*entryItr : nullptr;
+ };
+
+ for (TraitTreeLoadoutEntryEntry const* loadoutEntry : *loadoutEntries)
+ {
+ int32 addedRanks = loadoutEntry->NumPoints;
+ Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, loadoutEntry->SelectedTraitNodeID);
+
+ WorldPackets::Traits::TraitEntry newEntry;
+ newEntry.TraitNodeID = loadoutEntry->SelectedTraitNodeID;
+ newEntry.TraitNodeEntryID = loadoutEntry->SelectedTraitNodeEntryID;
+ if (!newEntry.TraitNodeEntryID)
+ newEntry.TraitNodeEntryID = node->Entries[0].Data->ID;
+
+ WorldPackets::Traits::TraitEntry* entryInConfig = findEntry(traitConfig, newEntry.TraitNodeID, newEntry.TraitNodeEntryID);
+
+ if (entryInConfig)
+ addedRanks -= entryInConfig->Rank;
+
+ newEntry.Rank = addedRanks;
+
+ if (!HasEnoughCurrency(newEntry, currencies))
+ break;
+
+ if (entryInConfig)
+ entryInConfig->Rank += addedRanks;
+ else
+ traitConfig.Entries.push_back(newEntry);
+
+ TakeCurrencyCost(newEntry, currencies);
+ }
+ }
+}
+}