aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sql/base/characters_database.sql4
-rw-r--r--sql/updates/characters/master/2024_08_04_00_characters.sql3
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.cpp4
-rw-r--r--src/server/game/Entities/Player/Player.cpp38
-rw-r--r--src/server/game/Spells/TraitMgr.cpp45
-rw-r--r--src/server/game/Spells/TraitMgr.h2
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);
}