From b03c326bcdb0c4913f6bdc478aad7bd0aefd7fa0 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 13 Mar 2026 10:13:54 -0700 Subject: [PATCH] feat: show logout countdown overlay with cancel button --- include/game/game_handler.hpp | 5 ++- include/ui/game_screen.hpp | 1 + src/game/game_handler.cpp | 11 ++++++ src/ui/game_screen.cpp | 63 +++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 74e7ed46..0f1320b4 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -497,6 +497,8 @@ public: // Logout commands void requestLogout(); void cancelLogout(); + bool isLoggingOut() const { return loggingOut_; } + float getLogoutCountdown() const { return logoutCountdown_; } // Stand state void setStandState(uint8_t state); // 0=stand, 1=sit, 2=sit_chair, 3=sleep, 4=sit_low_chair, 5=sit_medium_chair, 6=sit_high_chair, 7=dead, 8=kneel, 9=submerged @@ -2491,7 +2493,8 @@ private: std::unordered_map ignoreCache; // name -> guid // ---- Logout state ---- - bool loggingOut_ = false; + bool loggingOut_ = false; + float logoutCountdown_ = 0.0f; // seconds remaining before server logs us out (0 = instant/done) // ---- Display state ---- bool helmVisible_ = true; diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index e8fbca0f..f22ba4da 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -348,6 +348,7 @@ private: void renderTrainerWindow(game::GameHandler& gameHandler); void renderStableWindow(game::GameHandler& gameHandler); void renderTaxiWindow(game::GameHandler& gameHandler); + void renderLogoutCountdown(game::GameHandler& gameHandler); void renderDeathScreen(game::GameHandler& gameHandler); void renderReclaimCorpseButton(game::GameHandler& gameHandler); void renderResurrectDialog(game::GameHandler& gameHandler); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 0bc3fac6..8803257a 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -960,6 +960,12 @@ void GameHandler::update(float deltaTime) { updateCombatText(deltaTime); tickMinimapPings(deltaTime); + // Tick logout countdown + if (loggingOut_ && logoutCountdown_ > 0.0f) { + logoutCountdown_ -= deltaTime; + if (logoutCountdown_ < 0.0f) logoutCountdown_ = 0.0f; + } + // Update taxi landing cooldown if (taxiLandingCooldown_ > 0.0f) { taxiLandingCooldown_ -= deltaTime; @@ -11992,6 +11998,7 @@ void GameHandler::cancelLogout() { auto packet = LogoutCancelPacket::build(); socket->send(packet); loggingOut_ = false; + logoutCountdown_ = 0.0f; addSystemChatMessage("Logout cancelled."); LOG_INFO("Cancelled logout"); } @@ -21256,14 +21263,17 @@ void GameHandler::handleLogoutResponse(network::Packet& packet) { // Success - logout initiated if (data.instant) { addSystemChatMessage("Logging out..."); + logoutCountdown_ = 0.0f; } else { addSystemChatMessage("Logging out in 20 seconds..."); + logoutCountdown_ = 20.0f; } LOG_INFO("Logout response: success, instant=", (int)data.instant); } else { // Failure addSystemChatMessage("Cannot logout right now."); loggingOut_ = false; + logoutCountdown_ = 0.0f; LOG_WARNING("Logout failed, result=", data.result); } } @@ -21271,6 +21281,7 @@ void GameHandler::handleLogoutResponse(network::Packet& packet) { void GameHandler::handleLogoutComplete(network::Packet& /*packet*/) { addSystemChatMessage("Logout complete."); loggingOut_ = false; + logoutCountdown_ = 0.0f; LOG_INFO("Logout complete"); // Server will disconnect us } diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 7ff35b72..9a462959 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -726,6 +726,7 @@ void GameScreen::render(game::GameHandler& gameHandler) { if (showMinimap_) { renderMinimapMarkers(gameHandler); } + renderLogoutCountdown(gameHandler); renderDeathScreen(gameHandler); renderReclaimCorpseButton(gameHandler); renderResurrectDialog(gameHandler); @@ -14089,6 +14090,68 @@ void GameScreen::renderTaxiWindow(game::GameHandler& gameHandler) { } } +// ============================================================ +// Logout Countdown +// ============================================================ + +void GameScreen::renderLogoutCountdown(game::GameHandler& gameHandler) { + if (!gameHandler.isLoggingOut()) return; + + auto* window = core::Application::getInstance().getWindow(); + float screenW = window ? static_cast(window->getWidth()) : 1280.0f; + float screenH = window ? static_cast(window->getHeight()) : 720.0f; + + constexpr float W = 280.0f; + constexpr float H = 80.0f; + ImGui::SetNextWindowPos(ImVec2((screenW - W) * 0.5f, screenH * 0.5f - H * 0.5f - 60.0f), + ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(W, H), ImGuiCond_Always); + ImGui::SetNextWindowBgAlpha(0.88f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.08f, 0.18f, 0.95f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.5f, 0.5f, 0.8f, 1.0f)); + + if (ImGui::Begin("##LogoutCountdown", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus)) { + + float cd = gameHandler.getLogoutCountdown(); + if (cd > 0.0f) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 6.0f); + ImGui::SetCursorPosX((W - ImGui::CalcTextSize("Logging out in 20s...").x) * 0.5f); + ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.3f, 1.0f), + "Logging out in %ds...", static_cast(std::ceil(cd))); + + // Progress bar (20 second countdown) + float frac = 1.0f - std::min(cd / 20.0f, 1.0f); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.5f, 0.5f, 0.9f, 1.0f)); + ImGui::ProgressBar(frac, ImVec2(-1.0f, 8.0f), ""); + ImGui::PopStyleColor(); + ImGui::Spacing(); + } else { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 14.0f); + ImGui::SetCursorPosX((W - ImGui::CalcTextSize("Logging out...").x) * 0.5f); + ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.3f, 1.0f), "Logging out..."); + ImGui::Spacing(); + } + + // Cancel button — only while countdown is still running + if (cd > 0.0f) { + float btnW = 100.0f; + ImGui::SetCursorPosX((W - btnW) * 0.5f); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.1f, 0.1f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.15f, 0.15f, 1.0f)); + if (ImGui::Button("Cancel", ImVec2(btnW, 0))) { + gameHandler.cancelLogout(); + } + ImGui::PopStyleColor(2); + } + } + ImGui::End(); + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(); +} + // ============================================================ // Death Screen // ============================================================