TrinityCore
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 "IteratorPair.h"
21#include "MapUtils.h"
22#include "TraitPacketsCommon.h"
23#include "UpdateFields.h"
24
25namespace TraitMgr
26{
27namespace
28{
29struct NodeEntry;
30struct Node;
31struct NodeGroup;
32struct Tree;
33
34struct NodeEntry
35{
36 TraitNodeEntryEntry const* Data = nullptr;
37 std::vector<TraitCondEntry const*> Conditions;
38 std::vector<TraitCostEntry const*> Costs;
39};
40
41struct Node
42{
43 TraitNodeEntry const* Data = nullptr;
44 std::vector<NodeEntry> Entries;
45 std::vector<NodeGroup const*> Groups;
46 std::vector<std::pair<Node const*, TraitEdgeType>> ParentNodes; // TraitEdge::LeftTraitNodeID
47 std::vector<TraitCondEntry const*> Conditions;
48 std::vector<TraitCostEntry const*> Costs;
49};
50
51struct NodeGroup
52{
53 TraitNodeGroupEntry const* Data = nullptr;
54 std::vector<TraitCondEntry const*> Conditions;
55 std::vector<TraitCostEntry const*> Costs;
56 std::vector<Node const*> Nodes;
57};
58
59struct Tree
60{
61 TraitTreeEntry const* Data = nullptr;
62 std::vector<Node const*> Nodes;
63 std::vector<TraitCostEntry const*> Costs;
64 std::vector<TraitCurrencyEntry const*> Currencies;
66};
67
68std::unordered_map<int32, NodeGroup> _traitGroups;
69std::unordered_map<int32, Node> _traitNodes;
70std::unordered_map<int32, Tree> _traitTrees;
71std::array<int32, MAX_CLASSES> _skillLinesByClass;
72std::unordered_map<int32, std::vector<Tree const*>> _traitTreesBySkillLine;
73std::unordered_map<int32, std::vector<Tree const*>> _traitTreesByTraitSystem;
74int32 _configIdGenerator = 0;
75std::unordered_map<int32, std::vector<TraitCurrencySourceEntry const*>> _traitCurrencySourcesByCurrency;
76std::unordered_map<int32, std::vector<TraitDefinitionEffectPointsEntry const*>> _traitDefinitionEffectPointModifiers;
77std::unordered_map<int32, std::vector<TraitTreeLoadoutEntryEntry const*>> _traitTreeLoadoutsByChrSpecialization;
78}
79
80void Load()
81{
82 _configIdGenerator = time(nullptr);
83
84 std::unordered_map<int32, std::vector<TraitCondEntry const*>> nodeEntryConditions;
85 for (TraitNodeEntryXTraitCondEntry const* traitNodeEntryXTraitCondEntry : sTraitNodeEntryXTraitCondStore)
86 if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeEntryXTraitCondEntry->TraitCondID))
87 nodeEntryConditions[traitNodeEntryXTraitCondEntry->TraitNodeEntryID].push_back(traitCondEntry);
88
89 std::unordered_map<int32, std::vector<TraitCostEntry const*>> nodeEntryCosts;
90 for (TraitNodeEntryXTraitCostEntry const* traitNodeEntryXTraitCostEntry : sTraitNodeEntryXTraitCostStore)
91 if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitNodeEntryXTraitCostEntry->TraitCostID))
92 nodeEntryCosts[traitNodeEntryXTraitCostEntry->TraitNodeEntryID].push_back(traitCostEntry);
93
94 std::unordered_map<int32, std::vector<TraitCondEntry const*>> nodeGroupConditions;
95 for (TraitNodeGroupXTraitCondEntry const* traitNodeGroupXTraitCondEntry : sTraitNodeGroupXTraitCondStore)
96 if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeGroupXTraitCondEntry->TraitCondID))
97 nodeGroupConditions[traitNodeGroupXTraitCondEntry->TraitNodeGroupID].push_back(traitCondEntry);
98
99 std::unordered_map<int32, std::vector<TraitCostEntry const*>> nodeGroupCosts;
100 for (TraitNodeGroupXTraitCostEntry const* traitNodeGroupXTraitCostEntry : sTraitNodeGroupXTraitCostStore)
101 if (TraitCostEntry const* traitCondEntry = sTraitCostStore.LookupEntry(traitNodeGroupXTraitCostEntry->TraitCostID))
102 nodeGroupCosts[traitNodeGroupXTraitCostEntry->TraitNodeGroupID].push_back(traitCondEntry);
103
104 std::unordered_multimap<int32, int32> nodeGroups;
105 for (TraitNodeGroupXTraitNodeEntry const* traitNodeGroupXTraitNodeEntry : sTraitNodeGroupXTraitNodeStore)
106 nodeGroups.emplace(traitNodeGroupXTraitNodeEntry->TraitNodeID, traitNodeGroupXTraitNodeEntry->TraitNodeGroupID);
107
108 std::unordered_map<int32, std::vector<TraitCondEntry const*>> nodeConditions;
109 for (TraitNodeXTraitCondEntry const* traitNodeXTraitCondEntry : sTraitNodeXTraitCondStore)
110 if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeXTraitCondEntry->TraitCondID))
111 nodeConditions[traitNodeXTraitCondEntry->TraitNodeID].push_back(traitCondEntry);
112
113 std::unordered_map<int32, std::vector<TraitCostEntry const*>> nodeCosts;
114 for (TraitNodeXTraitCostEntry const* traitNodeXTraitCostEntry : sTraitNodeXTraitCostStore)
115 if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitNodeXTraitCostEntry->TraitCostID))
116 nodeCosts[traitNodeXTraitCostEntry->TraitNodeID].push_back(traitCostEntry);
117
118 std::unordered_multimap<int32, TraitNodeEntryEntry const*> nodeEntries;
119 for (TraitNodeXTraitNodeEntryEntry const* traitNodeXTraitNodeEntryEntry : sTraitNodeXTraitNodeEntryStore)
120 if (TraitNodeEntryEntry const* traitNodeEntryEntry = sTraitNodeEntryStore.LookupEntry(traitNodeXTraitNodeEntryEntry->TraitNodeEntryID))
121 nodeEntries.emplace(traitNodeXTraitNodeEntryEntry->TraitNodeID, traitNodeEntryEntry);
122
123 std::unordered_map<int32, std::vector<TraitCostEntry const*>> treeCosts;
124 for (TraitTreeXTraitCostEntry const* traitTreeXTraitCostEntry : sTraitTreeXTraitCostStore)
125 if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitTreeXTraitCostEntry->TraitCostID))
126 treeCosts[traitTreeXTraitCostEntry->TraitTreeID].push_back(traitCostEntry);
127
128 std::unordered_map<int32, std::vector<TraitCurrencyEntry const*>> treeCurrencies;
129 for (TraitTreeXTraitCurrencyEntry const* traitTreeXTraitCurrencyEntry : sTraitTreeXTraitCurrencyStore)
130 if (TraitCurrencyEntry const* traitCurrencyEntry = sTraitCurrencyStore.LookupEntry(traitTreeXTraitCurrencyEntry->TraitCurrencyID))
131 treeCurrencies[traitTreeXTraitCurrencyEntry->TraitTreeID].push_back(traitCurrencyEntry);
132
133 std::unordered_map<int32, std::vector<int32>> traitTreesIdsByTraitSystem;
134
135 for (TraitTreeEntry const* traitTree : sTraitTreeStore)
136 {
137 Tree& tree = _traitTrees[traitTree->ID];
138 tree.Data = traitTree;
139
140 if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(treeCosts, traitTree->ID))
141 tree.Costs = std::move(*costs);
142
143 if (std::vector<TraitCurrencyEntry const*>* currencies = Trinity::Containers::MapGetValuePtr(treeCurrencies, traitTree->ID))
144 tree.Currencies = std::move(*currencies);
145
146 if (traitTree->TraitSystemID)
147 {
148 traitTreesIdsByTraitSystem[traitTree->TraitSystemID].push_back(traitTree->ID);
149 tree.ConfigType = TraitConfigType::Generic;
150 }
151 }
152
153 for (TraitNodeGroupEntry const* traitNodeGroup : sTraitNodeGroupStore)
154 {
155 NodeGroup& nodeGroup = _traitGroups[traitNodeGroup->ID];
156 nodeGroup.Data = traitNodeGroup;
157
158 if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeGroupConditions, traitNodeGroup->ID))
159 nodeGroup.Conditions = std::move(*conditions);
160
161 if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeGroupCosts, traitNodeGroup->ID))
162 nodeGroup.Costs = std::move(*costs);
163 }
164
165 for (TraitNodeEntry const* traitNode : sTraitNodeStore)
166 {
167 Node& node = _traitNodes[traitNode->ID];
168 node.Data = traitNode;
169
170 if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitNode->TraitTreeID))
171 tree->Nodes.push_back(&node);
172
173 for (auto&& [_, traitNodeEntry] : Trinity::Containers::MapEqualRange(nodeEntries, traitNode->ID))
174 {
175 NodeEntry& entry = node.Entries.emplace_back();
176 entry.Data = traitNodeEntry;
177
178 if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeEntryConditions, traitNodeEntry->ID))
179 entry.Conditions = std::move(*conditions);
180
181 if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeEntryCosts, traitNodeEntry->ID))
182 entry.Costs = std::move(*costs);
183 }
184
185 for (auto&& [_, nodeGroupId] : Trinity::Containers::MapEqualRange(nodeGroups, traitNode->ID))
186 {
187 NodeGroup* nodeGroup = Trinity::Containers::MapGetValuePtr(_traitGroups, nodeGroupId);
188 if (!nodeGroup)
189 continue;
190
191 nodeGroup->Nodes.push_back(&node);
192 node.Groups.push_back(nodeGroup);
193 }
194
195 if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeConditions, traitNode->ID))
196 node.Conditions = std::move(*conditions);
197
198 if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeCosts, traitNode->ID))
199 node.Costs = std::move(*costs);
200 }
201
202 for (TraitEdgeEntry const* traitEdgeEntry : sTraitEdgeStore)
203 {
204 Node* left = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEdgeEntry->LeftTraitNodeID);
205 Node* right = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEdgeEntry->RightTraitNodeID);
206 if (!left || !right)
207 continue;
208
209 right->ParentNodes.emplace_back(left, static_cast<TraitEdgeType>(traitEdgeEntry->Type));
210 }
211
212 for (SkillLineXTraitTreeEntry const* skillLineXTraitTreeEntry : sSkillLineXTraitTreeStore)
213 {
214 Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, skillLineXTraitTreeEntry->TraitTreeID);
215 if (!tree)
216 continue;
217
218 SkillLineEntry const* skillLineEntry = sSkillLineStore.LookupEntry(skillLineXTraitTreeEntry->SkillLineID);
219 if (!skillLineEntry)
220 continue;
221
222 _traitTreesBySkillLine[skillLineXTraitTreeEntry->SkillLineID].push_back(tree);
223 if (skillLineEntry->CategoryID == SKILL_CATEGORY_CLASS)
224 {
225 for (SkillRaceClassInfoEntry const* skillRaceClassInfo : sDB2Manager.GetSkillRaceClassInfo(skillLineEntry->ID))
226 for (int32 i = 1; i < MAX_CLASSES; ++i)
227 if (skillRaceClassInfo->ClassMask & (1 << (i - 1)))
228 _skillLinesByClass[i] = skillLineXTraitTreeEntry->SkillLineID;
229
230 tree->ConfigType = TraitConfigType::Combat;
231 }
232 else
233 tree->ConfigType = TraitConfigType::Profession;
234 }
235
236 for (auto&& [traitSystemId, traitTreeIds] : traitTreesIdsByTraitSystem)
237 for (int32 traitTreeId : traitTreeIds)
238 if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitTreeId))
239 _traitTreesByTraitSystem[traitSystemId].push_back(tree);
240
241 for (TraitCurrencySourceEntry const* traitCurrencySource : sTraitCurrencySourceStore)
242 _traitCurrencySourcesByCurrency[traitCurrencySource->TraitCurrencyID].push_back(traitCurrencySource);
243
244 for (TraitDefinitionEffectPointsEntry const* traitDefinitionEffectPoints : sTraitDefinitionEffectPointsStore)
245 _traitDefinitionEffectPointModifiers[traitDefinitionEffectPoints->TraitDefinitionID].push_back(traitDefinitionEffectPoints);
246
247 std::unordered_map<int32, std::vector<TraitTreeLoadoutEntryEntry const*>> traitTreeLoadoutEntries;
248 for (TraitTreeLoadoutEntryEntry const* traitTreeLoadoutEntry : sTraitTreeLoadoutEntryStore)
249 traitTreeLoadoutEntries[traitTreeLoadoutEntry->TraitTreeLoadoutID].push_back(traitTreeLoadoutEntry);
250
251 for (TraitTreeLoadoutEntry const* traitTreeLoadout : sTraitTreeLoadoutStore)
252 {
253 if (std::vector<TraitTreeLoadoutEntryEntry const*>* entries = Trinity::Containers::MapGetValuePtr(traitTreeLoadoutEntries, traitTreeLoadout->ID))
254 {
255 std::sort(entries->begin(), entries->end(), [](TraitTreeLoadoutEntryEntry const* left, TraitTreeLoadoutEntryEntry const* right)
256 {
257 return left->OrderIndex < right->OrderIndex;
258 });
259 // there should be only one loadout per spec, we take last one encountered
260 _traitTreeLoadoutsByChrSpecialization[traitTreeLoadout->ChrSpecializationID] = std::move(*entries);
261 }
262 }
263}
264
270{
271 if (_configIdGenerator == std::numeric_limits<int32>::max())
272 _configIdGenerator = 0;
273
274 return ++_configIdGenerator;
275}
276
278{
279 Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitTreeId);
280 if (!tree)
282
283 return tree->ConfigType;
284}
285
291std::vector<Tree const*> const* GetTreesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig)
292{
293 switch (traitConfig.Type)
294 {
296 if (ChrSpecializationEntry const* chrSpecializationEntry = sChrSpecializationStore.LookupEntry(traitConfig.ChrSpecializationID))
297 return Trinity::Containers::MapGetValuePtr(_traitTreesBySkillLine, _skillLinesByClass[chrSpecializationEntry->ClassID]);
298 break;
300 return Trinity::Containers::MapGetValuePtr(_traitTreesBySkillLine, traitConfig.SkillLineID);
302 return Trinity::Containers::MapGetValuePtr(_traitTreesByTraitSystem, traitConfig.TraitSystemID);
303 default:
304 break;
305 }
306
307 return nullptr;
308}
309
310bool HasEnoughCurrency(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32> const& currencies)
311{
312 auto getCurrencyCount = [&](int32 currencyId)
313 {
314 int32 const* count = Trinity::Containers::MapGetValuePtr(currencies, currencyId);
315 return count ? *count : 0;
316 };
317
318 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
319 for (NodeGroup const* group : node->Groups)
320 for (TraitCostEntry const* cost : group->Costs)
321 if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
322 return false;
323
324 auto nodeEntryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; });
325 if (nodeEntryItr != node->Entries.end())
326 for (TraitCostEntry const* cost : nodeEntryItr->Costs)
327 if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
328 return false;
329
330 for (TraitCostEntry const* cost : node->Costs)
331 if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
332 return false;
333
334 if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
335 for (TraitCostEntry const* cost : tree->Costs)
336 if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank)
337 return false;
338
339 return true;
340}
341
342void TakeCurrencyCost(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& currencies)
343{
344 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
345 for (NodeGroup const* group : node->Groups)
346 for (TraitCostEntry const* cost : group->Costs)
347 currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
348
349 auto nodeEntryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; });
350 if (nodeEntryItr != node->Entries.end())
351 for (TraitCostEntry const* cost : nodeEntryItr->Costs)
352 currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
353
354 for (TraitCostEntry const* cost : node->Costs)
355 currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
356
357 if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
358 for (TraitCostEntry const* cost : tree->Costs)
359 currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank;
360}
361
362void FillOwnedCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player, std::map<int32, int32>& currencies)
363{
364 std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig);
365 if (!trees)
366 return;
367
368 auto hasTraitNodeEntry = [&traitConfig](int32 traitNodeEntryId)
369 {
370 return std::find_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), [traitNodeEntryId](WorldPackets::Traits::TraitEntry const& traitEntry)
371 {
372 return traitEntry.TraitNodeEntryID == traitNodeEntryId && (traitEntry.Rank > 0 || traitEntry.GrantedRanks > 0);
373 }) != traitConfig.Entries.end();
374 };
375
376 for (Tree const* tree : *trees)
377 {
378 for (TraitCurrencyEntry const* currency : tree->Currencies)
379 {
380 switch (currency->GetType())
381 {
383 {
384 int32& amount = currencies[currency->ID];
385 if (player.GetMoney() > uint64(std::numeric_limits<int32>::max() - amount))
386 amount = std::numeric_limits<int32>::max();
387 else
388 amount += player.GetMoney();
389 break;
390 }
392 currencies[currency->ID] += player.GetCurrencyQuantity(currency->CurrencyTypesID);
393 break;
395 if (std::vector<TraitCurrencySourceEntry const*> const* currencySources = Trinity::Containers::MapGetValuePtr(_traitCurrencySourcesByCurrency, currency->ID))
396 {
397 for (TraitCurrencySourceEntry const* currencySource : *currencySources)
398 {
399 if (currencySource->QuestID && !player.IsQuestRewarded(currencySource->QuestID))
400 continue;
401
402 if (currencySource->AchievementID && !player.HasAchieved(currencySource->AchievementID))
403 continue;
404
405 if (currencySource->PlayerLevel && player.GetLevel() < currencySource->PlayerLevel)
406 continue;
407
408 if (currencySource->TraitNodeEntryID && !hasTraitNodeEntry(currencySource->TraitNodeEntryID))
409 continue;
410
411 currencies[currencySource->TraitCurrencyID] += currencySource->Amount;
412 }
413 }
414 break;
415 default:
416 break;
417 }
418 }
419 }
420}
421
422void FillSpentCurrenciesMap(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& cachedCurrencies)
423{
424 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
425 for (NodeGroup const* group : node->Groups)
426 for (TraitCostEntry const* cost : group->Costs)
427 cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
428
429 auto nodeEntryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; });
430 if (nodeEntryItr != node->Entries.end())
431 for (TraitCostEntry const* cost : nodeEntryItr->Costs)
432 cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
433
434 for (TraitCostEntry const* cost : node->Costs)
435 cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
436
437 if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
438 for (TraitCostEntry const* cost : tree->Costs)
439 cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
440}
441
442void FillSpentCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig, std::map<int32, int32>& cachedCurrencies)
443{
444 for (WorldPackets::Traits::TraitEntry const& entry : traitConfig.Entries)
445 FillSpentCurrenciesMap(entry, cachedCurrencies);
446}
447
449 Optional<std::map<int32, int32>>& cachedCurrencies)
450{
451 if (condition->QuestID && !player.IsQuestRewarded(condition->QuestID))
452 return false;
453
454 if (condition->AchievementID && !player.HasAchieved(condition->AchievementID))
455 return false;
456
457 if (condition->SpecSetID)
458 {
459 uint32 chrSpecializationId = player.GetPrimarySpecialization();
460 if (traitConfig.Type == TraitConfigType::Combat)
461 chrSpecializationId = traitConfig.ChrSpecializationID;
462
463 if (!sDB2Manager.IsSpecSetMember(condition->SpecSetID, chrSpecializationId))
464 return false;
465 }
466
467 if (condition->TraitCurrencyID && condition->SpentAmountRequired)
468 {
469 if (!cachedCurrencies)
470 FillSpentCurrenciesMap(traitConfig, cachedCurrencies.emplace());
471
472 if (condition->TraitNodeGroupID)
473 {
474 auto itr = cachedCurrencies->try_emplace(condition->TraitCurrencyID, 0).first;
475 if (itr->second < condition->SpentAmountRequired)
476 return false;
477 }
478 else if (condition->TraitNodeID)
479 {
480 auto itr = cachedCurrencies->try_emplace(condition->TraitCurrencyID, 0).first;
481 if (itr->second < condition->SpentAmountRequired)
482 return false;
483 }
484 }
485
486 if (condition->RequiredLevel && player.GetLevel() < condition->RequiredLevel)
487 return false;
488
489 return true;
490}
491
492std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player)
493{
494 std::vector<UF::TraitEntry> entries;
495 std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig);
496 if (!trees)
497 return entries;
498
499 auto getOrCreateEntry = [&entries](int32 nodeId, int32 entryId)
500 {
501 auto itr = std::find_if(entries.begin(), entries.end(), [&](UF::TraitEntry const& traitEntry)
502 {
503 return traitEntry.TraitNodeID == nodeId && traitEntry.TraitNodeEntryID == entryId;
504 });
505 if (itr == entries.end())
506 {
507 itr = entries.emplace(entries.end());
508 itr->TraitNodeID = nodeId;
509 itr->TraitNodeEntryID = entryId;
510 itr->Rank = 0;
511 itr->GrantedRanks = 0;
512 }
513 return &*itr;
514 };
515
516 Optional<std::map<int32, int32>> cachedCurrencies;
517
518 for (Tree const* tree : *trees)
519 {
520 for (Node const* node : tree->Nodes)
521 {
522 for (NodeEntry const& entry : node->Entries)
523 for (TraitCondEntry const* condition : entry.Conditions)
524 if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies))
525 getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks;
526
527 for (TraitCondEntry const* condition : node->Conditions)
528 if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies))
529 for (NodeEntry const& entry : node->Entries)
530 getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks;
531
532 for (NodeGroup const* group : node->Groups)
533 for (TraitCondEntry const* condition : group->Conditions)
534 if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies))
535 for (NodeEntry const& entry : node->Entries)
536 getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks;
537 }
538 }
539
540 return entries;
541}
542
544{
545 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
546 if (!node)
547 return false;
548
549 auto entryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&](NodeEntry const& entry) { return entry.Data->ID == uint32(traitEntry.TraitNodeEntryID); });
550 if (entryItr == node->Entries.end())
551 return false;
552
553 if (entryItr->Data->MaxRanks < traitEntry.Rank + traitEntry.GrantedRanks)
554 return false;
555
556 return true;
557}
558
559LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies /*= false*/)
560{
561 auto getNodeEntryCount = [&](int32 traitNodeId)
562 {
563 return std::count_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), [traitNodeId](WorldPackets::Traits::TraitEntry const& traitEntry)
564 {
565 return traitEntry.TraitNodeID == traitNodeId;
566 });
567 };
568
569 auto getNodeEntry = [&](int32 traitNodeId, int32 traitNodeEntryId)
570 {
571 auto entryItr = std::find_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), [=](WorldPackets::Traits::TraitEntry const& traitEntry)
572 {
573 return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId;
574 });
575 return entryItr != traitConfig.Entries.end() ? &*entryItr : nullptr;
576 };
577
578 auto isNodeFullyFilled = [&](Node const* node)
579 {
580 if (node->Data->GetType() == TraitNodeType::Selection)
581 return std::any_of(node->Entries.begin(), node->Entries.end(), [&](NodeEntry const& nodeEntry)
582 {
583 WorldPackets::Traits::TraitEntry const* traitEntry = getNodeEntry(node->Data->ID, nodeEntry.Data->ID);
584 return traitEntry && (traitEntry->Rank + traitEntry->GrantedRanks) == nodeEntry.Data->MaxRanks;
585 });
586
587 return std::all_of(node->Entries.begin(), node->Entries.end(), [&](NodeEntry const& nodeEntry)
588 {
589 WorldPackets::Traits::TraitEntry const* traitEntry = getNodeEntry(node->Data->ID, nodeEntry.Data->ID);
590 return traitEntry && (traitEntry->Rank + traitEntry->GrantedRanks) == nodeEntry.Data->MaxRanks;
591 });
592 };
593
594 Optional<std::map<int32, int32>> spentCurrencies;
595 FillSpentCurrenciesMap(traitConfig, spentCurrencies.emplace());
596
597 auto meetsConditions = [&](std::vector<TraitCondEntry const*> const& conditions)
598 {
599 bool hasConditions = false;
600 for (TraitCondEntry const* condition : conditions)
601 {
602 if (condition->GetCondType() == TraitConditionType::Available || condition->GetCondType() == TraitConditionType::Visible)
603 {
604 if (MeetsTraitCondition(traitConfig, player, condition, spentCurrencies))
605 return true;
606
607 hasConditions = true;
608 }
609 }
610
611 return !hasConditions;
612 };
613
614 for (WorldPackets::Traits::TraitEntry const& traitEntry : traitConfig.Entries)
615 {
616 if (!IsValidEntry(traitEntry))
618
619 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
620 if (node->Data->GetType() == TraitNodeType::Selection)
621 if (getNodeEntryCount(traitEntry.TraitNodeID) != 1)
623
624 for (NodeEntry const& entry : node->Entries)
625 if (!meetsConditions(entry.Conditions))
627
628 if (!meetsConditions(node->Conditions))
630
631 for (NodeGroup const* group : node->Groups)
632 if (!meetsConditions(group->Conditions))
634
635 if (!node->ParentNodes.empty())
636 {
637 bool hasAnyParentTrait = false;
638 for (auto const& [parentNode, edgeType] : node->ParentNodes)
639 {
640 if (!isNodeFullyFilled(parentNode))
641 {
644
645 continue;
646 }
647
648 hasAnyParentTrait = true;
649 }
650
651 if (!hasAnyParentTrait)
653 }
654 }
655
656 std::map<int32, int32> grantedCurrencies;
657 FillOwnedCurrenciesMap(traitConfig, player, grantedCurrencies);
658
659 for (auto [traitCurrencyId, spentAmount] : *spentCurrencies)
660 {
661 if (sTraitCurrencyStore.AssertEntry(traitCurrencyId)->GetType() != TraitCurrencyType::TraitSourced)
662 continue;
663
664 if (!spentAmount)
665 continue;
666
667 int32* grantedCount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, traitCurrencyId);
668 if (!grantedCount || *grantedCount < spentAmount)
670 }
671
672 if (requireSpendingAllCurrencies && traitConfig.Type == TraitConfigType::Combat)
673 {
674 for (auto [traitCurrencyId, grantedAmount] : grantedCurrencies)
675 {
676 if (!grantedAmount)
677 continue;
678
679 int32* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, traitCurrencyId);
680 if (!spentAmount || *spentAmount != grantedAmount)
682 }
683 }
684
685 return LearnResult::Ok;
686}
687
688std::vector<TraitDefinitionEffectPointsEntry const*> const* GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId)
689{
690 return Trinity::Containers::MapGetValuePtr(_traitDefinitionEffectPointModifiers, traitDefinitionId);
691}
692
694{
695 traitConfig.Entries.clear();
696 std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig);
697 if (!trees)
698 return;
699
700 for (UF::TraitEntry const& grant : GetGrantedTraitEntriesForConfig(traitConfig, player))
701 {
702 WorldPackets::Traits::TraitEntry& newEntry = traitConfig.Entries.emplace_back();
703 newEntry.TraitNodeID = grant.TraitNodeID;
704 newEntry.TraitNodeEntryID = grant.TraitNodeEntryID;
705 newEntry.GrantedRanks = grant.GrantedRanks;
706 }
707
708 std::map<int32, int32> currencies;
709 FillOwnedCurrenciesMap(traitConfig, player, currencies);
710
711 if (std::vector<TraitTreeLoadoutEntryEntry const*> const* loadoutEntries = Trinity::Containers::MapGetValuePtr(_traitTreeLoadoutsByChrSpecialization, traitConfig.ChrSpecializationID))
712 {
713 auto findEntry = [](WorldPackets::Traits::TraitConfig& config, int32 traitNodeId, int32 traitNodeEntryId) -> WorldPackets::Traits::TraitEntry*
714 {
715 auto entryItr = std::find_if(config.Entries.begin(), config.Entries.end(), [=](WorldPackets::Traits::TraitEntry const& traitEntry)
716 {
717 return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId;
718 });
719 return entryItr != config.Entries.end() ? &*entryItr : nullptr;
720 };
721
722 for (TraitTreeLoadoutEntryEntry const* loadoutEntry : *loadoutEntries)
723 {
724 int32 addedRanks = loadoutEntry->NumPoints;
725 Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, loadoutEntry->SelectedTraitNodeID);
726
728 newEntry.TraitNodeID = loadoutEntry->SelectedTraitNodeID;
729 newEntry.TraitNodeEntryID = loadoutEntry->SelectedTraitNodeEntryID;
730 if (!newEntry.TraitNodeEntryID)
731 newEntry.TraitNodeEntryID = node->Entries[0].Data->ID;
732
733 WorldPackets::Traits::TraitEntry* entryInConfig = findEntry(traitConfig, newEntry.TraitNodeID, newEntry.TraitNodeEntryID);
734
735 if (entryInConfig)
736 addedRanks -= entryInConfig->Rank;
737
738 newEntry.Rank = addedRanks;
739
740 if (!HasEnoughCurrency(newEntry, currencies))
741 continue;
742
743 if (entryInConfig)
744 entryInConfig->Rank += addedRanks;
745 else
746 traitConfig.Entries.push_back(newEntry);
747
748 TakeCurrencyCost(newEntry, currencies);
749 }
750 }
751}
752}
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< 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:538
TraitConfigType
Definition: DBCEnums.h:2185
TraitEdgeType
Definition: DBCEnums.h:2200
int32_t int32
Definition: Define.h:138
uint64_t uint64
Definition: Define.h:141
uint32_t uint32
Definition: Define.h:142
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:37
TraitConfigType ConfigType
Definition: TraitMgr.cpp:65
std::vector< NodeGroup const * > Groups
Definition: TraitMgr.cpp:45
std::vector< Node const * > Nodes
Definition: TraitMgr.cpp:56
std::vector< std::pair< Node const *, TraitEdgeType > > ParentNodes
Definition: TraitMgr.cpp:46
std::vector< TraitCurrencyEntry const * > Currencies
Definition: TraitMgr.cpp:64
std::vector< TraitCostEntry const * > Costs
Definition: TraitMgr.cpp:38
std::vector< NodeEntry > Entries
Definition: TraitMgr.cpp:44
std::vector< Tree const * > const * GetTreesForConfig(WorldPackets::Traits::TraitConfig const &traitConfig)
Finds relevant TraitTree identifiers.
Definition: TraitMgr.cpp:291
void FillSpentCurrenciesMap(WorldPackets::Traits::TraitEntry const &entry, std::map< int32, int32 > &cachedCurrencies)
Definition: TraitMgr.cpp:422
void InitializeStarterBuildTraitConfig(WorldPackets::Traits::TraitConfig &traitConfig, PlayerDataAccessor player)
Definition: TraitMgr.cpp:693
std::vector< TraitDefinitionEffectPointsEntry const * > const * GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId)
Definition: TraitMgr.cpp:688
void Load()
Definition: TraitMgr.cpp:80
int32 GenerateNewTraitConfigId()
Definition: TraitMgr.cpp:269
LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig const &traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies)
Definition: TraitMgr.cpp:559
bool IsValidEntry(WorldPackets::Traits::TraitEntry const &traitEntry)
Definition: TraitMgr.cpp:543
void TakeCurrencyCost(WorldPackets::Traits::TraitEntry const &entry, std::map< int32, int32 > &currencies)
Definition: TraitMgr.cpp:342
LearnResult
Definition: TraitMgr.h:47
std::vector< UF::TraitEntry > GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const &traitConfig, PlayerDataAccessor player)
Definition: TraitMgr.cpp:492
void FillOwnedCurrenciesMap(WorldPackets::Traits::TraitConfig const &traitConfig, PlayerDataAccessor player, std::map< int32, int32 > &currencies)
Definition: TraitMgr.cpp:362
TraitConfigType GetConfigTypeForTree(int32 traitTreeId)
Definition: TraitMgr.cpp:277
bool HasEnoughCurrency(WorldPackets::Traits::TraitEntry const &entry, std::map< int32, int32 > const &currencies)
Definition: TraitMgr.cpp:310
bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const &traitConfig, PlayerDataAccessor player, TraitCondEntry const *condition, Optional< std::map< int32, int32 > > &cachedCurrencies)
Definition: TraitMgr.cpp:448
auto MapEqualRange(M &map, typename M::key_type const &key)
Definition: IteratorPair.h:60
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition: MapUtils.h:29
int32 TraitNodeGroupID
TraitConditionType GetCondType() const
int32 SpentAmountRequired
TraitCurrencyType GetType() const
int32 GetCurrencyQuantity(int32 currencyId) const
Definition: Player.cpp:30199
uint32 GetPrimarySpecialization() const
Definition: Player.cpp:30219
bool HasAchieved(int32 achievementId) const
Definition: Player.cpp:30214
bool IsQuestRewarded(int32 questId) const
Definition: Player.cpp:30209
std::vector< TraitEntry > Entries