Core/Players: Fix character creation

This commit is contained in:
Shauren
2026-08-22 21:07:44 +02:00
parent 3548f9e6d4
commit fdcd7d00d8
6 changed files with 106 additions and 84 deletions
+69 -58
View File
@@ -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;
+10 -18
View File
@@ -932,18 +932,19 @@ struct ClassAvailability
uint8 MinActiveExpansionLevel = 0;
};
struct RaceClassAvailability
{
uint8 RaceID = 0;
std::vector<ClassAvailability> Classes;
};
struct RaceUnlockRequirement
{
uint8 Expansion;
uint32 AchievementId;
};
struct RaceClassAvailability
{
uint8 RaceID = 0;
RaceUnlockRequirement UnlockRequirement;
std::vector<ClassAvailability> 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<uint8, RaceUnlockRequirement> 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<RaceClassAvailability> const& GetClassExpansionRequirements() const { return _classExpansionRequirementStore; }
std::vector<RaceClassAvailability> 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<uint32, Trainer::Trainer> _trainers;
std::map<std::tuple<uint32, uint32, uint32>, uint32> _creatureDefaultTrainers;
std::unordered_map<uint8, RaceUnlockRequirement> _raceUnlockRequirementStore;
std::vector<RaceClassAvailability> _classExpansionRequirementStore;
std::vector<RaceClassAvailability> _raceClassRequirementStore;
RealmNameContainer _realmNameStore;
SceneTemplateContainer _sceneTemplateStore;
+1 -1
View File
@@ -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
+22 -7
View File
@@ -476,15 +476,30 @@ void WorldSession::HandleCharEnum(CharacterDatabaseQueryHolder const& holder)
while (result->NextRow() && charEnum.Characters.size() < MAX_CHARACTERS_PER_REALM);
}
for (std::pair<uint8 const, RaceUnlockRequirement> 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());
@@ -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;
@@ -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<ClassUnlock> ClassUnlocks;
};