Core/Players: Fixed trait config validation

This commit is contained in:
Shauren
2024-08-28 12:16:33 +02:00
parent c0b31a9f76
commit 1f0a62bde5
4 changed files with 79 additions and 40 deletions

View File

@@ -4197,6 +4197,7 @@ struct TraitCondEntry
int32 TraitCondAccountElementID;
TraitConditionType GetCondType() const { return static_cast<TraitConditionType>(CondType); }
EnumFlag<TraitCondFlags> GetFlags() const { return static_cast<TraitCondFlags>(Flags); }
};
struct TraitCostEntry

View File

@@ -3059,7 +3059,8 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent
if (traitDefinitionId)
if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*traitDefinitionId))
AddOverrideSpell(traitDefinition->OverridesSpellID, spellId);
if (traitDefinition->OverridesSpellID)
AddOverrideSpell(traitDefinition->OverridesSpellID, spellId);
// update free primary prof.points (if any, can be none in case GM .learn prof. learning)
if (uint32 freeProfs = GetFreePrimaryProfessionPoints())
@@ -28662,8 +28663,7 @@ void Player::ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::
if (consumeCurrencies)
{
std::map<int32, int32> currencies;
for (WorldPackets::Traits::TraitEntry const& costEntry : costEntries)
TraitMgr::FillSpentCurrenciesMap(costEntry, currencies);
TraitMgr::FillSpentCurrenciesMap(costEntries, currencies);
for (auto [traitCurrencyId, amount] : currencies)
{

View File

@@ -466,30 +466,30 @@ void FillOwnedCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig
}
}
void FillSpentCurrenciesMap(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& cachedCurrencies)
void AddSpentCurrenciesForEntry(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& cachedCurrencies, int32 multiplier)
{
Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
for (NodeGroup const* group : node->Groups)
for (TraitCostEntry const* cost : group->Costs)
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank * multiplier;
auto nodeEntryItr = std::ranges::find_if(node->Entries, [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; });
if (nodeEntryItr != node->Entries.end())
for (TraitCostEntry const* cost : nodeEntryItr->Costs)
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank * multiplier;
for (TraitCostEntry const* cost : node->Costs)
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank * multiplier;
if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
for (TraitCostEntry const* cost : tree->Costs)
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank;
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank * multiplier;
}
void FillSpentCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig, std::map<int32, int32>& cachedCurrencies)
void FillSpentCurrenciesMap(std::vector<WorldPackets::Traits::TraitEntry> const& traitEntries, std::map<int32, int32>& cachedCurrencies)
{
for (WorldPackets::Traits::TraitEntry const& entry : traitConfig.Entries)
FillSpentCurrenciesMap(entry, cachedCurrencies);
for (WorldPackets::Traits::TraitEntry const& entry : traitEntries)
AddSpentCurrenciesForEntry(entry, cachedCurrencies, 1);
}
std::array<int32, 2> GetClassAndSpecTreeCurrencies(WorldPackets::Traits::TraitConfig const& traitConfig)
@@ -538,7 +538,7 @@ bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const& traitConfig, P
if (condition->TraitCurrencyID && condition->SpentAmountRequired)
{
if (!cachedCurrencies)
FillSpentCurrenciesMap(traitConfig, cachedCurrencies.emplace());
FillSpentCurrenciesMap(traitConfig.Entries, cachedCurrencies.emplace());
if (condition->TraitNodeGroupID || condition->TraitNodeID || condition->TraitNodeEntryID)
{
@@ -554,6 +554,67 @@ bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const& traitConfig, P
return true;
}
bool NodeMeetsTraitConditions(WorldPackets::Traits::TraitConfig const& traitConfig, Node const* node, uint32 traitNodeEntryId, PlayerDataAccessor player, Optional<std::map<int32, int32>>& spentCurrencies)
{
auto meetsConditions = [&](std::vector<TraitCondEntry const*> const& conditions)
{
struct
{
bool IsSufficient = false;
bool HasFailedConditions = false;
} result;
for (TraitCondEntry const* condition : conditions)
{
if (condition->GetCondType() == TraitConditionType::Available || condition->GetCondType() == TraitConditionType::Visible)
{
if (MeetsTraitCondition(traitConfig, player, condition, spentCurrencies))
{
if (condition->GetFlags().HasFlag(TraitCondFlags::IsSufficient))
{
result.IsSufficient = true;
break;
}
continue;
}
result.HasFailedConditions = true;
}
}
return result;
};
bool hasFailedConditions = false;
for (NodeEntry const& entry : node->Entries)
{
if (entry.Data->ID == traitNodeEntryId)
{
auto [IsSufficient, HasFailedConditions] = meetsConditions(entry.Conditions);
if (IsSufficient)
return true;
if (HasFailedConditions)
hasFailedConditions = true;
}
}
if (auto [IsSufficient, HasFailedConditions] = meetsConditions(node->Conditions); IsSufficient)
return true;
else if (HasFailedConditions)
hasFailedConditions = true;
for (NodeGroup const* group : node->Groups)
{
auto [IsSufficient, HasFailedConditions] = meetsConditions(group->Conditions);
if (IsSufficient)
return true;
if (HasFailedConditions)
hasFailedConditions = true;
}
return !hasFailedConditions;
};
std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player)
{
std::vector<UF::TraitEntry> entries;
@@ -654,24 +715,7 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
};
Optional<std::map<int32, int32>> spentCurrencies;
FillSpentCurrenciesMap(traitConfig, spentCurrencies.emplace());
auto meetsConditions = [&](std::vector<TraitCondEntry const*> const& conditions)
{
bool hasConditions = false;
for (TraitCondEntry const* condition : conditions)
{
if (condition->GetCondType() == TraitConditionType::Available || condition->GetCondType() == TraitConditionType::Visible)
{
if (MeetsTraitCondition(traitConfig, player, condition, spentCurrencies))
return true;
hasConditions = true;
}
}
return !hasConditions;
};
FillSpentCurrenciesMap(traitConfig.Entries, spentCurrencies.emplace());
auto isValidTraitEntry = [&](WorldPackets::Traits::TraitEntry const& traitEntry)
{
@@ -683,17 +727,9 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
if (getNodeEntryCount(traitEntry.TraitNodeID) != 1)
return LearnResult::Unknown;
for (NodeEntry const& entry : node->Entries)
if (int32(entry.Data->ID) == traitEntry.TraitNodeEntryID && !meetsConditions(entry.Conditions))
return LearnResult::Unknown;
if (!meetsConditions(node->Conditions))
if (!NodeMeetsTraitConditions(traitConfig, node, traitEntry.TraitNodeEntryID, player, spentCurrencies))
return LearnResult::Unknown;
for (NodeGroup const* group : node->Groups)
if (!meetsConditions(group->Conditions))
return LearnResult::Unknown;
if (!node->ParentNodes.empty())
{
bool hasAnyParentTrait = false;
@@ -725,6 +761,8 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
if (!removeInvalidEntries)
return result;
AddSpentCurrenciesForEntry(*itr, *spentCurrencies, -1);
if (!itr->GrantedRanks // fully remove entries that don't have granted ranks
|| !itr->Rank) // ... 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.erase(itr);

View File

@@ -77,7 +77,7 @@ private:
void Load();
int32 GenerateNewTraitConfigId();
TraitConfigType GetConfigTypeForTree(int32 traitTreeId);
void FillSpentCurrenciesMap(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& cachedCurrencies);
void FillSpentCurrenciesMap(std::vector<WorldPackets::Traits::TraitEntry> const& traitEntries, std::map<int32, int32>& cachedCurrencies);
std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player);
bool IsValidEntry(WorldPackets::Traits::TraitEntry const& traitEntry);
LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies = false, bool removeInvalidEntries = false);