diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 4c1fbb53d..f02a79e70 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -243,10 +243,22 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) nextAICheckDelay = 0; // Early return if bot is in invalid state - if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() || - bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld()) + if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() || bot->IsDuringRemoveFromWorld()) return; + // During timed logout countdown, cancel if bot enters combat (this cancellation is handled client-side for real players). + if (bot->GetSession()->isLogingOut()) + { + bool canLogoutInCombat = bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING); + if (bot->IsInCombat() && !canLogoutInCombat) + { + WorldPackets::Character::LogoutCancel cancelData = WorldPacket(CMSG_LOGOUT_CANCEL); + bot->GetSession()->HandleLogoutCancelOpcode(cancelData); + } + else + return; + } + // Handle cheat options (set bot health and power if cheats are enabled) if (bot->IsAlive() && (static_cast(GetCheat()) > 0 || static_cast(sPlayerbotAIConfig.botCheatMask) > 0)) @@ -715,30 +727,9 @@ void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fr Reset(true); } - // TODO: missing implementation to port - /*else if (filtered == "logout") - { - if (!(bot->IsStunnedByLogout() || bot->GetSession()->isLogingOut())) - { - if (type == CHAT_MSG_WHISPER) - TellPlayer(&fromPlayer, BOT_TEXT("logout_start")); - - if (master && master->GetPlayerbotMgr()) - SetShouldLogOut(true); - } - } - else if (filtered == "logout cancel") - { - if (bot->IsStunnedByLogout() || bot->GetSession()->isLogingOut()) - { - if (type == CHAT_MSG_WHISPER) - TellPlayer(&fromPlayer, BOT_TEXT("logout_cancel")); - - WorldPacket p; - bot->GetSession()->HandleLogoutCancelOpcode(p); - SetShouldLogOut(false); - } - } + // Commented-out logout commands blocks removed from here and implemented in HandleCommand. + // Remaining is a commented-out action delay command block. + /* else if ((filtered.size() > 5) && (filtered.substr(0, 5) == "wait ") && (filtered.find("wait for attack") == std::string::npos)) { @@ -1084,7 +1075,7 @@ void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fro TellMaster(message); } } - else if (filtered == "logout cancel") + else if (filtered == "cancel logout" || filtered == "logout cancel") { if (!bot->GetSession()->isLogingOut()) return; @@ -1100,9 +1091,7 @@ void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fro bot->GetSession()->HandleLogoutCancelOpcode(data); } else - { chatCommands.push_back(ChatCommandHolder(filtered, fromPlayer, type)); - } } void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) diff --git a/src/Bot/PlayerbotMgr.cpp b/src/Bot/PlayerbotMgr.cpp index 8327e2f36..fd205fe23 100644 --- a/src/Bot/PlayerbotMgr.cpp +++ b/src/Bot/PlayerbotMgr.cpp @@ -299,6 +299,11 @@ void PlayerbotHolder::LogoutAllBots() if (!botAI || botAI->IsRealPlayer()) continue; + // If bot is mid-countdown, cancel the timer so LogoutPlayerBot proceeds immediately. + WorldSession* session = bot->GetSession(); + if (session && session->isLogingOut()) + session->SetLogoutStartTime(0); + LogoutPlayerBot(bot->GetGUID()); } } @@ -361,36 +366,50 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) WorldSession* botWorldSessionPtr = bot->GetSession(); WorldSession* masterWorldSessionPtr = nullptr; + // If already in timed logout countdown, complete it once the 20-second timer expires. if (botWorldSessionPtr->isLogingOut()) + { + if (botWorldSessionPtr->ShouldLogOut(time(nullptr))) + { + std::string message = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "goodbye", "Goodbye!", {}); + botAI->TellMaster(message); + RemoveFromPlayerbotsMap(guid); + botWorldSessionPtr->LogoutPlayer(true); + delete botWorldSessionPtr; + } return; + } Player* master = botAI->GetMaster(); if (master) masterWorldSessionPtr = master->GetSession(); - // TODO: Review whether or not to implement timed logout. - // Unused block. Useful only for timed logout. -/* - // check for instant logout - bool logout = botWorldSessionPtr->ShouldLogOut(time(nullptr)); + // Instant logout checking: + bool logout = + bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || + bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || + (masterWorldSessionPtr && !masterWorldSessionPtr->GetPlayer()) || + // Master's socket is already gone (EXIT GAME -> EXIT NOW is the most typical cause). + // Force instant logout. Without this, the bot restarts its 20-second countdown and fires LogoutPlayer() 20 seconds + // after the master's Player object has been deleted, causing the bot's logout to crash on the now deleted master. + (masterWorldSessionPtr && masterWorldSessionPtr->IsSocketClosed()) || + (masterWorldSessionPtr && masterWorldSessionPtr->ShouldLogOut(time(nullptr))) || + // If the bot's master has security clearance for `InstantLogout` in worldserver.conf, so does the bot. + (master && + (master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || + master->HasUnitState(UNIT_STATE_IN_FLIGHT) || + (masterWorldSessionPtr && + masterWorldSessionPtr->GetSecurity() >= (AccountTypes)sWorld->getIntConfig(CONFIG_INSTANT_LOGOUT)))); - if (masterWorldSessionPtr && masterWorldSessionPtr->ShouldLogOut(time(nullptr))) - logout = true; + if (!logout) + { + // Start the 20-second logout countdown. CancelLogout() can interrupt this. + WorldPackets::Character::LogoutRequest data = WorldPacket(CMSG_LOGOUT_REQUEST); + botWorldSessionPtr->HandleLogoutRequestOpcode(data); + return; + } - if (masterWorldSessionPtr && !masterWorldSessionPtr->GetPlayer()) - logout = true; - - if (bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || - botWorldSessionPtr->GetSecurity() >= (AccountTypes)sWorld->getIntConfig(CONFIG_INSTANT_LOGOUT)) - logout = true; - - if (master && - (master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || master->HasUnitState(UNIT_STATE_IN_FLIGHT) || - (masterWorldSessionPtr && - masterWorldSessionPtr->GetSecurity() >= (AccountTypes)sWorld->getIntConfig(CONFIG_INSTANT_LOGOUT)))) - logout = true; -*/ - // Instant logout (the only option right now) { std::string message = PlayerbotTextMgr::instance().GetBotTextOrDefault( "goodbye", "Goodbye!", {}); @@ -1478,6 +1497,15 @@ void PlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) { SetNextCheckDelay(sPlayerbotAIConfig.reactDelay); CheckTellErrors(elapsed); + + // Complete timed logouts for added bots once the 20-second countdown has elapsed. + std::vector expiredLogouts; + for (auto const& [botGuid, bot] : playerBots) + if (bot && bot->GetSession() && bot->GetSession()->ShouldLogOut(time(nullptr))) + expiredLogouts.push_back(botGuid); + + for (ObjectGuid const& guid : expiredLogouts) + LogoutPlayerBot(guid); } void PlayerbotMgr::HandleCommand(uint32 type, std::string const text)