mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-08 01:53:52 +00:00
Add Up/Down arrow keys to cycle through character list and Enter to select. Claim arrow key ownership via SetKeyOwner to prevent ImGui nav from moving focus to other widgets. Lock Enter key until release to prevent the keypress from activating chat on the game screen. Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
576 lines
23 KiB
C++
576 lines
23 KiB
C++
#include "ui/character_screen.hpp"
|
|
#include "ui/ui_colors.hpp"
|
|
#include "rendering/character_preview.hpp"
|
|
#include "rendering/renderer.hpp"
|
|
#include "pipeline/asset_manager.hpp"
|
|
#include "core/application.hpp"
|
|
#include "core/logger.hpp"
|
|
#include <imgui.h>
|
|
#include <imgui_internal.h>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
|
|
namespace wowee { namespace ui {
|
|
|
|
CharacterScreen::CharacterScreen() {
|
|
}
|
|
|
|
static uint64_t hashEquipment(const std::vector<game::EquipmentItem>& eq) {
|
|
// FNV-1a 64-bit over (displayModel, inventoryType, enchantment)
|
|
uint64_t h = 1469598103934665603ull;
|
|
auto mix8 = [&](uint8_t b) {
|
|
h ^= b;
|
|
h *= 1099511628211ull;
|
|
};
|
|
auto mix32 = [&](uint32_t v) {
|
|
mix8(static_cast<uint8_t>(v & 0xFF));
|
|
mix8(static_cast<uint8_t>((v >> 8) & 0xFF));
|
|
mix8(static_cast<uint8_t>((v >> 16) & 0xFF));
|
|
mix8(static_cast<uint8_t>((v >> 24) & 0xFF));
|
|
};
|
|
for (const auto& it : eq) {
|
|
mix32(it.displayModel);
|
|
mix8(it.inventoryType);
|
|
mix32(it.enchantment);
|
|
}
|
|
return h;
|
|
}
|
|
|
|
static ImVec4 classColor(uint8_t classId) { return ui::getClassColor(classId); }
|
|
|
|
void CharacterScreen::render(game::GameHandler& gameHandler) {
|
|
ImGuiViewport* vp = ImGui::GetMainViewport();
|
|
const ImVec2 pad(24.0f, 24.0f);
|
|
ImVec2 winSize(vp->Size.x - pad.x * 2.0f, vp->Size.y - pad.y * 2.0f);
|
|
if (winSize.x < 860.0f) winSize.x = 860.0f;
|
|
if (winSize.y < 620.0f) winSize.y = 620.0f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(vp->Pos.x + (vp->Size.x - winSize.x) * 0.5f,
|
|
vp->Pos.y + (vp->Size.y - winSize.y) * 0.5f),
|
|
ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(winSize, ImGuiCond_Always);
|
|
|
|
ImGui::Begin("Character Selection", nullptr,
|
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
|
|
|
|
// Ensure we can render a preview even if the state transition hook didn't inject the AssetManager.
|
|
if (!assetManager_) {
|
|
assetManager_ = core::Application::getInstance().getAssetManager();
|
|
}
|
|
|
|
// Get character list
|
|
const auto& characters = gameHandler.getCharacters();
|
|
|
|
// Request character list if not available.
|
|
// Also show a loading state while CHAR_LIST_REQUESTED is in-flight (characters may be cleared to avoid stale UI).
|
|
if (characters.empty() &&
|
|
(gameHandler.getState() == game::WorldState::READY ||
|
|
gameHandler.getState() == game::WorldState::CHAR_LIST_REQUESTED)) {
|
|
ImGui::Text("Loading characters...");
|
|
if (gameHandler.getState() == game::WorldState::READY) {
|
|
gameHandler.requestCharacterList();
|
|
}
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// Handle disconnected state with no characters received
|
|
if (characters.empty() &&
|
|
(gameHandler.getState() == game::WorldState::DISCONNECTED ||
|
|
gameHandler.getState() == game::WorldState::FAILED)) {
|
|
ImGui::TextColored(ui::colors::kSoftRed, "Disconnected from server.");
|
|
ImGui::TextWrapped("The server closed the connection before sending the character list.");
|
|
ImGui::Spacing();
|
|
if (ImGui::Button("Back", ImVec2(120, 36))) { if (onBack) onBack(); }
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
if (characters.empty()) {
|
|
ImGui::Text("No characters available.");
|
|
// Bottom buttons even when empty
|
|
ImGui::Spacing();
|
|
if (ImGui::Button("Back", ImVec2(120, 36))) { if (onBack) onBack(); }
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Refresh", ImVec2(120, 36))) {
|
|
if (gameHandler.getState() == game::WorldState::READY ||
|
|
gameHandler.getState() == game::WorldState::CHAR_LIST_RECEIVED) {
|
|
gameHandler.requestCharacterList();
|
|
setStatus("Refreshing character list...");
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Create Character", ImVec2(160, 36))) { if (onCreateCharacter) onCreateCharacter(); }
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// If the list refreshed, keep selection stable by GUID.
|
|
if (selectedCharacterGuid != 0) {
|
|
const bool needReselect =
|
|
(selectedCharacterIndex < 0) ||
|
|
(selectedCharacterIndex >= static_cast<int>(characters.size())) ||
|
|
(characters[static_cast<size_t>(selectedCharacterIndex)].guid != selectedCharacterGuid);
|
|
if (needReselect) {
|
|
for (size_t i = 0; i < characters.size(); ++i) {
|
|
if (characters[i].guid == selectedCharacterGuid) {
|
|
selectedCharacterIndex = static_cast<int>(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restore last-selected character (once per screen visit)
|
|
if (!restoredLastCharacter) {
|
|
// Priority 1: Select newly created character if set
|
|
if (!newlyCreatedCharacterName.empty()) {
|
|
for (size_t i = 0; i < characters.size(); ++i) {
|
|
if (characters[i].name == newlyCreatedCharacterName) {
|
|
selectedCharacterIndex = static_cast<int>(i);
|
|
selectedCharacterGuid = characters[i].guid;
|
|
saveLastCharacter(characters[i].guid);
|
|
newlyCreatedCharacterName.clear();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Priority 2: Restore last selected character
|
|
if (selectedCharacterIndex < 0) {
|
|
uint64_t lastGuid = loadLastCharacter();
|
|
if (lastGuid != 0) {
|
|
for (size_t i = 0; i < characters.size(); ++i) {
|
|
if (characters[i].guid == lastGuid) {
|
|
selectedCharacterIndex = static_cast<int>(i);
|
|
selectedCharacterGuid = lastGuid;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Fall back to first character if nothing matched
|
|
if (selectedCharacterIndex < 0) {
|
|
selectedCharacterIndex = 0;
|
|
selectedCharacterGuid = characters[0].guid;
|
|
}
|
|
restoredLastCharacter = true;
|
|
}
|
|
|
|
// Status message
|
|
if (!statusMessage.empty()) {
|
|
ImVec4 color = statusIsError ? ui::colors::kRed : ui::colors::kBrightGreen;
|
|
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
|
ImGui::TextWrapped("%s", statusMessage.c_str());
|
|
ImGui::PopStyleColor();
|
|
ImGui::Spacing();
|
|
}
|
|
|
|
// ── Two-column layout: character list (left) | details (right) ──
|
|
float availW = ImGui::GetContentRegionAvail().x;
|
|
float detailPanelW = 360.0f;
|
|
float listW = availW - detailPanelW - ImGui::GetStyle().ItemSpacing.x;
|
|
if (listW < 300.0f) { listW = availW; detailPanelW = 0.0f; }
|
|
|
|
float listH = ImGui::GetContentRegionAvail().y - 50.0f; // reserve bottom row for buttons
|
|
|
|
// ── Left: Character list ──
|
|
ImGui::BeginChild("CharList", ImVec2(listW, listH), true);
|
|
ImGui::Text("Characters");
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::BeginTable("CharactersTable", 5,
|
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
|
|
ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp)) {
|
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 2.0f);
|
|
ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed, 45.0f);
|
|
ImGui::TableSetupColumn("Race", ImGuiTableColumnFlags_WidthStretch, 1.0f);
|
|
ImGui::TableSetupColumn("Class", ImGuiTableColumnFlags_WidthStretch, 1.2f);
|
|
ImGui::TableSetupColumn("Zone", ImGuiTableColumnFlags_WidthStretch, 1.5f);
|
|
ImGui::TableSetupScrollFreeze(0, 1);
|
|
ImGui::TableHeadersRow();
|
|
|
|
for (size_t i = 0; i < characters.size(); ++i) {
|
|
const auto& character = characters[i];
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
|
|
bool isSelected = (selectedCharacterIndex == static_cast<int>(i));
|
|
ImVec4 factionColor = getFactionColor(character.race);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, factionColor);
|
|
|
|
ImGui::PushID(static_cast<int>(i));
|
|
if (ImGui::Selectable(character.name.c_str(), isSelected,
|
|
ImGuiSelectableFlags_SpanAllColumns)) {
|
|
selectedCharacterIndex = static_cast<int>(i);
|
|
selectedCharacterGuid = character.guid;
|
|
saveLastCharacter(character.guid);
|
|
}
|
|
|
|
// Double-click to enter world
|
|
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
|
selectedCharacterIndex = static_cast<int>(i);
|
|
selectedCharacterGuid = character.guid;
|
|
saveLastCharacter(character.guid);
|
|
characterSelected = true;
|
|
gameHandler.selectCharacter(character.guid);
|
|
if (onCharacterSelected) onCharacterSelected(character.guid);
|
|
}
|
|
ImGui::PopID();
|
|
ImGui::PopStyleColor();
|
|
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::Text("%d", character.level);
|
|
|
|
ImGui::TableSetColumnIndex(2);
|
|
ImGui::Text("%s", game::getRaceName(character.race));
|
|
|
|
ImGui::TableSetColumnIndex(3);
|
|
ImGui::TextColored(classColor(static_cast<uint8_t>(character.characterClass)), "%s", game::getClassName(character.characterClass));
|
|
|
|
ImGui::TableSetColumnIndex(4);
|
|
{
|
|
std::string zoneName = gameHandler.getWhoAreaName(character.zoneId);
|
|
if (!zoneName.empty())
|
|
ImGui::TextUnformatted(zoneName.c_str());
|
|
else
|
|
ImGui::Text("%u", character.zoneId);
|
|
}
|
|
}
|
|
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
// Keyboard navigation: Up/Down to change selection, Enter to enter world
|
|
// Claim ownership of arrow keys so ImGui nav doesn't move focus between buttons
|
|
ImGuiID charListOwner = ImGui::GetID("CharListNav");
|
|
ImGui::SetKeyOwner(ImGuiKey_UpArrow, charListOwner);
|
|
ImGui::SetKeyOwner(ImGuiKey_DownArrow, charListOwner);
|
|
|
|
if (!characters.empty() && deleteConfirmStage == 0) {
|
|
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
|
|
if (selectedCharacterIndex > 0) {
|
|
selectedCharacterIndex--;
|
|
selectedCharacterGuid = characters[selectedCharacterIndex].guid;
|
|
saveLastCharacter(selectedCharacterGuid);
|
|
}
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
|
|
if (selectedCharacterIndex < static_cast<int>(characters.size()) - 1) {
|
|
selectedCharacterIndex++;
|
|
selectedCharacterGuid = characters[selectedCharacterIndex].guid;
|
|
saveLastCharacter(selectedCharacterGuid);
|
|
}
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)) {
|
|
if (selectedCharacterIndex >= 0 &&
|
|
selectedCharacterIndex < static_cast<int>(characters.size())) {
|
|
const auto& ch = characters[selectedCharacterIndex];
|
|
characterSelected = true;
|
|
saveLastCharacter(ch.guid);
|
|
// Claim Enter so the game screen doesn't activate chat on the same press
|
|
ImGui::SetKeyOwner(ImGuiKey_Enter, charListOwner, ImGuiInputFlags_LockUntilRelease);
|
|
ImGui::SetKeyOwner(ImGuiKey_KeypadEnter, charListOwner, ImGuiInputFlags_LockUntilRelease);
|
|
gameHandler.selectCharacter(ch.guid);
|
|
if (onCharacterSelected) onCharacterSelected(ch.guid);
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::EndChild();
|
|
|
|
// ── Right: Details panel ──
|
|
if (detailPanelW > 0.0f &&
|
|
selectedCharacterIndex >= 0 &&
|
|
selectedCharacterIndex < static_cast<int>(characters.size())) {
|
|
|
|
const auto& character = characters[selectedCharacterIndex];
|
|
|
|
// Keep the 3D preview in sync with the selected character.
|
|
if (assetManager_ && assetManager_->isInitialized()) {
|
|
if (!preview_) {
|
|
preview_ = std::make_unique<rendering::CharacterPreview>();
|
|
}
|
|
if (!previewInitialized_) {
|
|
previewInitialized_ = preview_->initialize(assetManager_);
|
|
if (!previewInitialized_) {
|
|
LOG_WARNING("CharacterScreen: failed to init CharacterPreview");
|
|
preview_.reset();
|
|
} else {
|
|
auto* renderer = core::Application::getInstance().getRenderer();
|
|
if (renderer) renderer->registerPreview(preview_.get());
|
|
}
|
|
}
|
|
if (preview_) {
|
|
const uint64_t equipHash = hashEquipment(character.equipment);
|
|
const bool changed =
|
|
(previewGuid_ != character.guid) ||
|
|
(previewAppearanceBytes_ != character.appearanceBytes) ||
|
|
(previewFacialFeatures_ != character.facialFeatures) ||
|
|
(previewUseFemaleModel_ != character.useFemaleModel) ||
|
|
(previewEquipHash_ != equipHash);
|
|
|
|
if (changed) {
|
|
uint8_t skin = character.appearanceBytes & 0xFF;
|
|
uint8_t face = (character.appearanceBytes >> 8) & 0xFF;
|
|
uint8_t hairStyle = (character.appearanceBytes >> 16) & 0xFF;
|
|
uint8_t hairColor = (character.appearanceBytes >> 24) & 0xFF;
|
|
|
|
if (preview_->loadCharacter(character.race, character.gender,
|
|
skin, face, hairStyle, hairColor,
|
|
character.facialFeatures, character.useFemaleModel)) {
|
|
preview_->applyEquipment(character.equipment);
|
|
}
|
|
|
|
previewGuid_ = character.guid;
|
|
previewAppearanceBytes_ = character.appearanceBytes;
|
|
previewFacialFeatures_ = character.facialFeatures;
|
|
previewUseFemaleModel_ = character.useFemaleModel;
|
|
previewEquipHash_ = equipHash;
|
|
}
|
|
|
|
// Drive preview animation and request composite for next beginFrame.
|
|
preview_->update(ImGui::GetIO().DeltaTime);
|
|
preview_->render();
|
|
preview_->requestComposite();
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
ImGui::BeginChild("CharDetails", ImVec2(detailPanelW, listH), true);
|
|
|
|
// 3D preview portrait
|
|
if (preview_ && preview_->getTextureId()) {
|
|
float imgW = ImGui::GetContentRegionAvail().x;
|
|
float imgH = imgW * (static_cast<float>(preview_->getHeight()) /
|
|
static_cast<float>(preview_->getWidth()));
|
|
// Clamp to avoid taking the entire panel
|
|
float maxH = 320.0f;
|
|
if (imgH > maxH) {
|
|
imgH = maxH;
|
|
imgW = imgH * (static_cast<float>(preview_->getWidth()) /
|
|
static_cast<float>(preview_->getHeight()));
|
|
}
|
|
ImGui::Image(
|
|
reinterpret_cast<ImTextureID>(preview_->getTextureId()),
|
|
ImVec2(imgW, imgH));
|
|
|
|
if (ImGui::IsItemHovered() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
|
preview_->rotate(ImGui::GetIO().MouseDelta.x * 0.2f);
|
|
}
|
|
ImGui::Spacing();
|
|
} else if (!assetManager_ || !assetManager_->isInitialized()) {
|
|
ImGui::TextDisabled("Preview unavailable (assets not loaded)");
|
|
ImGui::Spacing();
|
|
}
|
|
|
|
ImGui::TextColored(getFactionColor(character.race), "%s", character.name.c_str());
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
ImGui::Text("Level %d", character.level);
|
|
ImGui::Text("%s", game::getRaceName(character.race));
|
|
ImGui::TextColored(classColor(static_cast<uint8_t>(character.characterClass)), "%s", game::getClassName(character.characterClass));
|
|
ImGui::Text("%s", game::getGenderName(character.gender));
|
|
ImGui::Spacing();
|
|
{
|
|
std::string zoneName = gameHandler.getWhoAreaName(character.zoneId);
|
|
if (!zoneName.empty())
|
|
ImGui::TextUnformatted(zoneName.c_str());
|
|
else
|
|
ImGui::Text("Zone %u", character.zoneId);
|
|
}
|
|
|
|
if (character.hasGuild()) {
|
|
const std::string& guildName = gameHandler.lookupGuildName(character.guildId);
|
|
if (!guildName.empty())
|
|
ImGui::Text("<%s>", guildName.c_str());
|
|
else
|
|
ImGui::TextDisabled("<Guild %u>", character.guildId);
|
|
} else {
|
|
ImGui::TextDisabled("No Guild");
|
|
}
|
|
|
|
if (character.hasPet()) {
|
|
ImGui::Spacing();
|
|
ImGui::Text("Pet Lv%d (Family %d)", character.pet.level, character.pet.family);
|
|
}
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
// Enter World button — full width
|
|
float btnW = ImGui::GetContentRegionAvail().x;
|
|
bool disconnected = (gameHandler.getState() == game::WorldState::DISCONNECTED ||
|
|
gameHandler.getState() == game::WorldState::FAILED);
|
|
if (disconnected) {
|
|
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.3f, 1.0f), "Connection lost — click Back to reconnect");
|
|
}
|
|
if (disconnected) ImGui::BeginDisabled();
|
|
if (ImGui::Button("Enter World", ImVec2(btnW, 44))) {
|
|
characterSelected = true;
|
|
saveLastCharacter(character.guid);
|
|
std::stringstream ss;
|
|
ss << "Entering world with " << character.name << "...";
|
|
setStatus(ss.str());
|
|
gameHandler.selectCharacter(character.guid);
|
|
if (onCharacterSelected) onCharacterSelected(character.guid);
|
|
}
|
|
if (disconnected) ImGui::EndDisabled();
|
|
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
// ── Bottom button row ──
|
|
ImGui::Spacing();
|
|
if (ImGui::Button("Back", ImVec2(120, 36))) { if (onBack) onBack(); }
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Refresh", ImVec2(120, 36))) {
|
|
if (gameHandler.getState() == game::WorldState::READY ||
|
|
gameHandler.getState() == game::WorldState::CHAR_LIST_RECEIVED) {
|
|
gameHandler.requestCharacterList();
|
|
setStatus("Refreshing character list...");
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Create Character", ImVec2(160, 36))) {
|
|
if (onCreateCharacter) onCreateCharacter();
|
|
}
|
|
|
|
// Delete button — small, red, far right, only when a character is selected
|
|
if (selectedCharacterIndex >= 0 &&
|
|
selectedCharacterIndex < static_cast<int>(characters.size())) {
|
|
float deleteW = 80.0f;
|
|
ImGui::SameLine(ImGui::GetContentRegionMax().x - deleteW);
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.08f, 0.08f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.1f, 0.1f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.5f, 0.5f, 1.0f));
|
|
if (ImGui::Button("Delete", ImVec2(deleteW, 28))) {
|
|
deleteConfirmStage = 1;
|
|
ImGui::OpenPopup("DeleteConfirm1");
|
|
}
|
|
ImGui::PopStyleColor(3);
|
|
}
|
|
|
|
// First confirmation popup
|
|
if (ImGui::BeginPopupModal("DeleteConfirm1", nullptr,
|
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
|
|
const auto& ch = characters[selectedCharacterIndex];
|
|
ImGui::Text("Are you sure you want to delete");
|
|
ImGui::TextColored(getFactionColor(ch.race), "%s", ch.name.c_str());
|
|
ImGui::Text("Level %d %s %s?",
|
|
ch.level, game::getRaceName(ch.race), game::getClassName(ch.characterClass));
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
if (ImGui::Button("Yes, delete this character", ImVec2(240, 32))) {
|
|
ImGui::CloseCurrentPopup();
|
|
deleteConfirmStage = 2;
|
|
ImGui::OpenPopup("DeleteConfirm2");
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel", ImVec2(100, 32))) {
|
|
deleteConfirmStage = 0;
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// Second (final) confirmation popup
|
|
if (deleteConfirmStage == 2) {
|
|
ImGui::OpenPopup("DeleteConfirm2");
|
|
}
|
|
if (ImGui::BeginPopupModal("DeleteConfirm2", nullptr,
|
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
|
|
const auto& ch = characters[selectedCharacterIndex];
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ui::colors::kRed);
|
|
ImGui::Text("THIS CANNOT BE UNDONE!");
|
|
ImGui::PopStyleColor();
|
|
ImGui::Spacing();
|
|
ImGui::Text("Are you REALLY sure you want to permanently");
|
|
ImGui::Text("delete %s? This character will be gone forever.", ch.name.c_str());
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.7f, 0.0f, 0.0f, 1.0f));
|
|
if (ImGui::Button("DELETE PERMANENTLY", ImVec2(240, 32))) {
|
|
if (onDeleteCharacter) onDeleteCharacter(ch.guid);
|
|
deleteConfirmStage = 0;
|
|
selectedCharacterIndex = -1;
|
|
selectedCharacterGuid = 0;
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::PopStyleColor();
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel", ImVec2(100, 32))) {
|
|
deleteConfirmStage = 0;
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void CharacterScreen::setStatus(const std::string& message, bool isError) {
|
|
statusMessage = message;
|
|
statusIsError = isError;
|
|
}
|
|
|
|
void CharacterScreen::selectCharacterByName(const std::string& name) {
|
|
newlyCreatedCharacterName = name;
|
|
restoredLastCharacter = false; // Allow re-selection in render()
|
|
selectedCharacterIndex = -1;
|
|
}
|
|
|
|
ImVec4 CharacterScreen::getFactionColor(game::Race race) const {
|
|
// Alliance races: blue
|
|
if (race == game::Race::HUMAN ||
|
|
race == game::Race::DWARF ||
|
|
race == game::Race::NIGHT_ELF ||
|
|
race == game::Race::GNOME ||
|
|
race == game::Race::DRAENEI) {
|
|
return ImVec4(0.3f, 0.5f, 1.0f, 1.0f);
|
|
}
|
|
|
|
// Horde races: red
|
|
if (race == game::Race::ORC ||
|
|
race == game::Race::UNDEAD ||
|
|
race == game::Race::TAUREN ||
|
|
race == game::Race::TROLL ||
|
|
race == game::Race::BLOOD_ELF) {
|
|
return ui::colors::kRed;
|
|
}
|
|
|
|
return ui::colors::kWhite;
|
|
}
|
|
|
|
std::string CharacterScreen::getConfigDir() {
|
|
#ifdef _WIN32
|
|
const char* appdata = std::getenv("APPDATA");
|
|
return appdata ? std::string(appdata) + "\\wowee" : ".";
|
|
#else
|
|
const char* home = std::getenv("HOME");
|
|
return home ? std::string(home) + "/.wowee" : ".";
|
|
#endif
|
|
}
|
|
|
|
void CharacterScreen::saveLastCharacter(uint64_t guid) {
|
|
std::string dir = getConfigDir();
|
|
std::filesystem::create_directories(dir);
|
|
std::ofstream f(dir + "/last_character.cfg");
|
|
if (f) f << guid;
|
|
}
|
|
|
|
uint64_t CharacterScreen::loadLastCharacter() {
|
|
std::string path = getConfigDir() + "/last_character.cfg";
|
|
std::ifstream f(path);
|
|
uint64_t guid = 0;
|
|
if (f) f >> guid;
|
|
return guid;
|
|
}
|
|
|
|
}} // namespace wowee::ui
|