feat: show logout countdown overlay with cancel button

This commit is contained in:
Kelsi 2026-03-13 10:13:54 -07:00
parent 792d8e1cf5
commit b03c326bcd
4 changed files with 79 additions and 1 deletions

View file

@ -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<std::string, uint64_t> 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;

View file

@ -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);

View file

@ -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
}

View file

@ -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<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(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<int>(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
// ============================================================