fix(instance): Add queue timeout to prevent instance bot accumulation
Problem: Instance bots sitting in BG queue were considered "active" and never timed out. If the BG never popped (not enough players), bots accumulated indefinitely, causing "HARD CAP REACHED" errors (563 bots when only 19 requested). Root cause: - IsInActiveInstanceState() returns true for bots in BG/LFG queue - Active bots never trigger idle timeout - If BG never starts, bots wait in queue forever Solution: Add a 5-minute queue timeout separate from idle timeout: - Track _queueAccumulatorMs - time spent waiting in queue - Track _hasEnteredInstance - whether bot actually entered content - If bot is in queue for >5 minutes without entering instance → logout New member variables: - _instanceBotStartTime: when bot was marked as instance bot - _queueAccumulatorMs: accumulated queue wait time - _hasEnteredInstance: true once bot enters dungeon/BG/arena Constants: - INSTANCE_BOT_IDLE_TIMEOUT_MS = 60s (existing) - INSTANCE_BOT_QUEUE_TIMEOUT_MS = 5 minutes (new) This prevents bot explosion when BGs don't pop due to population imbalance. Co-Authored-By: Claude Opus 4.5 <[email protected]> Signed-off-by: luis <[email protected]>
This commit is contained in:
committed by
luis
co-authored by
Claude Opus 4.5
parent
61d7aec8f5
commit
ecec2fdf9e
@@ -3134,15 +3134,19 @@ void BotSession::SetInstanceBot(bool isInstanceBot)
|
||||
|
||||
if (isInstanceBot)
|
||||
{
|
||||
// Reset idle state when marked as instance bot
|
||||
// Reset idle and queue state when marked as instance bot
|
||||
_idleAccumulatorMs.store(0);
|
||||
_queueAccumulatorMs.store(0);
|
||||
_wasActiveLastCheck.store(true);
|
||||
_hasEnteredInstance.store(false);
|
||||
_instanceBotStartTime.store(GameTime::GetGameTimeMS());
|
||||
|
||||
Player* player = GetPlayer();
|
||||
TC_LOG_INFO("module.playerbot.instance",
|
||||
"Bot {} marked as INSTANCE BOT - will auto-logout after {}s if idle",
|
||||
"Bot {} marked as INSTANCE BOT - idle timeout: {}s, queue timeout: {}s",
|
||||
player ? player->GetName() : "unknown",
|
||||
INSTANCE_BOT_IDLE_TIMEOUT_MS / 1000);
|
||||
INSTANCE_BOT_IDLE_TIMEOUT_MS / 1000,
|
||||
INSTANCE_BOT_QUEUE_TIMEOUT_MS / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3203,23 +3207,82 @@ bool BotSession::UpdateIdleStateAndCheckLogout(uint32 diff)
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
bool isActive = IsInActiveInstanceState();
|
||||
// ========================================================================
|
||||
// CHECK 1: Has bot entered actual instanced content?
|
||||
// ========================================================================
|
||||
// If bot is inside an instance (dungeon/raid/BG/arena), mark it as having
|
||||
// "entered" - this means it's actively participating, not just waiting.
|
||||
Map* map = player->GetMap();
|
||||
bool inInstancedContent = map && (map->IsDungeon() || map->IsRaid() ||
|
||||
map->IsBattleground() || map->IsBattleArena());
|
||||
|
||||
if (isActive)
|
||||
if (inInstancedContent)
|
||||
{
|
||||
// Bot is active - reset idle accumulator
|
||||
if (_idleAccumulatorMs.load() > 0)
|
||||
// Bot is actually in instanced content - mark it and reset queue timer
|
||||
if (!_hasEnteredInstance.exchange(true))
|
||||
{
|
||||
TC_LOG_DEBUG("module.playerbot.instance",
|
||||
"Bot {} became ACTIVE (in queue/group/instance) - resetting idle timer",
|
||||
TC_LOG_INFO("module.playerbot.instance",
|
||||
"Bot {} ENTERED instanced content - queue timeout disabled",
|
||||
player->GetName());
|
||||
}
|
||||
_queueAccumulatorMs.store(0);
|
||||
_idleAccumulatorMs.store(0);
|
||||
_wasActiveLastCheck.store(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bot is idle - accumulate idle time
|
||||
// ========================================================================
|
||||
// CHECK 2: Is bot in an "active" state (queue/group)?
|
||||
// ========================================================================
|
||||
bool isActive = IsInActiveInstanceState();
|
||||
|
||||
if (isActive)
|
||||
{
|
||||
// Bot is in queue or group but NOT in actual instanced content
|
||||
// Reset idle timer but accumulate queue time (if never entered instance)
|
||||
if (_idleAccumulatorMs.load() > 0)
|
||||
{
|
||||
TC_LOG_DEBUG("module.playerbot.instance",
|
||||
"Bot {} became ACTIVE (in queue/group) - resetting idle timer",
|
||||
player->GetName());
|
||||
}
|
||||
_idleAccumulatorMs.store(0);
|
||||
_wasActiveLastCheck.store(true);
|
||||
|
||||
// If bot has never entered instance, accumulate queue time
|
||||
if (!_hasEnteredInstance.load())
|
||||
{
|
||||
uint32 currentQueue = _queueAccumulatorMs.load();
|
||||
uint32 newQueue = currentQueue + diff;
|
||||
_queueAccumulatorMs.store(newQueue);
|
||||
|
||||
// Check if queue timeout exceeded
|
||||
if (newQueue >= INSTANCE_BOT_QUEUE_TIMEOUT_MS)
|
||||
{
|
||||
TC_LOG_INFO("module.playerbot.instance",
|
||||
"⏰ Bot {} QUEUE TIMEOUT ({} seconds in queue without content starting) - "
|
||||
"scheduling logout to prevent accumulation",
|
||||
player->GetName(), INSTANCE_BOT_QUEUE_TIMEOUT_MS / 1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Log progress every 60 seconds
|
||||
uint32 oldMinutes = currentQueue / 60000;
|
||||
uint32 newMinutes = newQueue / 60000;
|
||||
if (newMinutes > oldMinutes)
|
||||
{
|
||||
TC_LOG_DEBUG("module.playerbot.instance",
|
||||
"Bot {} waiting in queue for {}m / {}m",
|
||||
player->GetName(), newQueue / 60000, INSTANCE_BOT_QUEUE_TIMEOUT_MS / 60000);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// CHECK 3: Bot is idle (not in queue/group/instance)
|
||||
// ========================================================================
|
||||
bool wasActive = _wasActiveLastCheck.exchange(false);
|
||||
if (wasActive)
|
||||
{
|
||||
@@ -3233,7 +3296,7 @@ bool BotSession::UpdateIdleStateAndCheckLogout(uint32 diff)
|
||||
uint32 newIdle = currentIdle + diff;
|
||||
_idleAccumulatorMs.store(newIdle);
|
||||
|
||||
// Check if timeout exceeded
|
||||
// Check if idle timeout exceeded
|
||||
if (newIdle >= INSTANCE_BOT_IDLE_TIMEOUT_MS)
|
||||
{
|
||||
TC_LOG_INFO("module.playerbot.instance",
|
||||
|
||||
@@ -204,6 +204,11 @@ public:
|
||||
/// Idle timeout for instance bots (60 seconds = 1 minute)
|
||||
static constexpr uint32 INSTANCE_BOT_IDLE_TIMEOUT_MS = 60 * 1000;
|
||||
|
||||
/// Queue timeout for instance bots (5 minutes)
|
||||
/// If a bot has been queued for content (BG/LFG) for longer than this without
|
||||
/// actually getting into the content, it will be logged out to prevent accumulation
|
||||
static constexpr uint32 INSTANCE_BOT_QUEUE_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
|
||||
// Process pending async login operations
|
||||
void ProcessPendingLogin();
|
||||
|
||||
@@ -505,14 +510,25 @@ private:
|
||||
/// Whether this bot is an instance bot (JIT or warm pool)
|
||||
std::atomic<bool> _isInstanceBot{false};
|
||||
|
||||
/// Time when bot was marked as instance bot (for queue timeout)
|
||||
std::atomic<uint32> _instanceBotStartTime{0};
|
||||
|
||||
/// Accumulated idle time in milliseconds
|
||||
/// Reset to 0 when bot enters queue or group, incremented when idle
|
||||
std::atomic<uint32> _idleAccumulatorMs{0};
|
||||
|
||||
/// Accumulated queue time in milliseconds (time spent waiting in queue without content starting)
|
||||
/// This prevents bots from sitting in queue forever when BG/LFG never pops
|
||||
std::atomic<uint32> _queueAccumulatorMs{0};
|
||||
|
||||
/// Whether bot was active (in queue/group) last check
|
||||
/// Used to detect transition from active to idle
|
||||
std::atomic<bool> _wasActiveLastCheck{true};
|
||||
|
||||
/// Whether bot has ever entered actual instanced content (dungeon/BG/arena)
|
||||
/// Used to distinguish "waiting in queue" from "actually playing"
|
||||
std::atomic<bool> _hasEnteredInstance{false};
|
||||
|
||||
// Deleted copy operations
|
||||
BotSession(BotSession const&) = delete;
|
||||
BotSession& operator=(BotSession const&) = delete;
|
||||
|
||||
Reference in New Issue
Block a user