TrinityCore
Loading...
Searching...
No Matches
TraitMgr.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "TraitMgr.h"
19#include "DB2Stores.h"
20#include "FlatSet.h"
21#include "IteratorPair.h"
22#include "MapUtils.h"
23#include "TraitPacketsCommon.h"
24#include "UpdateFields.h"
25
26namespace TraitMgr
27{
28namespace
29{
30struct NodeEntry;
31struct Node;
32struct NodeGroup;
33struct SubTree;
34struct Tree;
35
36struct NodeEntry
37{
38 TraitNodeEntryEntry const* Data = nullptr;
39 std::vector<TraitCondEntry const*> Conditions;
40 std::vector<TraitCostEntry const*> Costs;
41};
42
43struct Node
44{
45 TraitNodeEntry const* Data = nullptr;
46 std::vector<NodeEntry> Entries;
47 std::vector<NodeGroup const*> Groups;
48 std::vector<std::pair<Node const*, TraitEdgeType>> ParentNodes; // TraitEdge::LeftTraitNodeID
49 std::vector<TraitCondEntry const*> Conditions;
50 std::vector<TraitCostEntry const*> Costs;
51};
52
53struct NodeGroup
54{
55 TraitNodeGroupEntry const* Data = nullptr;
56 std::vector<TraitCondEntry const*> Conditions;
57 std::vector<TraitCostEntry const*> Costs;
58 std::vector<Node const*> Nodes;
59};
60
61struct SubTree
62{
63 TraitSubTreeEntry const* Data = nullptr;
64 std::vector<Node const*> Nodes;
66};
67
68struct Tree
69{
70 TraitTreeEntry const* Data = nullptr;
71 std::vector<Node const*> Nodes;
72 std::vector<TraitCostEntry const*> Costs;
73 std::vector<TraitCurrencyEntry const*> Currencies;
74 std::vector<SubTree const*> SubTrees;
76};
77
78std::unordered_map<uint32, NodeGroup> _traitGroups;
79std::unordered_map<int32, Node> _traitNodes;
80std::unordered_map<int32, SubTree> _traitSubTrees;
81std::unordered_map<int32, Tree> _traitTrees;
82std::array<int32, MAX_CLASSES> _skillLinesByClass;
83std::unordered_map<int32, std::vector<Tree const*>> _traitTreesBySkillLine;
84std::unordered_map<int32, std::vector<Tree const*>> _traitTreesByTraitSystem;
85int32 _configIdGenerator = 0;
86std::unordered_map<int32, std::vector<TraitCurrencySourceEntry const*>> _traitCurrencySourcesByCurrency;
87std::unordered_map<int32, std::vector<TraitDefinitionEffectPointsEntry const*>> _traitDefinitionEffectPointModifiers;
88std::unordered_map<int32, std::vector<TraitTreeLoadoutEntryEntry const*>> _traitTreeLoadoutsByChrSpecialization;
89}
90
91void Load()
92{
93 _configIdGenerator = time(nullptr);
94
95 std::unordered_map<uint32, std::vector<TraitCondEntry const*>> nodeEntryConditions;
96 for (TraitNodeEntryXTraitCondEntry const* traitNodeEntryXTraitCondEntry : sTraitNodeEntryXTraitCondStore)
97 if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeEntryXTraitCondEntry->TraitCondID))
98 nodeEntryConditions[traitNodeEntryXTraitCondEntry->TraitNodeEntryID].push_back(traitCondEntry);
99
100 std::unordered_map<uint32, std::vector<TraitCostEntry const*>> nodeEntryCosts;
101 for (TraitNodeEntryXTraitCostEntry const* traitNodeEntryXTraitCostEntry : sTraitNodeEntryXTraitCostStore)
102 if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitNodeEntryXTraitCostEntry->TraitCostID))
103 nodeEntryCosts[traitNodeEntryXTraitCostEntry->TraitNodeEntryID].push_back(traitCostEntry);
104
105 std::unordered_map<uint32, std::vector<TraitCondEntry const*>> nodeGroupConditions;
106 for (TraitNodeGroupXTraitCondEntry const* traitNodeGroupXTraitCondEntry : sTraitNodeGroupXTraitCondStore)
107 if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeGroupXTraitCondEntry->TraitCondID))
108 nodeGroupConditions[traitNodeGroupXTraitCondEntry->TraitNodeGroupID].push_back(traitCondEntry);
109
110 std::unordered_map<uint32, std::vector<TraitCostEntry const*>> nodeGroupCosts;
111 for (TraitNodeGroupXTraitCostEntry const* traitNodeGroupXTraitCostEntry : sTraitNodeGroupXTraitCostStore)
112 if (TraitCostEntry const* traitCondEntry = sTraitCostStore.LookupEntry(traitNodeGroupXTraitCostEntry->TraitCostID))
113 nodeGroupCosts[traitNodeGroupXTraitCostEntry->TraitNodeGroupID].push_back(traitCondEntry);
114
115 std::unordered_multimap<int32, uint32> nodeGroups;
116 for (TraitNodeGroupXTraitNodeEntry const* traitNodeGroupXTraitNodeEntry : sTraitNodeGroupXTraitNodeStore)
117 nodeGroups.emplace(traitNodeGroupXTraitNodeEntry->TraitNodeID, traitNodeGroupXTraitNodeEntry->TraitNodeGroupID);
118
119 std::unordered_map<uint32, std::vector<TraitCondEntry const*>> nodeConditions;
120 for (TraitNodeXTraitCondEntry const* traitNodeXTraitCondEntry : sTraitNodeXTraitCondStore)
121 if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeXTraitCondEntry->TraitCondID))
122 nodeConditions[traitNodeXTraitCondEntry->TraitNodeID].push_back(traitCondEntry);
123
124 std::unordered_map<uint32, std::vector<TraitCostEntry const*>> nodeCosts;
125 for (TraitNodeXTraitCostEntry const* traitNodeXTraitCostEntry : sTraitNodeXTraitCostStore)
126 if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitNodeXTraitCostEntry->TraitCostID))
127 nodeCosts[traitNodeXTraitCostEntry->TraitNodeID].push_back(traitCostEntry);
128
129 std::unordered_multimap<uint32, TraitNodeEntryEntry const*> nodeEntries;
130 for (TraitNodeXTraitNodeEntryEntry const* traitNodeXTraitNodeEntryEntry : sTraitNodeXTraitNodeEntryStore)
131 if (TraitNodeEntryEntry const* traitNodeEntryEntry = sTraitNodeEntryStore.LookupEntry(traitNodeXTraitNodeEntryEntry->TraitNodeEntryID))
132 nodeEntries.emplace(traitNodeXTraitNodeEntryEntry->TraitNodeID, traitNodeEntryEntry);
133
134 std::unordered_map<int32, std::vector<TraitCostEntry const*>> treeCosts;
135 for (TraitTreeXTraitCostEntry const* traitTreeXTraitCostEntry : sTraitTreeXTraitCostStore)
136 if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitTreeXTraitCostEntry->TraitCostID))
137 treeCosts[traitTreeXTraitCostEntry->TraitTreeID].push_back(traitCostEntry);
138
139 std::unordered_map<uint32, std::vector<TraitTreeXTraitCurrencyEntry const*>> treeCurrencies;
140 for (TraitTreeXTraitCurrencyEntry const* traitTreeXTraitCurrencyEntry : sTraitTreeXTraitCurrencyStore)
141 if (sTraitCurrencyStore.HasRecord(traitTreeXTraitCurrencyEntry->TraitCurrencyID))
142 treeCurrencies[traitTreeXTraitCurrencyEntry->TraitTreeID].push_back(traitTreeXTraitCurrencyEntry);
143
144 std::unordered_map<int32, std::vector<int32>> traitTreesIdsByTraitSystem;
145
146 for (TraitTreeEntry const* traitTree : sTraitTreeStore)
147 {
148 Tree& tree = _traitTrees[traitTree->ID];
149 tree.Data = traitTree;
150
151 if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(treeCosts, traitTree->ID))
152 tree.Costs = std::move(*costs);
153
154 if (std::vector<TraitTreeXTraitCurrencyEntry const*>* currencies = Trinity::Containers::MapGetValuePtr(treeCurrencies, traitTree->ID))
155 {
156 tree.Currencies.resize(currencies->size());
157 std::ranges::sort(*currencies, {}, &TraitTreeXTraitCurrencyEntry::Index);
158 std::ranges::transform(*currencies, tree.Currencies.begin(),
159 [](uint32 traitCurrencyId) { return sTraitCurrencyStore.AssertEntry(traitCurrencyId); },
161 }
162
163 if (traitTree->TraitSystemID)
164 {
165 traitTreesIdsByTraitSystem[traitTree->TraitSystemID].push_back(traitTree->ID);
166 tree.ConfigType = TraitConfigType::Generic;
167 }
168 }
169
170 for (TraitSubTreeEntry const* traitSubTree : sTraitSubTreeStore)
171 {
172 SubTree& subTree = _traitSubTrees[traitSubTree->ID];
173 subTree.Data = traitSubTree;
174
175 if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitSubTree->TraitTreeID))
176 tree->SubTrees.push_back(&subTree);
177 }
178
179 for (TraitNodeGroupEntry const* traitNodeGroup : sTraitNodeGroupStore)
180 {
181 NodeGroup& nodeGroup = _traitGroups[traitNodeGroup->ID];
182 nodeGroup.Data = traitNodeGroup;
183
184 if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeGroupConditions, traitNodeGroup->ID))
185 nodeGroup.Conditions = std::move(*conditions);
186
187 if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeGroupCosts, traitNodeGroup->ID))
188 nodeGroup.Costs = std::move(*costs);
189 }
190
191 for (TraitNodeEntry const* traitNode : sTraitNodeStore)
192 {
193 Node& node = _traitNodes[traitNode->ID];
194 node.Data = traitNode;
195
196 if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitNode->TraitTreeID))
197 tree->Nodes.push_back(&node);
198
199 for (auto&& [_, traitNodeEntry] : Trinity::Containers::MapEqualRange(nodeEntries, traitNode->ID))
200 {
201 NodeEntry& entry = node.Entries.emplace_back();
202 entry.Data = traitNodeEntry;
203
204 if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeEntryConditions, traitNodeEntry->ID))
205 entry.Conditions = std::move(*conditions);
206
207 if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeEntryCosts, traitNodeEntry->ID))
208 entry.Costs = std::move(*costs);
209 }
210
211 for (auto&& [_, nodeGroupId] : Trinity::Containers::MapEqualRange(nodeGroups, traitNode->ID))
212 {
213 NodeGroup* nodeGroup = Trinity::Containers::MapGetValuePtr(_traitGroups, nodeGroupId);
214 if (!nodeGroup)
215 continue;
216
217 nodeGroup->Nodes.push_back(&node);
218 node.Groups.push_back(nodeGroup);
219 }
220
221 if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeConditions, traitNode->ID))
222 node.Conditions = std::move(*conditions);
223
224 if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeCosts, traitNode->ID))
225 node.Costs = std::move(*costs);
226
227 if (SubTree* subTree = Trinity::Containers::MapGetValuePtr(_traitSubTrees, traitNode->TraitSubTreeID))
228 {
229 subTree->Nodes.push_back(&node);
230
231 for (NodeEntry const& nodeEntry : node.Entries)
232 for (TraitCostEntry const* cost : nodeEntry.Costs)
233 if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
234 subTree->Currencies.insert(traitCurrency);
235
236 for (NodeGroup const* nodeGroup : node.Groups)
237 for (TraitCostEntry const* cost : nodeGroup->Costs)
238 if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
239 subTree->Currencies.insert(traitCurrency);
240
241 for (TraitCostEntry const* cost : node.Costs)
242 if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
243 subTree->Currencies.insert(traitCurrency);
244
245 if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitNode->TraitTreeID))
246 for (TraitCostEntry const* cost : tree->Costs)
247 if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
248 subTree->Currencies.insert(traitCurrency);
249 }
250 }
251
252 for (TraitEdgeEntry const* traitEdgeEntry : sTraitEdgeStore)
253 {
254 Node* left = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEdgeEntry->LeftTraitNodeID);
255 Node* right = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEdgeEntry->RightTraitNodeID);
256 if (!left || !right)
257 continue;
258
259 right->ParentNodes.emplace_back(left, static_cast<TraitEdgeType>(traitEdgeEntry->Type));
260 }
261
262 for (SkillLineXTraitTreeEntry const* skillLineXTraitTreeEntry : sSkillLineXTraitTreeStore)
263 {
264 Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, skillLineXTraitTreeEntry->TraitTreeID);
265 if (!tree)
266 continue;
267
268 SkillLineEntry const* skillLineEntry = sSkillLineStore.LookupEntry(skillLineXTraitTreeEntry->SkillLineID);
269 if (!skillLineEntry)
270 continue;
271
272 _traitTreesBySkillLine[skillLineXTraitTreeEntry->SkillLineID].push_back(tree);
273 if (skillLineEntry->CategoryID == SKILL_CATEGORY_CLASS)
274 {
275 for (SkillRaceClassInfoEntry const* skillRaceClassInfo : sDB2Manager.GetSkillRaceClassInfo(skillLineEntry->ID))
276 for (int32 i = 1; i < MAX_CLASSES; ++i)
277 if (skillRaceClassInfo->ClassMask & (1 << (i - 1)))
278 _skillLinesByClass[i] = skillLineXTraitTreeEntry->SkillLineID;
279
280 tree->ConfigType = TraitConfigType::Combat;
281 }
282 else
283 tree->ConfigType = TraitConfigType::Profession;
284 }
285
286 for (auto&& [traitSystemId, traitTreeIds] : traitTreesIdsByTraitSystem)
287 for (int32 traitTreeId : traitTreeIds)
288 if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitTreeId))
289 _traitTreesByTraitSystem[traitSystemId].push_back(tree);
290
291 for (TraitCurrencySourceEntry const* traitCurrencySource : sTraitCurrencySourceStore)
292 _traitCurrencySourcesByCurrency[traitCurrencySource->TraitCurrencyID].push_back(traitCurrencySource);
293
294 for (TraitDefinitionEffectPointsEntry const* traitDefinitionEffectPoints : sTraitDefinitionEffectPointsStore)
295 _traitDefinitionEffectPointModifiers[traitDefinitionEffectPoints->TraitDefinitionID].push_back(traitDefinitionEffectPoints);
296
297 std::unordered_map<uint32, std::vector<TraitTreeLoadoutEntryEntry const*>> traitTreeLoadoutEntries;
298 for (TraitTreeLoadoutEntryEntry const* traitTreeLoadoutEntry : sTraitTreeLoadoutEntryStore)
299 traitTreeLoadoutEntries[traitTreeLoadoutEntry->TraitTreeLoadoutID].push_back(traitTreeLoadoutEntry);
300
301 for (TraitTreeLoadoutEntry const* traitTreeLoadout : sTraitTreeLoadoutStore)
302 {
303 if (std::vector<TraitTreeLoadoutEntryEntry const*>* entries = Trinity::Containers::MapGetValuePtr(traitTreeLoadoutEntries, traitTreeLoadout->ID))
304 {
305 std::ranges::sort(*entries, {}, & TraitTreeLoadoutEntryEntry::OrderIndex);
306 // there should be only one loadout per spec, we take last one encountered
307 _traitTreeLoadoutsByChrSpecialization[traitTreeLoadout->ChrSpecializationID] = std::move(*entries);
308 }
309 }
310}
311
317{
318 if (_configIdGenerator == std::numeric_limits<int32>::max())
319 _configIdGenerator = 0;
320
321 return ++_configIdGenerator;
322}
323
325{
326 Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitTreeId);
327 if (!tree)
329
330 return tree->ConfigType;
331}
332
338std::vector<Tree const*> const* GetTreesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig)
339{
340 switch (traitConfig.Type)
341 {
343 if (ChrSpecializationEntry const* chrSpecializationEntry = sChrSpecializationStore.LookupEntry(traitConfig.ChrSpecializationID))
344 return Trinity::Containers::MapGetValuePtr(_traitTreesBySkillLine, _skillLinesByClass[chrSpecializationEntry->ClassID]);
345 break;
347 return Trinity::Containers::MapGetValuePtr(_traitTreesBySkillLine, traitConfig.SkillLineID);
349 return Trinity::Containers::MapGetValuePtr(_traitTreesByTraitSystem, traitConfig.TraitSystemID);
350 default:
351 break;
352 }
353
354 return nullptr;
355}
356
357bool HasEnoughCurrency(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32> const& currencies)
358{
359 auto getCurrencyCount = [&](int32 currencyId)
360 {
361 int32 const* count = Trinity::Containers::MapGetValuePtr(currencies, currencyId);
362 return count ? *count : 0;
363 };
364
365 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
366 for (NodeGroup const* group : node->Groups)
367 for (TraitCostEntry const* cost : group->Costs)
368 if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
369 return false;
370
371 auto nodeEntryItr = std::ranges::find_if(node->Entries, [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; });
372 if (nodeEntryItr != node->Entries.end())
373 for (TraitCostEntry const* cost : nodeEntryItr->Costs)
374 if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
375 return false;
376
377 for (TraitCostEntry const* cost : node->Costs)
378 if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
379 return false;
380
381 if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
382 for (TraitCostEntry const* cost : tree->Costs)
383 if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
384 return false;
385
386 return true;
387}
388
389void TakeCurrencyCost(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& currencies)
390{
391 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
392 for (NodeGroup const* group : node->Groups)
393 for (TraitCostEntry const* cost : group->Costs)
394 currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
395
396 auto nodeEntryItr = std::ranges::find_if(node->Entries, [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; });
397 if (nodeEntryItr != node->Entries.end())
398 for (TraitCostEntry const* cost : nodeEntryItr->Costs)
399 currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
400
401 for (TraitCostEntry const* cost : node->Costs)
402 currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
403
404 if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
405 for (TraitCostEntry const* cost : tree->Costs)
406 currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
407}
408
409void FillOwnedCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player, std::map<int32, int32>& currencies)
410{
411 std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig);
412 if (!trees)
413 return;
414
415 auto hasTraitNodeEntry = [&traitConfig](int32 traitNodeEntryId)
416 {
417 return std::ranges::find_if(traitConfig.Entries, [traitNodeEntryId](WorldPackets::Traits::TraitEntry const& traitEntry)
418 {
419 return traitEntry.TraitNodeEntryID == traitNodeEntryId && (traitEntry.Rank > 0 || traitEntry.GrantedRanks > 0);
420 }) != traitConfig.Entries.end();
421 };
422
423 for (Tree const* tree : *trees)
424 {
425 for (TraitCurrencyEntry const* currency : tree->Currencies)
426 {
427 switch (currency->GetType())
428 {
430 {
431 int32& amount = currencies[currency->ID];
432 if (player.GetMoney() > uint64(std::numeric_limits<int32>::max() - amount))
433 amount = std::numeric_limits<int32>::max();
434 else
435 amount += player.GetMoney();
436 break;
437 }
439 currencies[currency->ID] += player.GetCurrencyQuantity(currency->CurrencyTypesID);
440 break;
442 if (std::vector<TraitCurrencySourceEntry const*> const* currencySources = Trinity::Containers::MapGetValuePtr(_traitCurrencySourcesByCurrency, currency->ID))
443 {
444 for (TraitCurrencySourceEntry const* currencySource : *currencySources)
445 {
446 if (currencySource->QuestID && !player.IsQuestRewarded(currencySource->QuestID))
447 continue;
448
449 if (currencySource->AchievementID && !player.HasAchieved(currencySource->AchievementID))
450 continue;
451
452 if (currencySource->PlayerLevel && player.GetLevel() < currencySource->PlayerLevel)
453 continue;
454
455 if (currencySource->TraitNodeEntryID && !hasTraitNodeEntry(currencySource->TraitNodeEntryID))
456 continue;
457
458 currencies[currencySource->TraitCurrencyID] += currencySource->Amount;
459 }
460 }
461 break;
463 if (currency->PlayerDataElementAccountID)
464 currencies[currency->ID] += std::visit([](auto value) { return static_cast<int32>(value); }, player.GetDataElementAccount(currency->CurrencyTypesID));
465 else if (currency->PlayerDataElementCharacterID)
466 currencies[currency->ID] += std::visit([](auto value) { return static_cast<int32>(value); }, player.GetDataElementCharacter(currency->CurrencyTypesID));
467 break;
468 default:
469 break;
470 }
471 }
472 }
473}
474
475std::vector<TraitCondEntry const*> GetGateConditionsForNode(Node const* node)
476{
477 std::vector<TraitCondEntry const*> gateConditions;
478
479 auto fillConditions = [&](std::vector<TraitCondEntry const*> const& conditions)
480 {
481 for (TraitCondEntry const* condition : conditions)
482 {
483 if (!condition->GetFlags().HasFlag(TraitCondFlags::IsGate))
484 continue;
485
486 auto cond = std::ranges::find(gateConditions, condition->TraitCurrencyID, &TraitCondEntry::TraitCurrencyID);
487 if (cond == gateConditions.end())
488 gateConditions.push_back(condition);
489 else if ((*cond)->SpentAmountRequired < condition->SpentAmountRequired)
490 *cond = condition;
491 }
492 };
493
494 fillConditions(node->Conditions);
495
496 for (NodeGroup const* group : node->Groups)
497 fillConditions(group->Conditions);
498
499 return gateConditions;
500}
501
502void AddSpentCurrenciesForEntry(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, SpentCurrency>& cachedCurrencies, int32 multiplier)
503{
504 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
505 std::vector<TraitCondEntry const*> gateConditions = GetGateConditionsForNode(node);
506
507 auto addCurrencies = [&](std::vector<TraitCostEntry const*> const& costs)
508 {
509 for (TraitCostEntry const* cost : costs)
510 {
511 int32 amount = cost->Amount * entry.Rank * multiplier;
512
513 SpentCurrency& cached = cachedCurrencies[cost->TraitCurrencyID];
514 cached.Total += amount;
515
516 int32 gate = 0;
517 auto gateCondition = std::ranges::find(gateConditions, cost->TraitCurrencyID, &TraitCondEntry::TraitCurrencyID);
518 if (gateCondition != gateConditions.end())
519 gate = (*gateCondition)->SpentAmountRequired;
520
521 auto gateCost = std::ranges::find(cached.ByGate, gate, Trinity::Containers::MapKey);
522 if (gateCost == cached.ByGate.end())
523 cached.ByGate.emplace_back(gate, amount);
524 else
525 gateCost->second += amount;
526 }
527 };
528
529 for (NodeGroup const* group : node->Groups)
530 addCurrencies(group->Costs);
531
532 auto nodeEntryItr = std::ranges::find_if(node->Entries, [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; });
533 if (nodeEntryItr != node->Entries.end())
534 addCurrencies(nodeEntryItr->Costs);
535
536 addCurrencies(node->Costs);
537
538 if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
539 addCurrencies(tree->Costs);
540}
541
542void FillSpentCurrenciesMap(std::vector<WorldPackets::Traits::TraitEntry> const& traitEntries, std::map<int32, SpentCurrency>& cachedCurrencies)
543{
544 for (WorldPackets::Traits::TraitEntry const& entry : traitEntries)
545 AddSpentCurrenciesForEntry(entry, cachedCurrencies, 1);
546}
547
549{
550 std::array<int32, 2> currencies = {};
551
552 if (std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig))
553 {
554 auto dest = currencies.begin();
555 for (auto treeItr = trees->begin(); treeItr != trees->end() && dest != currencies.end(); ++treeItr)
556 for (auto currencyItr = (*treeItr)->Currencies.begin(); currencyItr != (*treeItr)->Currencies.end() && dest != currencies.end(); ++currencyItr)
557 *dest++ = (*currencyItr)->ID;
558 }
559
560 return currencies;
561}
562
563std::span<TraitCurrencyEntry const* const> GetSubTreeCurrency(int32 traitSubTreeId)
564{
565 SubTree const* subTree = Trinity::Containers::MapGetValuePtr(_traitSubTrees, traitSubTreeId);
566 if (!subTree)
567 return {};
568
569 return subTree->Currencies;
570}
571
578
579TraitNodeRankCounts CountTraitNodeRanks(WorldPackets::Traits::TraitConfig const& traitConfig, int32 traitNodeGroupId, int32 traitNodeId, int32 traitNodeEntryId)
580{
582 for (WorldPackets::Traits::TraitEntry const& traitEntry : traitConfig.Entries)
583 {
584 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
585 if (traitNodeGroupId)
586 {
587 auto groupItr = std::ranges::find(node->Groups, uint32(traitNodeGroupId), [](NodeGroup const* group) { return group->Data->ID; });
588 if (groupItr != node->Groups.end())
589 ranks.Group += traitEntry.Rank;
590 }
591
592 if (traitNodeId == traitEntry.TraitNodeID)
593 ranks.Node += traitEntry.Rank;
594
595 if (traitNodeEntryId == traitEntry.TraitNodeEntryID)
596 ranks.Entry += traitEntry.Rank;
597 }
598
599 return ranks;
600}
601
603 Optional<std::map<int32, SpentCurrency>>& cachedCurrencies)
604{
605 if (condition->QuestID && !player.IsQuestRewarded(condition->QuestID))
606 return false;
607
608 if (condition->AchievementID && !player.HasAchieved(condition->AchievementID))
609 return false;
610
611 if (condition->SpecSetID)
612 {
613 uint32 chrSpecializationId = player.GetPrimarySpecialization();
614 if (traitConfig.Type == TraitConfigType::Combat)
615 chrSpecializationId = traitConfig.ChrSpecializationID;
616
617 if (!sDB2Manager.IsSpecSetMember(condition->SpecSetID, chrSpecializationId))
618 return false;
619 }
620
621 if (sTraitCurrencyStore.HasRecord(condition->TraitCurrencyID))
622 {
623 if (!cachedCurrencies)
624 FillSpentCurrenciesMap(traitConfig.Entries, cachedCurrencies.emplace());
625
626 if (condition->TraitNodeGroupID || condition->TraitNodeID || condition->TraitNodeEntryID)
627 {
628 int32 spentAmount = 0;
629 auto itr = cachedCurrencies->find(condition->TraitCurrencyID);
630 if (itr != cachedCurrencies->end())
631 for (auto [gate, spentBeforeGate] : itr->second.ByGate)
632 if (gate < condition->SpentAmountRequired)
633 spentAmount += spentBeforeGate;
634
635 if (spentAmount < condition->SpentAmountRequired)
636 return false;
637 }
638 }
639 else
640 {
641 if (condition->TraitNodeGroupID || condition->TraitNodeID || condition->TraitNodeEntryID)
642 {
643 TraitNodeRankCounts ranks = CountTraitNodeRanks(traitConfig, condition->TraitNodeGroupID, condition->TraitNodeID, condition->TraitNodeEntryID);
644 if (condition->SpentAmountRequired
645 && ranks.Group < condition->SpentAmountRequired
646 && ranks.Node < condition->SpentAmountRequired
647 && ranks.Entry < condition->SpentAmountRequired)
648 return false;
649 if (!condition->SpentAmountRequired && ranks.Group != 0 && ranks.Node != 0 && ranks.Entry != 0)
650 return false;
651 }
652 }
653
654 if (condition->RequiredLevel && player.GetLevel() < condition->RequiredLevel)
655 return false;
656
657 if (TraitCondAccountElementEntry const* accountElementCond = sTraitCondAccountElementStore.LookupEntry(condition->TraitCondAccountElementID))
658 {
659 int64 value = 0;
660 if (accountElementCond->PlayerDataElementAccountID)
661 value = std::visit([](auto v) { return static_cast<int64>(v); }, player.GetDataElementAccount(accountElementCond->PlayerDataElementAccountID));
662 else if (accountElementCond->PlayerDataElementCharacterID)
663 value = std::visit([](auto v) { return static_cast<int64>(v); }, player.GetDataElementCharacter(accountElementCond->PlayerDataElementCharacterID));
664
665 switch (accountElementCond->Comparison)
666 {
667 case 1: if (value != accountElementCond->ElementValueInt) return false; break;
668 case 2: if (value == accountElementCond->ElementValueInt) return false; break;
669 case 3: if (value >= accountElementCond->ElementValueInt) return false; break;
670 case 4: if (value > accountElementCond->ElementValueInt) return false; break;
671 case 5: if (value <= accountElementCond->ElementValueInt) return false; break;
672 case 6: if (value < accountElementCond->ElementValueInt) return false; break;
673 default:
674 return false;
675 }
676 }
677
678 return true;
679}
680
681bool NodeMeetsTraitConditions(WorldPackets::Traits::TraitConfig const& traitConfig, Node const* node, uint32 traitNodeEntryId, PlayerDataAccessor player,
682 Optional<std::map<int32, SpentCurrency>>& spentCurrencies)
683{
684 struct ConditionCheckResult
685 {
686 bool IsSufficient = false;
687 bool HasFailedConditions = false;
688 };
689
690 auto meetsConditions = [&traitConfig, player, &spentCurrencies]<Trinity::invocable_r<int32> RankSupplier>(std::vector<TraitCondEntry const*> const& conditions, TraitConditionType conditionType, RankSupplier getRank) -> ConditionCheckResult
691 {
692 ConditionCheckResult result;
693
694 Optional<int32> rank;
695 for (TraitCondEntry const* condition : conditions)
696 {
697 if (condition->GetCondType() != conditionType)
698 continue;
699
700 if (conditionType == TraitConditionType::RanksAllowed)
701 {
702 if (!rank.has_value())
703 rank = getRank();
704
705 if (*rank < condition->GrantedRanks)
706 continue;
707 }
708
709 if (!MeetsTraitCondition(traitConfig, player, condition, spentCurrencies))
710 {
711 result.HasFailedConditions = true;
712 continue;
713 }
714
715 if (condition->GetFlags().HasFlag(TraitCondFlags::IsSufficient))
716 {
717 result.IsSufficient = true;
718 break;
719 }
720 }
721
722 return result;
723 };
724
725 auto meetsConditionsOfType = [&](TraitConditionType conditionType)
726 {
727 bool hasFailedConditions = false;
728 ConditionCheckResult result = meetsConditions(node->Conditions, conditionType, [&] { return CountTraitNodeRanks(traitConfig, 0, node->Data->ID, traitNodeEntryId).Node; });
729 if (result.IsSufficient)
730 return true;
731 else if (result.HasFailedConditions)
732 hasFailedConditions = true;
733
734 for (NodeGroup const* group : node->Groups)
735 {
736 if (group->Conditions.empty())
737 continue;
738
739 result = meetsConditions(group->Conditions, conditionType, [&]{ return CountTraitNodeRanks(traitConfig, group->Data->ID, node->Data->ID, traitNodeEntryId).Group; });
740 if (result.IsSufficient)
741 return true;
742 if (result.HasFailedConditions)
743 hasFailedConditions = true;
744 }
745
746 for (NodeEntry const& entry : node->Entries)
747 {
748 if (entry.Data->ID != traitNodeEntryId || entry.Conditions.empty())
749 continue;
750
751 result = meetsConditions(entry.Conditions, conditionType, [&] { return CountTraitNodeRanks(traitConfig, 0, node->Data->ID, traitNodeEntryId).Entry; });
752 if (result.IsSufficient)
753 return true;
754 if (result.HasFailedConditions)
755 hasFailedConditions = true;
756 }
757
758 return !hasFailedConditions;
759 };
760
761 return meetsConditionsOfType(TraitConditionType::Visible)
762 && meetsConditionsOfType(TraitConditionType::Available)
763 && meetsConditionsOfType(TraitConditionType::RanksAllowed);
764}
765
766std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player)
767{
768 std::vector<UF::TraitEntry> entries;
769 std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig);
770 if (!trees)
771 return entries;
772
773 auto addGrantedRankToEntry = [&entries](int32 nodeId, NodeEntry const& entry, int32 grantedRanks)
774 {
775 auto itr = std::ranges::find_if(entries, [&](UF::TraitEntry const& traitEntry)
776 {
777 return traitEntry.TraitNodeID == nodeId && traitEntry.TraitNodeEntryID == int32(entry.Data->ID);
778 });
779 if (itr == entries.end())
780 {
781 itr = entries.emplace(entries.end());
782 itr->TraitNodeID = nodeId;
783 itr->TraitNodeEntryID = int32(entry.Data->ID);
784 itr->Rank = 0;
785 itr->GrantedRanks = 0;
786 }
787 itr->GrantedRanks += grantedRanks;
788 if (itr->GrantedRanks > entry.Data->MaxRanks)
789 itr->GrantedRanks = entry.Data->MaxRanks;
790 };
791
793
794 for (Tree const* tree : *trees)
795 {
796 for (Node const* node : tree->Nodes)
797 {
798 for (NodeEntry const& entry : node->Entries)
799 if (NodeMeetsTraitConditions(traitConfig, node, entry.Data->ID, player, cachedCurrencies))
800 for (TraitCondEntry const* condition : entry.Conditions)
801 if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies))
802 addGrantedRankToEntry(node->Data->ID, entry, condition->GrantedRanks);
803
804 for (TraitCondEntry const* condition : node->Conditions)
805 if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies))
806 for (NodeEntry const& entry : node->Entries)
807 if (NodeMeetsTraitConditions(traitConfig, node, entry.Data->ID, player, cachedCurrencies))
808 addGrantedRankToEntry(node->Data->ID, entry, condition->GrantedRanks);
809
810 for (NodeGroup const* group : node->Groups)
811 for (TraitCondEntry const* condition : group->Conditions)
812 if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies))
813 for (NodeEntry const& entry : node->Entries)
814 if (NodeMeetsTraitConditions(traitConfig, node, entry.Data->ID, player, cachedCurrencies))
815 addGrantedRankToEntry(node->Data->ID, entry, condition->GrantedRanks);
816 }
817 }
818
819 return entries;
820}
821
823{
824 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
825 if (!node)
826 return false;
827
828 auto entryItr = std::ranges::find_if(node->Entries, [&](NodeEntry const& entry) { return entry.Data->ID == uint32(traitEntry.TraitNodeEntryID); });
829 if (entryItr == node->Entries.end())
830 return false;
831
832 if (entryItr->Data->MaxRanks < traitEntry.Rank + traitEntry.GrantedRanks)
833 return false;
834
835 return true;
836}
837
838LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies /*= false*/, bool removeInvalidEntries /*= false*/)
839{
840 auto getNodeEntryCount = [&](int32 traitNodeId)
841 {
842 return std::ranges::count(traitConfig.Entries, traitNodeId, &WorldPackets::Traits::TraitEntry::TraitNodeID);
843 };
844
845 auto getNodeEntry = [&](int32 traitNodeId, int32 traitNodeEntryId)
846 {
847 auto entryItr = std::ranges::find_if(traitConfig.Entries, [=](WorldPackets::Traits::TraitEntry const& traitEntry)
848 {
849 return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId;
850 });
851 return entryItr != traitConfig.Entries.end() ? &*entryItr : nullptr;
852 };
853
854 auto isNodeFullyFilled = [&](Node const* node)
855 {
856 auto nodeEntryMatches = [&](NodeEntry const& nodeEntry)
857 {
858 WorldPackets::Traits::TraitEntry const* traitEntry = getNodeEntry(node->Data->ID, nodeEntry.Data->ID);
859 return traitEntry && (traitEntry->Rank + traitEntry->GrantedRanks) == nodeEntry.Data->MaxRanks;
860 };
861
862 if (node->Data->GetType() == TraitNodeType::Selection)
863 return std::ranges::any_of(node->Entries, nodeEntryMatches);
864
865 return std::ranges::all_of(node->Entries, nodeEntryMatches);
866 };
867
869 FillSpentCurrenciesMap(traitConfig.Entries, spentCurrencies.emplace());
870
871 auto isValidTraitEntry = [&](WorldPackets::Traits::TraitEntry const& traitEntry)
872 {
873 if (!IsValidEntry(traitEntry))
875
876 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
877 if (node->Data->GetType() == TraitNodeType::Selection || node->Data->GetType() == TraitNodeType::SubTreeSelection)
878 if (getNodeEntryCount(traitEntry.TraitNodeID) != 1)
880
881 if (!NodeMeetsTraitConditions(traitConfig, node, traitEntry.TraitNodeEntryID, player, spentCurrencies))
883
884 if (!node->ParentNodes.empty())
885 {
886 bool hasAnyParentTrait = false;
887 for (auto const& [parentNode, edgeType] : node->ParentNodes)
888 {
889 if (!isNodeFullyFilled(parentNode))
890 {
893
894 continue;
895 }
896
897 hasAnyParentTrait = true;
898 }
899
900 if (!hasAnyParentTrait)
902 }
903
904 return LearnResult::Ok;
905 };
906
907 for (auto itr = traitConfig.Entries.begin(); itr != traitConfig.Entries.end(); )
908 {
909 LearnResult result = isValidTraitEntry(*itr);
910 if (result != LearnResult::Ok)
911 {
912 if (!removeInvalidEntries)
913 return result;
914
915 AddSpentCurrenciesForEntry(*itr, *spentCurrencies, -1);
916
917 if (!itr->GrantedRanks // fully remove entries that don't have granted ranks
918 || !itr->Rank) // ... or entries that do have them and don't have any additional spent ranks (can happen if the same entry is revalidated after first removing all spent ranks)
919 traitConfig.Entries.erase(itr);
920 else
921 itr->Rank = 0;
922
923 // revalidate entire config - a removed entry will invalidate all other entries that depend on it
924 itr = traitConfig.Entries.begin();
925 }
926 else
927 ++itr;
928 }
929
930 struct SubtreeValidationData
931 {
932 std::vector<WorldPackets::Traits::TraitEntry> Entries;
933 bool IsSelected = false;
934 };
935 std::unordered_map<int32, SubtreeValidationData> subtrees;
936
937 for (WorldPackets::Traits::TraitEntry const& traitEntry : traitConfig.Entries)
938 {
939 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
940 auto entryItr = std::ranges::find(node->Entries, traitEntry.TraitNodeEntryID, [](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID); });
941 ASSERT(entryItr != node->Entries.end());
942
943 if (node->Data->GetType() == TraitNodeType::SubTreeSelection)
944 subtrees[entryItr->Data->TraitSubTreeID].IsSelected = true;
945
946 if (node->Data->TraitSubTreeID)
947 subtrees[node->Data->TraitSubTreeID].Entries.push_back(traitEntry);
948 }
949
950 for (WorldPackets::Traits::TraitSubTreeCache& subTree : traitConfig.SubTrees)
951 subTree.Active = false;
952
953 for (auto&& [selectedSubTreeId, data] : subtrees)
954 {
955 auto subtreeDataItr = std::ranges::find(traitConfig.SubTrees, selectedSubTreeId, &WorldPackets::Traits::TraitSubTreeCache::TraitSubTreeID);
956 if (subtreeDataItr == std::ranges::end(traitConfig.SubTrees))
957 {
958 subtreeDataItr = traitConfig.SubTrees.emplace(traitConfig.SubTrees.end());
959 subtreeDataItr->TraitSubTreeID = selectedSubTreeId;
960 }
961
962 subtreeDataItr->Entries = std::move(data.Entries);
963 subtreeDataItr->Active = data.IsSelected;
964 }
965
966 std::map<int32, int32> grantedCurrencies;
967 FillOwnedCurrenciesMap(traitConfig, player, grantedCurrencies);
968
969 for (auto const& [traitCurrencyId, spentAmount] : *spentCurrencies)
970 {
971 if (sTraitCurrencyStore.AssertEntry(traitCurrencyId)->GetType() != TraitCurrencyType::TraitSourced)
972 continue;
973
974 if (!spentAmount.Total)
975 continue;
976
977 int32* grantedCount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, traitCurrencyId);
978 if (!grantedCount || *grantedCount < spentAmount.Total)
980 }
981
982 if (requireSpendingAllCurrencies && traitConfig.Type == TraitConfigType::Combat)
983 {
984 // client checks only first two currencies for trait tree
985 for (int32 traitCurrencyId : GetClassAndSpecTreeCurrencies(traitConfig))
986 {
987 int32* grantedAmount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, traitCurrencyId);
988 if (!grantedAmount)
989 continue;
990
991 SpentCurrency* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, traitCurrencyId);
992 if (!spentAmount || spentAmount->Total != *grantedAmount)
994 }
995
996 for (auto&& [selectedTraitSubTreeId, data] : subtrees)
997 {
998 if (!data.IsSelected)
999 continue;
1000
1001 for (TraitCurrencyEntry const* subTreeCurrency : GetSubTreeCurrency(selectedTraitSubTreeId))
1002 {
1003 int32* grantedAmount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, subTreeCurrency->ID);
1004 if (!grantedAmount)
1005 continue;
1006
1007 SpentCurrency* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, subTreeCurrency->ID);
1008 if (!spentAmount || spentAmount->Total != *grantedAmount)
1010 }
1011 }
1012 }
1013
1014 return LearnResult::Ok;
1015}
1016
1017bool CanApplyTraitNode(UF::TraitConfig const& traitConfig, UF::TraitEntry const& traitEntry)
1018{
1019 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
1020 if (!node)
1021 return false;
1022
1023 if (node->Data->TraitSubTreeID)
1024 {
1025 auto subTreeItr = std::ranges::find(traitConfig.SubTrees, node->Data->TraitSubTreeID, &UF::TraitSubTreeCache::TraitSubTreeID);
1026 if (subTreeItr == std::ranges::end(traitConfig.SubTrees) || !subTreeItr->Active)
1027 return false;
1028 }
1029
1030 return true;
1031}
1032
1033std::vector<TraitDefinitionEffectPointsEntry const*> const* GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId)
1034{
1035 return Trinity::Containers::MapGetValuePtr(_traitDefinitionEffectPointModifiers, traitDefinitionId);
1036}
1037
1039{
1040 traitConfig.Entries.clear();
1041 std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig);
1042 if (!trees)
1043 return;
1044
1045 for (UF::TraitEntry const& grant : GetGrantedTraitEntriesForConfig(traitConfig, player))
1046 {
1047 WorldPackets::Traits::TraitEntry& newEntry = traitConfig.Entries.emplace_back();
1048 newEntry.TraitNodeID = grant.TraitNodeID;
1049 newEntry.TraitNodeEntryID = grant.TraitNodeEntryID;
1050 newEntry.GrantedRanks = grant.GrantedRanks;
1051 }
1052
1053 std::map<int32, int32> currencies;
1054 FillOwnedCurrenciesMap(traitConfig, player, currencies);
1055
1056 if (std::vector<TraitTreeLoadoutEntryEntry const*> const* loadoutEntries = Trinity::Containers::MapGetValuePtr(_traitTreeLoadoutsByChrSpecialization, traitConfig.ChrSpecializationID))
1057 {
1058 auto findEntry = [](WorldPackets::Traits::TraitConfig& config, int32 traitNodeId, int32 traitNodeEntryId) -> WorldPackets::Traits::TraitEntry*
1059 {
1060 auto entryItr = std::ranges::find_if(config.Entries, [=](WorldPackets::Traits::TraitEntry const& traitEntry)
1061 {
1062 return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId;
1063 });
1064 return entryItr != config.Entries.end() ? &*entryItr : nullptr;
1065 };
1066
1067 for (TraitTreeLoadoutEntryEntry const* loadoutEntry : *loadoutEntries)
1068 {
1069 int32 addedRanks = loadoutEntry->NumPoints;
1070 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, loadoutEntry->SelectedTraitNodeID);
1071
1073 newEntry.TraitNodeID = loadoutEntry->SelectedTraitNodeID;
1074 newEntry.TraitNodeEntryID = loadoutEntry->SelectedTraitNodeEntryID;
1075 if (!newEntry.TraitNodeEntryID)
1076 newEntry.TraitNodeEntryID = node->Entries[0].Data->ID;
1077
1078 WorldPackets::Traits::TraitEntry* entryInConfig = findEntry(traitConfig, newEntry.TraitNodeID, newEntry.TraitNodeEntryID);
1079
1080 if (entryInConfig)
1081 addedRanks -= entryInConfig->Rank;
1082
1083 newEntry.Rank = addedRanks;
1084
1085 if (!HasEnoughCurrency(newEntry, currencies))
1086 continue;
1087
1088 if (entryInConfig)
1089 entryInConfig->Rank += addedRanks;
1090 else
1091 traitConfig.Entries.push_back(newEntry);
1092
1093 TakeCurrencyCost(newEntry, currencies);
1094 }
1095 }
1096}
1097}
DB2Storage< TraitCondAccountElementEntry > sTraitCondAccountElementStore("TraitCondAccountElement.db2", &TraitCondAccountElementLoadInfo::Instance)
DB2Storage< TraitCurrencySourceEntry > sTraitCurrencySourceStore("TraitCurrencySource.db2", &TraitCurrencySourceLoadInfo::Instance)
DB2Storage< SkillLineEntry > sSkillLineStore("SkillLine.db2", &SkillLineLoadInfo::Instance)
DB2Storage< TraitTreeXTraitCostEntry > sTraitTreeXTraitCostStore("TraitTreeXTraitCost.db2", &TraitTreeXTraitCostLoadInfo::Instance)
DB2Storage< TraitNodeGroupXTraitCondEntry > sTraitNodeGroupXTraitCondStore("TraitNodeGroupXTraitCond.db2", &TraitNodeGroupXTraitCondLoadInfo::Instance)
DB2Storage< TraitNodeGroupXTraitNodeEntry > sTraitNodeGroupXTraitNodeStore("TraitNodeGroupXTraitNode.db2", &TraitNodeGroupXTraitNodeLoadInfo::Instance)
DB2Storage< TraitNodeEntry > sTraitNodeStore("TraitNode.db2", &TraitNodeLoadInfo::Instance)
DB2Storage< SkillLineXTraitTreeEntry > sSkillLineXTraitTreeStore("SkillLineXTraitTree.db2", &SkillLineXTraitTreeLoadInfo::Instance)
DB2Storage< TraitCostEntry > sTraitCostStore("TraitCost.db2", &TraitCostLoadInfo::Instance)
DB2Storage< TraitTreeLoadoutEntry > sTraitTreeLoadoutStore("TraitTreeLoadout.db2", &TraitTreeLoadoutLoadInfo::Instance)
DB2Storage< TraitNodeXTraitNodeEntryEntry > sTraitNodeXTraitNodeEntryStore("TraitNodeXTraitNodeEntry.db2", &TraitNodeXTraitNodeEntryLoadInfo::Instance)
DB2Storage< TraitNodeGroupXTraitCostEntry > sTraitNodeGroupXTraitCostStore("TraitNodeGroupXTraitCost.db2", &TraitNodeGroupXTraitCostLoadInfo::Instance)
DB2Storage< TraitTreeEntry > sTraitTreeStore("TraitTree.db2", &TraitTreeLoadInfo::Instance)
DB2Storage< TraitCondEntry > sTraitCondStore("TraitCond.db2", &TraitCondLoadInfo::Instance)
DB2Storage< TraitTreeXTraitCurrencyEntry > sTraitTreeXTraitCurrencyStore("TraitTreeXTraitCurrency.db2", &TraitTreeXTraitCurrencyLoadInfo::Instance)
DB2Storage< ChrSpecializationEntry > sChrSpecializationStore("ChrSpecialization.db2", &ChrSpecializationLoadInfo::Instance)
DB2Storage< TraitNodeEntryEntry > sTraitNodeEntryStore("TraitNodeEntry.db2", &TraitNodeEntryLoadInfo::Instance)
DB2Storage< TraitDefinitionEffectPointsEntry > sTraitDefinitionEffectPointsStore("TraitDefinitionEffectPoints.db2", &TraitDefinitionEffectPointsLoadInfo::Instance)
DB2Storage< TraitEdgeEntry > sTraitEdgeStore("TraitEdge.db2", &TraitEdgeLoadInfo::Instance)
DB2Storage< TraitNodeEntryXTraitCondEntry > sTraitNodeEntryXTraitCondStore("TraitNodeEntryXTraitCond.db2", &TraitNodeEntryXTraitCondLoadInfo::Instance)
DB2Storage< TraitCurrencyEntry > sTraitCurrencyStore("TraitCurrency.db2", &TraitCurrencyLoadInfo::Instance)
DB2Storage< TraitNodeXTraitCostEntry > sTraitNodeXTraitCostStore("TraitNodeXTraitCost.db2", &TraitNodeXTraitCostLoadInfo::Instance)
DB2Storage< TraitNodeXTraitCondEntry > sTraitNodeXTraitCondStore("TraitNodeXTraitCond.db2", &TraitNodeXTraitCondLoadInfo::Instance)
DB2Storage< TraitSubTreeEntry > sTraitSubTreeStore("TraitSubTree.db2", &TraitSubTreeLoadInfo::Instance)
DB2Storage< TraitNodeGroupEntry > sTraitNodeGroupStore("TraitNodeGroup.db2", &TraitNodeGroupLoadInfo::Instance)
DB2Storage< TraitNodeEntryXTraitCostEntry > sTraitNodeEntryXTraitCostStore("TraitNodeEntryXTraitCost.db2", &TraitNodeEntryXTraitCostLoadInfo::Instance)
DB2Storage< TraitTreeLoadoutEntryEntry > sTraitTreeLoadoutEntryStore("TraitTreeLoadoutEntry.db2", &TraitTreeLoadoutEntryLoadInfo::Instance)
#define sDB2Manager
Definition DB2Stores.h:569
TraitConfigType
Definition DBCEnums.h:2851
TraitConditionType
Definition DBCEnums.h:2841
TraitEdgeType
Definition DBCEnums.h:2867
int64_t int64
Definition Define.h:149
int32_t int32
Definition Define.h:150
uint64_t uint64
Definition Define.h:153
uint32_t uint32
Definition Define.h:154
#define ASSERT
Definition Errors.h:80
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
@ SKILL_CATEGORY_CLASS
#define MAX_CLASSES
std::vector< TraitCondEntry const * > Conditions
Definition TraitMgr.cpp:39
TraitConfigType ConfigType
Definition TraitMgr.cpp:75
std::vector< Node const * > Nodes
Definition TraitMgr.cpp:58
std::vector< SubTree const * > SubTrees
Definition TraitMgr.cpp:74
std::vector< std::pair< Node const *, TraitEdgeType > > ParentNodes
Definition TraitMgr.cpp:48
Trinity::Containers::FlatSet< TraitCurrencyEntry const * > Currencies
Definition TraitMgr.cpp:65
std::vector< TraitCostEntry const * > Costs
Definition TraitMgr.cpp:40
Definition Group.h:205
void AddSpentCurrenciesForEntry(WorldPackets::Traits::TraitEntry const &entry, std::map< int32, SpentCurrency > &cachedCurrencies, int32 multiplier)
Definition TraitMgr.cpp:502
std::vector< Tree const * > const * GetTreesForConfig(WorldPackets::Traits::TraitConfig const &traitConfig)
Finds relevant TraitTree identifiers.
Definition TraitMgr.cpp:338
std::vector< TraitCondEntry const * > GetGateConditionsForNode(Node const *node)
Definition TraitMgr.cpp:475
std::array< int32, 2 > GetClassAndSpecTreeCurrencies(WorldPackets::Traits::TraitConfig const &traitConfig)
Definition TraitMgr.cpp:548
bool CanApplyTraitNode(UF::TraitConfig const &traitConfig, UF::TraitEntry const &traitEntry)
bool NodeMeetsTraitConditions(WorldPackets::Traits::TraitConfig const &traitConfig, Node const *node, uint32 traitNodeEntryId, PlayerDataAccessor player, Optional< std::map< int32, SpentCurrency > > &spentCurrencies)
Definition TraitMgr.cpp:681
std::span< TraitCurrencyEntry const *const > GetSubTreeCurrency(int32 traitSubTreeId)
Definition TraitMgr.cpp:563
void InitializeStarterBuildTraitConfig(WorldPackets::Traits::TraitConfig &traitConfig, PlayerDataAccessor player)
std::vector< TraitDefinitionEffectPointsEntry const * > const * GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId)
void Load()
Definition TraitMgr.cpp:91
LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig &traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies, bool removeInvalidEntries)
Definition TraitMgr.cpp:838
int32 GenerateNewTraitConfigId()
Definition TraitMgr.cpp:316
bool IsValidEntry(WorldPackets::Traits::TraitEntry const &traitEntry)
Definition TraitMgr.cpp:822
void TakeCurrencyCost(WorldPackets::Traits::TraitEntry const &entry, std::map< int32, int32 > &currencies)
Definition TraitMgr.cpp:389
TraitNodeRankCounts CountTraitNodeRanks(WorldPackets::Traits::TraitConfig const &traitConfig, int32 traitNodeGroupId, int32 traitNodeId, int32 traitNodeEntryId)
Definition TraitMgr.cpp:579
std::vector< UF::TraitEntry > GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const &traitConfig, PlayerDataAccessor player)
Definition TraitMgr.cpp:766
bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const &traitConfig, PlayerDataAccessor player, TraitCondEntry const *condition, Optional< std::map< int32, SpentCurrency > > &cachedCurrencies)
Definition TraitMgr.cpp:602
void FillSpentCurrenciesMap(std::vector< WorldPackets::Traits::TraitEntry > const &traitEntries, std::map< int32, SpentCurrency > &cachedCurrencies)
Definition TraitMgr.cpp:542
void FillOwnedCurrenciesMap(WorldPackets::Traits::TraitConfig const &traitConfig, PlayerDataAccessor player, std::map< int32, int32 > &currencies)
Definition TraitMgr.cpp:409
TraitConfigType GetConfigTypeForTree(int32 traitTreeId)
Definition TraitMgr.cpp:324
bool HasEnoughCurrency(WorldPackets::Traits::TraitEntry const &entry, std::map< int32, int32 > const &currencies)
Definition TraitMgr.cpp:357
constexpr auto MapKey
Definition MapUtils.h:72
auto MapEqualRange(M &map, typename M::key_type const &key)
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition MapUtils.h:37
int32 TraitCondAccountElementID
int32 SpentAmountRequired
std::variant< int64, float > GetDataElementCharacter(uint32 dataElementId) const
Definition Player.cpp:31568
int32 GetCurrencyQuantity(int32 currencyId) const
Definition Player.cpp:31538
std::variant< int64, float > GetDataElementAccount(uint32 dataElementId) const
Definition Player.cpp:31563
uint32 GetPrimarySpecialization() const
Definition Player.cpp:31558
bool HasAchieved(int32 achievementId) const
Definition Player.cpp:31553
bool IsQuestRewarded(int32 questId) const
Definition Player.cpp:31548
std::vector< std::pair< int32, int32 > > ByGate
Definition TraitMgr.h:83
DynamicUpdateField< UF::TraitSubTreeCache, 0, 2 > SubTrees
std::vector< TraitSubTreeCache > SubTrees
std::vector< TraitEntry > Entries