Skip to content

Commit 66fce97

Browse files
committed
NPCBots: Implement bot rent cost. New config param: NpcBot.Cost.Rent. Config param renamed: NpcBot.Cost -> NpcBot.Cost.Hire.
1 parent b57e14f commit 66fce97

File tree

7 files changed

+113
-27
lines changed

7 files changed

+113
-27
lines changed

src/server/game/AI/NpcBots/bot_ai.cpp

+34-5
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,6 @@ static void ApplyBotPercentModFloatVar(float &var, float val, bool apply)
163163

164164
static uint16 __rand; //calculated for each bot separately once every updateAI tick
165165

166-
static std::set<uint32> BotCustomSpells;
167-
168166
bot_ai::bot_ai(Creature* creature) : CreatureAI(creature),
169167
_botData(const_cast<NpcBotData*>(BotDataMgr::SelectNpcBotData(creature->GetEntry()))),
170168
_botExtras(const_cast<NpcBotExtras*>(BotDataMgr::SelectNpcBotExtras(creature->GetEntry())))
@@ -275,6 +273,8 @@ bot_ai::bot_ai(Creature* creature) : CreatureAI(creature),
275273

276274
_wmoAreaUpdateTimer = 0;
277275

276+
_rentTimer = 0;
277+
278278
_contestedPvPTimer = 0;
279279
_groupUpdateTimer = BOT_GROUP_UPDATE_TIMER;
280280

@@ -346,8 +346,7 @@ const std::string& bot_ai::LocalizedNpcText(Player const* forPlayer, uint32 text
346346

347347
if (!unk_botstrings.contains(textId))
348348
{
349-
BOT_LOG_ERROR("entities.player", "NPCBots: bot text string #{} is not localized, at least for {}",
350-
textId, localeNames[loc]);
349+
BOT_LOG_ERROR("entities.player", "NPCBots: bot text string #{} is not localized, at least for {}", textId, localeNames[loc]);
351350

352351
std::ostringstream msg;
353352
msg << (loc == DEFAULT_LOCALE ? "<undefined string " : "<unlocalized string ") << textId << ">";
@@ -601,7 +600,10 @@ void bot_ai::ResetBotAI(uint8 resetType)
601600
if (resetType & BOTAI_RESET_MASK_RESET_MASTER)
602601
master = reinterpret_cast<Player*>(me);
603602
if (resetType & BOTAI_RESET_MASK_ABANDON_MASTER)
603+
{
604604
_ownerGuid = 0;
605+
_rentTimer = 0;
606+
}
605607
if (resetType == BOTAI_RESET_INIT || resetType == BOTAI_RESET_LOGOUT)
606608
{
607609
_checkOwershipTimer = (BotMgr::GetOwnershipExpireTime() && _botData->owner) ? (resetType == BOTAI_RESET_INIT) ? 1000 : CalculateOwnershipCheckTime() : 0;
@@ -7917,7 +7919,7 @@ bool bot_ai::OnGossipHello(Player* player, uint32 /*option*/)
79177919
{
79187920
if (IAmFree() && !IsWanderer())
79197921
{
7920-
uint32 cost = BotMgr::GetNpcBotCost(player->GetLevel(), _botclass);
7922+
uint32 cost = BotMgr::GetNpcBotCostHire(player->GetLevel(), _botclass);
79217923

79227924
int8 reason = 0;
79237925
if (me->HasAura(BERSERK))
@@ -7959,6 +7961,8 @@ bool bot_ai::OnGossipHello(Player* player, uint32 /*option*/)
79597961
message2 << LocalizedNpcText(player, BOT_TEXT_HIREOPTION_DEFAULT);
79607962
}
79617963

7964+
message1 << "\n(" << BotMgr::GetNpcBotCostStr(player->GetLevel(), _botclass) << ")";
7965+
79627966
if (!reason)
79637967
player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_TAXI, message2.str(), GOSSIP_SENDER_HIRE, GOSSIP_ACTION_INFO_DEF + 0, message1.str(), cost, false);
79647968
else
@@ -17942,6 +17946,26 @@ bool bot_ai::GlobalUpdate(uint32 diff)
1794217946
{
1794317947
_updateTimerEx2 = urand(2000, 4000);
1794417948

17949+
//Rent Collecting
17950+
if (_rentTimer >= RENT_COLLECT_TIMER && BotMgr::GetNpcBotCostRent() && !HasBotCommandState(BOT_COMMAND_UNBIND) && !IAmFree())
17951+
{
17952+
uint32 rent_money = 0;
17953+
while (_rentTimer >= RENT_COLLECT_TIMER)
17954+
{
17955+
rent_money += uint32(uint64(BotMgr::GetNpcBotCostRent()) * (RENT_COLLECT_TIMER / 1000) / (RENT_TIMER / 1000));
17956+
_rentTimer -= RENT_COLLECT_TIMER;
17957+
}
17958+
17959+
rent_money = std::max<uint32>(rent_money, 1);
17960+
if (!master->HasEnoughMoney(rent_money))
17961+
{
17962+
master->GetSession()->SendNotification(LocalizedNpcText(master, BOT_TEXT_HIREFAIL_COST).c_str());
17963+
master->GetBotMgr()->RemoveBot(me->GetGUID(), BOT_REMOVE_UNAFFORD);
17964+
return false;
17965+
}
17966+
master->ModifyMoney(-int32(rent_money));
17967+
}
17968+
1794517969
if (BotMgr::HideBotSpawns() && IAmFree() && !IsWanderer())
1794617970
{
1794717971
// !!bot may be out of world!!
@@ -18697,6 +18721,11 @@ void bot_ai::CommonTimers(uint32 diff)
1869718721

1869818722
if (IAmFree())
1869918723
UpdateReviveTimer(diff);
18724+
else
18725+
{
18726+
if (BotMgr::GetNpcBotCostRent() && me->IsInWorld() && !HasBotCommandState(BOT_COMMAND_UNBIND))
18727+
_rentTimer += diff;
18728+
}
1870018729

1870118730
if (me->IsInWorld())
1870218731
{

src/server/game/AI/NpcBots/bot_ai.h

+1
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,7 @@ class bot_ai : public CreatureAI
713713
uint32 lastdiff, checkAurasTimer, checkMasterTimer, roleTimer, ordersTimer, regenTimer, _updateTimerMedium, _updateTimerEx1, _updateTimerEx2;
714714
uint32 _checkOwershipTimer;
715715
uint32 _moveBehindTimer;
716+
uint32 _rentTimer;
716717
uint32 _wmoAreaUpdateTimer;
717718
uint32 waitTimer;
718719
uint32 itemsAutouseTimer;

src/server/game/AI/NpcBots/botcommon.h

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ enum BotCommonValues
5252
REVIVE_TIMER_SHORT = 60000, //1 Minute
5353
INOUTDOORS_ENSURE_TIMER = 1500,
5454
BOT_GROUP_UPDATE_TIMER = 2000,
55+
RENT_TIMER = 3600000, //1 Hour
56+
RENT_COLLECT_TIMER = 600000, //10 Minutes
5557
//VEHICLE CREATURES
5658
CREATURE_NEXUS_SKYTALON_1 = 32535, // [Q] Aces High
5759
CREATURE_EOE_SKYTALON_N = 30161, // Eye of Eternity

src/server/game/AI/NpcBots/botgiver.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ class script_bot_giver : public CreatureScript
178178

179179
uint8 botclass = action - GOSSIP_ACTION_INFO_DEF;
180180

181-
uint32 cost = BotMgr::GetNpcBotCost(player->GetLevel(), botclass);
181+
uint32 cost = BotMgr::GetNpcBotCostHire(player->GetLevel(), botclass);
182182
if (!player->HasEnoughMoney(cost))
183183
{
184184
WhisperTo(player, bot_ai::LocalizedNpcText(player, BOT_TEXT_HIREFAIL_COST).c_str());

src/server/game/AI/NpcBots/botmgr.cpp

+57-14
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ uint8 _npcBotOwnerExpireMode;
6767
int32 _botInfoPacketsLimit;
6868
uint32 _gearBankCapacity;
6969
uint32 _gearBankEquipmentSetsCount;
70-
uint32 _npcBotsCost;
70+
uint32 _npcBotsCostHire;
71+
uint32 _npcBotsCostRent;
7172
uint32 _npcBotUpdateDelayBase;
7273
uint32 _npcBotEngageDelayDPS_default;
7374
uint32 _npcBotEngageDelayHeal_default;
@@ -281,6 +282,7 @@ void AddNpcBotScripts()
281282
BotMgr::BotMgr(Player* const master) : _owner(master), _dpstracker(new DPSTracker())
282283
{
283284
_quickrecall = false;
285+
_update_lock = false;
284286
_data = nullptr;
285287
}
286288
BotMgr::~BotMgr()
@@ -384,7 +386,8 @@ void BotMgr::LoadConfig(bool reload)
384386
_limitNpcBotsRaids = sConfigMgr->GetBoolDefault("NpcBot.Limit.Raid", true);
385387
_hideSpawns = sConfigMgr->GetBoolDefault("NpcBot.HideSpawns", false);
386388
_botInfoPacketsLimit = sConfigMgr->GetIntDefault("NpcBot.InfoPacketsLimit", -1);
387-
_npcBotsCost = sConfigMgr->GetIntDefault("NpcBot.Cost", 1000000);
389+
_npcBotsCostHire = sConfigMgr->GetIntDefault("NpcBot.Cost.Hire", 1000000);
390+
_npcBotsCostRent = sConfigMgr->GetIntDefault("NpcBot.Cost.Rent", 0);
388391
_npcBotUpdateDelayBase = sConfigMgr->GetIntDefault("NpcBot.UpdateDelay.Base", 0);
389392
_npcBotEngageDelayDPS_default = sConfigMgr->GetIntDefault("NpcBot.EngageDelay.DPS", 0);
390393
_npcBotEngageDelayHeal_default = sConfigMgr->GetIntDefault("NpcBot.EngageDelay.Heal", 0);
@@ -1190,6 +1193,12 @@ bool BotMgr::IsWanderingWorldBot(Creature const* bot)
11901193

11911194
void BotMgr::Update(uint32 diff)
11921195
{
1196+
while (!_delayedRemoveList.empty())
1197+
{
1198+
decltype(_delayedRemoveList)::iterator itr = _delayedRemoveList.begin();
1199+
RemoveBot(itr->first, itr->second);
1200+
}
1201+
11931202
//remove temp bots from bot map before updating it
11941203
while (!_removeList.empty())
11951204
{
@@ -1217,6 +1226,8 @@ void BotMgr::Update(uint32 diff)
12171226
if (partyCombat)
12181227
bot_ai::CalculateAoeSpots(_owner, _aoespots);
12191228

1229+
_update_lock = true;
1230+
12201231
for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr)
12211232
{
12221233
//guid = itr->first;
@@ -1264,6 +1275,8 @@ void BotMgr::Update(uint32 diff)
12641275
ai->canUpdate = false;
12651276
}
12661277

1278+
_update_lock = false;
1279+
12671280
if (_quickrecall)
12681281
{
12691282
_quickrecall = false;
@@ -1805,6 +1818,14 @@ void BotMgr::RemoveAllBots(uint8 removetype)
18051818
//Bot is being abandoned by player
18061819
void BotMgr::RemoveBot(ObjectGuid guid, uint8 removetype)
18071820
{
1821+
if (_update_lock)
1822+
{
1823+
_delayedRemoveList.emplace_back(guid, BotRemoveType(removetype));
1824+
return;
1825+
}
1826+
else if (!_delayedRemoveList.empty())
1827+
_delayedRemoveList.remove_if([=](decltype(_delayedRemoveList)::value_type const& p) { return p.first == guid; });
1828+
18081829
BotMap::const_iterator itr = _bots.find(guid);
18091830
ASSERT(itr != _bots.end(), "Trying to remove bot which does not belong to this botmgr(a)!!");
18101831
//ASSERT(_owner->IsInWorld(), "Trying to remove bot while not in world(a)!!");
@@ -1843,16 +1864,16 @@ void BotMgr::RemoveBot(ObjectGuid guid, uint8 removetype)
18431864
BotAIResetType resetType;
18441865
switch (removetype)
18451866
{
1846-
case BOT_REMOVE_DISMISS: resetType = BOTAI_RESET_DISMISS; break;
1847-
case BOT_REMOVE_UNBIND: resetType = BOTAI_RESET_UNBIND; break;
1848-
default: resetType = BOTAI_RESET_LOGOUT; break;
1867+
case BOT_REMOVE_DISMISS: case BOT_REMOVE_UNAFFORD: resetType = BOTAI_RESET_DISMISS; break;
1868+
case BOT_REMOVE_UNBIND: resetType = BOTAI_RESET_UNBIND; break;
1869+
default: resetType = BOTAI_RESET_LOGOUT; break;
18491870
}
18501871
bot->GetBotAI()->ResetBotAI(resetType);
18511872

18521873
bot->SetFaction(bot->GetCreatureTemplate()->faction);
18531874
bot->SetLevel(bot->GetCreatureTemplate()->minlevel);
18541875

1855-
if (removetype == BOT_REMOVE_DISMISS)
1876+
if (resetType == BOTAI_RESET_DISMISS)
18561877
{
18571878
BotDataMgr::ResetNpcBotTransmogData(bot->GetEntry(), false);
18581879
uint32 newOwner = 0;
@@ -1925,7 +1946,7 @@ BotAddResult BotMgr::AddBot(Creature* bot)
19251946
//}
19261947
if (!owned)
19271948
{
1928-
uint32 cost = GetNpcBotCost(_owner->GetLevel(), bot->GetBotClass());
1949+
uint32 cost = GetNpcBotCostHire(_owner->GetLevel(), bot->GetBotClass());
19291950
if (!_owner->HasEnoughMoney(cost))
19301951
{
19311952
ChatHandler ch(_owner->GetSession());
@@ -2067,7 +2088,12 @@ bool BotMgr::RemoveAllBotsFromGroup()
20672088
return true;
20682089
}
20692090

2070-
uint32 BotMgr::GetNpcBotCost(uint8 level, uint8 botclass)
2091+
uint32 BotMgr::GetNpcBotCostRent()
2092+
{
2093+
return _npcBotsCostRent;
2094+
}
2095+
2096+
uint32 BotMgr::GetNpcBotCostHire(uint8 level, uint8 botclass)
20712097
{
20722098
//assuming default 1000000
20732099
//level 1: 500 //5 silver
@@ -2078,11 +2104,11 @@ uint32 BotMgr::GetNpcBotCost(uint8 level, uint8 botclass)
20782104
//rest is linear
20792105
//rare / rareelite bots have their cost adjusted
20802106
uint32 cost =
2081-
level < 10 ? _npcBotsCost / 2000 : //5 silver
2082-
level < 20 ? _npcBotsCost / 100 : //1 gold
2083-
level < 30 ? _npcBotsCost / 20 : //5 gold
2084-
level < 40 ? _npcBotsCost / 5 : //20 gold
2085-
(_npcBotsCost * (level - (level % 10))) / DEFAULT_MAX_LEVEL; //50 - 100 gold
2107+
level < 10 ? _npcBotsCostHire / 2000 : //5 silver
2108+
level < 20 ? _npcBotsCostHire / 100 : //1 gold
2109+
level < 30 ? _npcBotsCostHire / 20 : //5 gold
2110+
level < 40 ? _npcBotsCostHire / 5 : //20 gold
2111+
(_npcBotsCostHire * (level - (level % 10))) / DEFAULT_MAX_LEVEL; //50 - 100 gold
20862112

20872113
switch (botclass)
20882114
{
@@ -2110,7 +2136,7 @@ std::string BotMgr::GetNpcBotCostStr(uint8 level, uint8 botclass)
21102136
{
21112137
std::ostringstream money;
21122138

2113-
if (uint32 cost = GetNpcBotCost(level, botclass))
2139+
if (uint32 cost = GetNpcBotCostHire(level, botclass))
21142140
{
21152141
uint32 gold = uint32(cost / GOLD);
21162142
cost -= (gold * GOLD);
@@ -2125,6 +2151,23 @@ std::string BotMgr::GetNpcBotCostStr(uint8 level, uint8 botclass)
21252151
money << cost << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t";
21262152
}
21272153

2154+
if (uint32 rcost = GetNpcBotCostRent())
2155+
{
2156+
uint32 gold = uint32(rcost / GOLD);
2157+
rcost -= (gold * GOLD);
2158+
uint32 silver = uint32(rcost / SILVER);
2159+
rcost -= (silver * SILVER);
2160+
2161+
money << " + |TInterface\\Icons\\INV_Misc_PocketWatch_01:16|t";
2162+
2163+
if (gold != 0)
2164+
money << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t";
2165+
if (silver != 0)
2166+
money << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t";
2167+
if (rcost)
2168+
money << rcost << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t";
2169+
}
2170+
21282171
return money.str();
21292172
}
21302173

src/server/game/AI/NpcBots/botmgr.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ enum BotRemoveType
6767
BOT_REMOVE_DISMISS = 1,
6868
BOT_REMOVE_UNSUMMON = 2,
6969
BOT_REMOVE_UNBIND = 3,
70+
BOT_REMOVE_UNAFFORD = 4,
7071
BOT_REMOVE_BY_DEFAULT = BOT_REMOVE_LOGOUT
7172
};
7273

@@ -261,7 +262,8 @@ class TC_GAME_API BotMgr
261262
bool HasBotPetType(uint32 petType) const;
262263
bool IsBeingResurrected(WorldObject const* corpse) const;
263264

264-
static uint32 GetNpcBotCost(uint8 level, uint8 botclass);
265+
static uint32 GetNpcBotCostRent();
266+
static uint32 GetNpcBotCostHire(uint8 level, uint8 botclass);
265267
static std::string GetNpcBotCostStr(uint8 level, uint8 botclass);
266268
static uint8 BotClassByClassName(std::string const& className);
267269
static uint8 GetBotPlayerClass(uint8 bot_class);
@@ -368,10 +370,12 @@ class TC_GAME_API BotMgr
368370
Player* const _owner;
369371
BotMap _bots;
370372
std::list<ObjectGuid> _removeList;
373+
std::list<std::pair<ObjectGuid, BotRemoveType>> _delayedRemoveList;
371374
DPSTracker* const _dpstracker;
372375
NpcBotMgrData* _data;
373376

374377
bool _quickrecall;
378+
bool _update_lock;
375379

376380
AoeSpotsVec _aoespots;
377381

src/server/worldserver/worldserver.conf.dist

+13-6
Original file line numberDiff line numberDiff line change
@@ -4416,12 +4416,19 @@ NpcBot.Limit.Raid = 1
44164416
NpcBot.HideSpawns = 1
44174417

44184418
#
4419-
# NpcBot.Cost
4420-
# Description: Bot recruitment cost (in copper).
4421-
# Note: This value is for level 80 characters, for lower levels cost is reduced.
4422-
# Default: 1000000 (100 gold)
4423-
4424-
NpcBot.Cost = 1000000
4419+
# NpcBot.Cost.Hire
4420+
# NpcBot.Cost.Rent
4421+
# Description: Bot recruitment cost (in copper). Hire cost is the amount player has to pay
4422+
# to recruit a bot. Rent cost is the amount player has to pay per 1 hour
4423+
# to keep a bot with them, bot is automatically removed if player doesn't have
4424+
# enough money to pay the rent cost.
4425+
# Note1: Hire cost is for level 80 characters, for lower levels cost is reduced greatly
4426+
# Note2: Rent is collected every 10 minutes so at least 6 copper per hour is taken
4427+
# Default: 1000000 - (NpcBot.Cost.Hire, 100 gold)
4428+
# 0 - (NpcBot.Cost.Rent)
4429+
4430+
NpcBot.Cost.Hire = 1000000
4431+
NpcBot.Cost.Rent = 0
44254432

44264433
#
44274434
# NpcBot.UpdateDelay.Base

0 commit comments

Comments
 (0)