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
15960Player* 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
0 commit comments