diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 9f166b0e88..b9c9cea5b5 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -10529,55 +10529,10 @@ PlayerInfo const* ObjectMgr::GetPlayerInfo(uint32 race, uint32 class_) const void ObjectMgr::LoadRaceAndClassExpansionRequirements() { uint32 oldMSTime = getMSTime(); - _raceUnlockRequirementStore.clear(); - - // 0 1 2 - QueryResult result = WorldDatabase.Query("SELECT raceID, expansion, achievementId FROM `race_unlock_requirement`"); - - if (result) - { - do - { - Field* fields = result->Fetch(); - - uint8 raceID = fields[0].GetUInt8(); - uint8 expansion = fields[1].GetUInt8(); - uint32 achievementId = fields[2].GetUInt32(); - - ChrRacesEntry const* raceEntry = sChrRacesStore.LookupEntry(raceID); - if (!raceEntry) - { - TC_LOG_ERROR("sql.sql", "Race {} defined in `race_unlock_requirement` does not exists, skipped.", raceID); - continue; - } - - if (expansion >= MAX_ACCOUNT_EXPANSIONS) - { - TC_LOG_ERROR("sql.sql", "Race {} defined in `race_unlock_requirement` has incorrect expansion {}, skipped.", raceID, expansion); - continue; - } - - if (achievementId && !sAchievementStore.LookupEntry(achievementId)) - { - TC_LOG_ERROR("sql.sql", "Race {} defined in `race_unlock_requirement` has incorrect achievement {}, skipped.", raceID, achievementId); - continue; - } - - RaceUnlockRequirement& raceUnlockRequirement = _raceUnlockRequirementStore[raceID]; - raceUnlockRequirement.Expansion = expansion; - raceUnlockRequirement.AchievementId = achievementId; - } - while (result->NextRow()); - TC_LOG_INFO("server.loading", ">> Loaded {} race expansion requirements in {} ms.", _raceUnlockRequirementStore.size(), GetMSTimeDiffToNow(oldMSTime)); - } - else - TC_LOG_INFO("server.loading", ">> Loaded 0 race expansion requirements. DB table `race_expansion_requirement` is empty."); - - oldMSTime = getMSTime(); - _classExpansionRequirementStore.clear(); + _raceClassRequirementStore.clear(); // 0 1 2 3 - result = WorldDatabase.Query("SELECT ClassID, RaceID, ActiveExpansionLevel, AccountExpansionLevel FROM `class_expansion_requirement`"); + QueryResult result = WorldDatabase.Query("SELECT ClassID, RaceID, ActiveExpansionLevel, AccountExpansionLevel FROM `class_expansion_requirement`"); if (result) { @@ -10633,7 +10588,7 @@ void ObjectMgr::LoadRaceAndClassExpansionRequirements() for (auto&& race : temp) { - RaceClassAvailability& raceClassAvailability = _classExpansionRequirementStore.emplace_back(); + RaceClassAvailability& raceClassAvailability = _raceClassRequirementStore.emplace_back(); raceClassAvailability.RaceID = race.first; @@ -10652,21 +10607,77 @@ void ObjectMgr::LoadRaceAndClassExpansionRequirements() } else TC_LOG_INFO("server.loading", ">> Loaded 0 class expansion requirements. DB table `class_expansion_requirement` is empty."); + + oldMSTime = getMSTime(); + + // 0 1 2 + result = WorldDatabase.Query("SELECT raceID, expansion, achievementId FROM `race_unlock_requirement`"); + + if (result) + { + uint32 loadedRows = 0; + do + { + Field* fields = result->Fetch(); + + uint8 raceID = fields[0].GetUInt8(); + uint8 expansion = fields[1].GetUInt8(); + uint32 achievementId = fields[2].GetUInt32(); + + ChrRacesEntry const* raceEntry = sChrRacesStore.LookupEntry(raceID); + if (!raceEntry) + { + TC_LOG_ERROR("sql.sql", "Race {} defined in `race_unlock_requirement` does not exists, skipped.", raceID); + continue; + } + + if (expansion >= MAX_ACCOUNT_EXPANSIONS) + { + TC_LOG_ERROR("sql.sql", "Race {} defined in `race_unlock_requirement` has incorrect expansion {}, skipped.", raceID, expansion); + continue; + } + + if (achievementId && !sAchievementStore.LookupEntry(achievementId)) + { + TC_LOG_ERROR("sql.sql", "Race {} defined in `race_unlock_requirement` has incorrect achievement {}, skipped.", raceID, achievementId); + continue; + } + + auto itr = std::ranges::find(_raceClassRequirementStore, raceID, &RaceClassAvailability::RaceID); + if (itr == _raceClassRequirementStore.end()) + { + itr = _raceClassRequirementStore.emplace(_raceClassRequirementStore.end()); + itr->RaceID = raceID; + } + + RaceUnlockRequirement& raceUnlockRequirement = itr->UnlockRequirement; + raceUnlockRequirement.Expansion = expansion; + raceUnlockRequirement.AchievementId = achievementId; + ++loadedRows; + } + while (result->NextRow()); + TC_LOG_INFO("server.loading", ">> Loaded {} race expansion requirements in {} ms.", loadedRows, GetMSTimeDiffToNow(oldMSTime)); + } + else + TC_LOG_INFO("server.loading", ">> Loaded 0 race expansion requirements. DB table `race_expansion_requirement` is empty."); +} + +RaceUnlockRequirement const* ObjectMgr::GetRaceUnlockRequirement(uint8 raceId) const +{ + auto raceItr = std::ranges::find(_raceClassRequirementStore, raceId, &RaceClassAvailability::RaceID); + if (raceItr == _raceClassRequirementStore.end()) + return nullptr; + + return &raceItr->UnlockRequirement; } ClassAvailability const* ObjectMgr::GetClassExpansionRequirement(uint8 raceId, uint8 classId) const { - auto raceItr = std::find_if(_classExpansionRequirementStore.begin(), _classExpansionRequirementStore.end(), [raceId](RaceClassAvailability const& raceClass) - { - return raceClass.RaceID == raceId; - }); - if (raceItr == _classExpansionRequirementStore.end()) + auto raceItr = std::ranges::find(_raceClassRequirementStore, raceId, &RaceClassAvailability::RaceID); + if (raceItr == _raceClassRequirementStore.end()) return nullptr; - auto classItr = std::find_if(raceItr->Classes.begin(), raceItr->Classes.end(), [classId](ClassAvailability const& classAvailability) - { - return classAvailability.ClassID == classId; - }); + auto classItr = std::ranges::find(raceItr->Classes, classId, &ClassAvailability::ClassID); if (classItr == raceItr->Classes.end()) return nullptr; @@ -10675,7 +10686,7 @@ ClassAvailability const* ObjectMgr::GetClassExpansionRequirement(uint8 raceId, u ClassAvailability const* ObjectMgr::GetClassExpansionRequirementFallback(uint8 classId) const { - for (RaceClassAvailability const& raceClassAvailability : _classExpansionRequirementStore) + for (RaceClassAvailability const& raceClassAvailability : _raceClassRequirementStore) for (ClassAvailability const& classAvailability : raceClassAvailability.Classes) if (classAvailability.ClassID == classId) return &classAvailability; diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 267e2f9a1b..8b1b78a8cd 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -932,18 +932,19 @@ struct ClassAvailability uint8 MinActiveExpansionLevel = 0; }; -struct RaceClassAvailability -{ - uint8 RaceID = 0; - std::vector Classes; -}; - struct RaceUnlockRequirement { uint8 Expansion; uint32 AchievementId; }; +struct RaceClassAvailability +{ + uint8 RaceID = 0; + RaceUnlockRequirement UnlockRequirement; + std::vector Classes; +}; + enum QueryDataGroup { QUERY_DATA_CREATURES = 0x01, @@ -1653,16 +1654,8 @@ class TC_GAME_API ObjectMgr std::string GetPhaseName(uint32 phaseId) const; - std::unordered_map const& GetRaceUnlockRequirements() const { return _raceUnlockRequirementStore; } - RaceUnlockRequirement const* GetRaceUnlockRequirement(uint8 race) const - { - auto itr = _raceUnlockRequirementStore.find(race); - if (itr != _raceUnlockRequirementStore.end()) - return &itr->second; - return nullptr; - } - - std::vector const& GetClassExpansionRequirements() const { return _classExpansionRequirementStore; } + std::vector const& GetRaceClassRequirements() const { return _raceClassRequirementStore; } + RaceUnlockRequirement const* GetRaceUnlockRequirement(uint8 raceId) const; ClassAvailability const* GetClassExpansionRequirement(uint8 raceId, uint8 classId) const; ClassAvailability const* GetClassExpansionRequirementFallback(uint8 classId) const; @@ -1861,8 +1854,7 @@ class TC_GAME_API ObjectMgr std::unordered_map _trainers; std::map, uint32> _creatureDefaultTrainers; - std::unordered_map _raceUnlockRequirementStore; - std::vector _classExpansionRequirementStore; + std::vector _raceClassRequirementStore; RealmNameContainer _realmNameStore; SceneTemplateContainer _sceneTemplateStore; diff --git a/src/server/game/Handlers/AuthHandler.cpp b/src/server/game/Handlers/AuthHandler.cpp index dbc8880af6..4df03dd7f4 100644 --- a/src/server/game/Handlers/AuthHandler.cpp +++ b/src/server/game/Handlers/AuthHandler.cpp @@ -54,7 +54,7 @@ void WorldSession::SendAuthResponse(uint32 code, bool queued, uint32 queuePos) for (auto&& templ : sCharacterTemplateDataStore->GetCharacterTemplates()) response.SuccessInfo->Templates.push_back(&templ.second); - response.SuccessInfo->AvailableClasses = &sObjectMgr->GetClassExpansionRequirements(); + response.SuccessInfo->AvailableClasses = &sObjectMgr->GetRaceClassRequirements(); // TEMPORARY - prevent creating characters in uncompletable zone // This has the side effect of disabling Exile's Reach choice clientside without actually forcing character templates diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index c55d37306c..518d9fac76 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -476,15 +476,30 @@ void WorldSession::HandleCharEnum(CharacterDatabaseQueryHolder const& holder) while (result->NextRow() && charEnum.Characters.size() < MAX_CHARACTERS_PER_REALM); } - for (std::pair const& requirement : sObjectMgr->GetRaceUnlockRequirements()) + for (RaceClassAvailability const& requirement : sObjectMgr->GetRaceClassRequirements()) { - WorldPackets::Character::EnumCharactersResult::RaceUnlock raceUnlock; - raceUnlock.RaceID = requirement.first; - raceUnlock.HasUnlockedLicense = GetAccountExpansion() >= requirement.second.Expansion; - raceUnlock.HasUnlockedAchievement = requirement.second.AchievementId != 0 + WorldPackets::Character::EnumCharactersResult::RaceUnlock& raceUnlock = charEnum.RaceUnlockData.emplace_back(); + raceUnlock.RaceID = requirement.RaceID; + raceUnlock.HasUnlockedLicense = GetAccountExpansion() >= requirement.UnlockRequirement.Expansion; + raceUnlock.HasUnlockedAchievement = requirement.UnlockRequirement.AchievementId != 0 && (sWorld->getBoolConfig(CONFIG_CHARACTER_CREATING_DISABLE_ALLIED_RACE_ACHIEVEMENT_REQUIREMENT) - /* || HasAccountAchievement(requirement.second.AchievementId)*/); - charEnum.RaceUnlockData.push_back(raceUnlock); + /* || HasAccountAchievement(requirement.UnlockRequirement.AchievementId)*/); + raceUnlock.HasEntitlement = true; + + for (ClassAvailability const& classRequirement : requirement.Classes) + { + WorldPackets::Character::EnumCharactersResult::ClassUnlock& classUnlock = raceUnlock.ClassUnlocks.emplace_back(); + classUnlock.ClassID = classRequirement.ClassID; + //classUnlock.AchievementID = classRequirement.AchievementId; + classUnlock.HasExpansion = GetAccountExpansion() >= classRequirement.AccountExpansionLevel && GetExpansion() >= classRequirement.ActiveExpansionLevel; + classUnlock.HasUnlockedAchievement = true/*classRequirement.AchievementId == 0 || HasAccountAchievement(classRequirement.AchievementId)*/; + classUnlock.HasEntitlement = true; + } + + raceUnlock.DoesNotHaveAvailableClasses = std::ranges::none_of(raceUnlock.ClassUnlocks, [](WorldPackets::Character::EnumCharactersResult::ClassUnlock const& classUnlock) + { + return classUnlock.HasExpansion && classUnlock.HasUnlockedAchievement && classUnlock.HasEntitlement; + }); } SendPacket(charEnum.Write()); diff --git a/src/server/game/Server/Packets/CharacterPackets.cpp b/src/server/game/Server/Packets/CharacterPackets.cpp index 679eba5bf6..309b4fef91 100644 --- a/src/server/game/Server/Packets/CharacterPackets.cpp +++ b/src/server/game/Server/Packets/CharacterPackets.cpp @@ -342,8 +342,10 @@ ByteBuffer& operator<<(ByteBuffer& data, EnumCharactersResult::RaceUnlock const& data << Bits<1>(raceUnlock.HasUnlockedLicense); data << Bits<1>(raceUnlock.HasUnlockedAchievement); data << Bits<1>(raceUnlock.HasHeritageArmorUnlockAchievement); + data << Bits<1>(raceUnlock.HasEntitlement); data << Bits<1>(raceUnlock.HideRaceOnClient); data << Bits<1>(raceUnlock.FactionBalanceDisabled); + data << Bits<1>(raceUnlock.DoesNotHaveAvailableClasses); data.FlushBits(); return data; diff --git a/src/server/game/Server/Packets/CharacterPackets.h b/src/server/game/Server/Packets/CharacterPackets.h index 887fb6f3ad..9806bb00bd 100644 --- a/src/server/game/Server/Packets/CharacterPackets.h +++ b/src/server/game/Server/Packets/CharacterPackets.h @@ -248,8 +248,10 @@ namespace WorldPackets bool HasUnlockedLicense = false; bool HasUnlockedAchievement = false; bool HasHeritageArmorUnlockAchievement = false; + bool HasEntitlement = false; bool HideRaceOnClient = false; bool FactionBalanceDisabled = false; + bool DoesNotHaveAvailableClasses = false; std::vector ClassUnlocks; };