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.INS_CHAR_FISHINGSTEPS, "INSERT INTO character_fishingsteps (guid, fishingSteps) VALUES (?, ?)");
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.INS_CHAR_TRAIT_ENTRIES, "INSERT INTO character_trait_entry (guid, traitConfigId, traitNodeId, traitNodeEntryId, `rank`, grantedRanks) VALUES (?, ?, ?, ?, ?, ?)");
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`) VALUES (?, ?, ?, ?, ?)");
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.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();
if (!entriesResult.IsEmpty())
{
// 0 1, 2 3 4
// SELECT traitConfigId, traitNodeId, traitNodeEntryId, rank, grantedRanks FROM character_trait_entry WHERE guid = ?
// 0 1 2 3
// SELECT traitConfigId, traitNodeId, traitNodeEntryId, rank FROM character_trait_entry WHERE guid = ?
do
{
TraitEntryPacket traitEntry = new();
traitEntry.TraitNodeID = entriesResult.Read<int>(1);
traitEntry.TraitNodeEntryID = entriesResult.Read<int>(2);
traitEntry.Rank = entriesResult.Read<int>(3);
traitEntry.GrantedRanks = entriesResult.Read<int>(4);
if (!TraitMgr.IsValidEntry(traitEntry))
continue;
@@ -1103,10 +1102,26 @@ namespace Game.Entities
traitConfig.Name = configsResult.Read<string>(7);
foreach (var traitEntry in traitEntriesByConfig.LookupByKey(traitConfig.ID))
traitConfig.Entries.Add(traitEntry);
foreach (var grantedEntry in TraitMgr.GetGrantedTraitEntriesForConfig(traitConfig, this))
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();
foreach (TraitEntry grantedEntry in TraitMgr.GetGrantedTraitEntriesForConfig(traitConfig, this))
@@ -1118,6 +1133,10 @@ namespace Game.Entities
} while (configsResult.NextRow());
}
// Remove orphaned trait entries from database
foreach (var (traitConfigID, _) in traitEntriesByConfig)
m_traitConfigStates[traitConfigID] = PlayerSpellState.Removed;
bool hasConfigForSpec(int specId)
{
return m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
@@ -2430,7 +2449,6 @@ namespace Game.Entities
stmt.AddValue(2, traitEntry.TraitNodeID);
stmt.AddValue(3, traitEntry.TraitNodeEntryID);
stmt.AddValue(4, traitEntry.Rank);
stmt.AddValue(5, traitEntry.GrantedRanks);
trans.Append(stmt);
}
}
+35 -10
View File
@@ -524,19 +524,21 @@ namespace Game
if (trees == null)
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)
{
foundTraitEntry = new();
foundTraitEntry.TraitNodeID = (int)nodeId;
foundTraitEntry.TraitNodeEntryID = (int)entryId;
foundTraitEntry.TraitNodeEntryID = (int)entry.Data.Id;
foundTraitEntry.Rank = 0;
foundTraitEntry.GrantedRanks = 0;
entries.Add(foundTraitEntry);
}
return foundTraitEntry;
foundTraitEntry.GrantedRanks += grantedRanks;
if (foundTraitEntry.GrantedRanks > entry.Data.MaxRanks)
foundTraitEntry.GrantedRanks = entry.Data.MaxRanks;
}
Dictionary<int, int> cachedCurrencies = null;
@@ -548,18 +550,18 @@ namespace Game
foreach (NodeEntry entry in node.Entries)
foreach (TraitCondRecord condition in entry.Conditions)
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)
if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies))
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 (TraitCondRecord condition in group.Conditions)
if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies))
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;
}
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)
{
@@ -630,13 +632,13 @@ namespace Game
return !hasConditions;
}
foreach (TraitEntryPacket traitEntry in traitConfig.Entries)
var isValidTraitEntry = TalentLearnResult (TraitEntryPacket traitEntry) =>
{
if (!IsValidEntry(traitEntry))
return TalentLearnResult.FailedUnknown;
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)
return TalentLearnResult.FailedUnknown;
@@ -670,6 +672,29 @@ namespace Game
if (!hasAnyParentTrait)
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();