mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-07 17:43:51 +00:00
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND, anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1) lookup, and flyVariant() compact 218-element ground→FLY_* resolver. Expand AnimationController into a full state machine with 20+ named states: spell cast (directed→omni→cast fallback chain, instant one-shot release), hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle, stealth animation substitution, loot, fishing channel, sit/sleep/kneel down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons (BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY, vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS), emote state variants, off-hand/pierce/dual-wield alternation, NPC birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell. Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo (12 constants) namespaces; replace all magic numbers in spell_handler.cpp. Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire GameObjectStateCallback. Add DBC cross-validation on world entry. Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and asset_pipeline_gui.py. Add tests/test_animation_ids.cpp. Bug fixes included: - Stand state 1 was animating READY_2H(27) — fixed to SITTING(97) - Spell casts ended freeze-frame — add one-shot release animation - NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff) - Chair sits (states 2/4/5/6) incorrectly played floor-sit transition - STOP(3) used for all spell casts — replaced with model-aware chain
1115 lines
46 KiB
C++
1115 lines
46 KiB
C++
#include "ui/dialog_manager.hpp"
|
|
#include "ui/inventory_screen.hpp"
|
|
#include "ui/chat_panel.hpp"
|
|
#include "ui/ui_colors.hpp"
|
|
#include "game/game_handler.hpp"
|
|
#include "core/application.hpp"
|
|
|
|
#include <imgui.h>
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
#include <chrono>
|
|
|
|
namespace wowee { namespace ui {
|
|
|
|
namespace {
|
|
using namespace wowee::ui::colors;
|
|
constexpr auto& kColorDarkGray = kDarkGray;
|
|
constexpr auto& kColorGreen = kGreen;
|
|
} // namespace
|
|
|
|
// Build a WoW-format item link string for chat insertion.
|
|
// Format: |cff<qualHex>|Hitem:<itemId>:0:0:0:0:0:0:0:0|h[<name>]|h|r
|
|
static std::string buildItemChatLink(uint32_t itemId, uint8_t quality, const std::string& name) {
|
|
static constexpr const char* kQualHex[] = {"9d9d9d","ffffff","1eff00","0070dd","a335ee","ff8000","e6cc80","e6cc80"};
|
|
uint8_t qi = quality < 8 ? quality : 1;
|
|
char buf[512];
|
|
snprintf(buf, sizeof(buf), "|cff%s|Hitem:%u:0:0:0:0:0:0:0:0|h[%s]|h|r",
|
|
kQualHex[qi], itemId, name.c_str());
|
|
return buf;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Render early dialogs (group invite through LFG role check)
|
|
// ---------------------------------------------------------------------------
|
|
void DialogManager::renderDialogs(game::GameHandler& gameHandler,
|
|
InventoryScreen& inventoryScreen,
|
|
ChatPanel& chatPanel) {
|
|
renderGroupInvitePopup(gameHandler);
|
|
renderDuelRequestPopup(gameHandler);
|
|
renderDuelCountdown(gameHandler);
|
|
renderLootRollPopup(gameHandler, inventoryScreen, chatPanel);
|
|
renderTradeRequestPopup(gameHandler);
|
|
renderTradeWindow(gameHandler, inventoryScreen, chatPanel);
|
|
renderSummonRequestPopup(gameHandler);
|
|
renderSharedQuestPopup(gameHandler);
|
|
renderItemTextWindow(gameHandler);
|
|
renderGuildInvitePopup(gameHandler);
|
|
renderReadyCheckPopup(gameHandler);
|
|
renderBgInvitePopup(gameHandler);
|
|
renderBfMgrInvitePopup(gameHandler);
|
|
renderLfgProposalPopup(gameHandler);
|
|
renderLfgRoleCheckPopup(gameHandler);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Render late dialogs (resurrect, talent wipe, pet unlearn)
|
|
// ---------------------------------------------------------------------------
|
|
void DialogManager::renderLateDialogs(game::GameHandler& gameHandler) {
|
|
renderResurrectDialog(gameHandler);
|
|
renderTalentWipeConfirmDialog(gameHandler);
|
|
renderPetUnlearnConfirmDialog(gameHandler);
|
|
}
|
|
|
|
// ============================================================
|
|
// Group Invite Popup
|
|
// ============================================================
|
|
|
|
void DialogManager::renderGroupInvitePopup(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.hasPendingGroupInvite()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 200), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always);
|
|
|
|
if (ImGui::Begin("Group Invite", nullptr, kDialogFlags)) {
|
|
ImGui::Text("%s has invited you to a group.", gameHandler.getPendingInviterName().c_str());
|
|
ImGui::Spacing();
|
|
|
|
if (ImGui::Button("Accept", ImVec2(130, 30))) {
|
|
gameHandler.acceptGroupInvite();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Decline", ImVec2(130, 30))) {
|
|
gameHandler.declineGroupInvite();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void DialogManager::renderDuelRequestPopup(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.hasPendingDuelRequest()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 250), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always);
|
|
|
|
if (ImGui::Begin("Duel Request", nullptr, kDialogFlags)) {
|
|
ImGui::Text("%s challenges you to a duel!", gameHandler.getDuelChallengerName().c_str());
|
|
ImGui::Spacing();
|
|
|
|
if (ImGui::Button("Accept", ImVec2(130, 30))) {
|
|
gameHandler.acceptDuel();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Decline", ImVec2(130, 30))) {
|
|
gameHandler.forfeitDuel();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void DialogManager::renderDuelCountdown(game::GameHandler& gameHandler) {
|
|
float remaining = gameHandler.getDuelCountdownRemaining();
|
|
if (remaining <= 0.0f) return;
|
|
|
|
ImVec2 displaySize = ImGui::GetIO().DisplaySize;
|
|
float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f;
|
|
float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f;
|
|
|
|
auto* dl = ImGui::GetForegroundDrawList();
|
|
ImFont* font = ImGui::GetFont();
|
|
float fontSize = ImGui::GetFontSize();
|
|
|
|
// Show integer countdown or "Fight!" when under 0.5s
|
|
char buf[32];
|
|
if (remaining > 0.5f) {
|
|
snprintf(buf, sizeof(buf), "%d", static_cast<int>(std::ceil(remaining)));
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "Fight!");
|
|
}
|
|
|
|
// Large font by scaling — use 4x font size for dramatic effect
|
|
float scale = 4.0f;
|
|
float scaledSize = fontSize * scale;
|
|
ImVec2 textSz = font->CalcTextSizeA(scaledSize, FLT_MAX, 0.0f, buf);
|
|
float tx = (screenW - textSz.x) * 0.5f;
|
|
float ty = screenH * 0.35f - textSz.y * 0.5f;
|
|
|
|
// Pulsing alpha: fades in and out per second
|
|
float pulse = 0.75f + 0.25f * std::sin(static_cast<float>(ImGui::GetTime()) * 6.28f);
|
|
uint8_t alpha = static_cast<uint8_t>(255 * pulse);
|
|
|
|
// Color: golden countdown, red "Fight!"
|
|
ImU32 color = (remaining > 0.5f)
|
|
? IM_COL32(255, 200, 50, alpha)
|
|
: IM_COL32(255, 60, 60, alpha);
|
|
|
|
// Drop shadow
|
|
dl->AddText(font, scaledSize, ImVec2(tx + 2.0f, ty + 2.0f), IM_COL32(0, 0, 0, alpha / 2), buf);
|
|
dl->AddText(font, scaledSize, ImVec2(tx, ty), color, buf);
|
|
}
|
|
|
|
void DialogManager::renderItemTextWindow(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.isItemTextOpen()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - 200, screenH * 0.15f),
|
|
ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver);
|
|
|
|
bool open = true;
|
|
if (!ImGui::Begin("Book", &open, ImGuiWindowFlags_NoCollapse)) {
|
|
ImGui::End();
|
|
if (!open) gameHandler.closeItemText();
|
|
return;
|
|
}
|
|
if (!open) {
|
|
ImGui::End();
|
|
gameHandler.closeItemText();
|
|
return;
|
|
}
|
|
|
|
// Parchment-toned background text
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.1f, 0.0f, 1.0f));
|
|
ImGui::TextWrapped("%s", gameHandler.getItemText().c_str());
|
|
ImGui::PopStyleColor();
|
|
|
|
ImGui::Spacing();
|
|
if (ImGui::Button("Close", ImVec2(80, 0))) {
|
|
gameHandler.closeItemText();
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void DialogManager::renderSharedQuestPopup(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.hasPendingSharedQuest()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 490), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(350, 0), ImGuiCond_Always);
|
|
|
|
if (ImGui::Begin("Shared Quest", nullptr, kDialogFlags)) {
|
|
ImGui::Text("%s has shared a quest with you:", gameHandler.getSharedQuestSharerName().c_str());
|
|
ImGui::TextColored(colors::kBrightGold, "\"%s\"", gameHandler.getSharedQuestTitle().c_str());
|
|
ImGui::Spacing();
|
|
|
|
if (ImGui::Button("Accept", ImVec2(130, 30))) {
|
|
gameHandler.acceptSharedQuest();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Decline", ImVec2(130, 30))) {
|
|
gameHandler.declineSharedQuest();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void DialogManager::renderSummonRequestPopup(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.hasPendingSummonRequest()) return;
|
|
|
|
// Tick the timeout down
|
|
float dt = ImGui::GetIO().DeltaTime;
|
|
gameHandler.tickSummonTimeout(dt);
|
|
if (!gameHandler.hasPendingSummonRequest()) return; // expired
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 430), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(350, 0), ImGuiCond_Always);
|
|
|
|
if (ImGui::Begin("Summon Request", nullptr, kDialogFlags)) {
|
|
ImGui::Text("%s is summoning you.", gameHandler.getSummonerName().c_str());
|
|
float t = gameHandler.getSummonTimeoutSec();
|
|
if (t > 0.0f) {
|
|
ImGui::Text("Time remaining: %.0fs", t);
|
|
}
|
|
ImGui::Spacing();
|
|
|
|
if (ImGui::Button("Accept", ImVec2(130, 30))) {
|
|
gameHandler.acceptSummon();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Decline", ImVec2(130, 30))) {
|
|
gameHandler.declineSummon();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void DialogManager::renderTradeRequestPopup(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.hasPendingTradeRequest()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 370), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always);
|
|
|
|
if (ImGui::Begin("Trade Request", nullptr, kDialogFlags)) {
|
|
ImGui::Text("%s wants to trade with you.", gameHandler.getTradePeerName().c_str());
|
|
ImGui::Spacing();
|
|
|
|
if (ImGui::Button("Accept", ImVec2(130, 30))) {
|
|
gameHandler.acceptTradeRequest();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Decline", ImVec2(130, 30))) {
|
|
gameHandler.declineTradeRequest();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void DialogManager::renderTradeWindow(game::GameHandler& gameHandler,
|
|
InventoryScreen& inventoryScreen,
|
|
ChatPanel& chatPanel) {
|
|
if (!gameHandler.isTradeOpen()) return;
|
|
|
|
const auto& mySlots = gameHandler.getMyTradeSlots();
|
|
const auto& peerSlots = gameHandler.getPeerTradeSlots();
|
|
const uint64_t myGold = gameHandler.getMyTradeGold();
|
|
const uint64_t peerGold = gameHandler.getPeerTradeGold();
|
|
const auto& peerName = gameHandler.getTradePeerName();
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2.0f - 240.0f, screenH / 2.0f - 180.0f), ImGuiCond_Once);
|
|
ImGui::SetNextWindowSize(ImVec2(480.0f, 360.0f), ImGuiCond_Once);
|
|
|
|
bool open = true;
|
|
if (ImGui::Begin(("Trade with " + peerName).c_str(), &open,
|
|
kDialogFlags)) {
|
|
|
|
auto formatGold = [](uint64_t copper, char* buf, size_t bufsz) {
|
|
uint64_t g = copper / 10000;
|
|
uint64_t s = (copper % 10000) / 100;
|
|
uint64_t c = copper % 100;
|
|
if (g > 0) std::snprintf(buf, bufsz, "%llug %llus %lluc",
|
|
(unsigned long long)g, (unsigned long long)s, (unsigned long long)c);
|
|
else if (s > 0) std::snprintf(buf, bufsz, "%llus %lluc",
|
|
(unsigned long long)s, (unsigned long long)c);
|
|
else std::snprintf(buf, bufsz, "%lluc", (unsigned long long)c);
|
|
};
|
|
|
|
auto renderSlotColumn = [&](const char* label,
|
|
const std::array<game::GameHandler::TradeSlot,
|
|
game::GameHandler::TRADE_SLOT_COUNT>& slots,
|
|
uint64_t gold, bool isMine) {
|
|
ImGui::Text("%s", label);
|
|
ImGui::Separator();
|
|
|
|
for (int i = 0; i < game::GameHandler::TRADE_SLOT_COUNT; ++i) {
|
|
const auto& slot = slots[i];
|
|
ImGui::PushID(i * (isMine ? 1 : -1) - (isMine ? 0 : 100));
|
|
|
|
if (slot.occupied && slot.itemId != 0) {
|
|
const auto* info = gameHandler.getItemInfo(slot.itemId);
|
|
std::string name = (info && info->valid && !info->name.empty())
|
|
? info->name
|
|
: ("Item " + std::to_string(slot.itemId));
|
|
if (slot.stackCount > 1)
|
|
name += " x" + std::to_string(slot.stackCount);
|
|
ImVec4 qc = (info && info->valid)
|
|
? InventoryScreen::getQualityColor(static_cast<game::ItemQuality>(info->quality))
|
|
: ImVec4(1.0f, 0.9f, 0.5f, 1.0f);
|
|
if (info && info->valid && info->displayInfoId != 0) {
|
|
VkDescriptorSet iconTex = inventoryScreen.getItemIcon(info->displayInfoId);
|
|
if (iconTex) {
|
|
ImGui::Image((ImTextureID)(uintptr_t)iconTex, ImVec2(16, 16));
|
|
ImGui::SameLine();
|
|
}
|
|
}
|
|
ImGui::TextColored(qc, "%d. %s", i + 1, name.c_str());
|
|
if (isMine && ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
|
gameHandler.clearTradeItem(static_cast<uint8_t>(i));
|
|
}
|
|
if (ImGui::IsItemHovered()) {
|
|
if (info && info->valid) inventoryScreen.renderItemTooltip(*info);
|
|
else if (isMine) ImGui::SetTooltip("Double-click to remove");
|
|
}
|
|
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
|
ImGui::GetIO().KeyShift && info && info->valid && !info->name.empty()) {
|
|
std::string link = buildItemChatLink(info->entry, info->quality, info->name);
|
|
chatPanel.insertChatLink(link);
|
|
}
|
|
} else {
|
|
ImGui::TextDisabled(" %d. (empty)", i + 1);
|
|
|
|
// Allow dragging inventory items into trade slots via right-click context menu
|
|
char addItemId[16]; snprintf(addItemId, sizeof(addItemId), "##additem%d", i);
|
|
if (isMine && ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
|
ImGui::OpenPopup(addItemId);
|
|
}
|
|
}
|
|
|
|
if (isMine) {
|
|
char addItemId[16]; snprintf(addItemId, sizeof(addItemId), "##additem%d", i);
|
|
// Drag-from-inventory: show small popup listing bag items
|
|
if (ImGui::BeginPopup(addItemId)) {
|
|
ImGui::TextDisabled("Add from inventory:");
|
|
const auto& inv = gameHandler.getInventory();
|
|
// Backpack slots 0-15 (bag=255)
|
|
for (int si = 0; si < game::Inventory::BACKPACK_SLOTS; ++si) {
|
|
const auto& slot = inv.getBackpackSlot(si);
|
|
if (slot.empty()) continue;
|
|
const auto* ii = gameHandler.getItemInfo(slot.item.itemId);
|
|
std::string iname = (ii && ii->valid && !ii->name.empty())
|
|
? ii->name
|
|
: (!slot.item.name.empty() ? slot.item.name
|
|
: ("Item " + std::to_string(slot.item.itemId)));
|
|
if (ImGui::Selectable(iname.c_str())) {
|
|
// bag=255 = main backpack
|
|
gameHandler.setTradeItem(static_cast<uint8_t>(i), 255u,
|
|
static_cast<uint8_t>(si));
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Gold row
|
|
char gbuf[48];
|
|
formatGold(gold, gbuf, sizeof(gbuf));
|
|
ImGui::Spacing();
|
|
if (isMine) {
|
|
ImGui::Text("Gold offered: %s", gbuf);
|
|
static char goldInput[32] = "0";
|
|
ImGui::SetNextItemWidth(120.0f);
|
|
if (ImGui::InputText("##goldset", goldInput, sizeof(goldInput),
|
|
ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
uint64_t copper = std::strtoull(goldInput, nullptr, 10);
|
|
gameHandler.setTradeGold(copper);
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled("(copper, Enter to set)");
|
|
} else {
|
|
ImGui::Text("Gold offered: %s", gbuf);
|
|
}
|
|
};
|
|
|
|
// Two-column layout: my offer | peer offer
|
|
float colW = ImGui::GetContentRegionAvail().x * 0.5f - 4.0f;
|
|
ImGui::BeginChild("##myoffer", ImVec2(colW, 240.0f), true);
|
|
renderSlotColumn("Your offer", mySlots, myGold, true);
|
|
ImGui::EndChild();
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::BeginChild("##peroffer", ImVec2(colW, 240.0f), true);
|
|
renderSlotColumn((peerName + "'s offer").c_str(), peerSlots, peerGold, false);
|
|
ImGui::EndChild();
|
|
|
|
// Buttons
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
float bw = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
|
if (ImGui::Button("Accept Trade", ImVec2(bw, 0))) {
|
|
gameHandler.acceptTrade();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel", ImVec2(bw, 0))) {
|
|
gameHandler.cancelTrade();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
|
|
if (!open) {
|
|
gameHandler.cancelTrade();
|
|
}
|
|
}
|
|
|
|
void DialogManager::renderLootRollPopup(game::GameHandler& gameHandler,
|
|
InventoryScreen& inventoryScreen,
|
|
ChatPanel& chatPanel) {
|
|
if (!gameHandler.hasPendingLootRoll()) return;
|
|
|
|
const auto& roll = gameHandler.getPendingLootRoll();
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 310), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(350, 0), ImGuiCond_Always);
|
|
|
|
if (ImGui::Begin("Loot Roll", nullptr, kDialogFlags)) {
|
|
// Quality color for item name
|
|
uint8_t q = roll.itemQuality;
|
|
ImVec4 col = ui::getQualityColor(static_cast<game::ItemQuality>(q));
|
|
|
|
// Countdown bar
|
|
{
|
|
auto now = std::chrono::steady_clock::now();
|
|
float elapsedMs = static_cast<float>(
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(now - roll.rollStartedAt).count());
|
|
float totalMs = static_cast<float>(roll.rollCountdownMs > 0 ? roll.rollCountdownMs : 60000);
|
|
float fraction = 1.0f - std::min(elapsedMs / totalMs, 1.0f);
|
|
float remainSec = (totalMs - elapsedMs) / 1000.0f;
|
|
if (remainSec < 0.0f) remainSec = 0.0f;
|
|
|
|
// Color: green → yellow → red
|
|
ImVec4 barColor;
|
|
if (fraction > 0.5f)
|
|
barColor = ImVec4(0.2f + (1.0f - fraction) * 1.4f, 0.85f, 0.2f, 1.0f);
|
|
else if (fraction > 0.2f)
|
|
barColor = ImVec4(1.0f, fraction * 1.7f, 0.1f, 1.0f);
|
|
else {
|
|
float pulse = 0.7f + 0.3f * std::sin(static_cast<float>(ImGui::GetTime()) * 6.0f);
|
|
barColor = ImVec4(pulse, 0.1f * pulse, 0.1f * pulse, 1.0f);
|
|
}
|
|
|
|
char timeBuf[16];
|
|
std::snprintf(timeBuf, sizeof(timeBuf), "%.0fs", remainSec);
|
|
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, barColor);
|
|
ImGui::ProgressBar(fraction, ImVec2(-1, 12), timeBuf);
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
ImGui::Text("An item is up for rolls:");
|
|
|
|
// Show item icon if available
|
|
const auto* rollInfo = gameHandler.getItemInfo(roll.itemId);
|
|
uint32_t rollDisplayId = rollInfo ? rollInfo->displayInfoId : 0;
|
|
VkDescriptorSet rollIcon = rollDisplayId ? inventoryScreen.getItemIcon(rollDisplayId) : VK_NULL_HANDLE;
|
|
if (rollIcon) {
|
|
ImGui::Image((ImTextureID)(uintptr_t)rollIcon, ImVec2(24, 24));
|
|
ImGui::SameLine();
|
|
}
|
|
// Prefer live item info (arrives via SMSG_ITEM_QUERY_SINGLE_RESPONSE after the
|
|
// roll popup opens); fall back to the name cached at SMSG_LOOT_START_ROLL time.
|
|
const char* displayName = (rollInfo && rollInfo->valid && !rollInfo->name.empty())
|
|
? rollInfo->name.c_str()
|
|
: roll.itemName.c_str();
|
|
if (rollInfo && rollInfo->valid)
|
|
col = ui::getQualityColor(static_cast<game::ItemQuality>(rollInfo->quality));
|
|
ImGui::TextColored(col, "[%s]", displayName);
|
|
if (ImGui::IsItemHovered() && rollInfo && rollInfo->valid) {
|
|
inventoryScreen.renderItemTooltip(*rollInfo);
|
|
}
|
|
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
|
ImGui::GetIO().KeyShift && rollInfo && rollInfo->valid && !rollInfo->name.empty()) {
|
|
std::string link = buildItemChatLink(rollInfo->entry, rollInfo->quality, rollInfo->name);
|
|
chatPanel.insertChatLink(link);
|
|
}
|
|
ImGui::Spacing();
|
|
|
|
// voteMask bits: 0x01=pass, 0x02=need, 0x04=greed, 0x08=disenchant
|
|
const uint8_t vm = roll.voteMask;
|
|
bool first = true;
|
|
if (vm & 0x02) {
|
|
if (ImGui::Button("Need", ImVec2(80, 30)))
|
|
gameHandler.sendLootRoll(roll.objectGuid, roll.slot, 0);
|
|
first = false;
|
|
}
|
|
if (vm & 0x04) {
|
|
if (!first) ImGui::SameLine();
|
|
if (ImGui::Button("Greed", ImVec2(80, 30)))
|
|
gameHandler.sendLootRoll(roll.objectGuid, roll.slot, 1);
|
|
first = false;
|
|
}
|
|
if (vm & 0x08) {
|
|
if (!first) ImGui::SameLine();
|
|
if (ImGui::Button("Disenchant", ImVec2(95, 30)))
|
|
gameHandler.sendLootRoll(roll.objectGuid, roll.slot, 2);
|
|
first = false;
|
|
}
|
|
if (vm & 0x01) {
|
|
if (!first) ImGui::SameLine();
|
|
if (ImGui::Button("Pass", ImVec2(70, 30)))
|
|
gameHandler.sendLootRoll(roll.objectGuid, roll.slot, 96);
|
|
}
|
|
|
|
// Live roll results from group members
|
|
if (!roll.playerRolls.empty()) {
|
|
ImGui::Separator();
|
|
ImGui::TextDisabled("Rolls so far:");
|
|
// Roll-type label + color
|
|
static constexpr const char* kRollLabels[] = {"Need", "Greed", "Disenchant", "Pass"};
|
|
static constexpr ImVec4 kRollColors[] = {
|
|
ImVec4(0.2f, 0.9f, 0.2f, 1.0f), // Need — green
|
|
ImVec4(0.3f, 0.6f, 1.0f, 1.0f), // Greed — blue
|
|
ImVec4(0.7f, 0.3f, 0.9f, 1.0f), // Disenchant — purple
|
|
kColorDarkGray, // Pass — gray
|
|
};
|
|
auto rollTypeIndex = [](uint8_t t) -> int {
|
|
if (t == 0) return 0;
|
|
if (t == 1) return 1;
|
|
if (t == 2) return 2;
|
|
return 3; // pass (96 or unknown)
|
|
};
|
|
|
|
if (ImGui::BeginTable("##lootrolls", 3,
|
|
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg)) {
|
|
ImGui::TableSetupColumn("Player", ImGuiTableColumnFlags_WidthStretch);
|
|
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 72.0f);
|
|
ImGui::TableSetupColumn("Roll", ImGuiTableColumnFlags_WidthFixed, 32.0f);
|
|
for (const auto& r : roll.playerRolls) {
|
|
int ri = rollTypeIndex(r.rollType);
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::TextUnformatted(r.playerName.c_str());
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::TextColored(kRollColors[ri], "%s", kRollLabels[ri]);
|
|
ImGui::TableSetColumnIndex(2);
|
|
if (r.rollType != 96) {
|
|
ImGui::TextColored(kRollColors[ri], "%d", static_cast<int>(r.rollNum));
|
|
} else {
|
|
ImGui::TextDisabled("—");
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void DialogManager::renderGuildInvitePopup(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.hasPendingGuildInvite()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 250), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(350, 0), ImGuiCond_Always);
|
|
|
|
if (ImGui::Begin("Guild Invite", nullptr, kDialogFlags)) {
|
|
ImGui::TextWrapped("%s has invited you to join %s.",
|
|
gameHandler.getPendingGuildInviterName().c_str(),
|
|
gameHandler.getPendingGuildInviteGuildName().c_str());
|
|
ImGui::Spacing();
|
|
|
|
if (ImGui::Button("Accept", ImVec2(155, 30))) {
|
|
gameHandler.acceptGuildInvite();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Decline", ImVec2(155, 30))) {
|
|
gameHandler.declineGuildInvite();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void DialogManager::renderReadyCheckPopup(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.hasPendingReadyCheck()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, screenH / 2 - 60), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(350, 0), ImGuiCond_Always);
|
|
|
|
if (ImGui::Begin("Ready Check", nullptr, kDialogFlags)) {
|
|
const std::string& initiator = gameHandler.getReadyCheckInitiator();
|
|
if (initiator.empty()) {
|
|
ImGui::Text("A ready check has been initiated!");
|
|
} else {
|
|
ImGui::TextWrapped("%s has initiated a ready check!", initiator.c_str());
|
|
}
|
|
ImGui::Spacing();
|
|
|
|
if (ImGui::Button("Ready", ImVec2(155, 30))) {
|
|
gameHandler.respondToReadyCheck(true);
|
|
gameHandler.dismissReadyCheck();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Not Ready", ImVec2(155, 30))) {
|
|
gameHandler.respondToReadyCheck(false);
|
|
gameHandler.dismissReadyCheck();
|
|
}
|
|
|
|
// Live player responses
|
|
const auto& results = gameHandler.getReadyCheckResults();
|
|
if (!results.empty()) {
|
|
ImGui::Separator();
|
|
if (ImGui::BeginTable("##rcresults", 2,
|
|
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg)) {
|
|
ImGui::TableSetupColumn("Player", ImGuiTableColumnFlags_WidthStretch);
|
|
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 72.0f);
|
|
for (const auto& r : results) {
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::TextUnformatted(r.name.c_str());
|
|
ImGui::TableSetColumnIndex(1);
|
|
if (r.ready) {
|
|
ImGui::TextColored(ImVec4(0.2f, 0.9f, 0.2f, 1.0f), "Ready");
|
|
} else {
|
|
ImGui::TextColored(ImVec4(0.9f, 0.3f, 0.3f, 1.0f), "Not Ready");
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void DialogManager::renderBgInvitePopup(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.hasPendingBgInvite()) return;
|
|
|
|
const auto& queues = gameHandler.getBgQueues();
|
|
// Find the first WAIT_JOIN slot
|
|
const game::GameHandler::BgQueueSlot* slot = nullptr;
|
|
for (const auto& s : queues) {
|
|
if (s.statusId == 2) { slot = &s; break; }
|
|
}
|
|
if (!slot) return;
|
|
|
|
// Compute time remaining
|
|
auto now = std::chrono::steady_clock::now();
|
|
double elapsed = std::chrono::duration<double>(now - slot->inviteReceivedTime).count();
|
|
double remaining = static_cast<double>(slot->inviteTimeout) - elapsed;
|
|
|
|
// If invite has expired, clear it silently (server will handle the queue)
|
|
if (remaining <= 0.0) {
|
|
gameHandler.declineBattlefield(slot->queueSlot);
|
|
return;
|
|
}
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 190, screenH / 2 - 70), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(380, 0), ImGuiCond_Always);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.08f, 0.18f, 0.95f));
|
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.4f, 0.4f, 1.0f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4(0.15f, 0.15f, 0.4f, 1.0f));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
|
|
|
const ImGuiWindowFlags popupFlags =
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse;
|
|
|
|
if (ImGui::Begin("Battleground Ready!", nullptr, popupFlags)) {
|
|
// BG name from stored queue data
|
|
std::string bgName = slot->bgName.empty() ? "Battleground" : slot->bgName;
|
|
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.2f, 1.0f), "%s", bgName.c_str());
|
|
ImGui::TextWrapped("A spot has opened! You have %d seconds to enter.", static_cast<int>(remaining));
|
|
ImGui::Spacing();
|
|
|
|
// Countdown progress bar
|
|
float frac = static_cast<float>(remaining / static_cast<double>(slot->inviteTimeout));
|
|
frac = std::clamp(frac, 0.0f, 1.0f);
|
|
ImVec4 barColor = frac > 0.5f ? colors::kHealthGreen
|
|
: frac > 0.25f ? ImVec4(0.9f, 0.7f, 0.1f, 1.0f)
|
|
: colors::kDarkRed;
|
|
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, barColor);
|
|
char countdownLabel[32];
|
|
snprintf(countdownLabel, sizeof(countdownLabel), "%ds", static_cast<int>(remaining));
|
|
ImGui::ProgressBar(frac, ImVec2(-1, 16), countdownLabel);
|
|
ImGui::PopStyleColor();
|
|
ImGui::Spacing();
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnGreen);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kFriendlyGreen);
|
|
if (ImGui::Button("Enter Battleground", ImVec2(180, 30))) {
|
|
gameHandler.acceptBattlefield(slot->queueSlot);
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnRed);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kDangerRed);
|
|
if (ImGui::Button("Leave Queue", ImVec2(175, 30))) {
|
|
gameHandler.declineBattlefield(slot->queueSlot);
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
}
|
|
ImGui::End();
|
|
|
|
ImGui::PopStyleVar();
|
|
ImGui::PopStyleColor(3);
|
|
}
|
|
|
|
void DialogManager::renderBfMgrInvitePopup(game::GameHandler& gameHandler) {
|
|
// Only shown on WotLK servers (outdoor battlefields like Wintergrasp use the BF Manager)
|
|
if (!gameHandler.hasBfMgrInvite()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2.0f - 190.0f, screenH / 2.0f - 55.0f), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(380.0f, 0.0f), ImGuiCond_Always);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.10f, 0.20f, 0.96f));
|
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.5f, 0.5f, 1.0f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4(0.15f, 0.15f, 0.45f, 1.0f));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
|
|
|
const ImGuiWindowFlags flags =
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse;
|
|
|
|
if (ImGui::Begin("Battlefield", nullptr, flags)) {
|
|
// Resolve zone name for Wintergrasp (zoneId 4197)
|
|
uint32_t zoneId = gameHandler.getBfMgrZoneId();
|
|
const char* zoneName = nullptr;
|
|
if (zoneId == 4197) zoneName = "Wintergrasp";
|
|
else if (zoneId == 5095) zoneName = "Tol Barad";
|
|
|
|
if (zoneName) {
|
|
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.2f, 1.0f), "%s", zoneName);
|
|
} else {
|
|
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.2f, 1.0f), "Outdoor Battlefield");
|
|
}
|
|
ImGui::Spacing();
|
|
ImGui::TextWrapped("You are invited to join the outdoor battlefield. Do you want to enter?");
|
|
ImGui::Spacing();
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnGreen);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kFriendlyGreen);
|
|
if (ImGui::Button("Enter Battlefield", ImVec2(178, 28))) {
|
|
gameHandler.acceptBfMgrInvite();
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnRed);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kDangerRed);
|
|
if (ImGui::Button("Decline", ImVec2(175, 28))) {
|
|
gameHandler.declineBfMgrInvite();
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
}
|
|
ImGui::End();
|
|
|
|
ImGui::PopStyleVar();
|
|
ImGui::PopStyleColor(3);
|
|
}
|
|
|
|
void DialogManager::renderLfgProposalPopup(game::GameHandler& gameHandler) {
|
|
using LfgState = game::GameHandler::LfgState;
|
|
if (gameHandler.getLfgState() != LfgState::Proposal) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2.0f - 175.0f, screenH / 2.0f - 65.0f), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(350.0f, 0.0f), ImGuiCond_Always);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.14f, 0.08f, 0.96f));
|
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.3f, 0.8f, 0.3f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4(0.1f, 0.3f, 0.1f, 1.0f));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
|
|
|
const ImGuiWindowFlags flags =
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse;
|
|
|
|
if (ImGui::Begin("Dungeon Finder", nullptr, flags)) {
|
|
ImGui::TextColored(kColorGreen, "A group has been found!");
|
|
ImGui::Spacing();
|
|
ImGui::TextWrapped("Please accept or decline to join the dungeon.");
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnGreen);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kFriendlyGreen);
|
|
if (ImGui::Button("Accept", ImVec2(155.0f, 30.0f))) {
|
|
gameHandler.lfgAcceptProposal(gameHandler.getLfgProposalId(), true);
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnRed);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kDangerRed);
|
|
if (ImGui::Button("Decline", ImVec2(155.0f, 30.0f))) {
|
|
gameHandler.lfgAcceptProposal(gameHandler.getLfgProposalId(), false);
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
}
|
|
ImGui::End();
|
|
|
|
ImGui::PopStyleVar();
|
|
ImGui::PopStyleColor(3);
|
|
}
|
|
|
|
void DialogManager::renderLfgRoleCheckPopup(game::GameHandler& gameHandler) {
|
|
using LfgState = game::GameHandler::LfgState;
|
|
if (gameHandler.getLfgState() != LfgState::RoleCheck) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
float screenH = window ? static_cast<float>(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 DialogManager::renderResurrectDialog(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.showResurrectDialog()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
|
|
|
float dlgW = 300.0f;
|
|
float dlgH = 110.0f;
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - dlgW / 2, screenH * 0.3f), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(dlgW, dlgH), ImGuiCond_Always);
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f);
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.1f, 0.1f, 0.15f, 0.95f));
|
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.4f, 0.4f, 0.8f, 1.0f));
|
|
|
|
if (ImGui::Begin("##ResurrectDialog", nullptr,
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar)) {
|
|
|
|
ImGui::Spacing();
|
|
const std::string& casterName = gameHandler.getResurrectCasterName();
|
|
std::string text = casterName.empty()
|
|
? "Return to life?"
|
|
: casterName + " wishes to resurrect you.";
|
|
float textW = ImGui::CalcTextSize(text.c_str()).x;
|
|
ImGui::SetCursorPosX(std::max(4.0f, (dlgW - textW) / 2));
|
|
ImGui::TextColored(ImVec4(0.8f, 0.9f, 1.0f, 1.0f), "%s", text.c_str());
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Spacing();
|
|
|
|
float btnW = 100.0f;
|
|
float spacing = 20.0f;
|
|
ImGui::SetCursorPosX((dlgW - btnW * 2 - spacing) / 2);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnDkGreen);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kBtnDkGreenHover);
|
|
if (ImGui::Button("Accept", ImVec2(btnW, 30))) {
|
|
gameHandler.acceptResurrect();
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
|
|
ImGui::SameLine(0, spacing);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnDkRed);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kBtnDkRedHover);
|
|
if (ImGui::Button("Decline", ImVec2(btnW, 30))) {
|
|
gameHandler.declineResurrect();
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
}
|
|
ImGui::End();
|
|
ImGui::PopStyleColor(2);
|
|
ImGui::PopStyleVar();
|
|
}
|
|
|
|
// ============================================================
|
|
// Talent Wipe Confirm Dialog
|
|
// ============================================================
|
|
|
|
void DialogManager::renderTalentWipeConfirmDialog(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.showTalentWipeConfirmDialog()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
|
|
|
float dlgW = 340.0f;
|
|
float dlgH = 130.0f;
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - dlgW / 2, screenH * 0.3f), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(dlgW, dlgH), ImGuiCond_Always);
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f);
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.1f, 0.1f, 0.15f, 0.95f));
|
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.8f, 0.7f, 0.2f, 1.0f));
|
|
|
|
if (ImGui::Begin("##TalentWipeDialog", nullptr,
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar)) {
|
|
|
|
ImGui::Spacing();
|
|
uint32_t cost = gameHandler.getTalentWipeCost();
|
|
uint32_t gold = cost / 10000;
|
|
uint32_t silver = (cost % 10000) / 100;
|
|
uint32_t copper = cost % 100;
|
|
char costStr[64];
|
|
if (gold > 0)
|
|
std::snprintf(costStr, sizeof(costStr), "%ug %us %uc", gold, silver, copper);
|
|
else if (silver > 0)
|
|
std::snprintf(costStr, sizeof(costStr), "%us %uc", silver, copper);
|
|
else
|
|
std::snprintf(costStr, sizeof(costStr), "%uc", copper);
|
|
|
|
std::string text = "Reset your talents for ";
|
|
text += costStr;
|
|
text += "?";
|
|
float textW = ImGui::CalcTextSize(text.c_str()).x;
|
|
ImGui::SetCursorPosX(std::max(4.0f, (dlgW - textW) / 2));
|
|
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.4f, 1.0f), "%s", text.c_str());
|
|
|
|
ImGui::Spacing();
|
|
ImGui::SetCursorPosX(8.0f);
|
|
ImGui::TextDisabled("All talent points will be refunded.");
|
|
ImGui::Spacing();
|
|
|
|
float btnW = 110.0f;
|
|
float spacing = 20.0f;
|
|
ImGui::SetCursorPosX((dlgW - btnW * 2 - spacing) / 2);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnDkGreen);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kBtnDkGreenHover);
|
|
if (ImGui::Button("Confirm", ImVec2(btnW, 30))) {
|
|
gameHandler.confirmTalentWipe();
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
|
|
ImGui::SameLine(0, spacing);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnDkRed);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kBtnDkRedHover);
|
|
if (ImGui::Button("Cancel", ImVec2(btnW, 30))) {
|
|
gameHandler.cancelTalentWipe();
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
}
|
|
ImGui::End();
|
|
ImGui::PopStyleColor(2);
|
|
ImGui::PopStyleVar();
|
|
}
|
|
|
|
void DialogManager::renderPetUnlearnConfirmDialog(game::GameHandler& gameHandler) {
|
|
if (!gameHandler.showPetUnlearnDialog()) return;
|
|
|
|
auto* window = services_.window;
|
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
|
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
|
|
|
float dlgW = 340.0f;
|
|
float dlgH = 130.0f;
|
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - dlgW / 2, screenH * 0.3f), ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(dlgW, dlgH), ImGuiCond_Always);
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f);
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.1f, 0.1f, 0.15f, 0.95f));
|
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.8f, 0.7f, 0.2f, 1.0f));
|
|
|
|
if (ImGui::Begin("##PetUnlearnDialog", nullptr,
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar)) {
|
|
|
|
ImGui::Spacing();
|
|
uint32_t cost = gameHandler.getPetUnlearnCost();
|
|
uint32_t gold = cost / 10000;
|
|
uint32_t silver = (cost % 10000) / 100;
|
|
uint32_t copper = cost % 100;
|
|
char costStr[64];
|
|
if (gold > 0)
|
|
std::snprintf(costStr, sizeof(costStr), "%ug %us %uc", gold, silver, copper);
|
|
else if (silver > 0)
|
|
std::snprintf(costStr, sizeof(costStr), "%us %uc", silver, copper);
|
|
else
|
|
std::snprintf(costStr, sizeof(costStr), "%uc", copper);
|
|
|
|
std::string text = std::string("Reset your pet's talents for ") + costStr + "?";
|
|
float textW = ImGui::CalcTextSize(text.c_str()).x;
|
|
ImGui::SetCursorPosX(std::max(4.0f, (dlgW - textW) / 2));
|
|
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.4f, 1.0f), "%s", text.c_str());
|
|
|
|
ImGui::Spacing();
|
|
ImGui::SetCursorPosX(8.0f);
|
|
ImGui::TextDisabled("All pet talent points will be refunded.");
|
|
ImGui::Spacing();
|
|
|
|
float btnW = 110.0f;
|
|
float spacing = 20.0f;
|
|
ImGui::SetCursorPosX((dlgW - btnW * 2 - spacing) / 2);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnDkGreen);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kBtnDkGreenHover);
|
|
if (ImGui::Button("Confirm##petunlearn", ImVec2(btnW, 30))) {
|
|
gameHandler.confirmPetUnlearn();
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
|
|
ImGui::SameLine(0, spacing);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colors::kBtnDkRed);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors::kBtnDkRedHover);
|
|
if (ImGui::Button("Cancel##petunlearn", ImVec2(btnW, 30))) {
|
|
gameHandler.cancelPetUnlearn();
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
}
|
|
ImGui::End();
|
|
ImGui::PopStyleColor(2);
|
|
ImGui::PopStyleVar();
|
|
}
|
|
|
|
} // namespace ui
|
|
} // namespace wowee
|