mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Improve UI layout, spell casting, and realm selection screen
- Fix action bar, bag bar, chat window to track window resize (Always pos) - Show spell names in cast bar instead of spell IDs - Play precast/cast-complete sounds via SpellSoundManager - Fix hearthstone to use CMSG_CAST_SPELL directly (avoids slot sync issues) - Show map name instead of coordinates in hearthstone tooltip - Show cooldown time remaining in action bar tooltips - Search equipped slots and bags for action bar item icons - Redesign realm screen: back button, larger table/buttons, auto-select realm with characters, double-click to enter, proportional columns
This commit is contained in:
parent
b8f1f15eb4
commit
99723abfac
6 changed files with 261 additions and 103 deletions
|
|
@ -30,12 +30,15 @@ public:
|
||||||
onRealmSelected = callback;
|
onRealmSelected = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setOnBack(std::function<void()> cb) { onBack = std::move(cb); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset selection state (e.g., when switching servers)
|
* Reset selection state (e.g., when switching servers)
|
||||||
*/
|
*/
|
||||||
void reset() {
|
void reset() {
|
||||||
selectedRealmIndex = -1;
|
selectedRealmIndex = -1;
|
||||||
realmSelected = false;
|
realmSelected = false;
|
||||||
|
autoSelectAttempted = false;
|
||||||
selectedRealmName.clear();
|
selectedRealmName.clear();
|
||||||
selectedRealmAddress.clear();
|
selectedRealmAddress.clear();
|
||||||
statusMessage.clear();
|
statusMessage.clear();
|
||||||
|
|
@ -56,6 +59,7 @@ private:
|
||||||
// UI state
|
// UI state
|
||||||
int selectedRealmIndex = -1;
|
int selectedRealmIndex = -1;
|
||||||
bool realmSelected = false;
|
bool realmSelected = false;
|
||||||
|
bool autoSelectAttempted = false;
|
||||||
std::string selectedRealmName;
|
std::string selectedRealmName;
|
||||||
std::string selectedRealmAddress;
|
std::string selectedRealmAddress;
|
||||||
|
|
||||||
|
|
@ -64,6 +68,7 @@ private:
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
std::function<void(const std::string&, const std::string&)> onRealmSelected;
|
std::function<void(const std::string&, const std::string&)> onRealmSelected;
|
||||||
|
std::function<void()> onBack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update status message
|
* Update status message
|
||||||
|
|
|
||||||
|
|
@ -961,6 +961,15 @@ void Application::setupUICallbacks() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Realm screen back button - return to login
|
||||||
|
uiManager->getRealmScreen().setOnBack([this]() {
|
||||||
|
if (authHandler) {
|
||||||
|
authHandler->disconnect();
|
||||||
|
}
|
||||||
|
uiManager->getRealmScreen().reset();
|
||||||
|
setState(AppState::AUTHENTICATION);
|
||||||
|
});
|
||||||
|
|
||||||
// Character selection callback
|
// Character selection callback
|
||||||
uiManager->getCharacterScreen().setOnCharacterSelected([this](uint64_t characterGuid) {
|
uiManager->getCharacterScreen().setOnCharacterSelected([this](uint64_t characterGuid) {
|
||||||
LOG_INFO("Character selected: GUID=0x", std::hex, characterGuid, std::dec);
|
LOG_INFO("Character selected: GUID=0x", std::hex, characterGuid, std::dec);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include "game/opcodes.hpp"
|
#include "game/opcodes.hpp"
|
||||||
#include "game/update_field_table.hpp"
|
#include "game/update_field_table.hpp"
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
|
#include "audio/spell_sound_manager.hpp"
|
||||||
#include "pipeline/dbc_layout.hpp"
|
#include "pipeline/dbc_layout.hpp"
|
||||||
#include "network/world_socket.hpp"
|
#include "network/world_socket.hpp"
|
||||||
#include "network/packet.hpp"
|
#include "network/packet.hpp"
|
||||||
|
|
@ -6917,14 +6918,12 @@ void GameHandler::castSpell(uint32_t spellId, uint64_t targetGuid) {
|
||||||
|
|
||||||
if (casting) return; // Already casting
|
if (casting) return; // Already casting
|
||||||
|
|
||||||
// Hearthstone is item-bound; use the item rather than direct spell cast.
|
// Hearthstone: cast spell directly (server checks item in inventory)
|
||||||
if (spellId == 8690) {
|
// Using CMSG_CAST_SPELL is more reliable than CMSG_USE_ITEM which
|
||||||
LOG_INFO("Hearthstone spell intercepted, routing to useItemById(6948)");
|
// depends on slot indices matching between client and server.
|
||||||
useItemById(6948);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t target = targetGuid != 0 ? targetGuid : this->targetGuid;
|
uint64_t target = targetGuid != 0 ? targetGuid : this->targetGuid;
|
||||||
|
// Self-targeted spells like hearthstone should not send a target
|
||||||
|
if (spellId == 8690) target = 0;
|
||||||
auto packet = packetParsers_
|
auto packet = packetParsers_
|
||||||
? packetParsers_->buildCastSpell(spellId, target, ++castCount)
|
? packetParsers_->buildCastSpell(spellId, target, ++castCount)
|
||||||
: CastSpellPacket::build(spellId, target, ++castCount);
|
: CastSpellPacket::build(spellId, target, ++castCount);
|
||||||
|
|
@ -7031,6 +7030,13 @@ void GameHandler::handleSpellStart(network::Packet& packet) {
|
||||||
currentCastSpellId = data.spellId;
|
currentCastSpellId = data.spellId;
|
||||||
castTimeTotal = data.castTime / 1000.0f;
|
castTimeTotal = data.castTime / 1000.0f;
|
||||||
castTimeRemaining = castTimeTotal;
|
castTimeRemaining = castTimeTotal;
|
||||||
|
|
||||||
|
// Play precast (channeling) sound
|
||||||
|
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
||||||
|
if (auto* ssm = renderer->getSpellSoundManager()) {
|
||||||
|
ssm->playPrecast(audio::SpellSoundManager::MagicSchool::ARCANE, audio::SpellSoundManager::SpellPower::MEDIUM);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7040,6 +7046,13 @@ void GameHandler::handleSpellGo(network::Packet& packet) {
|
||||||
|
|
||||||
// Cast completed
|
// Cast completed
|
||||||
if (data.casterUnit == playerGuid) {
|
if (data.casterUnit == playerGuid) {
|
||||||
|
// Play cast-complete sound before clearing state
|
||||||
|
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
||||||
|
if (auto* ssm = renderer->getSpellSoundManager()) {
|
||||||
|
ssm->playCast(audio::SpellSoundManager::MagicSchool::ARCANE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
casting = false;
|
casting = false;
|
||||||
currentCastSpellId = 0;
|
currentCastSpellId = 0;
|
||||||
castTimeRemaining = 0.0f;
|
castTimeRemaining = 0.0f;
|
||||||
|
|
|
||||||
|
|
@ -543,14 +543,16 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) {
|
||||||
float chatH = 220.0f;
|
float chatH = 220.0f;
|
||||||
float chatX = 8.0f;
|
float chatX = 8.0f;
|
||||||
float chatY = screenH - chatH - 80.0f; // Above action bar
|
float chatY = screenH - chatH - 80.0f; // Above action bar
|
||||||
if (!chatWindowPosInit_) {
|
|
||||||
chatWindowPos_ = ImVec2(chatX, chatY);
|
|
||||||
chatWindowPosInit_ = true;
|
|
||||||
}
|
|
||||||
if (chatWindowLocked) {
|
if (chatWindowLocked) {
|
||||||
|
// Always recompute position from current window size when locked
|
||||||
|
chatWindowPos_ = ImVec2(chatX, chatY);
|
||||||
ImGui::SetNextWindowSize(ImVec2(chatW, chatH), ImGuiCond_Always);
|
ImGui::SetNextWindowSize(ImVec2(chatW, chatH), ImGuiCond_Always);
|
||||||
ImGui::SetNextWindowPos(chatWindowPos_, ImGuiCond_Always);
|
ImGui::SetNextWindowPos(chatWindowPos_, ImGuiCond_Always);
|
||||||
} else {
|
} else {
|
||||||
|
if (!chatWindowPosInit_) {
|
||||||
|
chatWindowPos_ = ImVec2(chatX, chatY);
|
||||||
|
chatWindowPosInit_ = true;
|
||||||
|
}
|
||||||
ImGui::SetNextWindowSize(ImVec2(chatW, chatH), ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowSize(ImVec2(chatW, chatH), ImGuiCond_FirstUseEver);
|
||||||
ImGui::SetNextWindowPos(chatWindowPos_, ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowPos(chatWindowPos_, ImGuiCond_FirstUseEver);
|
||||||
}
|
}
|
||||||
|
|
@ -3035,10 +3037,10 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
|
||||||
float barX = (screenW - barW) / 2.0f;
|
float barX = (screenW - barW) / 2.0f;
|
||||||
float barY = screenH - barH;
|
float barY = screenH - barH;
|
||||||
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(barX, barY), ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowPos(ImVec2(barX, barY), ImGuiCond_Always);
|
||||||
ImGui::SetNextWindowSize(ImVec2(barW, barH), ImGuiCond_Always);
|
ImGui::SetNextWindowSize(ImVec2(barW, barH), ImGuiCond_Always);
|
||||||
|
|
||||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize |
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
||||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
|
||||||
ImGuiWindowFlags_NoScrollbar;
|
ImGuiWindowFlags_NoScrollbar;
|
||||||
|
|
||||||
|
|
@ -3098,10 +3100,12 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
|
||||||
// Try to get icon texture for this slot
|
// Try to get icon texture for this slot
|
||||||
GLuint iconTex = 0;
|
GLuint iconTex = 0;
|
||||||
const game::ItemDef* barItemDef = nullptr;
|
const game::ItemDef* barItemDef = nullptr;
|
||||||
|
uint32_t itemDisplayInfoId = 0;
|
||||||
|
std::string itemNameFromQuery;
|
||||||
if (slot.type == game::ActionBarSlot::SPELL && slot.id != 0) {
|
if (slot.type == game::ActionBarSlot::SPELL && slot.id != 0) {
|
||||||
iconTex = getSpellIcon(slot.id, assetMgr);
|
iconTex = getSpellIcon(slot.id, assetMgr);
|
||||||
} else if (slot.type == game::ActionBarSlot::ITEM && slot.id != 0) {
|
} else if (slot.type == game::ActionBarSlot::ITEM && slot.id != 0) {
|
||||||
// Look up item in inventory for icon and name
|
// Search backpack
|
||||||
auto& inv = gameHandler.getInventory();
|
auto& inv = gameHandler.getInventory();
|
||||||
for (int bi = 0; bi < inv.getBackpackSize(); bi++) {
|
for (int bi = 0; bi < inv.getBackpackSize(); bi++) {
|
||||||
const auto& bs = inv.getBackpackSlot(bi);
|
const auto& bs = inv.getBackpackSlot(bi);
|
||||||
|
|
@ -3110,8 +3114,41 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Search equipped slots
|
||||||
|
if (!barItemDef) {
|
||||||
|
for (int ei = 0; ei < game::Inventory::NUM_EQUIP_SLOTS; ei++) {
|
||||||
|
const auto& es = inv.getEquipSlot(static_cast<game::EquipSlot>(ei));
|
||||||
|
if (!es.empty() && es.item.itemId == slot.id) {
|
||||||
|
barItemDef = &es.item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Search extra bags
|
||||||
|
if (!barItemDef) {
|
||||||
|
for (int bag = 0; bag < game::Inventory::NUM_BAG_SLOTS && !barItemDef; bag++) {
|
||||||
|
for (int si = 0; si < inv.getBagSize(bag); si++) {
|
||||||
|
const auto& bs = inv.getBagSlot(bag, si);
|
||||||
|
if (!bs.empty() && bs.item.itemId == slot.id) {
|
||||||
|
barItemDef = &bs.item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (barItemDef && barItemDef->displayInfoId != 0) {
|
if (barItemDef && barItemDef->displayInfoId != 0) {
|
||||||
iconTex = inventoryScreen.getItemIcon(barItemDef->displayInfoId);
|
itemDisplayInfoId = barItemDef->displayInfoId;
|
||||||
|
}
|
||||||
|
// Fallback: use item info cache (from server query responses)
|
||||||
|
if (itemDisplayInfoId == 0) {
|
||||||
|
if (auto* info = gameHandler.getItemInfo(slot.id)) {
|
||||||
|
itemDisplayInfoId = info->displayInfoId;
|
||||||
|
if (itemNameFromQuery.empty() && !info->name.empty())
|
||||||
|
itemNameFromQuery = info->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (itemDisplayInfoId != 0) {
|
||||||
|
iconTex = inventoryScreen.getItemIcon(itemDisplayInfoId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3201,18 +3238,49 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tooltip
|
// Tooltip
|
||||||
if (ImGui::IsItemHovered() && slot.type == game::ActionBarSlot::SPELL && slot.id != 0) {
|
if (ImGui::IsItemHovered() && !slot.isEmpty() && slot.id != 0) {
|
||||||
std::string fullName = getSpellName(slot.id);
|
|
||||||
ImGui::BeginTooltip();
|
ImGui::BeginTooltip();
|
||||||
ImGui::Text("%s", fullName.c_str());
|
if (slot.type == game::ActionBarSlot::SPELL) {
|
||||||
ImGui::TextDisabled("Spell ID: %u", slot.id);
|
std::string fullName = getSpellName(slot.id);
|
||||||
ImGui::EndTooltip();
|
ImGui::Text("%s", fullName.c_str());
|
||||||
} else if (ImGui::IsItemHovered() && slot.type == game::ActionBarSlot::ITEM && slot.id != 0) {
|
// Hearthstone: show bind point info
|
||||||
ImGui::BeginTooltip();
|
if (slot.id == 8690) {
|
||||||
if (barItemDef) {
|
uint32_t mapId = 0;
|
||||||
ImGui::Text("%s", barItemDef->name.c_str());
|
glm::vec3 pos;
|
||||||
} else {
|
if (gameHandler.getHomeBind(mapId, pos)) {
|
||||||
ImGui::Text("Item #%u", slot.id);
|
const char* mapName = "Unknown";
|
||||||
|
switch (mapId) {
|
||||||
|
case 0: mapName = "Eastern Kingdoms"; break;
|
||||||
|
case 1: mapName = "Kalimdor"; break;
|
||||||
|
case 530: mapName = "Outland"; break;
|
||||||
|
case 571: mapName = "Northrend"; break;
|
||||||
|
}
|
||||||
|
ImGui::TextColored(ImVec4(0.8f, 0.9f, 1.0f, 1.0f),
|
||||||
|
"Home: %s", mapName);
|
||||||
|
}
|
||||||
|
ImGui::TextDisabled("Use: Teleport home");
|
||||||
|
}
|
||||||
|
} else if (slot.type == game::ActionBarSlot::ITEM) {
|
||||||
|
if (barItemDef && !barItemDef->name.empty()) {
|
||||||
|
ImGui::Text("%s", barItemDef->name.c_str());
|
||||||
|
} else if (!itemNameFromQuery.empty()) {
|
||||||
|
ImGui::Text("%s", itemNameFromQuery.c_str());
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Item #%u", slot.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Show cooldown time remaining
|
||||||
|
if (onCooldown) {
|
||||||
|
float cd = slot.cooldownRemaining;
|
||||||
|
if (cd >= 60.0f) {
|
||||||
|
int mins = static_cast<int>(cd) / 60;
|
||||||
|
int secs = static_cast<int>(cd) % 60;
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f),
|
||||||
|
"Cooldown: %d min %d sec", mins, secs);
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f),
|
||||||
|
"Cooldown: %.1f sec", cd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndTooltip();
|
ImGui::EndTooltip();
|
||||||
}
|
}
|
||||||
|
|
@ -3301,10 +3369,10 @@ void GameScreen::renderBagBar(game::GameHandler& gameHandler) {
|
||||||
float barX = screenW - barW - 10.0f;
|
float barX = screenW - barW - 10.0f;
|
||||||
float barY = screenH - barH - 10.0f;
|
float barY = screenH - barH - 10.0f;
|
||||||
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(barX, barY), ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowPos(ImVec2(barX, barY), ImGuiCond_Always);
|
||||||
ImGui::SetNextWindowSize(ImVec2(barW, barH), ImGuiCond_Always);
|
ImGui::SetNextWindowSize(ImVec2(barW, barH), ImGuiCond_Always);
|
||||||
|
|
||||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize |
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
||||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
|
||||||
ImGuiWindowFlags_NoScrollbar;
|
ImGuiWindowFlags_NoScrollbar;
|
||||||
|
|
||||||
|
|
@ -3534,8 +3602,12 @@ void GameScreen::renderCastBar(game::GameHandler& gameHandler) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.8f, 0.6f, 0.2f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.8f, 0.6f, 0.2f, 1.0f));
|
||||||
|
|
||||||
char overlay[64];
|
char overlay[64];
|
||||||
snprintf(overlay, sizeof(overlay), "Spell %u (%.1fs)",
|
uint32_t currentSpellId = gameHandler.getCurrentCastSpellId();
|
||||||
gameHandler.getCurrentCastSpellId(), gameHandler.getCastTimeRemaining());
|
const std::string& spellName = gameHandler.getSpellName(currentSpellId);
|
||||||
|
if (!spellName.empty())
|
||||||
|
snprintf(overlay, sizeof(overlay), "%s (%.1fs)", spellName.c_str(), gameHandler.getCastTimeRemaining());
|
||||||
|
else
|
||||||
|
snprintf(overlay, sizeof(overlay), "Casting... (%.1fs)", gameHandler.getCastTimeRemaining());
|
||||||
ImGui::ProgressBar(progress, ImVec2(-1, 20), overlay);
|
ImGui::ProgressBar(progress, ImVec2(-1, 20), overlay);
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1415,11 +1415,20 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item) {
|
||||||
uint32_t mapId = 0;
|
uint32_t mapId = 0;
|
||||||
glm::vec3 pos;
|
glm::vec3 pos;
|
||||||
if (gameHandler_->getHomeBind(mapId, pos)) {
|
if (gameHandler_->getHomeBind(mapId, pos)) {
|
||||||
ImGui::TextColored(ImVec4(0.8f, 0.9f, 1.0f, 1.0f),
|
const char* mapName = "Unknown";
|
||||||
"Home: map %u (%.1f, %.1f, %.1f)", mapId, pos.x, pos.y, pos.z);
|
switch (mapId) {
|
||||||
|
case 0: mapName = "Eastern Kingdoms"; break;
|
||||||
|
case 1: mapName = "Kalimdor"; break;
|
||||||
|
case 530: mapName = "Outland"; break;
|
||||||
|
case 571: mapName = "Northrend"; break;
|
||||||
|
case 13: mapName = "Test"; break;
|
||||||
|
case 169: mapName = "Emerald Dream"; break;
|
||||||
|
}
|
||||||
|
ImGui::TextColored(ImVec4(0.8f, 0.9f, 1.0f, 1.0f), "Home: %s", mapName);
|
||||||
} else {
|
} else {
|
||||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Home: not set");
|
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Home: not set");
|
||||||
}
|
}
|
||||||
|
ImGui::TextDisabled("Use: Teleport home");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slot type
|
// Slot type
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@ void RealmScreen::render(auth::AuthHandler& authHandler) {
|
||||||
ImGui::Begin("Realm Selection", nullptr,
|
ImGui::Begin("Realm Selection", nullptr,
|
||||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
|
||||||
|
|
||||||
|
// Header with title and back button
|
||||||
|
if (ImGui::Button("Back", ImVec2(100, 36))) {
|
||||||
|
if (onBack) onBack();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 6.0f);
|
||||||
ImGui::Text("Select a Realm");
|
ImGui::Text("Select a Realm");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
@ -38,140 +44,184 @@ void RealmScreen::render(auth::AuthHandler& authHandler) {
|
||||||
if (realms.empty()) {
|
if (realms.empty()) {
|
||||||
ImGui::Text("No realms available. Requesting realm list...");
|
ImGui::Text("No realms available. Requesting realm list...");
|
||||||
authHandler.requestRealmList();
|
authHandler.requestRealmList();
|
||||||
} else if (realms.size() == 1 && !realmSelected && !realms[0].lock) {
|
|
||||||
// Auto-select the only available realm
|
|
||||||
selectedRealmIndex = 0;
|
|
||||||
realmSelected = true;
|
|
||||||
selectedRealmName = realms[0].name;
|
|
||||||
selectedRealmAddress = realms[0].address;
|
|
||||||
setStatus("Auto-selecting realm: " + realms[0].name);
|
|
||||||
if (onRealmSelected) {
|
|
||||||
onRealmSelected(selectedRealmName, selectedRealmAddress);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Realm table
|
// Auto-select: prefer realm with characters, then single realm, then first available
|
||||||
if (ImGui::BeginTable("RealmsTable", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
|
if (!autoSelectAttempted && !realmSelected) {
|
||||||
|
autoSelectAttempted = true;
|
||||||
|
|
||||||
|
// First: look for realm with characters
|
||||||
|
int bestRealm = -1;
|
||||||
|
for (size_t i = 0; i < realms.size(); ++i) {
|
||||||
|
if (!realms[i].lock && realms[i].characters > 0) {
|
||||||
|
bestRealm = static_cast<int>(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If only one realm and it's unlocked, auto-connect
|
||||||
|
if (realms.size() == 1 && !realms[0].lock) {
|
||||||
|
selectedRealmIndex = 0;
|
||||||
|
realmSelected = true;
|
||||||
|
selectedRealmName = realms[0].name;
|
||||||
|
selectedRealmAddress = realms[0].address;
|
||||||
|
setStatus("Auto-selecting realm: " + realms[0].name);
|
||||||
|
if (onRealmSelected) {
|
||||||
|
onRealmSelected(selectedRealmName, selectedRealmAddress);
|
||||||
|
}
|
||||||
|
} else if (bestRealm >= 0) {
|
||||||
|
// Pre-highlight realm with characters (don't auto-connect, let user confirm)
|
||||||
|
selectedRealmIndex = bestRealm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate row height for table - use more vertical space
|
||||||
|
float rowHeight = std::max(28.0f, ImGui::GetTextLineHeight() + 16.0f);
|
||||||
|
|
||||||
|
// Reserve space for bottom panel (selected realm info + buttons)
|
||||||
|
float bottomPanelHeight = 120.0f;
|
||||||
|
float tableHeight = ImGui::GetContentRegionAvail().y - bottomPanelHeight;
|
||||||
|
if (tableHeight < 200.0f) tableHeight = 200.0f;
|
||||||
|
|
||||||
|
// Realm table - fills available width and height
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(12.0f, 8.0f));
|
||||||
|
if (ImGui::BeginTable("RealmsTable", 5,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, tableHeight))) {
|
||||||
|
|
||||||
|
// Proportional columns
|
||||||
|
float totalW = ImGui::GetContentRegionAvail().x;
|
||||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 60.0f);
|
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, totalW * 0.12f);
|
||||||
ImGui::TableSetupColumn("Population", ImGuiTableColumnFlags_WidthFixed, 80.0f);
|
ImGui::TableSetupColumn("Population", ImGuiTableColumnFlags_WidthFixed, totalW * 0.14f);
|
||||||
ImGui::TableSetupColumn("Characters", ImGuiTableColumnFlags_WidthFixed, 80.0f);
|
ImGui::TableSetupColumn("Characters", ImGuiTableColumnFlags_WidthFixed, totalW * 0.12f);
|
||||||
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 100.0f);
|
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, totalW * 0.12f);
|
||||||
ImGui::TableHeadersRow();
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
for (size_t i = 0; i < realms.size(); ++i) {
|
for (size_t i = 0; i < realms.size(); ++i) {
|
||||||
const auto& realm = realms[i];
|
const auto& realm = realms[i];
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow(0, rowHeight);
|
||||||
|
|
||||||
// Name column (selectable)
|
// Name column (selectable, double-click to enter)
|
||||||
ImGui::TableSetColumnIndex(0);
|
ImGui::TableSetColumnIndex(0);
|
||||||
bool isSelected = (selectedRealmIndex == static_cast<int>(i));
|
bool isSelected = (selectedRealmIndex == static_cast<int>(i));
|
||||||
if (ImGui::Selectable(realm.name.c_str(), isSelected, ImGuiSelectableFlags_SpanAllColumns)) {
|
char nameLabel[256];
|
||||||
|
snprintf(nameLabel, sizeof(nameLabel), "%s##realm%zu", realm.name.c_str(), i);
|
||||||
|
if (ImGui::Selectable(nameLabel, isSelected,
|
||||||
|
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick,
|
||||||
|
ImVec2(0, rowHeight - 8.0f))) {
|
||||||
selectedRealmIndex = static_cast<int>(i);
|
selectedRealmIndex = static_cast<int>(i);
|
||||||
|
if (ImGui::IsMouseDoubleClicked(0) && !realm.lock) {
|
||||||
|
realmSelected = true;
|
||||||
|
selectedRealmName = realm.name;
|
||||||
|
selectedRealmAddress = realm.address;
|
||||||
|
setStatus("Connecting to realm: " + realm.name);
|
||||||
|
if (onRealmSelected) {
|
||||||
|
onRealmSelected(selectedRealmName, selectedRealmAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type column
|
// Type column
|
||||||
ImGui::TableSetColumnIndex(1);
|
ImGui::TableSetColumnIndex(1);
|
||||||
if (realm.icon == 0) {
|
if (realm.icon == 0) ImGui::Text("Normal");
|
||||||
ImGui::Text("Normal");
|
else if (realm.icon == 1) ImGui::Text("PvP");
|
||||||
} else if (realm.icon == 1) {
|
else if (realm.icon == 4) ImGui::Text("RP");
|
||||||
ImGui::Text("PvP");
|
else if (realm.icon == 6) ImGui::Text("RP-PvP");
|
||||||
} else if (realm.icon == 4) {
|
else ImGui::Text("Type %d", realm.icon);
|
||||||
ImGui::Text("RP");
|
|
||||||
} else if (realm.icon == 6) {
|
|
||||||
ImGui::Text("RP-PvP");
|
|
||||||
} else {
|
|
||||||
ImGui::Text("Type %d", realm.icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Population column
|
// Population column
|
||||||
ImGui::TableSetColumnIndex(2);
|
ImGui::TableSetColumnIndex(2);
|
||||||
ImVec4 popColor = getPopulationColor(realm.population);
|
ImVec4 popColor = getPopulationColor(realm.population);
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, popColor);
|
ImGui::PushStyleColor(ImGuiCol_Text, popColor);
|
||||||
if (realm.population < 0.5f) {
|
if (realm.population < 0.5f) ImGui::Text("Low");
|
||||||
ImGui::Text("Low");
|
else if (realm.population < 1.0f) ImGui::Text("Medium");
|
||||||
} else if (realm.population < 1.0f) {
|
else if (realm.population < 2.0f) ImGui::Text("High");
|
||||||
ImGui::Text("Medium");
|
else ImGui::Text("Full");
|
||||||
} else if (realm.population < 2.0f) {
|
|
||||||
ImGui::Text("High");
|
|
||||||
} else {
|
|
||||||
ImGui::Text("Full");
|
|
||||||
}
|
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
// Characters column
|
// Characters column
|
||||||
ImGui::TableSetColumnIndex(3);
|
ImGui::TableSetColumnIndex(3);
|
||||||
ImGui::Text("%d", realm.characters);
|
if (realm.characters > 0) {
|
||||||
|
ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f), "%d", realm.characters);
|
||||||
|
} else {
|
||||||
|
ImGui::TextDisabled("0");
|
||||||
|
}
|
||||||
|
|
||||||
// Status column
|
// Status column
|
||||||
ImGui::TableSetColumnIndex(4);
|
ImGui::TableSetColumnIndex(4);
|
||||||
const char* status = getRealmStatus(realm.flags);
|
const char* status = getRealmStatus(realm.flags);
|
||||||
if (realm.lock) {
|
if (realm.lock) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
|
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Locked");
|
||||||
ImGui::Text("Locked");
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
} else {
|
} else {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 1.0f, 0.3f, 1.0f));
|
ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "%s", status);
|
||||||
ImGui::Text("%s", status);
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
|
ImGui::PopStyleVar(); // CellPadding
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
// Selected realm info
|
// Bottom panel: selected realm info + action buttons
|
||||||
if (selectedRealmIndex >= 0 && selectedRealmIndex < static_cast<int>(realms.size())) {
|
if (selectedRealmIndex >= 0 && selectedRealmIndex < static_cast<int>(realms.size())) {
|
||||||
const auto& realm = realms[selectedRealmIndex];
|
const auto& realm = realms[selectedRealmIndex];
|
||||||
|
|
||||||
ImGui::Text("Selected Realm:");
|
ImGui::Text("Selected: %s", realm.name.c_str());
|
||||||
ImGui::Indent();
|
ImGui::SameLine();
|
||||||
ImGui::Text("Name: %s", realm.name.c_str());
|
ImGui::TextDisabled("(%s)", realm.address.c_str());
|
||||||
ImGui::Text("Address: %s", realm.address.c_str());
|
if (realm.characters > 0) {
|
||||||
ImGui::Text("Characters: %d", realm.characters);
|
ImGui::SameLine();
|
||||||
if (realm.hasVersionInfo()) {
|
ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f),
|
||||||
ImGui::Text("Version: %d.%d.%d (build %d)",
|
" - %d character%s", realm.characters, realm.characters > 1 ? "s" : "");
|
||||||
realm.majorVersion, realm.minorVersion, realm.patchVersion, realm.build);
|
}
|
||||||
|
if (realm.hasVersionInfo()) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextDisabled(" v%d.%d.%d",
|
||||||
|
realm.majorVersion, realm.minorVersion, realm.patchVersion);
|
||||||
}
|
}
|
||||||
ImGui::Unindent();
|
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
// Connect button
|
// Enter Realm button (large)
|
||||||
if (!realm.lock) {
|
if (!realm.lock) {
|
||||||
if (ImGui::Button("Enter Realm", ImVec2(120, 0))) {
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.45f, 0.15f, 1.0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.6f, 0.2f, 1.0f));
|
||||||
|
if (ImGui::Button("Enter Realm", ImVec2(200, 40))) {
|
||||||
realmSelected = true;
|
realmSelected = true;
|
||||||
selectedRealmName = realm.name;
|
selectedRealmName = realm.name;
|
||||||
selectedRealmAddress = realm.address;
|
selectedRealmAddress = realm.address;
|
||||||
setStatus("Connecting to realm: " + realm.name);
|
setStatus("Connecting to realm: " + realm.name);
|
||||||
|
|
||||||
// Call callback
|
|
||||||
if (onRealmSelected) {
|
if (onRealmSelected) {
|
||||||
onRealmSelected(selectedRealmName, selectedRealmAddress);
|
onRealmSelected(selectedRealmName, selectedRealmAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
} else {
|
} else {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
|
||||||
ImGui::Button("Realm Locked", ImVec2(120, 0));
|
ImGui::Button("Realm Locked", ImVec2(200, 40));
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine(0, 16.0f);
|
||||||
|
if (ImGui::Button("Refresh", ImVec2(120, 40))) {
|
||||||
|
authHandler.requestRealmList();
|
||||||
|
setStatus("Refreshing realm list...");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::TextDisabled("Click a realm to select it, or double-click to enter.");
|
||||||
|
ImGui::Spacing();
|
||||||
|
if (ImGui::Button("Refresh", ImVec2(120, 40))) {
|
||||||
|
authHandler.requestRealmList();
|
||||||
|
setStatus("Refreshing realm list...");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
// Refresh button
|
|
||||||
if (ImGui::Button("Refresh Realm List", ImVec2(150, 0))) {
|
|
||||||
authHandler.requestRealmList();
|
|
||||||
setStatus("Refreshing realm list...");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue