mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 08:03:50 +00:00
Filter races/classes in character creation screen by expansion profile constraints, add 10s server response timeout, and reset Warden crypto state on disconnect so reconnections use the correct session key.
575 lines
20 KiB
C++
575 lines
20 KiB
C++
#include "ui/character_create_screen.hpp"
|
|
#include "rendering/character_preview.hpp"
|
|
#include "game/game_handler.hpp"
|
|
#include "pipeline/asset_manager.hpp"
|
|
#include "pipeline/dbc_layout.hpp"
|
|
#include <imgui.h>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
|
|
namespace wowee {
|
|
namespace ui {
|
|
|
|
// Full WotLK race/class lists (used as defaults when no expansion constraints set)
|
|
static const game::Race kAllRaces[] = {
|
|
// Alliance
|
|
game::Race::HUMAN, game::Race::DWARF, game::Race::NIGHT_ELF,
|
|
game::Race::GNOME, game::Race::DRAENEI,
|
|
// Horde
|
|
game::Race::ORC, game::Race::UNDEAD, game::Race::TAUREN,
|
|
game::Race::TROLL, game::Race::BLOOD_ELF,
|
|
};
|
|
static constexpr int kAllRaceCount = 10;
|
|
static constexpr int kAllianceCount = 5;
|
|
|
|
static const game::Class kAllClasses[] = {
|
|
game::Class::WARRIOR, game::Class::PALADIN, game::Class::HUNTER,
|
|
game::Class::ROGUE, game::Class::PRIEST, game::Class::DEATH_KNIGHT,
|
|
game::Class::SHAMAN, game::Class::MAGE, game::Class::WARLOCK,
|
|
game::Class::DRUID,
|
|
};
|
|
|
|
|
|
CharacterCreateScreen::CharacterCreateScreen() {
|
|
reset();
|
|
}
|
|
|
|
CharacterCreateScreen::~CharacterCreateScreen() = default;
|
|
|
|
void CharacterCreateScreen::setExpansionConstraints(
|
|
const std::vector<uint32_t>& races, const std::vector<uint32_t>& classes) {
|
|
// Build filtered race list: alliance first, then horde
|
|
availableRaces_.clear();
|
|
expansionClasses_.clear();
|
|
|
|
if (!races.empty()) {
|
|
// Alliance races in display order
|
|
for (auto r : std::initializer_list<game::Race>{
|
|
game::Race::HUMAN, game::Race::DWARF, game::Race::NIGHT_ELF,
|
|
game::Race::GNOME, game::Race::DRAENEI}) {
|
|
if (std::find(races.begin(), races.end(), static_cast<uint32_t>(r)) != races.end()) {
|
|
availableRaces_.push_back(r);
|
|
}
|
|
}
|
|
allianceRaceCount_ = static_cast<int>(availableRaces_.size());
|
|
|
|
// Horde races in display order
|
|
for (auto r : std::initializer_list<game::Race>{
|
|
game::Race::ORC, game::Race::UNDEAD, game::Race::TAUREN,
|
|
game::Race::TROLL, game::Race::BLOOD_ELF}) {
|
|
if (std::find(races.begin(), races.end(), static_cast<uint32_t>(r)) != races.end()) {
|
|
availableRaces_.push_back(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!classes.empty()) {
|
|
for (auto cls : kAllClasses) {
|
|
if (std::find(classes.begin(), classes.end(), static_cast<uint32_t>(cls)) != classes.end()) {
|
|
expansionClasses_.push_back(cls);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no constraints provided, fall back to WotLK defaults
|
|
if (availableRaces_.empty()) {
|
|
availableRaces_.assign(kAllRaces, kAllRaces + kAllRaceCount);
|
|
allianceRaceCount_ = kAllianceCount;
|
|
}
|
|
|
|
raceIndex = 0;
|
|
classIndex = 0;
|
|
updateAvailableClasses();
|
|
}
|
|
|
|
void CharacterCreateScreen::reset() {
|
|
std::memset(nameBuffer, 0, sizeof(nameBuffer));
|
|
raceIndex = 0;
|
|
classIndex = 0;
|
|
genderIndex = 0;
|
|
skin = 0;
|
|
face = 0;
|
|
hairStyle = 0;
|
|
hairColor = 0;
|
|
facialHair = 0;
|
|
maxSkin = 9;
|
|
maxFace = 9;
|
|
maxHairStyle = 11;
|
|
maxHairColor = 9;
|
|
maxFacialHair = 8;
|
|
statusMessage.clear();
|
|
statusIsError = false;
|
|
createTimer_ = -1.0f;
|
|
hairColorIds_.clear();
|
|
|
|
// Populate default races if not yet set by setExpansionConstraints
|
|
if (availableRaces_.empty()) {
|
|
availableRaces_.assign(kAllRaces, kAllRaces + kAllRaceCount);
|
|
allianceRaceCount_ = kAllianceCount;
|
|
}
|
|
|
|
updateAvailableClasses();
|
|
|
|
// Reset preview tracking to force model reload on next render
|
|
prevRaceIndex_ = -1;
|
|
prevGenderIndex_ = -1;
|
|
prevSkin_ = -1;
|
|
prevFace_ = -1;
|
|
prevHairStyle_ = -1;
|
|
prevHairColor_ = -1;
|
|
prevFacialHair_ = -1;
|
|
prevRangeRace_ = -1;
|
|
prevRangeGender_ = -1;
|
|
prevRangeSkin_ = -1;
|
|
prevRangeHairStyle_ = -1;
|
|
}
|
|
|
|
void CharacterCreateScreen::initializePreview(pipeline::AssetManager* am) {
|
|
assetManager_ = am;
|
|
if (!preview_) {
|
|
preview_ = std::make_unique<rendering::CharacterPreview>();
|
|
preview_->initialize(am);
|
|
}
|
|
// Force model reload
|
|
prevRaceIndex_ = -1;
|
|
}
|
|
|
|
void CharacterCreateScreen::update(float deltaTime) {
|
|
if (preview_) {
|
|
preview_->update(deltaTime);
|
|
}
|
|
// Timeout waiting for server response
|
|
if (createTimer_ >= 0.0f) {
|
|
createTimer_ += deltaTime;
|
|
if (createTimer_ > 10.0f) {
|
|
createTimer_ = -1.0f;
|
|
setStatus("Server did not respond. Try again.", true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CharacterCreateScreen::setStatus(const std::string& msg, bool isError) {
|
|
statusMessage = msg;
|
|
statusIsError = isError;
|
|
if (isError || msg.empty()) {
|
|
createTimer_ = -1.0f; // Stop waiting on error/clear
|
|
}
|
|
}
|
|
|
|
void CharacterCreateScreen::updateAvailableClasses() {
|
|
availableClasses.clear();
|
|
if (availableRaces_.empty() || raceIndex >= static_cast<int>(availableRaces_.size())) return;
|
|
game::Race race = availableRaces_[raceIndex];
|
|
for (auto cls : kAllClasses) {
|
|
if (!game::isValidRaceClassCombo(race, cls)) continue;
|
|
// If expansion constraints set, only allow listed classes
|
|
if (!expansionClasses_.empty()) {
|
|
if (std::find(expansionClasses_.begin(), expansionClasses_.end(), cls) == expansionClasses_.end())
|
|
continue;
|
|
}
|
|
availableClasses.push_back(cls);
|
|
}
|
|
// Clamp class index
|
|
if (classIndex >= static_cast<int>(availableClasses.size())) {
|
|
classIndex = 0;
|
|
}
|
|
}
|
|
|
|
void CharacterCreateScreen::updatePreviewIfNeeded() {
|
|
if (!preview_) return;
|
|
|
|
bool changed = (raceIndex != prevRaceIndex_ ||
|
|
genderIndex != prevGenderIndex_ ||
|
|
bodyTypeIndex != prevBodyTypeIndex_ ||
|
|
skin != prevSkin_ ||
|
|
face != prevFace_ ||
|
|
hairStyle != prevHairStyle_ ||
|
|
hairColor != prevHairColor_ ||
|
|
facialHair != prevFacialHair_);
|
|
|
|
if (changed) {
|
|
bool useFemaleModel = (genderIndex == 2 && bodyTypeIndex == 1); // Nonbinary + Feminine
|
|
uint8_t hairColorId = 0;
|
|
if (!hairColorIds_.empty() && hairColor >= 0 && hairColor < static_cast<int>(hairColorIds_.size())) {
|
|
hairColorId = hairColorIds_[hairColor];
|
|
} else {
|
|
hairColorId = static_cast<uint8_t>(hairColor);
|
|
}
|
|
preview_->loadCharacter(
|
|
availableRaces_[raceIndex],
|
|
static_cast<game::Gender>(genderIndex),
|
|
static_cast<uint8_t>(skin),
|
|
static_cast<uint8_t>(face),
|
|
static_cast<uint8_t>(hairStyle),
|
|
hairColorId,
|
|
static_cast<uint8_t>(facialHair),
|
|
useFemaleModel);
|
|
|
|
prevRaceIndex_ = raceIndex;
|
|
prevGenderIndex_ = genderIndex;
|
|
prevBodyTypeIndex_ = bodyTypeIndex;
|
|
prevSkin_ = skin;
|
|
prevFace_ = face;
|
|
prevHairStyle_ = hairStyle;
|
|
prevHairColor_ = hairColor;
|
|
prevFacialHair_ = facialHair;
|
|
}
|
|
}
|
|
|
|
void CharacterCreateScreen::updateAppearanceRanges() {
|
|
if (raceIndex == prevRangeRace_ &&
|
|
genderIndex == prevRangeGender_ &&
|
|
skin == prevRangeSkin_ &&
|
|
hairStyle == prevRangeHairStyle_) {
|
|
return;
|
|
}
|
|
|
|
prevRangeRace_ = raceIndex;
|
|
prevRangeGender_ = genderIndex;
|
|
prevRangeSkin_ = skin;
|
|
prevRangeHairStyle_ = hairStyle;
|
|
|
|
maxSkin = 9;
|
|
maxFace = 9;
|
|
maxHairStyle = 11;
|
|
maxHairColor = 9;
|
|
maxFacialHair = 8;
|
|
hairColorIds_.clear();
|
|
|
|
if (!assetManager_) return;
|
|
auto dbc = assetManager_->loadDBC("CharSections.dbc");
|
|
if (!dbc) return;
|
|
|
|
uint32_t targetRaceId = static_cast<uint32_t>(availableRaces_[raceIndex]);
|
|
uint32_t targetSexId = (genderIndex == 1) ? 1u : 0u;
|
|
|
|
const auto* csL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
|
|
int skinMax = -1;
|
|
int hairStyleMax = -1;
|
|
for (uint32_t r = 0; r < dbc->getRecordCount(); r++) {
|
|
uint32_t raceId = dbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
|
uint32_t sexId = dbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
|
if (raceId != targetRaceId || sexId != targetSexId) continue;
|
|
|
|
uint32_t baseSection = dbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
|
uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
|
uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
|
|
|
if (baseSection == 0 && variationIndex == 0) {
|
|
skinMax = std::max(skinMax, static_cast<int>(colorIndex));
|
|
} else if (baseSection == 3) {
|
|
hairStyleMax = std::max(hairStyleMax, static_cast<int>(variationIndex));
|
|
}
|
|
}
|
|
|
|
if (skinMax >= 0) {
|
|
maxSkin = skinMax;
|
|
if (skin > maxSkin) skin = maxSkin;
|
|
}
|
|
if (hairStyleMax >= 0) {
|
|
maxHairStyle = hairStyleMax;
|
|
if (hairStyle > maxHairStyle) hairStyle = maxHairStyle;
|
|
}
|
|
|
|
int faceMax = -1;
|
|
std::vector<uint8_t> hairColorIds;
|
|
for (uint32_t r = 0; r < dbc->getRecordCount(); r++) {
|
|
uint32_t raceId = dbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
|
uint32_t sexId = dbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
|
if (raceId != targetRaceId || sexId != targetSexId) continue;
|
|
|
|
uint32_t baseSection = dbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
|
uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
|
uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
|
|
|
if (baseSection == 1 && colorIndex == static_cast<uint32_t>(skin)) {
|
|
faceMax = std::max(faceMax, static_cast<int>(variationIndex));
|
|
} else if (baseSection == 3 && variationIndex == static_cast<uint32_t>(hairStyle)) {
|
|
if (colorIndex <= 255) {
|
|
hairColorIds.push_back(static_cast<uint8_t>(colorIndex));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (faceMax >= 0) {
|
|
maxFace = faceMax;
|
|
if (face > maxFace) face = maxFace;
|
|
}
|
|
|
|
// Hair colors: use actual available DBC IDs (not "0..maxId"), since IDs may be sparse.
|
|
if (!hairColorIds.empty()) {
|
|
std::sort(hairColorIds.begin(), hairColorIds.end());
|
|
hairColorIds.erase(std::unique(hairColorIds.begin(), hairColorIds.end()), hairColorIds.end());
|
|
hairColorIds_ = std::move(hairColorIds);
|
|
maxHairColor = std::max(0, static_cast<int>(hairColorIds_.size()) - 1);
|
|
if (hairColor > maxHairColor) hairColor = maxHairColor;
|
|
if (hairColor < 0) hairColor = 0;
|
|
}
|
|
int facialMax = -1;
|
|
auto facialDbc = assetManager_->loadDBC("CharacterFacialHairStyles.dbc");
|
|
const auto* fhL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("CharacterFacialHairStyles") : nullptr;
|
|
if (facialDbc) {
|
|
for (uint32_t r = 0; r < facialDbc->getRecordCount(); r++) {
|
|
uint32_t raceId = facialDbc->getUInt32(r, fhL ? (*fhL)["RaceID"] : 0);
|
|
uint32_t sexId = facialDbc->getUInt32(r, fhL ? (*fhL)["SexID"] : 1);
|
|
if (raceId != targetRaceId || sexId != targetSexId) continue;
|
|
uint32_t variation = facialDbc->getUInt32(r, fhL ? (*fhL)["Variation"] : 2);
|
|
facialMax = std::max(facialMax, static_cast<int>(variation));
|
|
}
|
|
}
|
|
if (facialMax >= 0) {
|
|
maxFacialHair = facialMax;
|
|
} else if (targetSexId == 1) {
|
|
maxFacialHair = 0;
|
|
}
|
|
if (facialHair > maxFacialHair) {
|
|
facialHair = maxFacialHair;
|
|
}
|
|
}
|
|
|
|
void CharacterCreateScreen::render(game::GameHandler& /*gameHandler*/) {
|
|
// Render the preview to FBO before the ImGui frame
|
|
if (preview_) {
|
|
updatePreviewIfNeeded();
|
|
preview_->render();
|
|
}
|
|
|
|
ImVec2 displaySize = ImGui::GetIO().DisplaySize;
|
|
bool hasPreview = (preview_ && preview_->getTextureId() != 0);
|
|
float previewWidth = hasPreview ? 320.0f : 0.0f;
|
|
float controlsWidth = 540.0f;
|
|
float totalWidth = hasPreview ? (previewWidth + controlsWidth + 16.0f) : 600.0f;
|
|
float totalHeight = 580.0f;
|
|
|
|
ImGui::SetNextWindowSize(ImVec2(totalWidth, totalHeight), ImGuiCond_Always);
|
|
ImGui::SetNextWindowPos(ImVec2((displaySize.x - totalWidth) * 0.5f,
|
|
(displaySize.y - totalHeight) * 0.5f),
|
|
ImGuiCond_Always);
|
|
|
|
ImGui::Begin("Create Character", nullptr,
|
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize);
|
|
|
|
if (hasPreview) {
|
|
// Left panel: 3D preview
|
|
ImGui::BeginChild("##preview_panel", ImVec2(previewWidth, -40.0f), false);
|
|
{
|
|
// Display the FBO texture (flip Y for OpenGL)
|
|
float imgW = previewWidth - 8.0f;
|
|
float imgH = imgW * (static_cast<float>(preview_->getHeight()) /
|
|
static_cast<float>(preview_->getWidth()));
|
|
if (imgH > totalHeight - 80.0f) {
|
|
imgH = totalHeight - 80.0f;
|
|
imgW = imgH * (static_cast<float>(preview_->getWidth()) /
|
|
static_cast<float>(preview_->getHeight()));
|
|
}
|
|
|
|
ImGui::Image(
|
|
static_cast<ImTextureID>(preview_->getTextureId()),
|
|
ImVec2(imgW, imgH),
|
|
ImVec2(0.0f, 1.0f), // UV top-left (flipped Y)
|
|
ImVec2(1.0f, 0.0f)); // UV bottom-right (flipped Y)
|
|
|
|
// Mouse drag rotation on the preview image
|
|
if (ImGui::IsItemHovered() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
|
float deltaX = ImGui::GetIO().MouseDelta.x;
|
|
preview_->rotate(deltaX * 0.2f);
|
|
}
|
|
|
|
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Drag to rotate");
|
|
}
|
|
ImGui::EndChild();
|
|
|
|
ImGui::SameLine();
|
|
|
|
// Right panel: controls
|
|
ImGui::BeginChild("##controls_panel", ImVec2(0, -40.0f), false);
|
|
}
|
|
|
|
// Name input
|
|
ImGui::Text("Name:");
|
|
ImGui::SameLine(80);
|
|
ImGui::SetNextItemWidth(200);
|
|
ImGui::InputText("##name", nameBuffer, sizeof(nameBuffer));
|
|
|
|
ImGui::Spacing();
|
|
|
|
// Race selection (filtered by expansion)
|
|
int raceCount = static_cast<int>(availableRaces_.size());
|
|
ImGui::Text("Race:");
|
|
ImGui::Spacing();
|
|
ImGui::Indent(10.0f);
|
|
if (allianceRaceCount_ > 0) {
|
|
ImGui::TextColored(ImVec4(0.3f, 0.5f, 1.0f, 1.0f), "Alliance:");
|
|
ImGui::SameLine();
|
|
for (int i = 0; i < allianceRaceCount_; ++i) {
|
|
if (i > 0) ImGui::SameLine();
|
|
bool selected = (raceIndex == i);
|
|
if (selected) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 1.0f, 0.8f));
|
|
if (ImGui::SmallButton(game::getRaceName(availableRaces_[i]))) {
|
|
if (raceIndex != i) {
|
|
raceIndex = i;
|
|
classIndex = 0;
|
|
skin = face = hairStyle = hairColor = facialHair = 0;
|
|
updateAvailableClasses();
|
|
}
|
|
}
|
|
if (selected) ImGui::PopStyleColor();
|
|
}
|
|
}
|
|
if (allianceRaceCount_ < raceCount) {
|
|
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Horde:");
|
|
ImGui::SameLine();
|
|
for (int i = allianceRaceCount_; i < raceCount; ++i) {
|
|
if (i > allianceRaceCount_) ImGui::SameLine();
|
|
bool selected = (raceIndex == i);
|
|
if (selected) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 0.3f, 0.3f, 0.8f));
|
|
if (ImGui::SmallButton(game::getRaceName(availableRaces_[i]))) {
|
|
if (raceIndex != i) {
|
|
raceIndex = i;
|
|
classIndex = 0;
|
|
skin = face = hairStyle = hairColor = facialHair = 0;
|
|
updateAvailableClasses();
|
|
}
|
|
}
|
|
if (selected) ImGui::PopStyleColor();
|
|
}
|
|
}
|
|
ImGui::Unindent(10.0f);
|
|
|
|
ImGui::Spacing();
|
|
|
|
// Class selection
|
|
ImGui::Text("Class:");
|
|
ImGui::SameLine(80);
|
|
if (!availableClasses.empty()) {
|
|
ImGui::BeginGroup();
|
|
for (int i = 0; i < static_cast<int>(availableClasses.size()); ++i) {
|
|
if (i > 0) ImGui::SameLine();
|
|
bool selected = (classIndex == i);
|
|
if (selected) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.2f, 0.8f));
|
|
if (ImGui::SmallButton(game::getClassName(availableClasses[i]))) {
|
|
classIndex = i;
|
|
}
|
|
if (selected) ImGui::PopStyleColor();
|
|
}
|
|
ImGui::EndGroup();
|
|
}
|
|
|
|
ImGui::Spacing();
|
|
|
|
// Gender
|
|
ImGui::Text("Gender:");
|
|
ImGui::SameLine(80);
|
|
ImGui::RadioButton("Male", &genderIndex, 0);
|
|
ImGui::SameLine();
|
|
ImGui::RadioButton("Female", &genderIndex, 1);
|
|
ImGui::SameLine();
|
|
ImGui::RadioButton("Nonbinary", &genderIndex, 2);
|
|
|
|
// Body type selection for nonbinary
|
|
if (genderIndex == 2) { // Nonbinary
|
|
ImGui::Text("Body Type:");
|
|
ImGui::SameLine(80);
|
|
ImGui::RadioButton("Masculine", &bodyTypeIndex, 0);
|
|
ImGui::SameLine();
|
|
ImGui::RadioButton("Feminine", &bodyTypeIndex, 1);
|
|
}
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
// Appearance sliders
|
|
updateAppearanceRanges();
|
|
game::Gender currentGender = static_cast<game::Gender>(genderIndex);
|
|
|
|
ImGui::Text("Appearance");
|
|
ImGui::Spacing();
|
|
|
|
float sliderWidth = hasPreview ? 180.0f : 200.0f;
|
|
float labelCol = hasPreview ? 100.0f : 120.0f;
|
|
|
|
auto slider = [&](const char* label, int* val, int maxVal) {
|
|
ImGui::Text("%s", label);
|
|
ImGui::SameLine(labelCol);
|
|
ImGui::SetNextItemWidth(sliderWidth);
|
|
char id[32];
|
|
snprintf(id, sizeof(id), "##%s", label);
|
|
ImGui::SliderInt(id, val, 0, maxVal);
|
|
};
|
|
|
|
slider("Skin", &skin, maxSkin);
|
|
slider("Face", &face, maxFace);
|
|
slider("Hair Style", &hairStyle, maxHairStyle);
|
|
slider("Hair Color", &hairColor, maxHairColor);
|
|
slider("Facial Feature", &facialHair, maxFacialHair);
|
|
|
|
ImGui::Spacing();
|
|
|
|
// Status message
|
|
if (!statusMessage.empty()) {
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
ImVec4 color = statusIsError ? ImVec4(1.0f, 0.3f, 0.3f, 1.0f) : ImVec4(0.3f, 1.0f, 0.3f, 1.0f);
|
|
ImGui::TextColored(color, "%s", statusMessage.c_str());
|
|
}
|
|
|
|
if (hasPreview) {
|
|
ImGui::EndChild(); // controls_panel
|
|
}
|
|
|
|
// Bottom buttons (outside children)
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
if (ImGui::Button("Create", ImVec2(150, 35))) {
|
|
std::string name(nameBuffer);
|
|
// Trim whitespace
|
|
size_t start = name.find_first_not_of(" \t\r\n");
|
|
size_t end = name.find_last_not_of(" \t\r\n");
|
|
if (start == std::string::npos) {
|
|
name.clear();
|
|
} else {
|
|
name = name.substr(start, end - start + 1);
|
|
}
|
|
if (name.empty()) {
|
|
setStatus("Please enter a character name.", true);
|
|
} else if (availableClasses.empty()) {
|
|
setStatus("No valid class for this race.", true);
|
|
} else {
|
|
setStatus("Creating character...", false);
|
|
createTimer_ = 0.0f;
|
|
game::CharCreateData data;
|
|
data.name = name;
|
|
data.race = availableRaces_[raceIndex];
|
|
data.characterClass = availableClasses[classIndex];
|
|
data.gender = currentGender;
|
|
data.useFemaleModel = (genderIndex == 2 && bodyTypeIndex == 1); // Nonbinary + Feminine
|
|
data.skin = static_cast<uint8_t>(skin);
|
|
data.face = static_cast<uint8_t>(face);
|
|
data.hairStyle = static_cast<uint8_t>(hairStyle);
|
|
if (!hairColorIds_.empty() && hairColor >= 0 && hairColor < static_cast<int>(hairColorIds_.size())) {
|
|
data.hairColor = hairColorIds_[hairColor];
|
|
} else {
|
|
data.hairColor = static_cast<uint8_t>(hairColor);
|
|
}
|
|
data.facialHair = static_cast<uint8_t>(facialHair);
|
|
if (onCreate) {
|
|
onCreate(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Back", ImVec2(150, 35))) {
|
|
if (onCancel) {
|
|
onCancel();
|
|
}
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
} // namespace ui
|
|
} // namespace wowee
|