Core/Players: Implemented hero talents

This commit is contained in:
Shauren
2024-08-10 12:01:47 +02:00
parent 5912f4dd7a
commit d1ffe61727
12 changed files with 314 additions and 24 deletions

View File

@@ -0,0 +1,37 @@
--
-- Table structure for table `trait_sub_tree`
--
DROP TABLE IF EXISTS `trait_sub_tree`;
CREATE TABLE `trait_sub_tree` (
`Name` text,
`Description` text,
`ID` int unsigned NOT NULL DEFAULT '0',
`UiTextureAtlasElementID` int NOT NULL DEFAULT '0',
`TraitTreeID` int NOT NULL DEFAULT '0',
`VerifiedBuild` int NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`,`VerifiedBuild`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- Table structure for table `trait_sub_tree_locale`
--
DROP TABLE IF EXISTS `trait_sub_tree_locale`;
CREATE TABLE `trait_sub_tree_locale` (
`ID` int unsigned NOT NULL DEFAULT '0',
`locale` varchar(4) NOT NULL,
`Name_lang` text,
`Description_lang` text,
`VerifiedBuild` int NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST COLUMNS(locale)
(PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB,
PARTITION esES VALUES IN ('esES') ENGINE = InnoDB,
PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB,
PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB,
PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB,
PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB,
PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB,
PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB,
PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB,
PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB);

View File

@@ -1852,6 +1852,13 @@ void HotfixDatabaseConnection::DoPrepareStatements()
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY, "SELECT MAX(ID) + 1 FROM trait_node_x_trait_node_entry", CONNECTION_SYNCH);
// TraitSubTree.db2
PrepareStatement(HOTFIX_SEL_TRAIT_SUB_TREE, "SELECT Name, Description, ID, UiTextureAtlasElementID, TraitTreeID FROM trait_sub_tree"
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_SUB_TREE, "SELECT MAX(ID) + 1 FROM trait_sub_tree", CONNECTION_SYNCH);
PREPARE_LOCALE_STMT(HOTFIX_SEL_TRAIT_SUB_TREE, "SELECT ID, Name_lang, Description_lang FROM trait_sub_tree_locale WHERE (`VerifiedBuild` > 0) = ?"
" AND locale = ?", CONNECTION_SYNCH);
// TraitTree.db2
PrepareStatement(HOTFIX_SEL_TRAIT_TREE, "SELECT ID, TraitSystemID, Unused1000_1, FirstTraitNodeID, PlayerConditionID, Flags, Unused1000_2, "
"Unused1000_3 FROM trait_tree WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);

View File

@@ -1076,6 +1076,10 @@ enum HotfixDatabaseStatements : uint32
HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY,
HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY_MAX_ID,
HOTFIX_SEL_TRAIT_SUB_TREE,
HOTFIX_SEL_TRAIT_SUB_TREE_MAX_ID,
HOTFIX_SEL_TRAIT_SUB_TREE_LOCALE,
HOTFIX_SEL_TRAIT_TREE,
HOTFIX_SEL_TRAIT_TREE_MAX_ID,

View File

@@ -6089,6 +6089,20 @@ struct TraitNodeXTraitNodeEntryLoadInfo
static constexpr DB2LoadInfo Instance{ Fields, 4, &TraitNodeXTraitNodeEntryMeta::Instance, HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY };
};
struct TraitSubTreeLoadInfo
{
static constexpr DB2FieldMeta Fields[5] =
{
{ false, FT_STRING, "Name" },
{ false, FT_STRING, "Description" },
{ false, FT_INT, "ID" },
{ true, FT_INT, "UiTextureAtlasElementID" },
{ true, FT_INT, "TraitTreeID" },
};
static constexpr DB2LoadInfo Instance{ Fields, 5, &TraitSubTreeMeta::Instance, HOTFIX_SEL_TRAIT_SUB_TREE };
};
struct TraitTreeLoadInfo
{
static constexpr DB2FieldMeta Fields[8] =

View File

@@ -359,6 +359,7 @@ DB2Storage<TraitNodeGroupXTraitNodeEntry> sTraitNodeGroupXTraitNodeStore("
DB2Storage<TraitNodeXTraitCondEntry> sTraitNodeXTraitCondStore("TraitNodeXTraitCond.db2", &TraitNodeXTraitCondLoadInfo::Instance);
DB2Storage<TraitNodeXTraitCostEntry> sTraitNodeXTraitCostStore("TraitNodeXTraitCost.db2", &TraitNodeXTraitCostLoadInfo::Instance);
DB2Storage<TraitNodeXTraitNodeEntryEntry> sTraitNodeXTraitNodeEntryStore("TraitNodeXTraitNodeEntry.db2", &TraitNodeXTraitNodeEntryLoadInfo::Instance);
DB2Storage<TraitSubTreeEntry> sTraitSubTreeStore("TraitSubTree.db2", &TraitSubTreeLoadInfo::Instance);
DB2Storage<TraitTreeEntry> sTraitTreeStore("TraitTree.db2", &TraitTreeLoadInfo::Instance);
DB2Storage<TraitTreeLoadoutEntry> sTraitTreeLoadoutStore("TraitTreeLoadout.db2", &TraitTreeLoadoutLoadInfo::Instance);
DB2Storage<TraitTreeLoadoutEntryEntry> sTraitTreeLoadoutEntryStore("TraitTreeLoadoutEntry.db2", &TraitTreeLoadoutEntryLoadInfo::Instance);
@@ -970,6 +971,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
LOAD_DB2(sTraitNodeXTraitCondStore);
LOAD_DB2(sTraitNodeXTraitCostStore);
LOAD_DB2(sTraitNodeXTraitNodeEntryStore);
LOAD_DB2(sTraitSubTreeStore);
LOAD_DB2(sTraitTreeStore);
LOAD_DB2(sTraitTreeLoadoutStore);
LOAD_DB2(sTraitTreeLoadoutEntryStore);

View File

@@ -285,6 +285,7 @@ TC_GAME_API extern DB2Storage<TraitNodeGroupXTraitNodeEntry> sTraitNodeGr
TC_GAME_API extern DB2Storage<TraitNodeXTraitCondEntry> sTraitNodeXTraitCondStore;
TC_GAME_API extern DB2Storage<TraitNodeXTraitCostEntry> sTraitNodeXTraitCostStore;
TC_GAME_API extern DB2Storage<TraitNodeXTraitNodeEntryEntry> sTraitNodeXTraitNodeEntryStore;
TC_GAME_API extern DB2Storage<TraitSubTreeEntry> sTraitSubTreeStore;
TC_GAME_API extern DB2Storage<TraitTreeEntry> sTraitTreeStore;
TC_GAME_API extern DB2Storage<TraitTreeLoadoutEntry> sTraitTreeLoadoutStore;
TC_GAME_API extern DB2Storage<TraitTreeLoadoutEntryEntry> sTraitTreeLoadoutEntryStore;

View File

@@ -4349,6 +4349,15 @@ struct TraitNodeXTraitNodeEntryEntry
int32 Index;
};
struct TraitSubTreeEntry
{
LocalizedString Name;
LocalizedString Description;
uint32 ID;
int32 UiTextureAtlasElementID;
int32 TraitTreeID; // Parent tree
};
struct TraitTreeEntry
{
uint32 ID;

View File

@@ -27854,8 +27854,12 @@ void Player::_LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult
if (TraitMgr::ValidateConfig(traitConfig, this, false, true) != TraitMgr::LearnResult::Ok)
{
traitConfig.Entries.clear();
traitConfig.SubTrees.clear();
for (UF::TraitEntry const& grantedEntry : TraitMgr::GetGrantedTraitEntriesForConfig(traitConfig, this))
traitConfig.Entries.emplace_back(grantedEntry);
// rebuild subtrees
TraitMgr::ValidateConfig(traitConfig, this, false, true);
}
AddTraitConfig(traitConfig);
@@ -28395,6 +28399,22 @@ void Player::AddTraitConfig(WorldPackets::Traits::TraitConfig const& traitConfig
newEntry.Rank = traitEntry.Rank;
newEntry.GrantedRanks = traitEntry.GrantedRanks;
}
for (WorldPackets::Traits::TraitSubTreeCache const& traitSubTree : traitConfig.SubTrees)
{
UF::TraitSubTreeCache& newSubTree = AddDynamicUpdateFieldValue(setter.ModifyValue(&UF::TraitConfig::SubTrees));
newSubTree.TraitSubTreeID = traitSubTree.TraitSubTreeID;
newSubTree.Active = traitSubTree.Active;
for (WorldPackets::Traits::TraitEntry const& traitEntry : traitSubTree.Entries)
{
UF::TraitEntry& newEntry = newSubTree.Entries.emplace_back();
newEntry.TraitNodeID = traitEntry.TraitNodeID;
newEntry.TraitNodeEntryID = traitEntry.TraitNodeEntryID;
newEntry.Rank = traitEntry.Rank;
newEntry.GrantedRanks = traitEntry.GrantedRanks;
}
}
}
UF::TraitConfig const* Player::GetTraitConfig(int32 configId) const
@@ -28478,7 +28498,7 @@ void Player::ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::
for (int32 i = 0; i < int32(editedConfig.Entries.size()); ++i)
{
UF::TraitEntry const& oldEntry = editedConfig.Entries[i];
auto entryItr = std::find_if(newConfig.Entries.begin(), newConfig.Entries.end(), makeTraitEntryFinder(oldEntry.TraitNodeID, oldEntry.TraitNodeEntryID));
auto entryItr = std::ranges::find_if(newConfig.Entries, makeTraitEntryFinder(oldEntry.TraitNodeID, oldEntry.TraitNodeEntryID));
if (entryItr != newConfig.Entries.end())
continue;
@@ -28567,6 +28587,49 @@ void Player::ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::
}
}
for (std::size_t i = 0; i < newConfig.SubTrees.size(); ++i)
{
WorldPackets::Traits::TraitSubTreeCache const& newSubTree = newConfig.SubTrees[i];
int32 oldSubTreeIndex = editedConfig.SubTrees.FindIndexIf([&](UF::TraitSubTreeCache const& ufSubTree) { return ufSubTree.TraitSubTreeID == newSubTree.TraitSubTreeID; });
std::vector<UF::TraitEntry> subTreeEntries;
subTreeEntries.resize(newSubTree.Entries.size());
for (std::size_t j = 0; j < newSubTree.Entries.size(); ++j)
{
UF::TraitEntry& newUfEntry = subTreeEntries[j];
newUfEntry.TraitNodeID = newSubTree.Entries[j].TraitNodeID;
newUfEntry.TraitNodeEntryID = newSubTree.Entries[j].TraitNodeEntryID;
newUfEntry.Rank = newSubTree.Entries[j].Rank;
newUfEntry.GrantedRanks = newSubTree.Entries[j].GrantedRanks;
}
if (oldSubTreeIndex < 0)
{
UF::TraitSubTreeCache& newUfSubTree = AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData)
.ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex)
.ModifyValue(&UF::TraitConfig::SubTrees));
newUfSubTree.TraitSubTreeID = newSubTree.TraitSubTreeID;
newUfSubTree.Active = newSubTree.Active;
newUfSubTree.Entries = std::move(subTreeEntries);
}
else
{
bool wasActive = m_activePlayerData->TraitConfigs[editedIndex].SubTrees[oldSubTreeIndex].Active != 0;
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData)
.ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex)
.ModifyValue(&UF::TraitConfig::SubTrees, oldSubTreeIndex)
.ModifyValue(&UF::TraitSubTreeCache::Active), newSubTree.Active);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData)
.ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex)
.ModifyValue(&UF::TraitConfig::SubTrees, oldSubTreeIndex)
.ModifyValue(&UF::TraitSubTreeCache::Entries), std::move(subTreeEntries));
if (applyTraits && wasActive != newSubTree.Active)
for (WorldPackets::Traits::TraitEntry const& subTreeEntry : newSubTree.Entries)
ApplyTraitEntry(subTreeEntry.TraitNodeEntryID, subTreeEntry.Rank, subTreeEntry.GrantedRanks, newSubTree.Active);
}
}
m_traitConfigStates[editedConfigId] = PLAYERSPELL_CHANGED;
}
@@ -28612,7 +28675,8 @@ void Player::ApplyTraitConfig(int32 configId, bool apply)
return;
for (UF::TraitEntry const& traitEntry : traitConfig->Entries)
ApplyTraitEntry(traitEntry.TraitNodeEntryID, traitEntry.Rank, traitEntry.GrantedRanks, apply);
if (!apply || TraitMgr::CanApplyTraitNode(*traitConfig, traitEntry))
ApplyTraitEntry(traitEntry.TraitNodeEntryID, traitEntry.Rank, traitEntry.GrantedRanks, apply);
}
void Player::ApplyTraitEntry(int32 traitNodeEntryId, int32 /*rank*/, int32 /*grantedRanks*/, bool apply)

View File

@@ -88,23 +88,26 @@ void WorldSession::HandleTraitsCommitConfig(WorldPackets::Traits::TraitsCommitCo
return;
}
TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(traitNodeEntry->TraitDefinitionID);
if (!traitDefinition)
if (traitNodeEntry->TraitDefinitionID)
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
return;
}
TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(traitNodeEntry->TraitDefinitionID);
if (!traitDefinition)
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
return;
}
if (traitDefinition->SpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->SpellID))
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->SpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
return;
}
if (traitDefinition->SpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->SpellID))
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->SpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
return;
}
if (traitDefinition->VisibleSpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->VisibleSpellID))
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->VisibleSpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
return;
if (traitDefinition->VisibleSpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->VisibleSpellID))
{
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->VisibleSpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
return;
}
}
hasRemovedEntries = true;

View File

@@ -54,6 +54,8 @@ TraitConfig::TraitConfig(UF::TraitConfig const& ufConfig)
TraitSystemID = ufConfig.TraitSystemID;
for (UF::TraitEntry const& ufEntry : ufConfig.Entries)
Entries.emplace_back(ufEntry);
for (UF::TraitSubTreeCache const& ufSubTree : ufConfig.SubTrees)
SubTrees.emplace_back(ufSubTree);
Name = ufConfig.Name;
}

View File

@@ -17,6 +17,7 @@
#include "TraitMgr.h"
#include "DB2Stores.h"
#include "FlatSet.h"
#include "IteratorPair.h"
#include "MapUtils.h"
#include "TraitPacketsCommon.h"
@@ -29,6 +30,7 @@ namespace
struct NodeEntry;
struct Node;
struct NodeGroup;
struct SubTree;
struct Tree;
struct NodeEntry
@@ -56,17 +58,26 @@ struct NodeGroup
std::vector<Node const*> Nodes;
};
struct SubTree
{
TraitSubTreeEntry const* Data = nullptr;
std::vector<Node const*> Nodes;
Trinity::Containers::FlatSet<TraitCurrencyEntry const*> Currencies;
};
struct Tree
{
TraitTreeEntry const* Data = nullptr;
std::vector<Node const*> Nodes;
std::vector<TraitCostEntry const*> Costs;
std::vector<TraitCurrencyEntry const*> Currencies;
std::vector<SubTree const*> SubTrees;
TraitConfigType ConfigType = TraitConfigType::Invalid;
};
std::unordered_map<int32, NodeGroup> _traitGroups;
std::unordered_map<int32, Node> _traitNodes;
std::unordered_map<int32, SubTree> _traitSubTrees;
std::unordered_map<int32, Tree> _traitTrees;
std::array<int32, MAX_CLASSES> _skillLinesByClass;
std::unordered_map<int32, std::vector<Tree const*>> _traitTreesBySkillLine;
@@ -125,10 +136,10 @@ void Load()
if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitTreeXTraitCostEntry->TraitCostID))
treeCosts[traitTreeXTraitCostEntry->TraitTreeID].push_back(traitCostEntry);
std::unordered_map<int32, std::vector<TraitCurrencyEntry const*>> treeCurrencies;
std::unordered_map<int32, std::vector<TraitTreeXTraitCurrencyEntry const*>> treeCurrencies;
for (TraitTreeXTraitCurrencyEntry const* traitTreeXTraitCurrencyEntry : sTraitTreeXTraitCurrencyStore)
if (TraitCurrencyEntry const* traitCurrencyEntry = sTraitCurrencyStore.LookupEntry(traitTreeXTraitCurrencyEntry->TraitCurrencyID))
treeCurrencies[traitTreeXTraitCurrencyEntry->TraitTreeID].push_back(traitCurrencyEntry);
if (sTraitCurrencyStore.HasRecord(traitTreeXTraitCurrencyEntry->TraitCurrencyID))
treeCurrencies[traitTreeXTraitCurrencyEntry->TraitTreeID].push_back(traitTreeXTraitCurrencyEntry);
std::unordered_map<int32, std::vector<int32>> traitTreesIdsByTraitSystem;
@@ -140,8 +151,14 @@ void Load()
if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(treeCosts, traitTree->ID))
tree.Costs = std::move(*costs);
if (std::vector<TraitCurrencyEntry const*>* currencies = Trinity::Containers::MapGetValuePtr(treeCurrencies, traitTree->ID))
tree.Currencies = std::move(*currencies);
if (std::vector<TraitTreeXTraitCurrencyEntry const*>* currencies = Trinity::Containers::MapGetValuePtr(treeCurrencies, traitTree->ID))
{
tree.Currencies.resize(currencies->size());
std::ranges::sort(*currencies, {}, &TraitTreeXTraitCurrencyEntry::Index);
std::ranges::transform(*currencies, tree.Currencies.begin(),
[](uint32 traitCurrencyId) { return sTraitCurrencyStore.AssertEntry(traitCurrencyId); },
&TraitTreeXTraitCurrencyEntry::TraitCurrencyID);
}
if (traitTree->TraitSystemID)
{
@@ -150,6 +167,15 @@ void Load()
}
}
for (TraitSubTreeEntry const* traitSubTree : sTraitSubTreeStore)
{
SubTree& subTree = _traitSubTrees[traitSubTree->ID];
subTree.Data = traitSubTree;
if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitSubTree->TraitTreeID))
tree->SubTrees.push_back(&subTree);
}
for (TraitNodeGroupEntry const* traitNodeGroup : sTraitNodeGroupStore)
{
NodeGroup& nodeGroup = _traitGroups[traitNodeGroup->ID];
@@ -197,6 +223,30 @@ void Load()
if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeCosts, traitNode->ID))
node.Costs = std::move(*costs);
if (SubTree* subTree = Trinity::Containers::MapGetValuePtr(_traitSubTrees, traitNode->TraitSubTreeID))
{
subTree->Nodes.push_back(&node);
for (NodeEntry const& nodeEntry : node.Entries)
for (TraitCostEntry const* cost : nodeEntry.Costs)
if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
subTree->Currencies.insert(traitCurrency);
for (NodeGroup const* nodeGroup : node.Groups)
for (TraitCostEntry const* cost : nodeGroup->Costs)
if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
subTree->Currencies.insert(traitCurrency);
for (TraitCostEntry const* cost : node.Costs)
if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
subTree->Currencies.insert(traitCurrency);
if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitNode->TraitTreeID))
for (TraitCostEntry const* cost : tree->Costs)
if (TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(cost->TraitCurrencyID))
subTree->Currencies.insert(traitCurrency);
}
}
for (TraitEdgeEntry const* traitEdgeEntry : sTraitEdgeStore)
@@ -442,6 +492,30 @@ void FillSpentCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig
FillSpentCurrenciesMap(entry, cachedCurrencies);
}
std::array<int32, 2> GetClassAndSpecTreeCurrencies(WorldPackets::Traits::TraitConfig const& traitConfig)
{
std::array<int32, 2> currencies = {};
if (std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig))
{
auto dest = currencies.begin();
for (auto treeItr = trees->begin(); treeItr != trees->end() && dest != currencies.end(); ++treeItr)
for (auto currencyItr = (*treeItr)->Currencies.begin(); currencyItr != (*treeItr)->Currencies.end() && dest != currencies.end(); ++currencyItr)
*dest++ = (*currencyItr)->ID;
}
return currencies;
}
std::span<TraitCurrencyEntry const* const> GetSubTreeCurrency(int32 traitSubTreeId)
{
SubTree const* subTree = Trinity::Containers::MapGetValuePtr(_traitSubTrees, traitSubTreeId);
if (!subTree)
return {};
return subTree->Currencies;
}
bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player, TraitCondEntry const* condition,
Optional<std::map<int32, int32>>& cachedCurrencies)
{
@@ -616,7 +690,7 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
return LearnResult::Unknown;
for (NodeEntry const& entry : node->Entries)
if (!meetsConditions(entry.Conditions))
if (int32(entry.Data->ID) == traitEntry.TraitNodeEntryID && !meetsConditions(entry.Conditions))
return LearnResult::Unknown;
if (!meetsConditions(node->Conditions))
@@ -670,6 +744,42 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
++itr;
}
struct SubtreeValidationData
{
std::vector<WorldPackets::Traits::TraitEntry> Entries;
bool IsSelected = false;
};
std::unordered_map<int32, SubtreeValidationData> subtrees;
for (WorldPackets::Traits::TraitEntry const& traitEntry : traitConfig.Entries)
{
Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
auto entryItr = std::ranges::find(node->Entries, traitEntry.TraitNodeEntryID, [](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID); });
ASSERT(entryItr != node->Entries.end());
if (node->Data->GetType() == TraitNodeType::SubTreeSelection)
subtrees[entryItr->Data->TraitSubTreeID].IsSelected = true;
if (node->Data->TraitSubTreeID)
subtrees[node->Data->TraitSubTreeID].Entries.push_back(traitEntry);
}
for (WorldPackets::Traits::TraitSubTreeCache& subTree : traitConfig.SubTrees)
subTree.Active = false;
for (auto&& [selectedSubTreeId, data] : subtrees)
{
auto subtreeDataItr = std::ranges::find(traitConfig.SubTrees, selectedSubTreeId, &WorldPackets::Traits::TraitSubTreeCache::TraitSubTreeID);
if (subtreeDataItr == std::ranges::end(traitConfig.SubTrees))
{
subtreeDataItr = traitConfig.SubTrees.emplace(traitConfig.SubTrees.end());
subtreeDataItr->TraitSubTreeID = selectedSubTreeId;
}
subtreeDataItr->Entries = std::move(data.Entries);
subtreeDataItr->Active = data.IsSelected;
}
std::map<int32, int32> grantedCurrencies;
FillOwnedCurrenciesMap(traitConfig, player, grantedCurrencies);
@@ -688,20 +798,55 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
if (requireSpendingAllCurrencies && traitConfig.Type == TraitConfigType::Combat)
{
for (auto [traitCurrencyId, grantedAmount] : grantedCurrencies)
// client checks only first two currencies for trait tree
for (int32 traitCurrencyId : GetClassAndSpecTreeCurrencies(traitConfig))
{
int32* grantedAmount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, traitCurrencyId);
if (!grantedAmount)
continue;
int32* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, traitCurrencyId);
if (!spentAmount || *spentAmount != grantedAmount)
if (!spentAmount || *spentAmount != *grantedAmount)
return LearnResult::UnspentTalentPoints;
}
for (auto&& [selectedTraitSubTreeId, data] : subtrees)
{
if (!data.IsSelected)
continue;
for (TraitCurrencyEntry const* subTreeCurrency : GetSubTreeCurrency(selectedTraitSubTreeId))
{
int32* grantedAmount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, subTreeCurrency->ID);
if (!grantedAmount)
continue;
int32* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, subTreeCurrency->ID);
if (!spentAmount || *spentAmount != *grantedAmount)
return LearnResult::UnspentTalentPoints;
}
}
}
return LearnResult::Ok;
}
bool CanApplyTraitNode(UF::TraitConfig const& traitConfig, UF::TraitEntry const& traitEntry)
{
Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID);
if (!node)
return false;
if (node->Data->TraitSubTreeID)
{
auto subTreeItr = std::ranges::find(traitConfig.SubTrees, node->Data->TraitSubTreeID, &UF::TraitSubTreeCache::TraitSubTreeID);
if (subTreeItr == std::ranges::end(traitConfig.SubTrees) || !subTreeItr->Active)
return false;
}
return true;
}
std::vector<TraitDefinitionEffectPointsEntry const*> const* GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId)
{
return Trinity::Containers::MapGetValuePtr(_traitDefinitionEffectPointModifiers, traitDefinitionId);

View File

@@ -29,6 +29,7 @@ enum TalentLearnResult : int32;
namespace UF
{
struct TraitConfig;
struct TraitEntry;
}
@@ -80,6 +81,7 @@ void FillSpentCurrenciesMap(WorldPackets::Traits::TraitEntry const& entry, std::
std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player);
bool IsValidEntry(WorldPackets::Traits::TraitEntry const& traitEntry);
LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies = false, bool removeInvalidEntries = false);
bool CanApplyTraitNode(UF::TraitConfig const& traitConfig, UF::TraitEntry const& traitEntry);
std::vector<TraitDefinitionEffectPointsEntry const*> const* GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId);
void InitializeStarterBuildTraitConfig(WorldPackets::Traits::TraitConfig& traitConfig, PlayerDataAccessor player);
}