From 23ebfc7e859789b097e28a85f43aca3f20a17eba Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 20 Mar 2026 16:10:29 -0700 Subject: [PATCH] feat: add LFG role check confirmation popup with CMSG_LFG_SET_ROLES When the dungeon finder initiates a role check (SMSG_LFG_ROLE_CHECK_UPDATE state=2), show a centered popup with Tank/Healer/DPS checkboxes and Accept/Leave Queue buttons. Accept sends CMSG_LFG_SET_ROLES with the selected role mask. Previously only showed passive "Role check in progress" text with no way to respond. --- include/game/game_handler.hpp | 1 + include/ui/game_screen.hpp | 1 + src/game/game_handler.cpp | 11 ++++++ src/ui/game_screen.cpp | 66 +++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 1c2907fd..9873e22e 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1442,6 +1442,7 @@ public: // roles bitmask: 0x02=tank, 0x04=healer, 0x08=dps; pass LFGDungeonEntry ID void lfgJoin(uint32_t dungeonId, uint8_t roles); void lfgLeave(); + void lfgSetRoles(uint8_t roles); void lfgAcceptProposal(uint32_t proposalId, bool accept); void lfgSetBootVote(bool vote); void lfgTeleport(bool toLfgDungeon = true); diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index cd200126..5391978f 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -388,6 +388,7 @@ private: void renderBgInvitePopup(game::GameHandler& gameHandler); void renderBfMgrInvitePopup(game::GameHandler& gameHandler); void renderLfgProposalPopup(game::GameHandler& gameHandler); + void renderLfgRoleCheckPopup(game::GameHandler& gameHandler); void renderChatBubbles(game::GameHandler& gameHandler); void renderMailWindow(game::GameHandler& gameHandler); void renderMailComposeWindow(game::GameHandler& gameHandler); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 50c75051..862857f1 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -17172,6 +17172,17 @@ void GameHandler::lfgLeave() { LOG_INFO("Sent CMSG_LFG_LEAVE"); } +void GameHandler::lfgSetRoles(uint8_t roles) { + if (state != WorldState::IN_WORLD || !socket) return; + const uint32_t wire = wireOpcode(Opcode::CMSG_LFG_SET_ROLES); + if (wire == 0xFFFF) return; + + network::Packet pkt(static_cast(wire)); + pkt.writeUInt8(roles); + socket->send(pkt); + LOG_INFO("Sent CMSG_LFG_SET_ROLES: roles=", static_cast(roles)); +} + void GameHandler::lfgAcceptProposal(uint32_t proposalId, bool accept) { if (!socket) return; diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index f17188e9..19f50309 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -720,6 +720,7 @@ void GameScreen::render(game::GameHandler& gameHandler) { renderBgInvitePopup(gameHandler); renderBfMgrInvitePopup(gameHandler); renderLfgProposalPopup(gameHandler); + renderLfgRoleCheckPopup(gameHandler); renderGuildRoster(gameHandler); renderSocialFrame(gameHandler); renderBuffBar(gameHandler); @@ -14201,6 +14202,71 @@ void GameScreen::renderLfgProposalPopup(game::GameHandler& gameHandler) { ImGui::PopStyleColor(3); } +void GameScreen::renderLfgRoleCheckPopup(game::GameHandler& gameHandler) { + using LfgState = game::GameHandler::LfgState; + if (gameHandler.getLfgState() != LfgState::RoleCheck) 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; + + ImGui::SetNextWindowPos(ImVec2(screenW / 2.0f - 160.0f, screenH / 2.0f - 80.0f), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(320.0f, 0.0f), ImGuiCond_Always); + + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.08f, 0.18f, 0.96f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.3f, 0.5f, 0.9f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4(0.1f, 0.1f, 0.3f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f); + + const ImGuiWindowFlags flags = + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse; + + if (ImGui::Begin("Role Check##LfgRoleCheck", nullptr, flags)) { + ImGui::TextColored(ImVec4(0.4f, 0.7f, 1.0f, 1.0f), "Confirm your role:"); + ImGui::Spacing(); + + // Role checkboxes + bool isTank = (lfgRoles_ & 0x02) != 0; + bool isHealer = (lfgRoles_ & 0x04) != 0; + bool isDps = (lfgRoles_ & 0x08) != 0; + + if (ImGui::Checkbox("Tank", &isTank)) lfgRoles_ = (lfgRoles_ & ~0x02) | (isTank ? 0x02 : 0); + ImGui::SameLine(120.0f); + if (ImGui::Checkbox("Healer", &isHealer)) lfgRoles_ = (lfgRoles_ & ~0x04) | (isHealer ? 0x04 : 0); + ImGui::SameLine(220.0f); + if (ImGui::Checkbox("DPS", &isDps)) lfgRoles_ = (lfgRoles_ & ~0x08) | (isDps ? 0x08 : 0); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + bool hasRole = (lfgRoles_ & 0x0E) != 0; + if (!hasRole) ImGui::BeginDisabled(); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.4f, 0.15f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.6f, 0.2f, 1.0f)); + if (ImGui::Button("Accept", ImVec2(140.0f, 28.0f))) { + gameHandler.lfgSetRoles(lfgRoles_); + } + ImGui::PopStyleColor(2); + + if (!hasRole) ImGui::EndDisabled(); + + ImGui::SameLine(); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.15f, 0.15f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.6f, 0.2f, 0.2f, 1.0f)); + if (ImGui::Button("Leave Queue", ImVec2(140.0f, 28.0f))) { + gameHandler.lfgLeave(); + } + ImGui::PopStyleColor(2); + } + ImGui::End(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); +} + void GameScreen::renderGuildRoster(game::GameHandler& gameHandler) { // Guild Roster toggle (customizable keybind) if (!chatInputActive && !ImGui::GetIO().WantTextInput &&