diff options
author | Shauren <shauren.trinity@gmail.com> | 2024-08-04 18:24:10 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2024-08-04 18:24:10 +0200 |
commit | a7c8ac3505cb7e177b6f5299989d544a3b8cade9 (patch) | |
tree | 8cc8e91b4882a63d6cfdc48c63e52b33440fef81 /src | |
parent | b0b81dd31badd6e25f36a74bb519da9097e36ec1 (diff) |
Core/Players: Fixed trait configs getting into invalid state after talent tree changes between client patches
Closes #29398
Diffstat (limited to 'src')
-rw-r--r-- | src/server/database/Database/Implementation/CharacterDatabase.cpp | 4 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 38 | ||||
-rw-r--r-- | src/server/game/Spells/TraitMgr.cpp | 45 | ||||
-rw-r--r-- | src/server/game/Spells/TraitMgr.h | 2 |
4 files changed, 67 insertions, 22 deletions
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 147e8faa0d0..c1103239470 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -669,8 +669,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_CHAR_LIST_SLOT, "UPDATE characters SET slot = ? WHERE guid = ? AND account = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_CHAR_FISHINGSTEPS, "INSERT INTO character_fishingsteps (guid, fishingSteps) VALUES (?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHAR_TRAIT_ENTRIES, "SELECT traitConfigId, traitNodeId, traitNodeEntryId, `rank`, grantedRanks FROM character_trait_entry WHERE guid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_CHAR_TRAIT_ENTRIES, "INSERT INTO character_trait_entry (guid, traitConfigId, traitNodeId, traitNodeEntryId, `rank`, grantedRanks) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHAR_TRAIT_ENTRIES, "SELECT traitConfigId, traitNodeId, traitNodeEntryId, `rank` FROM character_trait_entry WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_TRAIT_ENTRIES, "INSERT INTO character_trait_entry (guid, traitConfigId, traitNodeId, traitNodeEntryId, `rank`) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES, "DELETE FROM character_trait_entry WHERE guid = ? AND traitConfigId = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES_BY_CHAR, "DELETE FROM character_trait_entry WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHAR_TRAIT_CONFIGS, "SELECT traitConfigId, type, chrSpecializationId, combatConfigFlags, localIdentifier, skillLineId, traitSystemId, `name` FROM character_trait_config WHERE guid = ?", CONNECTION_ASYNC); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 24b8522ee29..f539f903104 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -27780,11 +27780,11 @@ void Player::_LoadPvpTalents(PreparedQueryResult result) void Player::_LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult entriesResult) { - std::unordered_multimap<int32, WorldPackets::Traits::TraitEntry> traitEntriesByConfig; + std::unordered_map<int32, std::vector<WorldPackets::Traits::TraitEntry>> traitEntriesByConfig; if (entriesResult) { - // 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 { Field* fields = entriesResult->Fetch(); @@ -27792,12 +27792,11 @@ void Player::_LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult traitEntry.TraitNodeID = fields[1].GetInt32(); traitEntry.TraitNodeEntryID = fields[2].GetInt32(); traitEntry.Rank = fields[3].GetInt32(); - traitEntry.GrantedRanks = fields[4].GetInt32(); if (!TraitMgr::IsValidEntry(traitEntry)) continue; - traitEntriesByConfig.emplace(fields[0].GetInt32(), traitEntry); + traitEntriesByConfig[fields[0].GetInt32()].emplace_back(traitEntry); } while (entriesResult->NextRow()); } @@ -27831,10 +27830,28 @@ void Player::_LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult traitConfig.Name = fields[7].GetString(); - for (auto&& [_, traitEntry] : Trinity::Containers::MapEqualRange(traitEntriesByConfig, traitConfig.ID)) - traitConfig.Entries.emplace_back() = traitEntry; + for (UF::TraitEntry const& grantedEntry : TraitMgr::GetGrantedTraitEntriesForConfig(traitConfig, this)) + traitConfig.Entries.emplace_back(grantedEntry); - if (TraitMgr::ValidateConfig(traitConfig, this) != TraitMgr::LearnResult::Ok) + if (auto loadedEntriesNode = traitEntriesByConfig.extract(traitConfig.ID)) + { + for (WorldPackets::Traits::TraitEntry const& loadedEntry : loadedEntriesNode.mapped()) + { + auto itr = std::ranges::find_if(traitConfig.Entries, [&](WorldPackets::Traits::TraitEntry const& entry) + { + return entry.TraitNodeID == loadedEntry.TraitNodeID && entry.TraitNodeEntryID == loadedEntry.TraitNodeEntryID; + }); + if (itr == traitConfig.Entries.end()) + { + itr = traitConfig.Entries.emplace(traitConfig.Entries.end()); + itr->TraitNodeID = loadedEntry.TraitNodeID; + itr->TraitNodeEntryID = loadedEntry.TraitNodeEntryID; + } + itr->Rank = loadedEntry.Rank; + } + } + + if (TraitMgr::ValidateConfig(traitConfig, this, false, true) != TraitMgr::LearnResult::Ok) { traitConfig.Entries.clear(); for (UF::TraitEntry const& grantedEntry : TraitMgr::GetGrantedTraitEntriesForConfig(traitConfig, this)) @@ -27846,6 +27863,10 @@ void Player::_LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult } while (configsResult->NextRow()); } + // Remove orphaned trait entries from database + for (auto&& [traitConfigID, _] : traitEntriesByConfig) + m_traitConfigStates[traitConfigID] = PLAYERSPELL_REMOVED; + auto hasConfigForSpec = [&](int32 specId) { return m_activePlayerData->TraitConfigs.FindIndexIf([=](UF::TraitConfig const& traitConfig) @@ -28024,7 +28045,6 @@ void Player::_SaveTraits(CharacterDatabaseTransaction trans) stmt->setInt32(2, traitEntry.TraitNodeID); stmt->setInt32(3, traitEntry.TraitNodeEntryID); stmt->setInt32(4, traitEntry.Rank); - stmt->setInt32(5, traitEntry.GrantedRanks); trans->Append(stmt); } } diff --git a/src/server/game/Spells/TraitMgr.cpp b/src/server/game/Spells/TraitMgr.cpp index 3f0e73c7679..32b8c179545 100644 --- a/src/server/game/Spells/TraitMgr.cpp +++ b/src/server/game/Spells/TraitMgr.cpp @@ -493,21 +493,23 @@ std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits if (!trees) return entries; - auto getOrCreateEntry = [&entries](int32 nodeId, int32 entryId) + auto addGrantedRankToEntry = [&entries](int32 nodeId, NodeEntry const& entry, int32 grantedRanks) { auto itr = std::ranges::find_if(entries, [&](UF::TraitEntry const& traitEntry) { - return traitEntry.TraitNodeID == nodeId && traitEntry.TraitNodeEntryID == entryId; + return traitEntry.TraitNodeID == nodeId && traitEntry.TraitNodeEntryID == int32(entry.Data->ID); }); if (itr == entries.end()) { itr = entries.emplace(entries.end()); itr->TraitNodeID = nodeId; - itr->TraitNodeEntryID = entryId; + itr->TraitNodeEntryID = int32(entry.Data->ID); itr->Rank = 0; itr->GrantedRanks = 0; } - return &*itr; + itr->GrantedRanks += grantedRanks; + if (itr->GrantedRanks > entry.Data->MaxRanks) + itr->GrantedRanks = entry.Data->MaxRanks; }; Optional<std::map<int32, int32>> cachedCurrencies; @@ -519,18 +521,18 @@ std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits for (NodeEntry const& entry : node->Entries) for (TraitCondEntry const* condition : entry.Conditions) if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies)) - getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks; + addGrantedRankToEntry(node->Data->ID, entry, condition->GrantedRanks); for (TraitCondEntry const* condition : node->Conditions) if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies)) for (NodeEntry const& entry : node->Entries) - getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks; + addGrantedRankToEntry(node->Data->ID, entry, condition->GrantedRanks); for (NodeGroup const* group : node->Groups) for (TraitCondEntry const* condition : group->Conditions) if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies)) for (NodeEntry const& entry : node->Entries) - getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks; + addGrantedRankToEntry(node->Data->ID, entry, condition->GrantedRanks); } } @@ -553,7 +555,7 @@ bool IsValidEntry(WorldPackets::Traits::TraitEntry const& traitEntry) return true; } -LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig const& traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies /*= false*/) +LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies /*= false*/, bool removeInvalidEntries /*= false*/) { auto getNodeEntryCount = [&](int32 traitNodeId) { @@ -603,13 +605,13 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig const& traitConfig, return !hasConditions; }; - for (WorldPackets::Traits::TraitEntry const& traitEntry : traitConfig.Entries) + auto isValidTraitEntry = [&](WorldPackets::Traits::TraitEntry const& traitEntry) { if (!IsValidEntry(traitEntry)) return LearnResult::Unknown; Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID); - if (node->Data->GetType() == TraitNodeType::Selection) + if (node->Data->GetType() == TraitNodeType::Selection || node->Data->GetType() == TraitNodeType::SubTreeSelection) if (getNodeEntryCount(traitEntry.TraitNodeID) != 1) return LearnResult::Unknown; @@ -643,6 +645,29 @@ LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig const& traitConfig, if (!hasAnyParentTrait) return LearnResult::NotEnoughTalentsInPrimaryTree; } + + return LearnResult::Ok; + }; + + for (auto itr = traitConfig.Entries.begin(); itr != traitConfig.Entries.end(); ) + { + LearnResult result = isValidTraitEntry(*itr); + if (result != LearnResult::Ok) + { + if (!removeInvalidEntries) + return result; + + 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); + else + itr->Rank = 0; + + // revalidate entire config - a removed entry will invalidate all other entries that depend on it + itr = traitConfig.Entries.begin(); + } + else + ++itr; } std::map<int32, int32> grantedCurrencies; diff --git a/src/server/game/Spells/TraitMgr.h b/src/server/game/Spells/TraitMgr.h index 951d4e39e90..611be5b49be 100644 --- a/src/server/game/Spells/TraitMgr.h +++ b/src/server/game/Spells/TraitMgr.h @@ -79,7 +79,7 @@ TraitConfigType GetConfigTypeForTree(int32 traitTreeId); void FillSpentCurrenciesMap(WorldPackets::Traits::TraitEntry const& entry, 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 const& traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies = false); +LearnResult ValidateConfig(WorldPackets::Traits::TraitConfig& traitConfig, PlayerDataAccessor player, bool requireSpendingAllCurrencies = false, bool removeInvalidEntries = false); std::vector<TraitDefinitionEffectPointsEntry const*> const* GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId); void InitializeStarterBuildTraitConfig(WorldPackets::Traits::TraitConfig& traitConfig, PlayerDataAccessor player); } |