Core/Players: Implemented hero talents

Port From (https://github.com/TrinityCore/TrinityCore/commit/d1ffe61727f53505a3e2b94cae32f2ce3d28b07b)
This commit is contained in:
Hondacrx
2024-09-08 14:09:06 -04:00
parent 1bd1b1d62c
commit 54023d52d7
8 changed files with 301 additions and 32 deletions
@@ -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,
+2
View File
@@ -365,6 +365,7 @@ namespace Game.DataStorage
TraitNodeXTraitCondStorage = ReadDB2<TraitNodeXTraitCondRecord>("TraitNodeXTraitCond.db2", HotfixStatements.SEL_TRAIT_NODE_X_TRAIT_COND);
TraitNodeXTraitCostStorage = ReadDB2<TraitNodeXTraitCostRecord>("TraitNodeXTraitCost.db2", HotfixStatements.SEL_TRAIT_NODE_X_TRAIT_COST);
TraitNodeXTraitNodeEntryStorage = ReadDB2<TraitNodeXTraitNodeEntryRecord>("TraitNodeXTraitNodeEntry.db2", HotfixStatements.SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY);
TraitSubTreeStorage = ReadDB2<TraitSubTreeRecord>("TraitSubTree.db2", HotfixStatements.SEL_TRAIT_SUB_TREE, HotfixStatements.SEL_TRAIT_SUB_TREE_LOCALE);
TraitTreeStorage = ReadDB2<TraitTreeRecord>("TraitTree.db2", HotfixStatements.SEL_TRAIT_TREE);
TraitTreeLoadoutStorage = ReadDB2<TraitTreeLoadoutRecord>("TraitTreeLoadout.db2", HotfixStatements.SEL_TRAIT_TREE_LOADOUT);
TraitTreeLoadoutEntryStorage = ReadDB2<TraitTreeLoadoutEntryRecord>("TraitTreeLoadoutEntry.db2", HotfixStatements.SEL_TRAIT_TREE_LOADOUT_ENTRY);
@@ -811,6 +812,7 @@ namespace Game.DataStorage
public static DB6Storage<TraitNodeXTraitCondRecord> TraitNodeXTraitCondStorage;
public static DB6Storage<TraitNodeXTraitCostRecord> TraitNodeXTraitCostStorage;
public static DB6Storage<TraitNodeXTraitNodeEntryRecord> TraitNodeXTraitNodeEntryStorage;
public static DB6Storage<TraitSubTreeRecord> TraitSubTreeStorage;
public static DB6Storage<TraitTreeRecord> TraitTreeStorage;
public static DB6Storage<TraitTreeLoadoutRecord> TraitTreeLoadoutStorage;
public static DB6Storage<TraitTreeLoadoutEntryRecord> TraitTreeLoadoutEntryStorage;
@@ -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;
+4
View File
@@ -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);
+63 -1
View File
@@ -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<TraitEntry> 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,6 +1167,7 @@ namespace Game.Entities
return;
foreach (TraitEntry traitEntry in traitConfig.Entries)
if (!apply || TraitMgr.CanApplyTraitNode(traitConfig, traitEntry))
ApplyTraitEntry(traitEntry.TraitNodeEntryID, traitEntry.Rank, traitEntry.GrantedRanks, apply);
}
+3
View File
@@ -71,6 +71,8 @@ namespace Game
return;
}
if (traitNodeEntry.TraitDefinitionID != 0)
{
TraitDefinitionRecord traitDefinition = CliDB.TraitDefinitionStorage.LookupByKey(traitNodeEntry.TraitDefinitionID);
if (traitDefinition == null)
{
@@ -89,6 +91,7 @@ namespace Game
SendPacket(new TraitConfigCommitFailed(configId, traitDefinition.VisibleSpellID, (int)TalentLearnResult.FailedCantRemoveTalent));
return;
}
}
hasRemovedEntries = true;
}
+10 -8
View File
@@ -148,19 +148,19 @@ namespace Game.Networking.Packets
}
}
public class TraitSubTreeCache
public class TraitSubTreeCachePacket
{
public int TraitSubTreeID;
public List<TraitEntryPacket> 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<TraitEntryPacket> Entries = new();
public List<TraitSubTreeCache> SubTrees = new();
public List<TraitSubTreeCachePacket> 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();
+186 -8
View File
@@ -15,6 +15,7 @@ namespace Game
static Dictionary<int, NodeGroup> _traitGroups = new();
static Dictionary<int, Node> _traitNodes = new();
static Dictionary<int, SubTree> _traitSubTrees = new();
static Dictionary<int, Tree> _traitTrees = new();
static int[] _skillLinesByClass = new int[(int)Class.Max];
static MultiMap<int, Tree> _traitTreesBySkillLine = new();
@@ -96,12 +97,11 @@ namespace Game
treeCosts.Add(traitTreeXTraitCostEntry.TraitTreeID, traitCostEntry);
}
MultiMap<int, TraitCurrencyRecord> treeCurrencies = new();
MultiMap<int, TraitTreeXTraitCurrencyRecord> 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<int, int> 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<Tree> 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<TraitCurrencyRecord> 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<int, int> 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<int, SubtreeValidationData> 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<int, int> 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<TraitDefinitionEffectPointsRecord> GetTraitDefinitionEffectPointModifiers(int traitDefinitionId)
{
return _traitDefinitionEffectPointModifiers.LookupByKey(traitDefinitionId);
@@ -819,12 +983,26 @@ namespace Game
public List<Node> Nodes = new();
}
class SubTree
{
public TraitSubTreeRecord Data;
public List<Node> Nodes = new();
public List<TraitCurrencyRecord> Currencies = new();
}
class Tree
{
public TraitTreeRecord Data;
public List<Node> Nodes = new();
public List<TraitCostRecord> Costs = new();
public List<TraitCurrencyRecord> Currencies = new();
public List<SubTree> SubTrees = new();
public TraitConfigType ConfigType;
}
class SubtreeValidationData
{
public List<TraitEntryPacket> Entries = new();
public bool IsSelected;
}
}