Skip to content

Commit

Permalink
NPCBots: Implement bot rent cost. New config param: `NpcBot.Cost.Rent…
Browse files Browse the repository at this point in the history
…`. Config param renamed: `NpcBot.Cost` -> `NpcBot.Cost.Hire`.

(cherry picked from commit 66fce97251a1c1ab95f2ba3d6a98a69696ec4c1c)

# Conflicts:
#	src/server/worldserver/worldserver.conf.dist
  • Loading branch information
trickerer committed Dec 5, 2024
1 parent 22449ad commit 8ccb8f4
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 28 deletions.
19 changes: 13 additions & 6 deletions src/server/apps/worldserver/worldserver.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4704,12 +4704,19 @@ NpcBot.Limit.Raid = 1
NpcBot.HideSpawns = 1

#
# NpcBot.Cost
# Description: Bot recruitment cost (in copper).
# Note: This value is for level 80 characters, for lower levels cost is reduced.
# Default: 1000000 (100 gold)

NpcBot.Cost = 1000000
# NpcBot.Cost.Hire
# NpcBot.Cost.Rent
# Description: Bot recruitment cost (in copper). Hire cost is the amount player has to pay
# to recruit a bot. Rent cost is the amount player has to pay per 1 hour
# to keep a bot with them, bot is automatically removed if player doesn't have
# enough money to pay the rent cost.
# Note1: Hire cost is for level 80 characters, for lower levels cost is reduced greatly
# Note2: Rent is collected every 10 minutes so at least 6 copper per hour is taken
# Default: 1000000 - (NpcBot.Cost.Hire, 100 gold)
# 0 - (NpcBot.Cost.Rent)

NpcBot.Cost.Hire = 1000000
NpcBot.Cost.Rent = 0

#
# NpcBot.UpdateDelay.Base
Expand Down
39 changes: 34 additions & 5 deletions src/server/game/AI/NpcBots/bot_ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,6 @@ static void ApplyBotPercentModFloatVar(float &var, float val, bool apply)

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

static std::set<uint32> BotCustomSpells;

bot_ai::bot_ai(Creature* creature) : CreatureAI(creature),
_botData(const_cast<NpcBotData*>(BotDataMgr::SelectNpcBotData(creature->GetEntry()))),
_botExtras(const_cast<NpcBotExtras*>(BotDataMgr::SelectNpcBotExtras(creature->GetEntry())))
Expand Down Expand Up @@ -270,6 +268,8 @@ bot_ai::bot_ai(Creature* creature) : CreatureAI(creature),

_wmoAreaUpdateTimer = 0;

_rentTimer = 0;

_contestedPvPTimer = 0;
_groupUpdateTimer = BOT_GROUP_UPDATE_TIMER;

Expand Down Expand Up @@ -341,8 +341,7 @@ const std::string& bot_ai::LocalizedNpcText(Player const* forPlayer, uint32 text

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

std::ostringstream msg;
msg << (loc == DEFAULT_LOCALE ? "<undefined string " : "<unlocalized string ") << textId << ">";
Expand Down Expand Up @@ -596,7 +595,10 @@ void bot_ai::ResetBotAI(uint8 resetType)
if (resetType & BOTAI_RESET_MASK_RESET_MASTER)
master = reinterpret_cast<Player*>(me);
if (resetType & BOTAI_RESET_MASK_ABANDON_MASTER)
{
_ownerGuid = 0;
_rentTimer = 0;
}
if (resetType == BOTAI_RESET_INIT || resetType == BOTAI_RESET_LOGOUT)
{
_checkOwershipTimer = (BotMgr::GetOwnershipExpireTime() && _botData->owner) ? (resetType == BOTAI_RESET_INIT) ? 1000 : CalculateOwnershipCheckTime() : 0;
Expand Down Expand Up @@ -7931,7 +7933,7 @@ bool bot_ai::OnGossipHello(Player* player, uint32 /*option*/)
{
if (IAmFree() && !IsWanderer())
{
uint32 cost = BotMgr::GetNpcBotCost(player->GetLevel(), _botclass);
uint32 cost = BotMgr::GetNpcBotCostHire(player->GetLevel(), _botclass);

int8 reason = 0;
if (me->HasAura(BERSERK))
Expand Down Expand Up @@ -7973,6 +7975,8 @@ bool bot_ai::OnGossipHello(Player* player, uint32 /*option*/)
message2 << LocalizedNpcText(player, BOT_TEXT_HIREOPTION_DEFAULT);
}

message1 << "\n(" << BotMgr::GetNpcBotCostStr(player->GetLevel(), _botclass) << ")";

if (!reason)
player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_TAXI, message2.str(), GOSSIP_SENDER_HIRE, GOSSIP_ACTION_INFO_DEF + 0, message1.str(), cost, false);
else
Expand Down Expand Up @@ -17961,6 +17965,26 @@ bool bot_ai::GlobalUpdate(uint32 diff)
{
_updateTimerEx2 = urand(2000, 4000);

//Rent Collecting
if (_rentTimer >= RENT_COLLECT_TIMER && BotMgr::GetNpcBotCostRent() && !HasBotCommandState(BOT_COMMAND_UNBIND) && !IAmFree())
{
uint32 rent_money = 0;
while (_rentTimer >= RENT_COLLECT_TIMER)
{
rent_money += uint32(uint64(BotMgr::GetNpcBotCostRent()) * (RENT_COLLECT_TIMER / 1000) / (RENT_TIMER / 1000));
_rentTimer -= RENT_COLLECT_TIMER;
}

rent_money = std::max<uint32>(rent_money, 1);
if (!master->HasEnoughMoney(rent_money))
{
ChatHandler(master->GetSession()).SendNotification(LocalizedNpcText(master, BOT_TEXT_HIREFAIL_COST).c_str());
master->GetBotMgr()->RemoveBot(me->GetGUID(), BOT_REMOVE_UNAFFORD);
return false;
}
master->ModifyMoney(-int32(rent_money));
}

if (BotMgr::HideBotSpawns() && IAmFree() && !IsWanderer())
{
// !!bot may be out of world!!
Expand Down Expand Up @@ -18721,6 +18745,11 @@ void bot_ai::CommonTimers(uint32 diff)

if (IAmFree())
UpdateReviveTimer(diff);
else
{
if (BotMgr::GetNpcBotCostRent() && me->IsInWorld() && !HasBotCommandState(BOT_COMMAND_UNBIND))
_rentTimer += diff;
}

if (me->IsInWorld())
{
Expand Down
1 change: 1 addition & 0 deletions src/server/game/AI/NpcBots/bot_ai.h
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,7 @@ class bot_ai : public CreatureAI
uint32 lastdiff, checkAurasTimer, checkMasterTimer, roleTimer, ordersTimer, regenTimer, _updateTimerMedium, _updateTimerEx1, _updateTimerEx2;
uint32 _checkOwershipTimer;
uint32 _moveBehindTimer;
uint32 _rentTimer;
uint32 _wmoAreaUpdateTimer;
uint32 waitTimer;
uint32 itemsAutouseTimer;
Expand Down
2 changes: 1 addition & 1 deletion src/server/game/AI/NpcBots/botcommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3103,7 +3103,7 @@ class script_bot_commands : public CommandScript
}
if (owner->GetBotMgr()->GetBotsHidden())
{
handler->GetSession()->SendNotification("You can't do that while bots are hidden");
handler->SendNotification("You can't do that while bots are hidden");
handler->SetSentErrorMessage(true);
return false;
}
Expand Down
2 changes: 2 additions & 0 deletions src/server/game/AI/NpcBots/botcommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ enum BotCommonValues
REVIVE_TIMER_SHORT = 60000, //1 Minute
INOUTDOORS_ENSURE_TIMER = 1500,
BOT_GROUP_UPDATE_TIMER = 2000,
RENT_TIMER = 3600000, //1 Hour
RENT_COLLECT_TIMER = 600000, //10 Minutes
//VEHICLE CREATURES
CREATURE_NEXUS_SKYTALON_1 = 32535, // [Q] Aces High
CREATURE_EOE_SKYTALON_N = 30161, // Eye of Eternity
Expand Down
2 changes: 1 addition & 1 deletion src/server/game/AI/NpcBots/botgiver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class script_bot_giver : public CreatureScript

uint8 botclass = action - GOSSIP_ACTION_INFO_DEF;

uint32 cost = BotMgr::GetNpcBotCost(player->GetLevel(), botclass);
uint32 cost = BotMgr::GetNpcBotCostHire(player->GetLevel(), botclass);
if (!player->HasEnoughMoney(cost))
{
WhisperTo(player, me, bot_ai::LocalizedNpcText(player, BOT_TEXT_HIREFAIL_COST).c_str());
Expand Down
71 changes: 57 additions & 14 deletions src/server/game/AI/NpcBots/botmgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ uint8 _npcBotOwnerExpireMode;
int32 _botInfoPacketsLimit;
uint32 _gearBankCapacity;
uint32 _gearBankEquipmentSetsCount;
uint32 _npcBotsCost;
uint32 _npcBotsCostHire;
uint32 _npcBotsCostRent;
uint32 _npcBotUpdateDelayBase;
uint32 _npcBotEngageDelayDPS_default;
uint32 _npcBotEngageDelayHeal_default;
Expand Down Expand Up @@ -282,6 +283,7 @@ void AddNpcBotScripts()
BotMgr::BotMgr(Player* const master) : _owner(master), _dpstracker(new DPSTracker())
{
_quickrecall = false;
_update_lock = false;
_data = nullptr;
}
BotMgr::~BotMgr()
Expand Down Expand Up @@ -385,7 +387,8 @@ void BotMgr::LoadConfig(bool reload)
_limitNpcBotsRaids = sConfigMgr->GetBoolDefault("NpcBot.Limit.Raid", true);
_hideSpawns = sConfigMgr->GetBoolDefault("NpcBot.HideSpawns", false);
_botInfoPacketsLimit = sConfigMgr->GetIntDefault("NpcBot.InfoPacketsLimit", -1);
_npcBotsCost = sConfigMgr->GetIntDefault("NpcBot.Cost", 1000000);
_npcBotsCostHire = sConfigMgr->GetIntDefault("NpcBot.Cost.Hire", 1000000);
_npcBotsCostRent = sConfigMgr->GetIntDefault("NpcBot.Cost.Rent", 0);
_npcBotUpdateDelayBase = sConfigMgr->GetIntDefault("NpcBot.UpdateDelay.Base", 0);
_npcBotEngageDelayDPS_default = sConfigMgr->GetIntDefault("NpcBot.EngageDelay.DPS", 0);
_npcBotEngageDelayHeal_default = sConfigMgr->GetIntDefault("NpcBot.EngageDelay.Heal", 0);
Expand Down Expand Up @@ -1191,6 +1194,12 @@ bool BotMgr::IsWanderingWorldBot(Creature const* bot)

void BotMgr::Update(uint32 diff)
{
while (!_delayedRemoveList.empty())
{
decltype(_delayedRemoveList)::iterator itr = _delayedRemoveList.begin();
RemoveBot(itr->first, itr->second);
}

//remove temp bots from bot map before updating it
while (!_removeList.empty())
{
Expand Down Expand Up @@ -1218,6 +1227,8 @@ void BotMgr::Update(uint32 diff)
if (partyCombat)
bot_ai::CalculateAoeSpots(_owner, _aoespots);

_update_lock = true;

for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr)
{
//guid = itr->first;
Expand Down Expand Up @@ -1265,6 +1276,8 @@ void BotMgr::Update(uint32 diff)
ai->canUpdate = false;
}

_update_lock = false;

if (_quickrecall)
{
_quickrecall = false;
Expand Down Expand Up @@ -1806,6 +1819,14 @@ void BotMgr::RemoveAllBots(uint8 removetype)
//Bot is being abandoned by player
void BotMgr::RemoveBot(ObjectGuid guid, uint8 removetype)
{
if (_update_lock)
{
_delayedRemoveList.emplace_back(guid, BotRemoveType(removetype));
return;
}
else if (!_delayedRemoveList.empty())
_delayedRemoveList.remove_if([=](decltype(_delayedRemoveList)::value_type const& p) { return p.first == guid; });

BotMap::const_iterator itr = _bots.find(guid);
ASSERT(itr != _bots.end(), "Trying to remove bot which does not belong to this botmgr(a)!!");
//ASSERT(_owner->IsInWorld(), "Trying to remove bot while not in world(a)!!");
Expand Down Expand Up @@ -1844,16 +1865,16 @@ void BotMgr::RemoveBot(ObjectGuid guid, uint8 removetype)
BotAIResetType resetType;
switch (removetype)
{
case BOT_REMOVE_DISMISS: resetType = BOTAI_RESET_DISMISS; break;
case BOT_REMOVE_UNBIND: resetType = BOTAI_RESET_UNBIND; break;
default: resetType = BOTAI_RESET_LOGOUT; break;
case BOT_REMOVE_DISMISS: case BOT_REMOVE_UNAFFORD: resetType = BOTAI_RESET_DISMISS; break;
case BOT_REMOVE_UNBIND: resetType = BOTAI_RESET_UNBIND; break;
default: resetType = BOTAI_RESET_LOGOUT; break;
}
bot->GetBotAI()->ResetBotAI(resetType);

bot->SetFaction(bot->GetCreatureTemplate()->faction);
bot->SetLevel(bot->GetCreatureTemplate()->minlevel);

if (removetype == BOT_REMOVE_DISMISS)
if (resetType == BOTAI_RESET_DISMISS)
{
BotDataMgr::ResetNpcBotTransmogData(bot->GetEntry(), false);
uint32 newOwner = 0;
Expand Down Expand Up @@ -1926,7 +1947,7 @@ BotAddResult BotMgr::AddBot(Creature* bot)
//}
if (!owned)
{
uint32 cost = GetNpcBotCost(_owner->GetLevel(), bot->GetBotClass());
uint32 cost = GetNpcBotCostHire(_owner->GetLevel(), bot->GetBotClass());
if (!_owner->HasEnoughMoney(cost))
{
ChatHandler ch(_owner->GetSession());
Expand Down Expand Up @@ -2068,7 +2089,12 @@ bool BotMgr::RemoveAllBotsFromGroup()
return true;
}

uint32 BotMgr::GetNpcBotCost(uint8 level, uint8 botclass)
uint32 BotMgr::GetNpcBotCostRent()
{
return _npcBotsCostRent;
}

uint32 BotMgr::GetNpcBotCostHire(uint8 level, uint8 botclass)
{
//assuming default 1000000
//level 1: 500 //5 silver
Expand All @@ -2079,11 +2105,11 @@ uint32 BotMgr::GetNpcBotCost(uint8 level, uint8 botclass)
//rest is linear
//rare / rareelite bots have their cost adjusted
uint32 cost =
level < 10 ? _npcBotsCost / 2000 : //5 silver
level < 20 ? _npcBotsCost / 100 : //1 gold
level < 30 ? _npcBotsCost / 20 : //5 gold
level < 40 ? _npcBotsCost / 5 : //20 gold
(_npcBotsCost * (level - (level % 10))) / DEFAULT_MAX_LEVEL; //50 - 100 gold
level < 10 ? _npcBotsCostHire / 2000 : //5 silver
level < 20 ? _npcBotsCostHire / 100 : //1 gold
level < 30 ? _npcBotsCostHire / 20 : //5 gold
level < 40 ? _npcBotsCostHire / 5 : //20 gold
(_npcBotsCostHire * (level - (level % 10))) / DEFAULT_MAX_LEVEL; //50 - 100 gold

switch (botclass)
{
Expand Down Expand Up @@ -2111,7 +2137,7 @@ std::string BotMgr::GetNpcBotCostStr(uint8 level, uint8 botclass)
{
std::ostringstream money;

if (uint32 cost = GetNpcBotCost(level, botclass))
if (uint32 cost = GetNpcBotCostHire(level, botclass))
{
uint32 gold = uint32(cost / GOLD);
cost -= (gold * GOLD);
Expand All @@ -2126,6 +2152,23 @@ std::string BotMgr::GetNpcBotCostStr(uint8 level, uint8 botclass)
money << cost << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t";
}

if (uint32 rcost = GetNpcBotCostRent())
{
uint32 gold = uint32(rcost / GOLD);
rcost -= (gold * GOLD);
uint32 silver = uint32(rcost / SILVER);
rcost -= (silver * SILVER);

money << " + |TInterface\\Icons\\INV_Misc_PocketWatch_01:16|t";

if (gold != 0)
money << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t";
if (silver != 0)
money << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t";
if (rcost)
money << rcost << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t";
}

return money.str();
}

Expand Down
6 changes: 5 additions & 1 deletion src/server/game/AI/NpcBots/botmgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ enum BotRemoveType
BOT_REMOVE_DISMISS = 1,
BOT_REMOVE_UNSUMMON = 2,
BOT_REMOVE_UNBIND = 3,
BOT_REMOVE_UNAFFORD = 4,
BOT_REMOVE_BY_DEFAULT = BOT_REMOVE_LOGOUT
};

Expand Down Expand Up @@ -262,7 +263,8 @@ class AC_GAME_API BotMgr
bool HasBotPetType(uint32 petType) const;
bool IsBeingResurrected(WorldObject const* corpse) const;

static uint32 GetNpcBotCost(uint8 level, uint8 botclass);
static uint32 GetNpcBotCostRent();
static uint32 GetNpcBotCostHire(uint8 level, uint8 botclass);
static std::string GetNpcBotCostStr(uint8 level, uint8 botclass);
static uint8 BotClassByClassName(std::string const& className);
static uint8 GetBotPlayerClass(uint8 bot_class);
Expand Down Expand Up @@ -369,10 +371,12 @@ class AC_GAME_API BotMgr
Player* const _owner;
BotMap _bots;
std::list<ObjectGuid> _removeList;
std::list<std::pair<ObjectGuid, BotRemoveType>> _delayedRemoveList;
DPSTracker* const _dpstracker;
NpcBotMgrData* _data;

bool _quickrecall;
bool _update_lock;

AoeSpotsVec _aoespots;

Expand Down

0 comments on commit 8ccb8f4

Please sign in to comment.