Skip to content

Commit 26a135a

Browse files
authored
Rewrite RandomPlayerbotFactory for improved maintainability and future updates (mod-playerbots#1758)
* Rewrite RandomPlayerbotFactory - rewrite constructor and utility methods, simplify checks and logic * Update the comment to clarify the original logic for race selection * Remove magic numbers from CombineRaceAndGender method (gender) * Add checks for races and classes disabled during random bot creation
1 parent 983a55d commit 26a135a

2 files changed

Lines changed: 73 additions & 168 deletions

File tree

src/RandomPlayerbotFactory.cpp

Lines changed: 70 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -19,188 +19,99 @@
1919
#include "Log.h"
2020
#include "GuildMgr.h"
2121

22-
std::map<uint8, std::vector<uint8>> RandomPlayerbotFactory::availableRaces;
23-
24-
constexpr RandomPlayerbotFactory::NameRaceAndGender RandomPlayerbotFactory::CombineRaceAndGender(uint8 gender,
25-
uint8 race)
22+
constexpr RandomPlayerbotFactory::NameRaceAndGender RandomPlayerbotFactory::CombineRaceAndGender(uint8 race,
23+
uint8 gender)
2624
{
25+
NameRaceAndGender baseIndex;
2726
switch (race)
2827
{
28+
case RACE_ORC: baseIndex = NameRaceAndGender::OrcMale; break;
29+
case RACE_DWARF: baseIndex = NameRaceAndGender::DwarfMale; break;
30+
case RACE_NIGHTELF: baseIndex = NameRaceAndGender::NightelfMale; break;
31+
case RACE_TAUREN: baseIndex = NameRaceAndGender::TaurenMale; break;
32+
case RACE_GNOME: baseIndex = NameRaceAndGender::GnomeMale; break;
33+
case RACE_TROLL: baseIndex = NameRaceAndGender::TrollMale; break;
34+
case RACE_BLOODELF: baseIndex = NameRaceAndGender::BloodelfMale; break;
35+
case RACE_DRAENEI: baseIndex = NameRaceAndGender::DraeneiMale; break;
2936
case RACE_HUMAN:
30-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::GenericMale) + gender);
31-
case RACE_ORC:
32-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::OrcMale) + gender);
33-
case RACE_DWARF:
34-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::DwarfMale) + gender);
35-
case RACE_NIGHTELF:
36-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::NightelfMale) + gender);
3737
case RACE_UNDEAD_PLAYER:
38-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::GenericMale) + gender);
39-
case RACE_TAUREN:
40-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::TaurenMale) + gender);
41-
case RACE_GNOME:
42-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::GnomeMale) + gender);
43-
case RACE_TROLL:
44-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::TrollMale) + gender);
45-
case RACE_DRAENEI:
46-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::DraeneiMale) + gender);
47-
case RACE_BLOODELF:
48-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::BloodelfMale) + gender);
4938
default:
50-
LOG_ERROR("playerbots", "The race with ID %d does not have a naming category", race);
51-
return static_cast<NameRaceAndGender>(static_cast<uint8>(NameRaceAndGender::GenericMale) + gender);
39+
baseIndex = NameRaceAndGender::GenericMale;
40+
break;
5241
}
42+
43+
return static_cast<NameRaceAndGender>(static_cast<uint8>(baseIndex) + ((gender >= GENDER_NONE) ? GENDER_MALE : gender));
5344
}
5445

55-
RandomPlayerbotFactory::RandomPlayerbotFactory(uint32 accountId) : accountId(accountId)
46+
bool RandomPlayerbotFactory::IsValidRaceClassCombination(uint8 race, uint8 cls, uint32 expansion)
5647
{
57-
uint32 const expansion = sWorld->getIntConfig(CONFIG_EXPANSION);
58-
59-
availableRaces[CLASS_WARRIOR].push_back(RACE_HUMAN);
60-
availableRaces[CLASS_WARRIOR].push_back(RACE_NIGHTELF);
61-
availableRaces[CLASS_WARRIOR].push_back(RACE_GNOME);
62-
availableRaces[CLASS_WARRIOR].push_back(RACE_DWARF);
63-
availableRaces[CLASS_WARRIOR].push_back(RACE_ORC);
64-
availableRaces[CLASS_WARRIOR].push_back(RACE_UNDEAD_PLAYER);
65-
availableRaces[CLASS_WARRIOR].push_back(RACE_TAUREN);
66-
availableRaces[CLASS_WARRIOR].push_back(RACE_TROLL);
67-
if (expansion >= EXPANSION_THE_BURNING_CRUSADE)
68-
{
69-
availableRaces[CLASS_WARRIOR].push_back(RACE_DRAENEI);
70-
}
71-
72-
availableRaces[CLASS_PALADIN].push_back(RACE_HUMAN);
73-
availableRaces[CLASS_PALADIN].push_back(RACE_DWARF);
74-
if (expansion >= EXPANSION_THE_BURNING_CRUSADE)
75-
{
76-
availableRaces[CLASS_PALADIN].push_back(RACE_DRAENEI);
77-
availableRaces[CLASS_PALADIN].push_back(RACE_BLOODELF);
78-
}
79-
80-
availableRaces[CLASS_ROGUE].push_back(RACE_HUMAN);
81-
availableRaces[CLASS_ROGUE].push_back(RACE_DWARF);
82-
availableRaces[CLASS_ROGUE].push_back(RACE_NIGHTELF);
83-
availableRaces[CLASS_ROGUE].push_back(RACE_GNOME);
84-
availableRaces[CLASS_ROGUE].push_back(RACE_ORC);
85-
availableRaces[CLASS_ROGUE].push_back(RACE_UNDEAD_PLAYER);
86-
availableRaces[CLASS_ROGUE].push_back(RACE_TROLL);
87-
if (expansion >= EXPANSION_THE_BURNING_CRUSADE)
88-
{
89-
availableRaces[CLASS_ROGUE].push_back(RACE_BLOODELF);
90-
}
91-
92-
availableRaces[CLASS_PRIEST].push_back(RACE_HUMAN);
93-
availableRaces[CLASS_PRIEST].push_back(RACE_DWARF);
94-
availableRaces[CLASS_PRIEST].push_back(RACE_NIGHTELF);
95-
availableRaces[CLASS_PRIEST].push_back(RACE_TROLL);
96-
availableRaces[CLASS_PRIEST].push_back(RACE_UNDEAD_PLAYER);
97-
if (expansion >= EXPANSION_THE_BURNING_CRUSADE)
98-
{
99-
availableRaces[CLASS_PRIEST].push_back(RACE_DRAENEI);
100-
availableRaces[CLASS_PRIEST].push_back(RACE_BLOODELF);
101-
}
102-
103-
availableRaces[CLASS_MAGE].push_back(RACE_HUMAN);
104-
availableRaces[CLASS_MAGE].push_back(RACE_GNOME);
105-
availableRaces[CLASS_MAGE].push_back(RACE_UNDEAD_PLAYER);
106-
availableRaces[CLASS_MAGE].push_back(RACE_TROLL);
107-
if (expansion >= EXPANSION_THE_BURNING_CRUSADE)
108-
{
109-
availableRaces[CLASS_MAGE].push_back(RACE_DRAENEI);
110-
availableRaces[CLASS_MAGE].push_back(RACE_BLOODELF);
111-
}
112-
113-
availableRaces[CLASS_WARLOCK].push_back(RACE_HUMAN);
114-
availableRaces[CLASS_WARLOCK].push_back(RACE_GNOME);
115-
availableRaces[CLASS_WARLOCK].push_back(RACE_UNDEAD_PLAYER);
116-
availableRaces[CLASS_WARLOCK].push_back(RACE_ORC);
117-
if (expansion >= EXPANSION_THE_BURNING_CRUSADE)
118-
{
119-
availableRaces[CLASS_WARLOCK].push_back(RACE_BLOODELF);
120-
}
121-
122-
availableRaces[CLASS_SHAMAN].push_back(RACE_ORC);
123-
availableRaces[CLASS_SHAMAN].push_back(RACE_TAUREN);
124-
availableRaces[CLASS_SHAMAN].push_back(RACE_TROLL);
125-
if (expansion >= EXPANSION_THE_BURNING_CRUSADE)
126-
{
127-
availableRaces[CLASS_SHAMAN].push_back(RACE_DRAENEI);
128-
}
129-
130-
availableRaces[CLASS_HUNTER].push_back(RACE_DWARF);
131-
availableRaces[CLASS_HUNTER].push_back(RACE_NIGHTELF);
132-
availableRaces[CLASS_HUNTER].push_back(RACE_ORC);
133-
availableRaces[CLASS_HUNTER].push_back(RACE_TAUREN);
134-
availableRaces[CLASS_HUNTER].push_back(RACE_TROLL);
135-
if (expansion >= EXPANSION_THE_BURNING_CRUSADE)
136-
{
137-
availableRaces[CLASS_HUNTER].push_back(RACE_DRAENEI);
138-
availableRaces[CLASS_HUNTER].push_back(RACE_BLOODELF);
139-
}
48+
// skip expansion races if not playing with expansion
49+
if (expansion < EXPANSION_THE_BURNING_CRUSADE && (race == RACE_BLOODELF || race == RACE_DRAENEI))
50+
return false;
14051

141-
availableRaces[CLASS_DRUID].push_back(RACE_NIGHTELF);
142-
availableRaces[CLASS_DRUID].push_back(RACE_TAUREN);
52+
// skip expansion classes if not playing with expansion
53+
if (expansion < EXPANSION_WRATH_OF_THE_LICH_KING && cls == CLASS_DEATH_KNIGHT)
54+
return false;
14355

144-
if (expansion == EXPANSION_WRATH_OF_THE_LICH_KING)
145-
{
146-
availableRaces[CLASS_DEATH_KNIGHT].push_back(RACE_NIGHTELF);
147-
availableRaces[CLASS_DEATH_KNIGHT].push_back(RACE_TAUREN);
148-
availableRaces[CLASS_DEATH_KNIGHT].push_back(RACE_HUMAN);
149-
availableRaces[CLASS_DEATH_KNIGHT].push_back(RACE_ORC);
150-
availableRaces[CLASS_DEATH_KNIGHT].push_back(RACE_UNDEAD_PLAYER);
151-
availableRaces[CLASS_DEATH_KNIGHT].push_back(RACE_TROLL);
152-
availableRaces[CLASS_DEATH_KNIGHT].push_back(RACE_BLOODELF);
153-
availableRaces[CLASS_DEATH_KNIGHT].push_back(RACE_DRAENEI);
154-
availableRaces[CLASS_DEATH_KNIGHT].push_back(RACE_GNOME);
155-
availableRaces[CLASS_DEATH_KNIGHT].push_back(RACE_DWARF);
156-
}
56+
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(race, cls);
57+
return info != nullptr;
15758
}
15859

15960
Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls, std::unordered_map<NameRaceAndGender, std::vector<std::string>>& nameCache)
16061
{
161-
LOG_DEBUG("playerbots", "Creating new random bot for class {}", cls);
62+
LOG_DEBUG("playerbots", "Creating a new random bot for class: {}", cls);
63+
64+
const bool alliance = static_cast<bool>(urand(0, 1));
16265

163-
uint8 gender = rand() % 2 ? GENDER_MALE : GENDER_FEMALE;
164-
bool alliance = rand() % 2 ? true : false;
16566
std::vector<uint8> raceOptions;
166-
for (const auto& race : availableRaces[cls])
67+
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
16768
{
69+
// skip disabled with config races
70+
if ((1 << (race - 1)) & sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_DISABLED_RACEMASK))
71+
continue;
72+
73+
// Try to get 50/50 faction distribution for random bot population balance.
74+
// Without this check, races from the faction with more class options would dominate.
16875
if (alliance == IsAlliance(race))
16976
{
170-
raceOptions.push_back(race);
77+
if (IsValidRaceClassCombination(race, cls, sWorld->getIntConfig(CONFIG_EXPANSION)))
78+
raceOptions.push_back(race);
17179
}
17280
}
17381

17482
if (raceOptions.empty())
17583
{
176-
LOG_ERROR("playerbots", "No races available for class: {}", cls);
84+
LOG_ERROR("playerbots", "No races are available for class: {}", cls);
17785
return nullptr;
17886
}
17987

180-
uint8 race = raceOptions[urand(0, raceOptions.size() - 1)];
181-
182-
const auto raceAndGender = CombineRaceAndGender(gender, race);
88+
const uint8 race = raceOptions[urand(0, raceOptions.size() - 1)];
89+
const uint8 gender = urand(0, 1) ? GENDER_MALE : GENDER_FEMALE;
90+
const auto raceAndGender = CombineRaceAndGender(race, gender);
18391

18492
std::string name;
185-
if (nameCache.empty())
186-
{
187-
name = CreateRandomBotName(raceAndGender);
188-
}
189-
else
93+
if (!nameCache.empty())
19094
{
19195
if (nameCache[raceAndGender].empty())
19296
{
193-
LOG_ERROR("playerbots", "No name found for race and gender: {}", raceAndGender);
97+
LOG_ERROR("playerbots", "No names found for the specified race: {} and gender: {}",
98+
race, gender);
19499
return nullptr;
195100
}
101+
196102
uint32 i = urand(0, nameCache[raceAndGender].size() - 1);
197103
name = nameCache[raceAndGender][i];
198104
swap(nameCache[raceAndGender][i], nameCache[raceAndGender].back());
199105
nameCache[raceAndGender].pop_back();
200106
}
107+
else
108+
{
109+
name = CreateRandomBotName(raceAndGender);
110+
}
111+
201112
if (name.empty())
202113
{
203-
LOG_ERROR("playerbots", "Unable to get random bot name!");
114+
LOG_ERROR("playerbots", "Failed to get a valid random bot name");
204115
return nullptr;
205116
}
206117

@@ -246,19 +157,20 @@ Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls
246157
player->CleanupsBeforeDelete();
247158
delete player;
248159

249-
LOG_ERROR("playerbots", "Unable to create random bot for account {} - name: \"{}\"; race: {}; class: {}",
250-
accountId, name.c_str(), race, cls);
160+
LOG_ERROR("playerbots", "Unable to create random bot - name: \"{}\", race: {}, class: {}",
161+
name.c_str(), race, cls);
251162
return nullptr;
252163
}
253164

254165
player->setCinematic(2);
255166
player->SetAtLoginFlag(AT_LOGIN_NONE);
256167

257-
if (player->getClass() == CLASS_DEATH_KNIGHT)
168+
if (cls == CLASS_DEATH_KNIGHT)
258169
{
259170
player->learnSpell(50977, false);
260171
}
261-
LOG_DEBUG("playerbots", "Random bot created for account {} - name: \"{}\"; race: {}; class: {}", accountId,
172+
173+
LOG_DEBUG("playerbots", "Random bot created - name: \"{}\", race: {}, class: {}",
262174
name.c_str(), race, cls);
263175

264176
return player;
@@ -786,7 +698,7 @@ void RandomPlayerbotFactory::CreateRandomBots()
786698
}
787699

788700
LOG_DEBUG("playerbots", "Creating random bot characters for account: [{}/{}]", accountNumber + 1, totalAccountCount);
789-
RandomPlayerbotFactory factory(accountId);
701+
RandomPlayerbotFactory factory;
790702

791703
WorldSession* session = new WorldSession(accountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
792704
time_t(0), LOCALE_enUS, 0, false, false, 0, true);
@@ -798,29 +710,24 @@ void RandomPlayerbotFactory::CreateRandomBots()
798710
if (!((1 << (cls - 1)) & CLASSMASK_ALL_PLAYABLE) || !sChrClassesStore.LookupEntry(cls))
799711
continue;
800712

801-
if (bool const isClassDeathKnight = cls == CLASS_DEATH_KNIGHT;
802-
isClassDeathKnight && sWorld->getIntConfig(CONFIG_EXPANSION) != EXPANSION_WRATH_OF_THE_LICH_KING)
803-
{
713+
// skip disabled with config classes
714+
if ((1 << (cls - 1)) & sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_DISABLED_CLASSMASK))
804715
continue;
805-
}
806716

807-
if (cls != 10)
717+
Player* playerBot = factory.CreateRandomBot(session, cls, nameCache);
718+
if (!playerBot)
808719
{
809-
if (Player* playerBot = factory.CreateRandomBot(session, cls, nameCache))
810-
{
811-
playerBot->SaveToDB(true, false);
812-
sCharacterCache->AddCharacterCacheEntry(playerBot->GetGUID(), accountId, playerBot->GetName(),
813-
playerBot->getGender(), playerBot->getRace(),
814-
playerBot->getClass(), playerBot->GetLevel());
815-
playerBot->CleanupsBeforeDelete();
816-
delete playerBot;
817-
bot_creation++;
818-
}
819-
else
820-
{
821-
LOG_ERROR("playerbots", "Fail to create character for account {}", accountId);
822-
}
720+
LOG_ERROR("playerbots", "Fail to create character for account {}", accountId);
721+
continue;
823722
}
723+
724+
playerBot->SaveToDB(true, false);
725+
sCharacterCache->AddCharacterCacheEntry(playerBot->GetGUID(), accountId, playerBot->GetName(),
726+
playerBot->getGender(), playerBot->getRace(),
727+
playerBot->getClass(), playerBot->GetLevel());
728+
playerBot->CleanupsBeforeDelete();
729+
delete playerBot;
730+
bot_creation++;
824731
}
825732
}
826733

src/RandomPlayerbotFactory.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ class RandomPlayerbotFactory
4444
BloodelfFemale
4545
};
4646

47-
static constexpr NameRaceAndGender CombineRaceAndGender(uint8 gender, uint8 race);
47+
static constexpr NameRaceAndGender CombineRaceAndGender(uint8 race, uint8 gender);
4848

49-
RandomPlayerbotFactory(uint32 accountId);
49+
RandomPlayerbotFactory() {};
5050
virtual ~RandomPlayerbotFactory() {}
5151

5252
Player* CreateRandomBot(WorldSession* session, uint8 cls, std::unordered_map<NameRaceAndGender, std::vector<std::string>>& names);
@@ -58,11 +58,9 @@ class RandomPlayerbotFactory
5858
static uint32 CalculateAvailableCharsPerAccount();
5959

6060
private:
61+
static bool IsValidRaceClassCombination(uint8 race, uint8 class_, uint32 expansion);
6162
std::string const CreateRandomBotName(NameRaceAndGender raceAndGender);
6263
static std::string const CreateRandomArenaTeamName();
63-
64-
uint32 accountId;
65-
static std::map<uint8, std::vector<uint8>> availableRaces;
6664
};
6765

6866
#endif

0 commit comments

Comments
 (0)