Core/Players: Fixed trait config validation

Port From (https://github.com/TrinityCore/TrinityCore/commit/1f0a62bde5b927392a58e95a87d4d8bd93438d7e)
This commit is contained in:
Hondacrx
2024-10-09 00:42:58 -04:00
parent fbe4130a54
commit 0fd399e92e
4 changed files with 78 additions and 39 deletions
@@ -129,6 +129,7 @@ namespace Game.DataStorage
public int TraitCondAccountElementID; public int TraitCondAccountElementID;
public TraitConditionType GetCondType() { return (TraitConditionType)CondType; } public TraitConditionType GetCondType() { return (TraitConditionType)CondType; }
public bool HasFlag(TraitCondFlags flag) => Flags.HasAnyFlag((int)flag);
} }
public sealed class TraitCostRecord public sealed class TraitCostRecord
+1 -1
View File
@@ -2773,7 +2773,7 @@ namespace Game.Entities
if (traitDefinitionId.HasValue) if (traitDefinitionId.HasValue)
{ {
TraitDefinitionRecord traitDefinition = CliDB.TraitDefinitionStorage.LookupByKey(traitDefinitionId.Value); TraitDefinitionRecord traitDefinition = CliDB.TraitDefinitionStorage.LookupByKey(traitDefinitionId.Value);
if (traitDefinition != null) if (traitDefinition != null && traitDefinition.OverridesSpellID != 0)
AddOverrideSpell(traitDefinition.OverridesSpellID, spellId); AddOverrideSpell(traitDefinition.OverridesSpellID, spellId);
} }
@@ -1058,8 +1058,7 @@ namespace Game.Entities
if (consumeCurrencies) if (consumeCurrencies)
{ {
Dictionary<int, int> currencies = new(); Dictionary<int, int> currencies = new();
foreach (TraitEntryPacket costEntry in costEntries) TraitMgr.FillSpentCurrenciesMap(costEntries, currencies);
TraitMgr.FillSpentCurrenciesMap(costEntry, currencies);
foreach (var (traitCurrencyId, amount) in currencies) foreach (var (traitCurrencyId, amount) in currencies)
{ {
+75 -36
View File
@@ -477,7 +477,7 @@ namespace Game
} }
} }
public static void FillSpentCurrenciesMap(TraitEntryPacket entry, Dictionary<int, int> cachedCurrencies) public static void AddSpentCurrenciesForEntry(TraitEntryPacket entry, Dictionary<int, int> cachedCurrencies, int multiplier)
{ {
Node node = _traitNodes.LookupByKey(entry.TraitNodeID); Node node = _traitNodes.LookupByKey(entry.TraitNodeID);
foreach (NodeGroup group in node.Groups) foreach (NodeGroup group in node.Groups)
@@ -487,7 +487,7 @@ namespace Game
if (!cachedCurrencies.ContainsKey(cost.TraitCurrencyID)) if (!cachedCurrencies.ContainsKey(cost.TraitCurrencyID))
cachedCurrencies[cost.TraitCurrencyID] = 0; cachedCurrencies[cost.TraitCurrencyID] = 0;
cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank; cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank * multiplier;
} }
} }
@@ -499,7 +499,7 @@ namespace Game
if (!cachedCurrencies.ContainsKey(cost.TraitCurrencyID)) if (!cachedCurrencies.ContainsKey(cost.TraitCurrencyID))
cachedCurrencies[cost.TraitCurrencyID] = 0; cachedCurrencies[cost.TraitCurrencyID] = 0;
cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank; cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank * multiplier;
} }
} }
@@ -508,7 +508,7 @@ namespace Game
if (!cachedCurrencies.ContainsKey(cost.TraitCurrencyID)) if (!cachedCurrencies.ContainsKey(cost.TraitCurrencyID))
cachedCurrencies[cost.TraitCurrencyID] = 0; cachedCurrencies[cost.TraitCurrencyID] = 0;
cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank; cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank * multiplier;
} }
Tree tree = _traitTrees.LookupByKey(node.Data.TraitTreeID); Tree tree = _traitTrees.LookupByKey(node.Data.TraitTreeID);
@@ -519,15 +519,15 @@ namespace Game
if (!cachedCurrencies.ContainsKey(cost.TraitCurrencyID)) if (!cachedCurrencies.ContainsKey(cost.TraitCurrencyID))
cachedCurrencies[cost.TraitCurrencyID] = 0; cachedCurrencies[cost.TraitCurrencyID] = 0;
cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank; cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank * multiplier;
} }
} }
} }
public static void FillSpentCurrenciesMap(TraitConfigPacket traitConfig, Dictionary<int, int> cachedCurrencies) public static void FillSpentCurrenciesMap(List<TraitEntryPacket> traitEntries, Dictionary<int, int> cachedCurrencies)
{ {
foreach (TraitEntryPacket entry in traitConfig.Entries) foreach (TraitEntryPacket entry in traitEntries)
FillSpentCurrenciesMap(entry, cachedCurrencies); AddSpentCurrenciesForEntry(entry, cachedCurrencies, 1);
} }
public static int[] GetClassAndSpecTreeCurrencies(TraitConfigPacket traitConfig) public static int[] GetClassAndSpecTreeCurrencies(TraitConfigPacket traitConfig)
@@ -588,7 +588,7 @@ namespace Game
if (cachedCurrencies == null) if (cachedCurrencies == null)
{ {
cachedCurrencies = new(); cachedCurrencies = new();
FillSpentCurrenciesMap(traitConfig, cachedCurrencies); FillSpentCurrenciesMap(traitConfig.Entries, cachedCurrencies);
} }
if (condition.TraitNodeGroupID != 0 || condition.TraitNodeID != 0 || condition.TraitNodeEntryID != 0) if (condition.TraitNodeGroupID != 0 || condition.TraitNodeID != 0 || condition.TraitNodeEntryID != 0)
@@ -605,6 +605,68 @@ namespace Game
return true; return true;
} }
public static bool NodeMeetsTraitConditions(TraitConfigPacket traitConfig, Node node, uint traitNodeEntryId, Player player, Dictionary<int, int> spentCurrencies)
{
var meetsConditions = (bool isSufficient, bool hasFailedConditions) (List<TraitCondRecord> conditions) =>
{
bool isSufficient = false;
bool hasFailedConditions = false;
foreach (var condition in conditions)
{
if (condition.GetCondType() == TraitConditionType.Available || condition.GetCondType() == TraitConditionType.Visible)
{
if (MeetsTraitCondition(traitConfig, player, condition, ref spentCurrencies))
{
if (condition.HasFlag(TraitCondFlags.IsSufficient))
{
isSufficient = true;
break;
}
continue;
}
hasFailedConditions = true;
}
}
return (isSufficient, hasFailedConditions);
};
bool IsSufficient;
bool HasFailedConditions;
bool hasFailedConditions = false;
foreach (NodeEntry entry in node.Entries)
{
if (entry.Data.Id == traitNodeEntryId)
{
(IsSufficient, HasFailedConditions) = meetsConditions(entry.Conditions);
if (IsSufficient)
return true;
if (HasFailedConditions)
hasFailedConditions = true;
}
}
(IsSufficient, HasFailedConditions) = meetsConditions(node.Conditions);
if (IsSufficient)
return true;
else if (HasFailedConditions)
hasFailedConditions = true;
foreach (NodeGroup group in node.Groups)
{
(IsSufficient, HasFailedConditions) = meetsConditions(group.Conditions);
if (IsSufficient)
return true;
if (HasFailedConditions)
hasFailedConditions = true;
}
return !hasFailedConditions;
}
public static List<TraitEntry> GetGrantedTraitEntriesForConfig(TraitConfigPacket traitConfig, Player player) public static List<TraitEntry> GetGrantedTraitEntriesForConfig(TraitConfigPacket traitConfig, Player player)
{ {
List<TraitEntry> entries = new(); List<TraitEntry> entries = new();
@@ -701,24 +763,7 @@ namespace Game
}; };
Dictionary<int, int> spentCurrencies = new(); Dictionary<int, int> spentCurrencies = new();
FillSpentCurrenciesMap(traitConfig, spentCurrencies); FillSpentCurrenciesMap(traitConfig.Entries, spentCurrencies);
bool meetsConditions(List<TraitCondRecord> conditions)
{
bool hasConditions = false;
foreach (TraitCondRecord condition in conditions)
{
if (condition.GetCondType() == TraitConditionType.Available || condition.GetCondType() == TraitConditionType.Visible)
{
if (MeetsTraitCondition(traitConfig, player, condition, ref spentCurrencies))
return true;
hasConditions = true;
}
}
return !hasConditions;
}
var isValidTraitEntry = TalentLearnResult (TraitEntryPacket traitEntry) => var isValidTraitEntry = TalentLearnResult (TraitEntryPacket traitEntry) =>
{ {
@@ -730,17 +775,9 @@ namespace Game
if (getNodeEntryCount(traitEntry.TraitNodeID) != 1) if (getNodeEntryCount(traitEntry.TraitNodeID) != 1)
return TalentLearnResult.FailedUnknown; return TalentLearnResult.FailedUnknown;
foreach (NodeEntry entry in node.Entries) if (!NodeMeetsTraitConditions(traitConfig, node, (uint)traitEntry.TraitNodeEntryID, player, spentCurrencies))
if (entry.Data.Id == traitEntry.TraitNodeEntryID && !meetsConditions(entry.Conditions))
return TalentLearnResult.FailedUnknown;
if (!meetsConditions(node.Conditions))
return TalentLearnResult.FailedUnknown; return TalentLearnResult.FailedUnknown;
foreach (NodeGroup group in node.Groups)
if (!meetsConditions(group.Conditions))
return TalentLearnResult.FailedUnknown;
if (!node.ParentNodes.Empty()) if (!node.ParentNodes.Empty())
{ {
bool hasAnyParentTrait = false; bool hasAnyParentTrait = false;
@@ -772,6 +809,8 @@ namespace Game
if (!removeInvalidEntries) if (!removeInvalidEntries)
return result; return result;
AddSpentCurrenciesForEntry(traitConfig.Entries[i], spentCurrencies, -1);
if (traitConfig.Entries[i].GrantedRanks == 0 // fully remove entries that don't have granted ranks 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[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); traitConfig.Entries.RemoveAt(i);