From 4a445081d8705e048ae11a1115ae378ca417afb8 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 21:19:42 -0700 Subject: [PATCH] feat: latency indicator, BG queue status, and ToT improvements - Add latency indicator below minimap (color-coded: green/yellow/orange/red) using the lastLatency value measured via CMSG_PING/SMSG_PONG - Add BG queue status indicator below minimap when in WAIT_QUEUE (abbreviated name: AV/WSG/AB/EotS etc.) - Target-of-Target frame: add level display and click-to-target support - Expose getLatencyMs() accessor on GameHandler --- include/game/game_handler.hpp | 3 ++ src/ui/game_screen.cpp | 80 ++++++++++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 235def82..c0ec24c6 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -356,6 +356,9 @@ public: void declineBattlefield(uint32_t queueSlot = 0xFFFFFFFF); const std::array& getBgQueues() const { return bgQueues_; } + // Network latency (milliseconds, updated each PONG response) + uint32_t getLatencyMs() const { return lastLatency; } + // Logout commands void requestLogout(); void cancelLogout(); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index e626fa28..73d4e997 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -2464,6 +2464,10 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) { if (totEntity->getType() == game::ObjectType::UNIT || totEntity->getType() == game::ObjectType::PLAYER) { auto totUnit = std::static_pointer_cast(totEntity); + if (totUnit->getLevel() > 0) { + ImGui::SameLine(); + ImGui::TextDisabled("Lv%u", totUnit->getLevel()); + } uint32_t hp = totUnit->getHealth(); uint32_t maxHp = totUnit->getMaxHealth(); if (maxHp > 0) { @@ -2476,6 +2480,10 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) { ImGui::PopStyleColor(); } } + // Click to target the target-of-target + if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0)) { + gameHandler.setTarget(totGuid); + } } ImGui::End(); ImGui::PopStyleColor(2); @@ -9368,21 +9376,73 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) { } ImGui::End(); - // "New Mail" indicator below the minimap + // Indicators below the minimap (stacked: new mail, then BG queue, then latency) + float indicatorX = centerX - mapRadius; + float nextIndicatorY = centerY + mapRadius + 4.0f; + const float indicatorW = mapRadius * 2.0f; + constexpr float kIndicatorH = 22.0f; + ImGuiWindowFlags indicatorFlags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs; + + // "New Mail" indicator if (gameHandler.hasNewMail()) { - float indicatorX = centerX - mapRadius; - float indicatorY = centerY + mapRadius + 4.0f; - ImGui::SetNextWindowPos(ImVec2(indicatorX, indicatorY), ImGuiCond_Always); - ImGui::SetNextWindowSize(ImVec2(mapRadius * 2.0f, 22), ImGuiCond_Always); - ImGuiWindowFlags mailFlags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs; - if (ImGui::Begin("##NewMailIndicator", nullptr, mailFlags)) { - // Pulsing effect + ImGui::SetNextWindowPos(ImVec2(indicatorX, nextIndicatorY), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(indicatorW, kIndicatorH), ImGuiCond_Always); + if (ImGui::Begin("##NewMailIndicator", nullptr, indicatorFlags)) { float pulse = 0.7f + 0.3f * std::sin(static_cast(ImGui::GetTime()) * 3.0f); ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.0f, pulse), "New Mail!"); } ImGui::End(); + nextIndicatorY += kIndicatorH; + } + + // BG queue status indicator (when in queue but not yet invited) + for (const auto& slot : gameHandler.getBgQueues()) { + if (slot.statusId != 1) continue; // STATUS_WAIT_QUEUE only + + std::string bgName; + if (slot.arenaType > 0) { + bgName = std::to_string(slot.arenaType) + "v" + std::to_string(slot.arenaType) + " Arena"; + } else { + switch (slot.bgTypeId) { + case 1: bgName = "AV"; break; + case 2: bgName = "WSG"; break; + case 3: bgName = "AB"; break; + case 7: bgName = "EotS"; break; + case 9: bgName = "SotA"; break; + case 11: bgName = "IoC"; break; + default: bgName = "BG"; break; + } + } + + ImGui::SetNextWindowPos(ImVec2(indicatorX, nextIndicatorY), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(indicatorW, kIndicatorH), ImGuiCond_Always); + if (ImGui::Begin("##BgQueueIndicator", nullptr, indicatorFlags)) { + float pulse = 0.6f + 0.4f * std::sin(static_cast(ImGui::GetTime()) * 1.5f); + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, pulse), + "In Queue: %s", bgName.c_str()); + } + ImGui::End(); + nextIndicatorY += kIndicatorH; + break; // Show at most one queue slot indicator + } + + // Latency indicator (shown when in world and last latency is known) + uint32_t latMs = gameHandler.getLatencyMs(); + if (latMs > 0 && gameHandler.getState() == game::WorldState::IN_WORLD) { + ImVec4 latColor; + if (latMs < 100) latColor = ImVec4(0.3f, 1.0f, 0.3f, 0.8f); // Green < 100ms + else if (latMs < 250) latColor = ImVec4(1.0f, 1.0f, 0.3f, 0.8f); // Yellow < 250ms + else if (latMs < 500) latColor = ImVec4(1.0f, 0.6f, 0.1f, 0.8f); // Orange < 500ms + else latColor = ImVec4(1.0f, 0.2f, 0.2f, 0.8f); // Red >= 500ms + + ImGui::SetNextWindowPos(ImVec2(indicatorX, nextIndicatorY), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(indicatorW, kIndicatorH), ImGuiCond_Always); + if (ImGui::Begin("##LatencyIndicator", nullptr, indicatorFlags)) { + ImGui::TextColored(latColor, "%u ms", latMs); + } + ImGui::End(); } }