diff --git a/data/sql/playerbots/updates/2026_03_26_ai_playerbot_logout_texts.sql b/data/sql/playerbots/updates/2026_03_26_ai_playerbot_logout_texts.sql new file mode 100644 index 000000000..344e6109d --- /dev/null +++ b/data/sql/playerbots/updates/2026_03_26_ai_playerbot_logout_texts.sql @@ -0,0 +1,59 @@ +-- Translations for additional logout related messages +DELETE FROM ai_playerbot_texts WHERE name IN ('bot_not_your_master', 'bot_rndbot_no_logout'); +DELETE FROM ai_playerbot_texts_chance WHERE name IN ('bot_not_your_master', 'bot_rndbot_no_logout'); + +INSERT INTO `ai_playerbot_texts` + (`id`, `name`, `text`, `say_type`, `reply_type`, + `text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`, + `text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`) +VALUES ( + 1740, + 'bot_not_your_master', + "You are not my master!", + 0, 0, + -- koKR + "당신은 내 주인이 아닙니다!", + -- frFR + "Tu n'es pas mon maître !", + -- deDE + "Du bist nicht mein Meister!", + -- zhCN + "你不是我的主人!", + -- zhTW + "你不是我的主人!", + -- esES + "¡No eres mi amo!", + -- esMX + "¡No eres mi amo!", + -- ruRU + "Ты не мой хозяин!"); + +INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('bot_not_your_master', 100); + +INSERT INTO `ai_playerbot_texts` + (`id`, `name`, `text`, `say_type`, `reply_type`, + `text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`, + `text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`) +VALUES ( + 1741, + 'bot_rndbot_no_logout', + "You can't command me to logout!", + 0, 0, + -- koKR + "당신은 나에게 로그아웃을 명령할 수 없습니다!", + -- frFR + "Tu ne peux pas m'ordonner de me déconnecter !", + -- deDE + "Du kannst mir nicht befehlen, mich auszuloggen!", + -- zhCN + "你不能命令我下线!", + -- zhTW + "你不能命令我登出!", + -- esES + "¡No puedes ordenarme que cierre sesión!", + -- esMX + "¡No puedes ordenarme que cierre sesión!", + -- ruRU + "Ты не можешь приказать мне выйти из игры!"); + +INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('bot_rndbot_no_logout', 100); \ No newline at end of file diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 36631cead..eb758d645 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -1015,28 +1015,58 @@ void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fro } else if (filtered == "logout") { - if (!bot->GetSession()->isLogingOut()) + if (bot->GetSession()->isLogingOut()) + return; + + // Verify the command came from this bot's master. Also handles nullptr + if (fromPlayer != master) { if (type == CHAT_MSG_WHISPER) - TellMaster("I'm logging out!"); + { + std::string message = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bot_not_your_master", "You are not my master!", {}); + bot->Whisper(message, LANG_UNIVERSAL, fromPlayer); + } + return; + } - PlayerbotMgr* masterBotMgr = nullptr; - if (master) - masterBotMgr = GET_PLAYERBOT_MGR(master); - if (masterBotMgr) - masterBotMgr->LogoutPlayerBot(bot->GetGUID()); + PlayerbotMgr* masterBotMgr = GET_PLAYERBOT_MGR(master); + if (!masterBotMgr) + return; + + // Only respond if this bot is in master's collection (alt/addclass) + if (masterBotMgr->GetPlayerBot(bot->GetGUID())) + { + if (type == CHAT_MSG_WHISPER) + { + std::string message = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "logout_start", "I'm logging out!", {}); + TellMaster(message); + } + + masterBotMgr->LogoutPlayerBot(bot->GetGUID()); + } + else if (type == CHAT_MSG_WHISPER) + { + std::string message = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bot_rndbot_no_logout", "You can't command me to logout!", {}); + TellMaster(message); } } else if (filtered == "logout cancel") { - if (bot->GetSession()->isLogingOut()) - { - if (type == CHAT_MSG_WHISPER) - TellMaster("Logout cancelled!"); + if (!bot->GetSession()->isLogingOut()) + return; - WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL); - bot->GetSession()->HandleLogoutCancelOpcode(data); + if (type == CHAT_MSG_WHISPER) + { + std::string message = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "logout_cancel", "Logout cancelled!", {}); + TellMaster(message); } + + WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL); + bot->GetSession()->HandleLogoutCancelOpcode(data); } else { diff --git a/src/Bot/PlayerbotMgr.cpp b/src/Bot/PlayerbotMgr.cpp index 9859877f9..c3b614a98 100644 --- a/src/Bot/PlayerbotMgr.cpp +++ b/src/Bot/PlayerbotMgr.cpp @@ -362,6 +362,9 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) 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)); @@ -373,61 +376,22 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) 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; - } - - TravelTarget* target = nullptr; - if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values. +*/ + // Instant logout (the only option right now) { - target = botAI->GetAiObjectContext()->GetValue("travel target")->Get(); - } - - // Peiru: Allow bots to always instant logout to see if this resolves logout crashes - logout = true; - - // if no instant logout, request normal logout - if (!logout) - { - if (bot->GetSession()->isLogingOut()) - return; - else if (bot) - { - botAI->TellMaster("I'm logging out!"); - WorldPackets::Character::LogoutRequest data = WorldPacket(CMSG_LOGOUT_REQUEST); - botWorldSessionPtr->HandleLogoutRequestOpcode(data); - if (!bot) - { - RemoveFromPlayerbotsMap(guid); - delete botWorldSessionPtr; - if (target) - delete target; - } - return; - } - else - { - RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap - delete botWorldSessionPtr; // finally delete the bot's WorldSession - if (target) - delete target; - } - return; - } // if instant logout possible, do it - else if (bot && (logout || !botWorldSessionPtr->isLogingOut())) - { - botAI->TellMaster("Goodbye!"); - RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap - botWorldSessionPtr->LogoutPlayer(true); // this will delete the bot Player object and PlayerbotAI object - delete botWorldSessionPtr; // finally delete the bot's WorldSession + std::string message = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "goodbye", "Goodbye!", {}); + botAI->TellMaster(message); + RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap + botWorldSessionPtr->LogoutPlayer(true); // this will delete the bot Player object and PlayerbotAI object + delete botWorldSessionPtr; // finally delete the bot's WorldSession } } }