From 54023d52d7d45d4407f3d4b013d163098553f6f8 Mon Sep 17 00:00:00 2001 From: Hondacrx Date: Sun, 8 Sep 2024 14:09:06 -0400 Subject: [PATCH] Core/Players: Implemented hero talents Port From (https://github.com/TrinityCore/TrinityCore/commit/d1ffe61727f53505a3e2b94cae32f2ce3d28b07b) --- .../Database/Databases/HotfixDatabase.cs | 9 + Source/Game/DataStorage/CliDB.cs | 2 + Source/Game/DataStorage/Structs/T_Records.cs | 9 + Source/Game/Entities/Player/Player.DB.cs | 4 + Source/Game/Entities/Player/Player.Talents.cs | 66 +++++- Source/Game/Handlers/TraitHandler.cs | 31 +-- .../Game/Networking/Packets/TraitPackets.cs | 18 +- Source/Game/Spells/TraitManager.cs | 194 +++++++++++++++++- 8 files changed, 301 insertions(+), 32 deletions(-) diff --git a/Source/Framework/Database/Databases/HotfixDatabase.cs b/Source/Framework/Database/Databases/HotfixDatabase.cs index 827c2f48a..bd6e93d43 100644 --- a/Source/Framework/Database/Databases/HotfixDatabase.cs +++ b/Source/Framework/Database/Databases/HotfixDatabase.cs @@ -1497,6 +1497,12 @@ namespace Framework.Database PrepareStatement(HotfixStatements.SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY, "SELECT ID, TraitNodeID, TraitNodeEntryID, `Index` FROM trait_node_x_trait_node_entry" + " WHERE (`VerifiedBuild` > 0) = ?"); + // TraitSubTree.db2 + PrepareStatement(HotfixStatements.SEL_TRAIT_SUB_TREE, "SELECT Name, Description, ID, UiTextureAtlasElementID, TraitTreeID FROM trait_sub_tree" + + " WHERE (`VerifiedBuild` > 0) = ?"); + PrepareStatement(HotfixStatements.SEL_TRAIT_SUB_TREE_LOCALE, "SELECT ID, Name_lang, Description_lang FROM trait_sub_tree_locale WHERE (`VerifiedBuild` > 0) = ?" + + " AND locale = ?"); + // TraitTree.db2 PrepareStatement(HotfixStatements.SEL_TRAIT_TREE, "SELECT ID, TraitSystemID, Unused1000_1, FirstTraitNodeID, PlayerConditionID, Flags, Unused1000_2, " + "Unused1000_3 FROM trait_tree WHERE (`VerifiedBuild` > 0) = ?"); @@ -2350,6 +2356,9 @@ namespace Framework.Database SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY, + SEL_TRAIT_SUB_TREE, + SEL_TRAIT_SUB_TREE_LOCALE, + SEL_TRAIT_TREE, SEL_TRAIT_TREE_LOADOUT, diff --git a/Source/Game/DataStorage/CliDB.cs b/Source/Game/DataStorage/CliDB.cs index a034e726c..c381f2fb5 100644 --- a/Source/Game/DataStorage/CliDB.cs +++ b/Source/Game/DataStorage/CliDB.cs @@ -365,6 +365,7 @@ namespace Game.DataStorage TraitNodeXTraitCondStorage = ReadDB2("TraitNodeXTraitCond.db2", HotfixStatements.SEL_TRAIT_NODE_X_TRAIT_COND); TraitNodeXTraitCostStorage = ReadDB2("TraitNodeXTraitCost.db2", HotfixStatements.SEL_TRAIT_NODE_X_TRAIT_COST); TraitNodeXTraitNodeEntryStorage = ReadDB2("TraitNodeXTraitNodeEntry.db2", HotfixStatements.SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY); + TraitSubTreeStorage = ReadDB2("TraitSubTree.db2", HotfixStatements.SEL_TRAIT_SUB_TREE, HotfixStatements.SEL_TRAIT_SUB_TREE_LOCALE); TraitTreeStorage = ReadDB2("TraitTree.db2", HotfixStatements.SEL_TRAIT_TREE); TraitTreeLoadoutStorage = ReadDB2("TraitTreeLoadout.db2", HotfixStatements.SEL_TRAIT_TREE_LOADOUT); TraitTreeLoadoutEntryStorage = ReadDB2("TraitTreeLoadoutEntry.db2", HotfixStatements.SEL_TRAIT_TREE_LOADOUT_ENTRY); @@ -811,6 +812,7 @@ namespace Game.DataStorage public static DB6Storage TraitNodeXTraitCondStorage; public static DB6Storage TraitNodeXTraitCostStorage; public static DB6Storage TraitNodeXTraitNodeEntryStorage; + public static DB6Storage TraitSubTreeStorage; public static DB6Storage TraitTreeStorage; public static DB6Storage TraitTreeLoadoutStorage; public static DB6Storage TraitTreeLoadoutEntryStorage; diff --git a/Source/Game/DataStorage/Structs/T_Records.cs b/Source/Game/DataStorage/Structs/T_Records.cs index 2698e3d5e..e0e9aaa1f 100644 --- a/Source/Game/DataStorage/Structs/T_Records.cs +++ b/Source/Game/DataStorage/Structs/T_Records.cs @@ -283,6 +283,15 @@ namespace Game.DataStorage public int Index; } + public sealed class TraitSubTreeRecord + { + public LocalizedString Name; + public LocalizedString Description; + public uint ID; + public int UiTextureAtlasElementID; + public int TraitTreeID; // Parent tree + } + public sealed class TraitTreeRecord { public uint Id; diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index c7dee2747..1794c1cd5 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -1124,8 +1124,12 @@ namespace Game.Entities if (TraitMgr.ValidateConfig(traitConfig, this, false, true) != TalentLearnResult.LearnOk) { traitConfig.Entries.Clear(); + traitConfig.SubTrees.Clear(); foreach (TraitEntry grantedEntry in TraitMgr.GetGrantedTraitEntriesForConfig(traitConfig, this)) traitConfig.Entries.Add(new TraitEntryPacket(grantedEntry)); + + // rebuild subtrees + TraitMgr.ValidateConfig(traitConfig, this, false, true); } AddTraitConfig(traitConfig); diff --git a/Source/Game/Entities/Player/Player.Talents.cs b/Source/Game/Entities/Player/Player.Talents.cs index 6a9fbfd46..f3efcbe61 100644 --- a/Source/Game/Entities/Player/Player.Talents.cs +++ b/Source/Game/Entities/Player/Player.Talents.cs @@ -899,6 +899,25 @@ namespace Game.Entities newEntry.GrantedRanks = traitEntry.GrantedRanks; AddDynamicUpdateFieldValue(setter.ModifyValue(setter.Entries), newEntry); } + + foreach (var traitSubTree in traitConfig.SubTrees) + { + TraitSubTreeCache newSubTree = new(); + newSubTree.TraitSubTreeID = traitSubTree.TraitSubTreeID; + newSubTree.Active = traitSubTree.Active ? 1 : 0u; + + foreach (var traitEntry in traitSubTree.Entries) + { + TraitEntry newEntry = new(); + newEntry.TraitNodeID = traitEntry.TraitNodeID; + newEntry.TraitNodeEntryID = traitEntry.TraitNodeEntryID; + newEntry.Rank = traitEntry.Rank; + newEntry.GrantedRanks = traitEntry.GrantedRanks; + newSubTree.Entries.Add(newEntry); + } + + AddDynamicUpdateFieldValue(setter.ModifyValue(setter.SubTrees), newSubTree); + } } public TraitConfig GetTraitConfig(int configId) @@ -1062,7 +1081,49 @@ namespace Game.Entities } } - m_traitConfigStates[(int)editedConfigId] = PlayerSpellState.Changed; + for (int i = 0; i < newConfig.SubTrees.Count; ++i) + { + var newSubTree = newConfig.SubTrees[i]; + int oldSubTreeIndex = editedConfig.SubTrees.FindIndexIf(ufSubTree => ufSubTree.TraitSubTreeID == newSubTree.TraitSubTreeID); + + List subTreeEntries = new(); + for (int j = 0; j < newSubTree.Entries.Count; ++j) + { + 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) + { + TraitSubTreeCache newUfSubTree = new(); + newUfSubTree.TraitSubTreeID = newSubTree.TraitSubTreeID; + newUfSubTree.Active = newSubTree.Active ? 1 : 0u; + newUfSubTree.Entries = subTreeEntries; + + TraitConfig traitConfig = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, editedIndex); + AddDynamicUpdateFieldValue(traitConfig.ModifyValue(traitConfig.SubTrees), newUfSubTree); + } + else + { + bool wasActive = m_activePlayerData.TraitConfigs[editedIndex].SubTrees[oldSubTreeIndex].Active != 0; + + TraitConfig traitConfig = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, editedIndex); + TraitSubTreeCache traitSubTreeCache = traitConfig.ModifyValue(traitConfig.SubTrees, oldSubTreeIndex); + + traitSubTreeCache.Active = newSubTree.Active ? 1 : 0u; + traitSubTreeCache.Entries = subTreeEntries; + SetUpdateFieldValue(traitConfig.ModifyValue(traitConfig.SubTrees, oldSubTreeIndex), traitSubTreeCache); + + + if (applyTraits && wasActive != newSubTree.Active) + foreach (var subTreeEntry in newSubTree.Entries) + ApplyTraitEntry(subTreeEntry.TraitNodeEntryID, subTreeEntry.Rank, subTreeEntry.GrantedRanks, newSubTree.Active); + } + } + + m_traitConfigStates[editedConfigId] = PlayerSpellState.Changed; } public void RenameTraitConfig(int editedConfigId, string newName) @@ -1106,7 +1167,8 @@ namespace Game.Entities return; foreach (TraitEntry traitEntry in 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 ApplyTraitEntry(int traitNodeEntryId, int rank, int grantedRanks, bool apply) diff --git a/Source/Game/Handlers/TraitHandler.cs b/Source/Game/Handlers/TraitHandler.cs index c0f8c2674..1528bbb68 100644 --- a/Source/Game/Handlers/TraitHandler.cs +++ b/Source/Game/Handlers/TraitHandler.cs @@ -71,23 +71,26 @@ namespace Game return; } - TraitDefinitionRecord traitDefinition = CliDB.TraitDefinitionStorage.LookupByKey(traitNodeEntry.TraitDefinitionID); - if (traitDefinition == null) + if (traitNodeEntry.TraitDefinitionID != 0) { - SendPacket(new TraitConfigCommitFailed(configId, 0, (int)TalentLearnResult.FailedUnknown)); - return; - } + TraitDefinitionRecord traitDefinition = CliDB.TraitDefinitionStorage.LookupByKey(traitNodeEntry.TraitDefinitionID); + if (traitDefinition == null) + { + SendPacket(new TraitConfigCommitFailed(configId, 0, (int)TalentLearnResult.FailedUnknown)); + return; + } - if (traitDefinition.SpellID != 0 && _player.GetSpellHistory().HasCooldown(traitDefinition.SpellID)) - { - SendPacket(new TraitConfigCommitFailed(configId, traitDefinition.SpellID, (int)TalentLearnResult.FailedCantRemoveTalent)); - return; - } + if (traitDefinition.SpellID != 0 && _player.GetSpellHistory().HasCooldown(traitDefinition.SpellID)) + { + SendPacket(new TraitConfigCommitFailed(configId, traitDefinition.SpellID, (int)TalentLearnResult.FailedCantRemoveTalent)); + return; + } - if (traitDefinition.VisibleSpellID != 0 && _player.GetSpellHistory().HasCooldown((uint)traitDefinition.VisibleSpellID)) - { - SendPacket(new TraitConfigCommitFailed(configId, traitDefinition.VisibleSpellID, (int)TalentLearnResult.FailedCantRemoveTalent)); - return; + if (traitDefinition.VisibleSpellID != 0 && _player.GetSpellHistory().HasCooldown((uint)traitDefinition.VisibleSpellID)) + { + SendPacket(new TraitConfigCommitFailed(configId, traitDefinition.VisibleSpellID, (int)TalentLearnResult.FailedCantRemoveTalent)); + return; + } } hasRemovedEntries = true; diff --git a/Source/Game/Networking/Packets/TraitPackets.cs b/Source/Game/Networking/Packets/TraitPackets.cs index a13354cec..ce3755c5d 100644 --- a/Source/Game/Networking/Packets/TraitPackets.cs +++ b/Source/Game/Networking/Packets/TraitPackets.cs @@ -148,19 +148,19 @@ namespace Game.Networking.Packets } } - public class TraitSubTreeCache + public class TraitSubTreeCachePacket { public int TraitSubTreeID; public List Entries = new(); public bool Active; - public TraitSubTreeCache() { } - public TraitSubTreeCache(TraitSubTreeCache ufSubTreeCache) + public TraitSubTreeCachePacket() { } + public TraitSubTreeCachePacket(TraitSubTreeCache ufSubTreeCache) { TraitSubTreeID = ufSubTreeCache.TraitSubTreeID; foreach (var ufEntry in ufSubTreeCache.Entries) - Entries.Add(ufEntry); - Active = ufSubTreeCache.Active; + Entries.Add(new TraitEntryPacket(ufEntry)); + Active = ufSubTreeCache.Active == 0 ? false : true; } public void Read(WorldPacket data) @@ -203,7 +203,7 @@ namespace Game.Networking.Packets public uint SkillLineID; public int TraitSystemID; public List Entries = new(); - public List SubTrees = new(); + public List SubTrees = new(); public string Name = ""; public TraitConfigPacket() { } @@ -219,6 +219,8 @@ namespace Game.Networking.Packets TraitSystemID = ufConfig.TraitSystemID; foreach (TraitEntry ufEntry in ufConfig.Entries) Entries.Add(new TraitEntryPacket(ufEntry)); + foreach (var ufSubTree in ufConfig.SubTrees) + SubTrees.Add(new TraitSubTreeCachePacket(ufSubTree)); Name = ufConfig.Name; } @@ -257,7 +259,7 @@ namespace Game.Networking.Packets for (var i = 0; i < subtreesSize; ++i) { - TraitSubTreeCache subtrees = new(); + TraitSubTreeCachePacket subtrees = new(); subtrees.Read(data); SubTrees.Add(subtrees); } @@ -294,7 +296,7 @@ namespace Game.Networking.Packets data.WriteBits(Name.GetByteCount(), 9); - foreach (TraitSubTreeCache traitSubTreeCache in SubTrees) + foreach (TraitSubTreeCachePacket traitSubTreeCache in SubTrees) traitSubTreeCache.Write(data); data.FlushBits(); diff --git a/Source/Game/Spells/TraitManager.cs b/Source/Game/Spells/TraitManager.cs index c846c527b..9307c3a8d 100644 --- a/Source/Game/Spells/TraitManager.cs +++ b/Source/Game/Spells/TraitManager.cs @@ -15,6 +15,7 @@ namespace Game static Dictionary _traitGroups = new(); static Dictionary _traitNodes = new(); + static Dictionary _traitSubTrees = new(); static Dictionary _traitTrees = new(); static int[] _skillLinesByClass = new int[(int)Class.Max]; static MultiMap _traitTreesBySkillLine = new(); @@ -96,12 +97,11 @@ namespace Game treeCosts.Add(traitTreeXTraitCostEntry.TraitTreeID, traitCostEntry); } - MultiMap treeCurrencies = new(); + MultiMap treeCurrencies = new(); foreach (TraitTreeXTraitCurrencyRecord traitTreeXTraitCurrencyEntry in CliDB.TraitTreeXTraitCurrencyStorage.Values) { - TraitCurrencyRecord traitCurrencyEntry = CliDB.TraitCurrencyStorage.LookupByKey(traitTreeXTraitCurrencyEntry.TraitCurrencyID); - if (traitCurrencyEntry != null) - treeCurrencies.Add(traitTreeXTraitCurrencyEntry.TraitTreeID, traitCurrencyEntry); + if (CliDB.TraitCurrencyStorage.HasRecord((uint)traitTreeXTraitCurrencyEntry.TraitCurrencyID)) + treeCurrencies.Add(traitTreeXTraitCurrencyEntry.TraitTreeID, traitTreeXTraitCurrencyEntry); } MultiMap traitTreesIdsByTraitSystem = new(); @@ -116,7 +116,10 @@ namespace Game var currencies = treeCurrencies.LookupByKey(traitTree.Id); if (currencies != null) - tree.Currencies = currencies; + { + currencies.OrderBy(p => p.Index); + tree.Currencies.AddRange(currencies.Select(p => CliDB.TraitCurrencyStorage.LookupByKey(p.TraitCurrencyID))); + } if (traitTree.TraitSystemID != 0) { @@ -127,7 +130,19 @@ namespace Game _traitTrees[(int)traitTree.Id] = tree; } - foreach (TraitNodeGroupRecord traitNodeGroup in CliDB.TraitNodeGroupStorage.Values) + foreach (var (_, traitSubTree) in CliDB.TraitSubTreeStorage) + { + SubTree subTree = new(); + subTree.Data = traitSubTree; + + Tree tree = _traitTrees.LookupByKey(traitSubTree.TraitTreeID); + if (tree != null) + tree.SubTrees.Add(subTree); + + _traitSubTrees[(int)traitSubTree.ID] = subTree; + } + + foreach (var (_, traitNodeGroup) in CliDB.TraitNodeGroupStorage) { NodeGroup nodeGroup = new(); nodeGroup.Data = traitNodeGroup; @@ -186,6 +201,50 @@ namespace Game if (costs1 != null) node.Costs = costs1; + SubTree subTree = _traitSubTrees.LookupByKey(traitNode.TraitSubTreeID); + if (subTree != null) + { + subTree.Nodes.Add(node); + + foreach (NodeEntry nodeEntry in node.Entries) + { + foreach (TraitCostRecord cost in nodeEntry.Costs) + { + TraitCurrencyRecord traitCurrency = CliDB.TraitCurrencyStorage.LookupByKey(cost.TraitCurrencyID); + if (traitCurrency != null) + subTree.Currencies.Add(traitCurrency); + } + } + + foreach (NodeGroup nodeGroup in node.Groups) + { + foreach (TraitCostRecord cost in nodeGroup.Costs) + { + TraitCurrencyRecord traitCurrency = CliDB.TraitCurrencyStorage.LookupByKey(cost.TraitCurrencyID); + if (traitCurrency != null) + subTree.Currencies.Add(traitCurrency); + } + } + + foreach (TraitCostRecord cost in node.Costs) + { + TraitCurrencyRecord traitCurrency = CliDB.TraitCurrencyStorage.LookupByKey(cost.TraitCurrencyID); + if (traitCurrency != null) + subTree.Currencies.Add(traitCurrency); + } + + Tree tree1 = _traitTrees.LookupByKey(traitNode.TraitTreeID); + if (tree1 != null) + { + foreach (TraitCostRecord cost in tree1.Costs) + { + TraitCurrencyRecord traitCurrency = CliDB.TraitCurrencyStorage.LookupByKey(cost.TraitCurrencyID); + if (traitCurrency != null) + subTree.Currencies.Add(traitCurrency); + } + } + } + _traitNodes[(int)traitNode.Id] = node; } @@ -471,6 +530,41 @@ namespace Game FillSpentCurrenciesMap(entry, cachedCurrencies); } + public static int[] GetClassAndSpecTreeCurrencies(TraitConfigPacket traitConfig) + { + int[] currencies = new int[2]; + + List trees = GetTreesForConfig(traitConfig); + if (trees != null) + { + int destIndex = 0; + foreach (var tree in trees) + { + if (destIndex >= currencies.Length) + break; + + foreach (var currency in tree.Currencies) + { + if (destIndex >= currencies.Length) + break; + + currencies[destIndex++] = (int)currency.Id; + } + } + } + + return currencies; + } + + public static List GetSubTreeCurrency(int traitSubTreeId) + { + SubTree subTree = _traitSubTrees.LookupByKey(traitSubTreeId); + if (subTree == null) + return null; + + return subTree.Currencies; + } + public static bool MeetsTraitCondition(TraitConfigPacket traitConfig, Player player, TraitCondRecord condition, ref Dictionary cachedCurrencies) { if (condition.QuestID != 0 && !player.IsQuestRewarded(condition.QuestID)) @@ -643,7 +737,7 @@ namespace Game return TalentLearnResult.FailedUnknown; foreach (NodeEntry entry in node.Entries) - if (!meetsConditions(entry.Conditions)) + if (entry.Data.Id == traitEntry.TraitNodeEntryID && !meetsConditions(entry.Conditions)) return TalentLearnResult.FailedUnknown; if (!meetsConditions(node.Conditions)) @@ -697,6 +791,41 @@ namespace Game ++i; } + Dictionary subtrees = new(); + + foreach (TraitEntryPacket traitEntry in traitConfig.Entries) + { + Node node = _traitNodes.LookupByKey(traitEntry.TraitNodeID); + var entryItr = node.Entries.Find(nodeEntry => nodeEntry.Data.Id == traitEntry.TraitNodeEntryID); + Cypher.Assert(entryItr != null); + + if (!subtrees.ContainsKey(entryItr.Data.TraitSubTreeID)) + subtrees[entryItr.Data.TraitSubTreeID] = new(); + + if (node.Data.GetNodeType() == TraitNodeType.SubTreeSelection) + subtrees[entryItr.Data.TraitSubTreeID].IsSelected = true; + + if (node.Data.TraitSubTreeID != 0) + subtrees[node.Data.TraitSubTreeID].Entries.Add(traitEntry); + } + + foreach (TraitSubTreeCachePacket subTree in traitConfig.SubTrees) + subTree.Active = false; + + foreach (var (selectedSubTreeId, data) in subtrees) + { + var subtreeDataItr = traitConfig.SubTrees.Find(p => p.TraitSubTreeID == selectedSubTreeId); + if (subtreeDataItr == null) + { + subtreeDataItr = new TraitSubTreeCachePacket(); + subtreeDataItr.TraitSubTreeID = selectedSubTreeId; + traitConfig.SubTrees.Add(subtreeDataItr); + } + + subtreeDataItr.Entries = data.Entries; + subtreeDataItr.Active = data.IsSelected; + } + Dictionary grantedCurrencies = new(); FillOwnedCurrenciesMap(traitConfig, player, grantedCurrencies); @@ -716,8 +845,10 @@ namespace Game if (requireSpendingAllCurrencies && traitConfig.Type == TraitConfigType.Combat) { - foreach (var (traitCurrencyId, grantedAmount) in grantedCurrencies) + // client checks only first two currencies for trait tree + foreach (int traitCurrencyId in GetClassAndSpecTreeCurrencies(traitConfig)) { + int grantedAmount = grantedCurrencies.LookupByKey(traitCurrencyId); if (grantedAmount == 0) continue; @@ -725,11 +856,44 @@ namespace Game if (spentAmount == 0 || spentAmount != grantedAmount) return TalentLearnResult.UnspentTalentPoints; } + + foreach (var (selectedTraitSubTreeId, data) in subtrees) + { + if (!data.IsSelected) + continue; + + foreach (TraitCurrencyRecord subTreeCurrency in GetSubTreeCurrency(selectedTraitSubTreeId)) + { + int grantedAmount = grantedCurrencies.LookupByKey(subTreeCurrency.Id); + if (grantedAmount == 0) + continue; + + int spentAmount = spentCurrencies.LookupByKey(subTreeCurrency.Id); + if (spentAmount == 0 || spentAmount != grantedAmount) + return TalentLearnResult.UnspentTalentPoints; + } + } } return TalentLearnResult.LearnOk; } + public static bool CanApplyTraitNode(TraitConfig traitConfig, TraitEntry traitEntry) + { + Node node = _traitNodes.LookupByKey(traitEntry.TraitNodeID); + if (node == null) + return false; + + if (node.Data.TraitSubTreeID != 0) + { + var subTreeItr = traitConfig.SubTrees._values.Find(p => p.TraitSubTreeID == node.Data.TraitSubTreeID); + if (subTreeItr == null || subTreeItr.Active == 0) + return false; + } + + return true; + } + public static List GetTraitDefinitionEffectPointModifiers(int traitDefinitionId) { return _traitDefinitionEffectPointModifiers.LookupByKey(traitDefinitionId); @@ -819,12 +983,26 @@ namespace Game public List Nodes = new(); } + class SubTree + { + public TraitSubTreeRecord Data; + public List Nodes = new(); + public List Currencies = new(); + } + class Tree { public TraitTreeRecord Data; public List Nodes = new(); public List Costs = new(); public List Currencies = new(); + public List SubTrees = new(); public TraitConfigType ConfigType; } + + class SubtreeValidationData + { + public List Entries = new(); + public bool IsSelected; + } }