Skip to content

Commit

Permalink
NPCBots: Re-implement NPCBot ownership expiration timer. Fix a bug wh…
Browse files Browse the repository at this point in the history
…ere last login timestamp would be checked instead of last logout one.
  • Loading branch information
trickerer committed Mar 12, 2024
1 parent dadcd21 commit 4a865fb
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--
ALTER TABLE `characters_npcbot` ADD `hire_time` timestamp NULL DEFAULT '0000-00-00 00:00:00' AFTER `faction`;
UPDATE `characters_npcbot` SET `hire_time` = CURRENT_TIMESTAMP() WHERE `owner` != 0;
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,8 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_INS_DESERTER_TRACK, "INSERT INTO battleground_deserters (guid, type, datetime) VALUES (?, ?, NOW())", CONNECTION_ASYNC);

// NPCBots
PrepareStatement(CHAR_UPD_NPCBOT_OWNER, "UPDATE characters_npcbot SET owner = ? WHERE entry = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_NPCBOT_OWNER_ALL, "UPDATE characters_npcbot SET owner = ? WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_NPCBOT_OWNER, "UPDATE characters_npcbot SET owner = ?, hire_time = FROM_UNIXTIME(?) WHERE entry = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_NPCBOT_OWNER_ALL, "UPDATE characters_npcbot SET owner = ?, hire_time = FROM_UNIXTIME(?) WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_NPCBOT_ROLES, "UPDATE characters_npcbot SET roles = ? WHERE entry = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_NPCBOT_EQUIP_BY_ITEM_INSTANCE, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, guid, itemEntry, owner_guid "
"FROM item_instance WHERE guid IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_SYNCH);
Expand Down
70 changes: 58 additions & 12 deletions src/server/game/AI/NpcBots/bot_ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ bool bot_ai::SetBotOwner(Player* newowner)

master = newowner;
_ownerGuid = newowner->GetGUID().GetCounter();
_checkOwershipTimer = BotMgr::GetOwnershipExpireTime() ? CalculateOwnershipCheckTime() : 0;

ASSERT(me->IsInWorld());
AbortTeleport();
Expand All @@ -467,6 +468,9 @@ void bot_ai::CheckOwnerExpiry()
if (!BotMgr::GetOwnershipExpireTime())
return; //disabled

if (IsTempBot())
return;

NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry());
ASSERT(npcBotData, "bot_ai::CheckOwnerExpiry(): data not found!");

Expand All @@ -479,18 +483,28 @@ void bot_ai::CheckOwnerExpiry()
ObjectGuid ownerGuid = ObjectGuid(HighGuid::Player, 0, npcBotData->owner);
time_t timeNow = time(0);
time_t expireTime = time_t(BotMgr::GetOwnershipExpireTime());
uint32 accId = sCharacterCache->GetCharacterAccountIdByGuid(ownerGuid);
QueryResult result = accId ? LoginDatabase.PQuery("SELECT UNIX_TIMESTAMP(last_login) FROM account WHERE id = {}", accId) : nullptr;
time_t baseTimeStamp;

Field* fields = result ? result->Fetch() : nullptr;
time_t lastLoginTime = fields ? time_t(fields[0].GetUInt32()) : timeNow;
if (BotMgr::GetOwnershipExpireMode() == BOT_OWNERSHIP_EXPIRE_OFFLINE)
{
uint32 accId = sCharacterCache->GetCharacterAccountIdByGuid(ownerGuid);
QueryResult result = accId ? CharacterDatabase.PQuery("SELECT MAX(logout_time) FROM characters WHERE account = {}", accId) : nullptr;

Field* fields = result ? result->Fetch() : nullptr;
time_t lastLoginTime = fields ? time_t(fields[0].GetUInt32()) : timeNow;
baseTimeStamp = lastLoginTime;
}
else //if (BotMgr::GetOwnershipExpireMode() == BOT_OWNERSHIP_EXPIRE_HIRE)
{
baseTimeStamp = time_t(npcBotData->hire_time);
}

//either expired or owner does not exist
if (timeNow >= lastLoginTime + expireTime)
if (timeNow >= baseTimeStamp + expireTime)
{
std::string name = "unknown";
sCharacterCache->GetCharacterNameByGuid(ownerGuid, name);
TC_LOG_DEBUG("server.loading", ">> {}'s (guid: {}) ownership over bot {} ({}) has expired!",
TC_LOG_DEBUG("npcbots", "{}'s (guid: {}) ownership over bot {} ({}) has expired!",
name, npcBotData->owner, me->GetName(), me->GetEntry());

//send all items back
Expand Down Expand Up @@ -540,6 +554,7 @@ void bot_ai::CheckOwnerExpiry()
}

//hard reset owner
_ownerGuid = 0;
uint32 newOwner = 0;
BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_OWNER, &newOwner);
//...spec
Expand All @@ -548,6 +563,9 @@ void bot_ai::CheckOwnerExpiry()
//...and roles
uint32 roleMask = DefaultRolesForClass(npcBotExtra->bclass, spec);
BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_ROLES, &roleMask);

if (Group* gr = GetGroup())
gr->RemoveMember(me->GetGUID());
}
}

Expand All @@ -572,14 +590,16 @@ void bot_ai::ResetBotAI(uint8 resetType)
master = reinterpret_cast<Player*>(me);
if (resetType & BOTAI_RESET_MASK_ABANDON_MASTER)
_ownerGuid = 0;
if (resetType == BOTAI_RESET_INIT)
if (resetType == BOTAI_RESET_INIT || resetType == BOTAI_RESET_LOGOUT)
{
homepos.Relocate(me);
if (!IsTempBot())
CheckOwnerExpiry();
NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry());
_checkOwershipTimer = (BotMgr::GetOwnershipExpireTime() && npcBotData->owner) ?
((resetType == BOTAI_RESET_INIT || BotMgr::GetOwnershipExpireMode() == BOT_OWNERSHIP_EXPIRE_HIRE) ? 1000 : CalculateOwnershipCheckTime()) : 0;
if (resetType == BOTAI_RESET_INIT)
homepos.Relocate(me);
else //if (resetType == BOTAI_RESET_LOGOUT)
_saveStats();
}
if (resetType == BOTAI_RESET_LOGOUT)
_saveStats();

if (!IsWanderer() || BotMgr::IsWanderingWorldBot(me))
{
Expand Down Expand Up @@ -14893,6 +14913,11 @@ void bot_ai::FindMaster()
}
}

uint32 bot_ai::CalculateOwnershipCheckTime()
{
return std::min<uint32>(BotMgr::GetOwnershipExpireTime(), urand(58 * MINUTE * IN_MILLISECONDS, 62 * MINUTE * IN_MILLISECONDS));
}

bool bot_ai::IAmFree() const
{
if (!_ownerGuid)
Expand Down Expand Up @@ -16853,6 +16878,26 @@ bool bot_ai::GlobalUpdate(uint32 diff)
}
}
}
else
{
if (_checkOwershipTimer && _checkOwershipTimer <= diff)
{
if (IAmFree())
{
NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry());
if (npcBotData->owner != 0)
{
CheckOwnerExpiry();
if (npcBotData->owner == 0)
{
_checkOwershipTimer = 0;
return false;
}
}
}
_checkOwershipTimer = CalculateOwnershipCheckTime();
}
}

//db saves with cd
// 1) disabled spells
Expand Down Expand Up @@ -17530,6 +17575,7 @@ void bot_ai::CommonTimers(uint32 diff)
if (roleTimer > diff) roleTimer -= diff;
if (ordersTimer > diff) ordersTimer -= diff;
if (checkMasterTimer > diff) checkMasterTimer -= diff;
if (_checkOwershipTimer > diff) _checkOwershipTimer -= diff;

if (_powersTimer > diff) _powersTimer -= diff;
if (_chaseTimer > diff) _chaseTimer -= diff;
Expand Down
2 changes: 2 additions & 0 deletions src/server/game/AI/NpcBots/bot_ai.h
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ class bot_ai : public CreatureAI

private:
void FindMaster();
uint32 CalculateOwnershipCheckTime();

void _OnHealthUpdate() const;
void _OnManaUpdate() const;
Expand Down Expand Up @@ -675,6 +676,7 @@ class bot_ai : public CreatureAI
//timers
uint32 _reviveTimer, _powersTimer, _chaseTimer, _engageTimer, _potionTimer;
uint32 lastdiff, checkAurasTimer, checkMasterTimer, roleTimer, ordersTimer, regenTimer, _updateTimerMedium, _updateTimerEx1;
uint32 _checkOwershipTimer;
uint32 _moveBehindTimer;
uint32 _wmoAreaUpdateTimer;
uint32 waitTimer;
Expand Down
22 changes: 15 additions & 7 deletions src/server/game/AI/NpcBots/botdatamgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -788,9 +788,11 @@ void BotDataMgr::LoadNpcBots(bool spawn)
else
TC_LOG_INFO("server.loading", ">> Bots transmog data is not loaded. Table `characters_npcbot_transmog` is empty!");

// 0 1 2 3 4 5 6 7 8 9 10 11 12 13
result = CharacterDatabase.Query("SELECT entry, owner, roles, spec, faction, equipMhEx, equipOhEx, equipRhEx, equipHead, equipShoulders, equipChest, equipWaist, equipLegs, equipFeet,"
// 14 15 16 17 18 19 20 21 22 23
// 0 1 2 3 4 5
result = CharacterDatabase.Query("SELECT entry, owner, roles, spec, faction, UNIX_TIMESTAMP(hire_time),"
// 6 7 8 9 10 11 12 13 14
"equipMhEx, equipOhEx, equipRhEx, equipHead, equipShoulders, equipChest, equipWaist, equipLegs, equipFeet,"
// 15 16 17 18 19 20 21 22 23 24
"equipWrist, equipHands, equipBack, equipBody, equipFinger1, equipFinger2, equipTrinket1, equipTrinket2, equipNeck, spells_disabled FROM characters_npcbot");

if (result)
Expand Down Expand Up @@ -821,6 +823,7 @@ void BotDataMgr::LoadNpcBots(bool spawn)
botData->roles = field[++index].GetUInt32();
botData->spec = field[++index].GetUInt8();
botData->faction = field[++index].GetUInt32();
botData->hire_time = field[++index].GetUInt64();

for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i)
botData->equips[i] = field[++index].GetUInt32();
Expand Down Expand Up @@ -2293,15 +2296,19 @@ void BotDataMgr::UpdateNpcBotData(uint32 entry, NpcBotDataUpdateType updateType,
switch (updateType)
{
case NPCBOT_UPDATE_OWNER:
{
if (itr->second->owner == *(uint32*)(data))
break;
itr->second->owner = *(uint32*)(data);
itr->second->hire_time = itr->second->owner ? uint64(time(0)) : 1ULL;
bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_OWNER);
//"UPDATE characters_npcbot SET owner = ? WHERE entry = ?", CONNECTION_ASYNC
//"UPDATE characters_npcbot SET owner = ?, hire_time = FROM_UNIXTIME(?) WHERE entry = ?", CONNECTION_ASYNC
bstmt->setUInt32(0, itr->second->owner);
bstmt->setUInt32(1, entry);
bstmt->setUInt64(1, itr->second->hire_time);
bstmt->setUInt32(2, entry);
CharacterDatabase.Execute(bstmt);
//break; //no break: erase transmogs
}
[[fallthrough]];
case NPCBOT_UPDATE_TRANSMOG_ERASE:
bstmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOT_TRANSMOG);
Expand Down Expand Up @@ -2451,9 +2458,10 @@ void BotDataMgr::UpdateNpcBotDataAll(uint32 playerGuid, NpcBotDataUpdateType upd
{
case NPCBOT_UPDATE_OWNER:
bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_OWNER_ALL);
//"UPDATE characters_npcbot SET owner = ? WHERE owner = ?", CONNECTION_ASYNC
//"UPDATE characters_npcbot SET owner = ?, hire_time = FROM_UNIXTIME(?) WHERE owner = ?", CONNECTION_ASYNC
bstmt->setUInt32(0, *(uint32*)(data));
bstmt->setUInt32(1, playerGuid);
bstmt->setUInt64(1, *(uint32*)(data) ? uint64(time(0)) : 1ULL);
bstmt->setUInt32(2, playerGuid);
CharacterDatabase.Execute(bstmt);
//break; //no break: erase transmogs
[[fallthrough]];
Expand Down
3 changes: 2 additions & 1 deletion src/server/game/AI/NpcBots/botdatamgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ struct NpcBotData
friend struct WanderingBotsGenerator;
public:
uint32 owner;
uint64 hire_time;
uint32 roles;
uint32 faction;
uint8 spec;
uint32 equips[BOT_INVENTORY_SIZE];
DisabledSpellsContainer disabled_spells;

private:
explicit NpcBotData(uint32 iroles, uint32 ifaction, uint8 ispec = 1) : owner(0), roles(iroles), faction(ifaction), spec(ispec)
explicit NpcBotData(uint32 iroles, uint32 ifaction, uint8 ispec = 1) : owner(0), hire_time(0), roles(iroles), faction(ifaction), spec(ispec)
{
for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i)
equips[i] = 0;
Expand Down
6 changes: 6 additions & 0 deletions src/server/game/AI/NpcBots/botmgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ uint8 _offTankingTargetIconFlags;
uint8 _dpsTargetIconFlags;
uint8 _rangedDpsTargetIconFlags;
uint8 _noDpsTargetIconFlags;
uint8 _npcBotOwnerExpireMode;
int32 _botInfoPacketsLimit;
uint32 _npcBotsCost;
uint32 _npcBotUpdateDelayBase;
Expand Down Expand Up @@ -330,6 +331,7 @@ void BotMgr::LoadConfig(bool reload)
_npcBotEngageDelayDPS_default = sConfigMgr->GetIntDefault("NpcBot.EngageDelay.DPS", 0);
_npcBotEngageDelayHeal_default = sConfigMgr->GetIntDefault("NpcBot.EngageDelay.Heal", 0);
_npcBotOwnerExpireTime = sConfigMgr->GetIntDefault("NpcBot.OwnershipExpireTime", 0);
_npcBotOwnerExpireMode = sConfigMgr->GetIntDefault("NpcBot.OwnershipExpireMode", 0);
_botPvP = sConfigMgr->GetBoolDefault("NpcBot.PvP", true);
_botMovementFoodInterrupt = sConfigMgr->GetBoolDefault("NpcBot.Movements.InterruptFood", false);
_displayEquipment = sConfigMgr->GetBoolDefault("NpcBot.EquipmentDisplay.Enable", true);
Expand Down Expand Up @@ -765,6 +767,10 @@ uint32 BotMgr::GetOwnershipExpireTime()
{
return _npcBotOwnerExpireTime;
}
uint8 BotMgr::GetOwnershipExpireMode()
{
return _npcBotOwnerExpireMode;
}
uint32 BotMgr::GetDesiredWanderingBotsCount()
{
return _desiredWanderingBotsCount;
Expand Down
7 changes: 7 additions & 0 deletions src/server/game/AI/NpcBots/botmgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ enum BotRemoveType
BOT_REMOVE_BY_DEFAULT = BOT_REMOVE_LOGOUT
};

enum BotOwnershipExpireMode
{
BOT_OWNERSHIP_EXPIRE_OFFLINE = 0,
BOT_OWNERSHIP_EXPIRE_HIRE = 1
};

enum BotAttackRange
{
BOT_ATTACK_RANGE_SHORT = 1,
Expand Down Expand Up @@ -123,6 +129,7 @@ class TC_GAME_API BotMgr
static uint8 GetNoDPSTargetIconFlags();
static uint32 GetBaseUpdateDelay();
static uint32 GetOwnershipExpireTime();
static uint8 GetOwnershipExpireMode();
static uint32 GetDesiredWanderingBotsCount();
static uint32 GetBGTargetTeamPlayersCount(BattlegroundTypeId bgTypeId);
static float GetBotHKHonorRate();
Expand Down
14 changes: 11 additions & 3 deletions src/server/worldserver/worldserver.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4533,14 +4533,22 @@ NpcBot.InfoPacketsLimit = -1

#
# NpcBot.OwnershipExpireTime
# Description: Maxmum time since last account login (in seconds) before bot ownership
# is automatically cancelled (at server loading).
# Note: All items will be refund and mailed back to player.
# Description: Time (in seconds) before bot ownership is automatically cancelled.
# Note: NPCBot ownership may only expire after player goes offline.
# Note2: All items will be mailed back to player as usual.
# Default: 0 - (Disabled)
# 604800 - (7 Days)

NpcBot.OwnershipExpireTime = 0

#
# NpcBot.OwnershipExpireMode
# Description: Ownership expiration mode. Determines when the timer starts.
# Default: 0 - (Player goes offline, timer resets if player logs back in)
# 1 - (Player hires the bot, timer never resets)

NpcBot.OwnershipExpireMode = 0

#
# NpcBot.EnrageOnDismiss
# Description: Enable Berserk buff when bot is dismissed and related hostile reaction.
Expand Down

0 comments on commit 4a865fb

Please sign in to comment.