Core/Players: Fixed trait configs getting into invalid state after talent tree changes between client patches

Port From (https://github.com/TrinityCore/TrinityCore/commit/a7c8ac3505cb7e177b6f5299989d544a3b8cade9)
This commit is contained in:
Hondacrx
2024-09-02 22:46:11 -04:00
parent ba2aa0d0c0
commit 957ca9898d
3 changed files with 63 additions and 20 deletions
@@ -641,8 +641,8 @@ namespace Framework.Database
PrepareStatement(CharStatements.UPD_CHAR_LIST_SLOT, "UPDATE characters SET slot = ? WHERE guid = ? AND account = ?"); PrepareStatement(CharStatements.UPD_CHAR_LIST_SLOT, "UPDATE characters SET slot = ? WHERE guid = ? AND account = ?");
PrepareStatement(CharStatements.INS_CHAR_FISHINGSTEPS, "INSERT INTO character_fishingsteps (guid, fishingSteps) VALUES (?, ?)"); PrepareStatement(CharStatements.INS_CHAR_FISHINGSTEPS, "INSERT INTO character_fishingsteps (guid, fishingSteps) VALUES (?, ?)");
PrepareStatement(CharStatements.DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?"); PrepareStatement(CharStatements.DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?");
PrepareStatement(CharStatements.SEL_CHAR_TRAIT_ENTRIES, "SELECT traitConfigId, traitNodeId, traitNodeEntryId, `rank`, grantedRanks FROM character_trait_entry WHERE guid = ?"); PrepareStatement(CharStatements.SEL_CHAR_TRAIT_ENTRIES, "SELECT traitConfigId, traitNodeId, traitNodeEntryId, `rank` FROM character_trait_entry WHERE guid = ?");
PrepareStatement(CharStatements.INS_CHAR_TRAIT_ENTRIES, "INSERT INTO character_trait_entry (guid, traitConfigId, traitNodeId, traitNodeEntryId, `rank`, grantedRanks) VALUES (?, ?, ?, ?, ?, ?)"); PrepareStatement(CharStatements.INS_CHAR_TRAIT_ENTRIES, "INSERT INTO character_trait_entry (guid, traitConfigId, traitNodeId, traitNodeEntryId, `rank`) VALUES (?, ?, ?, ?, ?)");
PrepareStatement(CharStatements.DEL_CHAR_TRAIT_ENTRIES, "DELETE FROM character_trait_entry WHERE guid = ? AND traitConfigId = ?"); PrepareStatement(CharStatements.DEL_CHAR_TRAIT_ENTRIES, "DELETE FROM character_trait_entry WHERE guid = ? AND traitConfigId = ?");
PrepareStatement(CharStatements.DEL_CHAR_TRAIT_ENTRIES_BY_CHAR, "DELETE FROM character_trait_entry WHERE guid = ?"); PrepareStatement(CharStatements.DEL_CHAR_TRAIT_ENTRIES_BY_CHAR, "DELETE FROM character_trait_entry WHERE guid = ?");
PrepareStatement(CharStatements.SEL_CHAR_TRAIT_CONFIGS, "SELECT traitConfigId, type, chrSpecializationId, combatConfigFlags, localIdentifier, skillLineId, traitSystemId, `name` FROM character_trait_config WHERE guid = ?"); PrepareStatement(CharStatements.SEL_CHAR_TRAIT_CONFIGS, "SELECT traitConfigId, type, chrSpecializationId, combatConfigFlags, localIdentifier, skillLineId, traitSystemId, `name` FROM character_trait_config WHERE guid = ?");
+25 -7
View File
@@ -1057,15 +1057,14 @@ namespace Game.Entities
MultiMap<int, TraitEntryPacket> traitEntriesByConfig = new(); MultiMap<int, TraitEntryPacket> traitEntriesByConfig = new();
if (!entriesResult.IsEmpty()) if (!entriesResult.IsEmpty())
{ {
// 0 1, 2 3 4 // 0 1 2 3
// SELECT traitConfigId, traitNodeId, traitNodeEntryId, rank, grantedRanks FROM character_trait_entry WHERE guid = ? // SELECT traitConfigId, traitNodeId, traitNodeEntryId, rank FROM character_trait_entry WHERE guid = ?
do do
{ {
TraitEntryPacket traitEntry = new(); TraitEntryPacket traitEntry = new();
traitEntry.TraitNodeID = entriesResult.Read<int>(1); traitEntry.TraitNodeID = entriesResult.Read<int>(1);
traitEntry.TraitNodeEntryID = entriesResult.Read<int>(2); traitEntry.TraitNodeEntryID = entriesResult.Read<int>(2);
traitEntry.Rank = entriesResult.Read<int>(3); traitEntry.Rank = entriesResult.Read<int>(3);
traitEntry.GrantedRanks = entriesResult.Read<int>(4);
if (!TraitMgr.IsValidEntry(traitEntry)) if (!TraitMgr.IsValidEntry(traitEntry))
continue; continue;
@@ -1103,10 +1102,26 @@ namespace Game.Entities
traitConfig.Name = configsResult.Read<string>(7); traitConfig.Name = configsResult.Read<string>(7);
foreach (var traitEntry in traitEntriesByConfig.LookupByKey(traitConfig.ID)) foreach (var grantedEntry in TraitMgr.GetGrantedTraitEntriesForConfig(traitConfig, this))
traitConfig.Entries.Add(traitEntry); traitConfig.Entries.Add(new TraitEntryPacket(grantedEntry));
if (TraitMgr.ValidateConfig(traitConfig, this) != TalentLearnResult.LearnOk) var loadedEntriesNode = traitEntriesByConfig.LookupByKey(traitConfig.ID);
if (loadedEntriesNode != null)
{
foreach (var loadedEntry in loadedEntriesNode)
{
var itr = traitConfig.Entries.Find(entry => entry.TraitNodeID == loadedEntry.TraitNodeID && entry.TraitNodeEntryID == loadedEntry.TraitNodeEntryID);
if (itr == null)
{
traitConfig.Entries.Add(traitConfig.Entries.Last());
traitConfig.Entries[^1].TraitNodeID = loadedEntry.TraitNodeID;
traitConfig.Entries[^1].TraitNodeEntryID = loadedEntry.TraitNodeEntryID;
}
itr.Rank = loadedEntry.Rank;
}
}
if (TraitMgr.ValidateConfig(traitConfig, this, false, true) != TalentLearnResult.LearnOk)
{ {
traitConfig.Entries.Clear(); traitConfig.Entries.Clear();
foreach (TraitEntry grantedEntry in TraitMgr.GetGrantedTraitEntriesForConfig(traitConfig, this)) foreach (TraitEntry grantedEntry in TraitMgr.GetGrantedTraitEntriesForConfig(traitConfig, this))
@@ -1118,6 +1133,10 @@ namespace Game.Entities
} while (configsResult.NextRow()); } while (configsResult.NextRow());
} }
// Remove orphaned trait entries from database
foreach (var (traitConfigID, _) in traitEntriesByConfig)
m_traitConfigStates[traitConfigID] = PlayerSpellState.Removed;
bool hasConfigForSpec(int specId) bool hasConfigForSpec(int specId)
{ {
return m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig => return m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
@@ -2430,7 +2449,6 @@ namespace Game.Entities
stmt.AddValue(2, traitEntry.TraitNodeID); stmt.AddValue(2, traitEntry.TraitNodeID);
stmt.AddValue(3, traitEntry.TraitNodeEntryID); stmt.AddValue(3, traitEntry.TraitNodeEntryID);
stmt.AddValue(4, traitEntry.Rank); stmt.AddValue(4, traitEntry.Rank);
stmt.AddValue(5, traitEntry.GrantedRanks);
trans.Append(stmt); trans.Append(stmt);
} }
} }
+36 -11
View File
@@ -360,7 +360,7 @@ namespace Game
bool hasTraitNodeEntry(int traitNodeEntryId) bool hasTraitNodeEntry(int traitNodeEntryId)
{ {
return traitConfig.Entries.Any(traitEntry => traitEntry.TraitNodeEntryID == traitNodeEntryId && (traitEntry.Rank > 0 || traitEntry.GrantedRanks > 0)); return traitConfig.Entries.Any(traitEntry => traitEntry.TraitNodeEntryID == traitNodeEntryId && (traitEntry.Rank > 0 || traitEntry.GrantedRanks > 0));
} }
foreach (Tree tree in trees) foreach (Tree tree in trees)
{ {
@@ -524,19 +524,21 @@ namespace Game
if (trees == null) if (trees == null)
return entries; return entries;
TraitEntry getOrCreateEntry(uint nodeId, uint entryId) void addGrantedRankToEntry(uint nodeId, NodeEntry entry, int grantedRanks)
{ {
TraitEntry foundTraitEntry = entries.Find(traitEntry => traitEntry.TraitNodeID == nodeId && traitEntry.TraitNodeEntryID == entryId); TraitEntry foundTraitEntry = entries.Find(traitEntry => traitEntry.TraitNodeID == nodeId && traitEntry.TraitNodeEntryID == entry.Data.Id);
if (foundTraitEntry == null) if (foundTraitEntry == null)
{ {
foundTraitEntry = new(); foundTraitEntry = new();
foundTraitEntry.TraitNodeID = (int)nodeId; foundTraitEntry.TraitNodeID = (int)nodeId;
foundTraitEntry.TraitNodeEntryID = (int)entryId; foundTraitEntry.TraitNodeEntryID = (int)entry.Data.Id;
foundTraitEntry.Rank = 0; foundTraitEntry.Rank = 0;
foundTraitEntry.GrantedRanks = 0; foundTraitEntry.GrantedRanks = 0;
entries.Add(foundTraitEntry); entries.Add(foundTraitEntry);
} }
return foundTraitEntry; foundTraitEntry.GrantedRanks += grantedRanks;
if (foundTraitEntry.GrantedRanks > entry.Data.MaxRanks)
foundTraitEntry.GrantedRanks = entry.Data.MaxRanks;
} }
Dictionary<int, int> cachedCurrencies = null; Dictionary<int, int> cachedCurrencies = null;
@@ -548,18 +550,18 @@ namespace Game
foreach (NodeEntry entry in node.Entries) foreach (NodeEntry entry in node.Entries)
foreach (TraitCondRecord condition in entry.Conditions) foreach (TraitCondRecord condition in entry.Conditions)
if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies)) if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies))
getOrCreateEntry(node.Data.Id, entry.Data.Id).GrantedRanks += condition.GrantedRanks; addGrantedRankToEntry(node.Data.Id, entry, condition.GrantedRanks);
foreach (TraitCondRecord condition in node.Conditions) foreach (TraitCondRecord condition in node.Conditions)
if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies)) if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies))
foreach (NodeEntry entry in node.Entries) foreach (NodeEntry entry in node.Entries)
getOrCreateEntry(node.Data.Id, entry.Data.Id).GrantedRanks += condition.GrantedRanks; addGrantedRankToEntry(node.Data.Id, entry, condition.GrantedRanks);
foreach (NodeGroup group in node.Groups) foreach (NodeGroup group in node.Groups)
foreach (TraitCondRecord condition in group.Conditions) foreach (TraitCondRecord condition in group.Conditions)
if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies)) if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies))
foreach (NodeEntry entry in node.Entries) foreach (NodeEntry entry in node.Entries)
getOrCreateEntry(node.Data.Id, entry.Data.Id).GrantedRanks += condition.GrantedRanks; addGrantedRankToEntry(node.Data.Id, entry, condition.GrantedRanks);
} }
} }
@@ -582,7 +584,7 @@ namespace Game
return true; return true;
} }
public static TalentLearnResult ValidateConfig(TraitConfigPacket traitConfig, Player player, bool requireSpendingAllCurrencies = false) public static TalentLearnResult ValidateConfig(TraitConfigPacket traitConfig, Player player, bool requireSpendingAllCurrencies = false, bool removeInvalidEntries = false)
{ {
int getNodeEntryCount(int traitNodeId) int getNodeEntryCount(int traitNodeId)
{ {
@@ -630,13 +632,13 @@ namespace Game
return !hasConditions; return !hasConditions;
} }
foreach (TraitEntryPacket traitEntry in traitConfig.Entries) var isValidTraitEntry = TalentLearnResult (TraitEntryPacket traitEntry) =>
{ {
if (!IsValidEntry(traitEntry)) if (!IsValidEntry(traitEntry))
return TalentLearnResult.FailedUnknown; return TalentLearnResult.FailedUnknown;
Node node = _traitNodes.LookupByKey(traitEntry.TraitNodeID); Node node = _traitNodes.LookupByKey(traitEntry.TraitNodeID);
if (node.Data.GetNodeType() == TraitNodeType.Selection) if (node.Data.GetNodeType() == TraitNodeType.Selection || node.Data.GetNodeType() == TraitNodeType.SubTreeSelection)
if (getNodeEntryCount(traitEntry.TraitNodeID) != 1) if (getNodeEntryCount(traitEntry.TraitNodeID) != 1)
return TalentLearnResult.FailedUnknown; return TalentLearnResult.FailedUnknown;
@@ -670,6 +672,29 @@ namespace Game
if (!hasAnyParentTrait) if (!hasAnyParentTrait)
return TalentLearnResult.FailedNotEnoughTalentsInPrimaryTree; return TalentLearnResult.FailedNotEnoughTalentsInPrimaryTree;
} }
return TalentLearnResult.LearnOk;
};
for (var i = 0; i != traitConfig.Entries.Count;)
{
TalentLearnResult result = isValidTraitEntry(traitConfig.Entries[i]);
if (result != TalentLearnResult.LearnOk)
{
if (!removeInvalidEntries)
return result;
if (traitConfig.Entries[i].GrantedRanks == 0 // fully remove entries that don't have granted ranks
|| traitConfig.Entries[i].Rank == 0) // ... or entries that do have them and don't have any additional spent ranks (can happen if the same entry is revalidated after first removing all spent ranks)
traitConfig.Entries.RemoveAt(i);
else
traitConfig.Entries[i].Rank = 0;
// revalidate entire config - a removed entry will invalidate all other entries that depend on it
i = 0;
}
else
++i;
} }
Dictionary<int, int> grantedCurrencies = new(); Dictionary<int, int> grantedCurrencies = new();