diff --git a/src/modules/Playerbot/Session/BotSession.cpp b/src/modules/Playerbot/Session/BotSession.cpp index 8c34a7162..fb29d40a3 100644 --- a/src/modules/Playerbot/Session/BotSession.cpp +++ b/src/modules/Playerbot/Session/BotSession.cpp @@ -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", diff --git a/src/modules/Playerbot/Session/BotSession.h b/src/modules/Playerbot/Session/BotSession.h index 277d7e5bd..08d0783f3 100644 --- a/src/modules/Playerbot/Session/BotSession.h +++ b/src/modules/Playerbot/Session/BotSession.h @@ -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 _isInstanceBot{false}; + /// Time when bot was marked as instance bot (for queue timeout) + std::atomic _instanceBotStartTime{0}; + /// Accumulated idle time in milliseconds /// Reset to 0 when bot enters queue or group, incremented when idle std::atomic _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 _queueAccumulatorMs{0}; + /// Whether bot was active (in queue/group) last check /// Used to detect transition from active to idle std::atomic _wasActiveLastCheck{true}; + /// Whether bot has ever entered actual instanced content (dungeon/BG/arena) + /// Used to distinguish "waiting in queue" from "actually playing" + std::atomic _hasEnteredInstance{false}; + // Deleted copy operations BotSession(BotSession const&) = delete; BotSession& operator=(BotSession const&) = delete;