mirror of
https://github.com/araxiaonline/TrinityCore.git
synced 2026-06-13 03:32:28 -04:00
Core/Players: Improve trait config validation
This commit is contained in:
14
sql/updates/hotfixes/master/2026_02_03_00_hotfixes.sql
Normal file
14
sql/updates/hotfixes/master/2026_02_03_00_hotfixes.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
--
|
||||
-- Table structure for table `trait_cond_account_element`
|
||||
--
|
||||
DROP TABLE IF EXISTS `trait_cond_account_element`;
|
||||
CREATE TABLE `trait_cond_account_element` (
|
||||
`ElementValueInt` bigint NOT NULL DEFAULT '0',
|
||||
`ID` int unsigned NOT NULL DEFAULT '0',
|
||||
`PlayerDataElementAccountID` int unsigned NOT NULL DEFAULT '0',
|
||||
`Comparison` tinyint unsigned NOT NULL DEFAULT '0',
|
||||
`Unused1110` int NOT NULL DEFAULT '0',
|
||||
`PlayerDataElementCharacterID` int NOT NULL DEFAULT '0',
|
||||
`VerifiedBuild` int NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`ID`,`VerifiedBuild`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -1850,6 +1850,11 @@ void HotfixDatabaseConnection::DoPrepareStatements()
|
||||
"TraitCondAccountElementID FROM trait_cond WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_COND, "SELECT MAX(ID) + 1 FROM trait_cond", CONNECTION_SYNCH);
|
||||
|
||||
// TraitCondAccountElement.db2
|
||||
PrepareStatement(HOTFIX_SEL_TRAIT_COND_ACCOUNT_ELEMENT, "SELECT ElementValueInt, ID, PlayerDataElementAccountID, Comparison, Unused1110, "
|
||||
"PlayerDataElementCharacterID FROM trait_cond_account_element WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_COND_ACCOUNT_ELEMENT, "SELECT MAX(ID) + 1 FROM trait_cond_account_element", CONNECTION_SYNCH);
|
||||
|
||||
// TraitCost.db2
|
||||
PrepareStatement(HOTFIX_SEL_TRAIT_COST, "SELECT InternalName, ID, Amount, TraitCurrencyID, CurveID FROM trait_cost WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_COST, "SELECT MAX(ID) + 1 FROM trait_cost", CONNECTION_SYNCH);
|
||||
|
||||
@@ -1066,6 +1066,9 @@ enum HotfixDatabaseStatements : uint32
|
||||
HOTFIX_SEL_TRAIT_COND,
|
||||
HOTFIX_SEL_TRAIT_COND_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_TRAIT_COND_ACCOUNT_ELEMENT,
|
||||
HOTFIX_SEL_TRAIT_COND_ACCOUNT_ELEMENT_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_TRAIT_COST,
|
||||
HOTFIX_SEL_TRAIT_COST_MAX_ID,
|
||||
|
||||
|
||||
@@ -6181,6 +6181,21 @@ struct TraitCondLoadInfo
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 17, &TraitCondMeta::Instance, HOTFIX_SEL_TRAIT_COND };
|
||||
};
|
||||
|
||||
struct TraitCondAccountElementLoadInfo
|
||||
{
|
||||
static constexpr DB2FieldMeta Fields[6] =
|
||||
{
|
||||
{ .IsSigned = true, .Type = FT_LONG, .Name = "ElementValueInt" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "PlayerDataElementAccountID" },
|
||||
{ .IsSigned = false, .Type = FT_BYTE, .Name = "Comparison" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Unused1110" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "PlayerDataElementCharacterID" },
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 6, &TraitCondAccountElementMeta::Instance, HOTFIX_SEL_TRAIT_COND_ACCOUNT_ELEMENT };
|
||||
};
|
||||
|
||||
struct TraitCostLoadInfo
|
||||
{
|
||||
static constexpr DB2FieldMeta Fields[5] =
|
||||
|
||||
@@ -353,6 +353,7 @@ DB2Storage<TaxiPathNodeEntry> sTaxiPathNodeStore("TaxiPathNode
|
||||
DB2Storage<TotemCategoryEntry> sTotemCategoryStore("TotemCategory.db2", &TotemCategoryLoadInfo::Instance);
|
||||
DB2Storage<ToyEntry> sToyStore("Toy.db2", &ToyLoadInfo::Instance);
|
||||
DB2Storage<TraitCondEntry> sTraitCondStore("TraitCond.db2", &TraitCondLoadInfo::Instance);
|
||||
DB2Storage<TraitCondAccountElementEntry> sTraitCondAccountElementStore("TraitCondAccountElement.db2", &TraitCondAccountElementLoadInfo::Instance);
|
||||
DB2Storage<TraitCostEntry> sTraitCostStore("TraitCost.db2", &TraitCostLoadInfo::Instance);
|
||||
DB2Storage<TraitCurrencyEntry> sTraitCurrencyStore("TraitCurrency.db2", &TraitCurrencyLoadInfo::Instance);
|
||||
DB2Storage<TraitCurrencySourceEntry> sTraitCurrencySourceStore("TraitCurrencySource.db2", &TraitCurrencySourceLoadInfo::Instance);
|
||||
@@ -982,6 +983,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
|
||||
LOAD_DB2(sTotemCategoryStore);
|
||||
LOAD_DB2(sToyStore);
|
||||
LOAD_DB2(sTraitCondStore);
|
||||
LOAD_DB2(sTraitCondAccountElementStore);
|
||||
LOAD_DB2(sTraitCostStore);
|
||||
LOAD_DB2(sTraitCurrencyStore);
|
||||
LOAD_DB2(sTraitCurrencySourceStore);
|
||||
|
||||
@@ -276,6 +276,7 @@ TC_GAME_API extern DB2Storage<TaxiNodesEntry> sTaxiNodesSt
|
||||
TC_GAME_API extern DB2Storage<TaxiPathEntry> sTaxiPathStore;
|
||||
TC_GAME_API extern DB2Storage<TaxiPathNodeEntry> sTaxiPathNodeStore;
|
||||
TC_GAME_API extern DB2Storage<TraitCondEntry> sTraitCondStore;
|
||||
TC_GAME_API extern DB2Storage<TraitCondAccountElementEntry> sTraitCondAccountElementStore;
|
||||
TC_GAME_API extern DB2Storage<TraitCostEntry> sTraitCostStore;
|
||||
TC_GAME_API extern DB2Storage<TraitCurrencyEntry> sTraitCurrencyStore;
|
||||
TC_GAME_API extern DB2Storage<TraitCurrencySourceEntry> sTraitCurrencySourceStore;
|
||||
|
||||
@@ -4419,6 +4419,16 @@ struct TraitCondEntry
|
||||
EnumFlag<TraitCondFlags> GetFlags() const { return static_cast<TraitCondFlags>(Flags); }
|
||||
};
|
||||
|
||||
struct TraitCondAccountElementEntry
|
||||
{
|
||||
int64 ElementValueInt;
|
||||
uint32 ID;
|
||||
uint32 PlayerDataElementAccountID;
|
||||
uint8 Comparison;
|
||||
int32 Unused1110;
|
||||
int32 PlayerDataElementCharacterID;
|
||||
};
|
||||
|
||||
struct TraitCostEntry
|
||||
{
|
||||
char const* InternalName;
|
||||
|
||||
@@ -28860,10 +28860,10 @@ void Player::ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::
|
||||
|
||||
if (consumeCurrencies)
|
||||
{
|
||||
std::map<int32, int32> currencies;
|
||||
std::map<int32, TraitMgr::SpentCurrency> currencies;
|
||||
TraitMgr::FillSpentCurrenciesMap(costEntries, currencies);
|
||||
|
||||
for (auto [traitCurrencyId, amount] : currencies)
|
||||
for (auto const& [traitCurrencyId, amount] : currencies)
|
||||
{
|
||||
TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(traitCurrencyId);
|
||||
if (!traitCurrency)
|
||||
@@ -28872,10 +28872,10 @@ void Player::ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::
|
||||
switch (traitCurrency->GetType())
|
||||
{
|
||||
case TraitCurrencyType::Gold:
|
||||
ModifyMoney(-amount);
|
||||
ModifyMoney(-amount.Total);
|
||||
break;
|
||||
case TraitCurrencyType::CurrencyTypesBased:
|
||||
RemoveCurrency(traitCurrency->CurrencyTypesID, amount /* TODO: CurrencyDestroyReason */);
|
||||
RemoveCurrency(traitCurrency->CurrencyTypesID, amount.Total /* TODO: CurrencyDestroyReason */);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -472,27 +472,74 @@ void FillOwnedCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig
|
||||
}
|
||||
}
|
||||
|
||||
void AddSpentCurrenciesForEntry(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& cachedCurrencies, int32 multiplier)
|
||||
std::vector<TraitCondEntry const*> GetGateConditionsForNode(Node const* node)
|
||||
{
|
||||
std::vector<TraitCondEntry const*> gateConditions;
|
||||
|
||||
auto fillConditions = [&](std::vector<TraitCondEntry const*> const& conditions)
|
||||
{
|
||||
for (TraitCondEntry const* condition : conditions)
|
||||
{
|
||||
if (!condition->GetFlags().HasFlag(TraitCondFlags::IsGate))
|
||||
continue;
|
||||
|
||||
auto cond = std::ranges::find(gateConditions, condition->TraitCurrencyID, &TraitCondEntry::TraitCurrencyID);
|
||||
if (cond == gateConditions.end())
|
||||
gateConditions.push_back(condition);
|
||||
else if ((*cond)->SpentAmountRequired < condition->SpentAmountRequired)
|
||||
*cond = condition;
|
||||
}
|
||||
};
|
||||
|
||||
fillConditions(node->Conditions);
|
||||
|
||||
for (NodeGroup const* group : node->Groups)
|
||||
fillConditions(group->Conditions);
|
||||
|
||||
return gateConditions;
|
||||
}
|
||||
|
||||
void AddSpentCurrenciesForEntry(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, SpentCurrency>& cachedCurrencies, int32 multiplier)
|
||||
{
|
||||
Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID);
|
||||
std::vector<TraitCondEntry const*> gateConditions = GetGateConditionsForNode(node);
|
||||
|
||||
auto addCurrencies = [&](std::vector<TraitCostEntry const*> const& costs)
|
||||
{
|
||||
for (TraitCostEntry const* cost : costs)
|
||||
{
|
||||
int32 amount = cost->Amount * entry.Rank * multiplier;
|
||||
|
||||
SpentCurrency& cached = cachedCurrencies[cost->TraitCurrencyID];
|
||||
cached.Total += amount;
|
||||
|
||||
int32 gate = 0;
|
||||
auto gateCondition = std::ranges::find(gateConditions, cost->TraitCurrencyID, &TraitCondEntry::TraitCurrencyID);
|
||||
if (gateCondition != gateConditions.end())
|
||||
gate = (*gateCondition)->SpentAmountRequired;
|
||||
|
||||
auto gateCost = std::ranges::find(cached.ByGate, gate, Trinity::Containers::MapKey);
|
||||
if (gateCost == cached.ByGate.end())
|
||||
cached.ByGate.emplace_back(gate, amount);
|
||||
else
|
||||
gateCost->second += amount;
|
||||
}
|
||||
};
|
||||
|
||||
for (NodeGroup const* group : node->Groups)
|
||||
for (TraitCostEntry const* cost : group->Costs)
|
||||
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank * multiplier;
|
||||
addCurrencies(group->Costs);
|
||||
|
||||
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 * multiplier;
|
||||
addCurrencies(nodeEntryItr->Costs);
|
||||
|
||||
for (TraitCostEntry const* cost : node->Costs)
|
||||
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank * multiplier;
|
||||
addCurrencies(node->Costs);
|
||||
|
||||
if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID))
|
||||
for (TraitCostEntry const* cost : tree->Costs)
|
||||
cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank * multiplier;
|
||||
addCurrencies(tree->Costs);
|
||||
}
|
||||
|
||||
void FillSpentCurrenciesMap(std::vector<WorldPackets::Traits::TraitEntry> const& traitEntries, std::map<int32, int32>& cachedCurrencies)
|
||||
void FillSpentCurrenciesMap(std::vector<WorldPackets::Traits::TraitEntry> const& traitEntries, std::map<int32, SpentCurrency>& cachedCurrencies)
|
||||
{
|
||||
for (WorldPackets::Traits::TraitEntry const& entry : traitEntries)
|
||||
AddSpentCurrenciesForEntry(entry, cachedCurrencies, 1);
|
||||
@@ -523,7 +570,7 @@ std::span<TraitCurrencyEntry const* const> GetSubTreeCurrency(int32 traitSubTree
|
||||
}
|
||||
|
||||
bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player, TraitCondEntry const* condition,
|
||||
Optional<std::map<int32, int32>>& cachedCurrencies)
|
||||
Optional<std::map<int32, SpentCurrency>>& cachedCurrencies)
|
||||
{
|
||||
if (condition->QuestID && !player.IsQuestRewarded(condition->QuestID))
|
||||
return false;
|
||||
@@ -548,8 +595,14 @@ bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const& traitConfig, P
|
||||
|
||||
if (condition->TraitNodeGroupID || condition->TraitNodeID || condition->TraitNodeEntryID)
|
||||
{
|
||||
auto itr = cachedCurrencies->try_emplace(condition->TraitCurrencyID, 0).first;
|
||||
if (itr->second < condition->SpentAmountRequired)
|
||||
int32 spentAmount = 0;
|
||||
auto itr = cachedCurrencies->find(condition->TraitCurrencyID);
|
||||
if (itr != cachedCurrencies->end())
|
||||
for (auto [gate, spentBeforeGate] : itr->second.ByGate)
|
||||
if (gate < condition->SpentAmountRequired)
|
||||
spentAmount += spentBeforeGate;
|
||||
|
||||
if (spentAmount < condition->SpentAmountRequired)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -557,12 +610,34 @@ bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const& traitConfig, P
|
||||
if (condition->RequiredLevel && player.GetLevel() < condition->RequiredLevel)
|
||||
return false;
|
||||
|
||||
if (TraitCondAccountElementEntry const* accountElementCond = sTraitCondAccountElementStore.LookupEntry(condition->TraitCondAccountElementID))
|
||||
{
|
||||
int64 value = 0;
|
||||
if (accountElementCond->PlayerDataElementAccountID)
|
||||
value = std::visit([](auto v) { return static_cast<int64>(v); }, player.GetDataElementAccount(accountElementCond->PlayerDataElementAccountID));
|
||||
else if (accountElementCond->PlayerDataElementCharacterID)
|
||||
value = std::visit([](auto v) { return static_cast<int64>(v); }, player.GetDataElementCharacter(accountElementCond->PlayerDataElementCharacterID));
|
||||
|
||||
switch (accountElementCond->Comparison)
|
||||
{
|
||||
case 1: if (value != accountElementCond->ElementValueInt) return false; break;
|
||||
case 2: if (value == accountElementCond->ElementValueInt) return false; break;
|
||||
case 3: if (value >= accountElementCond->ElementValueInt) return false; break;
|
||||
case 4: if (value > accountElementCond->ElementValueInt) return false; break;
|
||||
case 5: if (value <= accountElementCond->ElementValueInt) return false; break;
|
||||
case 6: if (value < accountElementCond->ElementValueInt) return false; break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NodeMeetsTraitConditions(WorldPackets::Traits::TraitConfig const& traitConfig, Node const* node, uint32 traitNodeEntryId, PlayerDataAccessor player, Optional<std::map<int32, int32>>& spentCurrencies)
|
||||
bool NodeMeetsTraitConditions(WorldPackets::Traits::TraitConfig const& traitConfig, Node const* node, uint32 traitNodeEntryId, PlayerDataAccessor player,
|
||||
Optional<std::map<int32, SpentCurrency>>& spentCurrencies)
|
||||
{
|
||||
auto meetsConditions = [&](std::vector<TraitCondEntry const*> const& conditions)
|
||||
auto meetsConditions = [&](std::vector<TraitCondEntry const*> const& conditions, TraitConditionType conditionType)
|
||||
{
|
||||
struct
|
||||
{
|
||||
@@ -572,54 +647,59 @@ bool NodeMeetsTraitConditions(WorldPackets::Traits::TraitConfig const& traitConf
|
||||
|
||||
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;
|
||||
}
|
||||
if (condition->GetCondType() != conditionType)
|
||||
continue;
|
||||
|
||||
if (!MeetsTraitCondition(traitConfig, player, condition, spentCurrencies))
|
||||
{
|
||||
result.HasFailedConditions = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (condition->GetFlags().HasFlag(TraitCondFlags::IsSufficient))
|
||||
{
|
||||
result.IsSufficient = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
bool hasFailedConditions = false;
|
||||
for (NodeEntry const& entry : node->Entries)
|
||||
auto meetsConditionsOfType = [&](TraitConditionType conditionType)
|
||||
{
|
||||
if (entry.Data->ID == traitNodeEntryId)
|
||||
bool hasFailedConditions = false;
|
||||
if (auto [IsSufficient, HasFailedConditions] = meetsConditions(node->Conditions, conditionType); IsSufficient)
|
||||
return true;
|
||||
else if (HasFailedConditions)
|
||||
hasFailedConditions = true;
|
||||
|
||||
for (NodeGroup const* group : node->Groups)
|
||||
{
|
||||
auto [IsSufficient, HasFailedConditions] = meetsConditions(entry.Conditions);
|
||||
auto [IsSufficient, HasFailedConditions] = meetsConditions(group->Conditions, conditionType);
|
||||
if (IsSufficient)
|
||||
return true;
|
||||
if (HasFailedConditions)
|
||||
hasFailedConditions = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto [IsSufficient, HasFailedConditions] = meetsConditions(node->Conditions); IsSufficient)
|
||||
return true;
|
||||
else if (HasFailedConditions)
|
||||
hasFailedConditions = true;
|
||||
for (NodeEntry const& entry : node->Entries)
|
||||
{
|
||||
if (entry.Data->ID == traitNodeEntryId)
|
||||
{
|
||||
auto [IsSufficient, HasFailedConditions] = meetsConditions(entry.Conditions, conditionType);
|
||||
if (IsSufficient)
|
||||
return true;
|
||||
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;
|
||||
};
|
||||
|
||||
return !hasFailedConditions;
|
||||
};
|
||||
return meetsConditionsOfType(TraitConditionType::Visible) && meetsConditionsOfType(TraitConditionType::Available);
|
||||
}
|
||||
|
||||
std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player)
|
||||
{
|
||||
@@ -647,7 +727,7 @@ std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits
|
||||
itr->GrantedRanks = entry.Data->MaxRanks;
|
||||
};
|
||||
|
||||
Optional<std::map<int32, int32>> cachedCurrencies;
|
||||
Optional<std::map<int32, SpentCurrency>> cachedCurrencies;
|
||||
|
||||
for (Tree const* tree : *trees)
|
||||
{
|
||||
@@ -720,7 +800,7 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
|
||||
return std::ranges::all_of(node->Entries, nodeEntryMatches);
|
||||
};
|
||||
|
||||
Optional<std::map<int32, int32>> spentCurrencies;
|
||||
Optional<std::map<int32, SpentCurrency>> spentCurrencies;
|
||||
FillSpentCurrenciesMap(traitConfig.Entries, spentCurrencies.emplace());
|
||||
|
||||
auto isValidTraitEntry = [&](WorldPackets::Traits::TraitEntry const& traitEntry)
|
||||
@@ -821,16 +901,16 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
|
||||
std::map<int32, int32> grantedCurrencies;
|
||||
FillOwnedCurrenciesMap(traitConfig, player, grantedCurrencies);
|
||||
|
||||
for (auto [traitCurrencyId, spentAmount] : *spentCurrencies)
|
||||
for (auto const& [traitCurrencyId, spentAmount] : *spentCurrencies)
|
||||
{
|
||||
if (sTraitCurrencyStore.AssertEntry(traitCurrencyId)->GetType() != TraitCurrencyType::TraitSourced)
|
||||
continue;
|
||||
|
||||
if (!spentAmount)
|
||||
if (!spentAmount.Total)
|
||||
continue;
|
||||
|
||||
int32* grantedCount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, traitCurrencyId);
|
||||
if (!grantedCount || *grantedCount < spentAmount)
|
||||
if (!grantedCount || *grantedCount < spentAmount.Total)
|
||||
return LearnResult::NotEnoughTalentsInPrimaryTree;
|
||||
}
|
||||
|
||||
@@ -843,8 +923,8 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
|
||||
if (!grantedAmount)
|
||||
continue;
|
||||
|
||||
int32* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, traitCurrencyId);
|
||||
if (!spentAmount || *spentAmount != *grantedAmount)
|
||||
SpentCurrency* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, traitCurrencyId);
|
||||
if (!spentAmount || spentAmount->Total != *grantedAmount)
|
||||
return LearnResult::UnspentTalentPoints;
|
||||
}
|
||||
|
||||
@@ -859,8 +939,8 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, Playe
|
||||
if (!grantedAmount)
|
||||
continue;
|
||||
|
||||
int32* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, subTreeCurrency->ID);
|
||||
if (!spentAmount || *spentAmount != *grantedAmount)
|
||||
SpentCurrency* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, subTreeCurrency->ID);
|
||||
if (!spentAmount || spentAmount->Total != *grantedAmount)
|
||||
return LearnResult::UnspentTalentPoints;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,10 +77,16 @@ private:
|
||||
Player const* _player;
|
||||
};
|
||||
|
||||
struct SpentCurrency
|
||||
{
|
||||
int32 Total = 0;
|
||||
std::vector<std::pair<int32, int32>> ByGate;
|
||||
};
|
||||
|
||||
void Load();
|
||||
int32 GenerateNewTraitConfigId();
|
||||
TraitConfigType GetConfigTypeForTree(int32 traitTreeId);
|
||||
void FillSpentCurrenciesMap(std::vector<WorldPackets::Traits::TraitEntry> const& traitEntries, std::map<int32, int32>& cachedCurrencies);
|
||||
void FillSpentCurrenciesMap(std::vector<WorldPackets::Traits::TraitEntry> const& traitEntries, std::map<int32, SpentCurrency>& 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);
|
||||
|
||||
Reference in New Issue
Block a user