diff options
-rw-r--r-- | sql/base/characters_database.sql | 4 | ||||
-rw-r--r-- | sql/updates/characters/master/2024_08_04_00_characters.sql | 3 | ||||
-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 |
6 files changed, 72 insertions, 24 deletions
diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index 22dabc91602..44c876cc49a 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -1775,7 +1775,6 @@ CREATE TABLE `character_trait_entry` ( `traitNodeId` int NOT NULL, `traitNodeEntryId` int NOT NULL, `rank` int NOT NULL DEFAULT '0', - `grantedRanks` int NOT NULL DEFAULT '0', PRIMARY KEY (`guid`,`traitConfigId`,`traitNodeId`,`traitNodeEntryId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; @@ -3743,7 +3742,8 @@ INSERT INTO `updates` VALUES ('2024_04_12_00_characters.sql','043E023F998DA77170C9D2D0162CAA340290B215','ARCHIVED','2024-04-12 00:23:51',0), ('2024_04_28_00_characters.sql','F80F476704BE535B5DCB0BCEBDD56024FCFBBAA2','ARCHIVED','2024-04-28 19:26:58',0), ('2024_05_11_00_characters.sql','A65765D87C1BA181561A6517040DC1A3A8103B71','ARCHIVED','2024-05-11 03:06:52',0), -('2024_07_31_00_characters.sql','F7E7AE0B8077CB9A1EA0AE4F49693BB05A742AC3','RELEASED','2024-07-31 16:18:36',0); +('2024_07_31_00_characters.sql','F7E7AE0B8077CB9A1EA0AE4F49693BB05A742AC3','RELEASED','2024-07-31 16:18:36',0), +('2024_08_04_00_characters.sql','7D153C59998416E6EA1455086730A2321AD0F2A8','RELEASED','2024-08-04 17:58:59',0); /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/characters/master/2024_08_04_00_characters.sql b/sql/updates/characters/master/2024_08_04_00_characters.sql new file mode 100644 index 00000000000..1a350bcf9a1 --- /dev/null +++ b/sql/updates/characters/master/2024_08_04_00_characters.sql @@ -0,0 +1,3 @@ +DELETE FROM `character_trait_entry` WHERE `Rank`=0; + +ALTER TABLE `character_trait_entry` DROP `grantedRanks`; 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); } |