diff --git a/include/ui/character_create_screen.hpp b/include/ui/character_create_screen.hpp index 5525477f..9e15912b 100644 --- a/include/ui/character_create_screen.hpp +++ b/include/ui/character_create_screen.hpp @@ -29,6 +29,9 @@ public: void reset(); void initializePreview(pipeline::AssetManager* am); + /** Set allowed races/classes from expansion profile. Empty = allow all (WotLK default). */ + void setExpansionConstraints(const std::vector& races, const std::vector& classes); + private: char nameBuffer[13] = {}; // WoW max name = 12 chars + null int raceIndex = 0; @@ -47,6 +50,11 @@ private: std::vector availableClasses; void updateAvailableClasses(); + // Expansion-filtered race/class lists + std::vector availableRaces_; // Alliance-first, then horde order + int allianceRaceCount_ = 0; // How many of availableRaces_ are alliance + std::vector expansionClasses_; // Allowed classes (empty = all) + std::function onCreate; std::function onCancel; @@ -65,6 +73,7 @@ private: int prevRangeGender_ = -1; int prevRangeSkin_ = -1; int prevRangeHairStyle_ = -1; + float createTimer_ = -1.0f; // >=0 while waiting for SMSG_CHAR_CREATE response bool draggingPreview_ = false; float dragStartX_ = 0.0f; diff --git a/src/core/application.cpp b/src/core/application.cpp index 0d3e3e23..e8db5e66 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1721,6 +1721,12 @@ void Application::setupUICallbacks() { // "Create Character" button on character screen uiManager->getCharacterScreen().setOnCreateCharacter([this]() { uiManager->getCharacterCreateScreen().reset(); + // Apply expansion race/class constraints before showing the screen + if (expansionRegistry_ && expansionRegistry_->getActive()) { + auto* profile = expansionRegistry_->getActive(); + uiManager->getCharacterCreateScreen().setExpansionConstraints( + profile->races, profile->classes); + } uiManager->getCharacterCreateScreen().initializePreview(assetManager.get()); setState(AppState::CHARACTER_CREATION); }); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 56c0a24a..d45e65cc 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -150,6 +150,12 @@ bool GameHandler::connect(const std::string& host, wardenGateNextStatusLog_ = 2.0f; wardenPacketsAfterGate_ = 0; wardenCharEnumBlockedLogged_ = false; + wardenCrypto_.reset(); + wardenState_ = WardenState::WAIT_MODULE_USE; + wardenModuleHash_.clear(); + wardenModuleKey_.clear(); + wardenModuleSize_ = 0; + wardenModuleData_.clear(); // Generate random client seed this->clientSeed = generateClientSeed(); @@ -200,6 +206,12 @@ void GameHandler::disconnect() { wardenGateNextStatusLog_ = 2.0f; wardenPacketsAfterGate_ = 0; wardenCharEnumBlockedLogged_ = false; + wardenCrypto_.reset(); + wardenState_ = WardenState::WAIT_MODULE_USE; + wardenModuleHash_.clear(); + wardenModuleKey_.clear(); + wardenModuleSize_ = 0; + wardenModuleData_.clear(); setState(WorldState::DISCONNECTED); LOG_INFO("Disconnected from world server"); } diff --git a/src/ui/character_create_screen.cpp b/src/ui/character_create_screen.cpp index e33650de..b1c548c4 100644 --- a/src/ui/character_create_screen.cpp +++ b/src/ui/character_create_screen.cpp @@ -5,11 +5,13 @@ #include "pipeline/dbc_layout.hpp" #include #include +#include namespace wowee { namespace ui { -static const game::Race allRaces[] = { +// 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, @@ -17,22 +19,69 @@ static const game::Race allRaces[] = { game::Race::ORC, game::Race::UNDEAD, game::Race::TAUREN, game::Race::TROLL, game::Race::BLOOD_ELF, }; -static constexpr int RACE_COUNT = 10; -static constexpr int ALLIANCE_COUNT = 5; +static constexpr int kAllRaceCount = 10; +static constexpr int kAllianceCount = 5; -static const game::Class allClasses[] = { +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& races, const std::vector& 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::HUMAN, game::Race::DWARF, game::Race::NIGHT_ELF, + game::Race::GNOME, game::Race::DRAENEI}) { + if (std::find(races.begin(), races.end(), static_cast(r)) != races.end()) { + availableRaces_.push_back(r); + } + } + allianceRaceCount_ = static_cast(availableRaces_.size()); + + // Horde races in display order + for (auto r : std::initializer_list{ + 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(r)) != races.end()) { + availableRaces_.push_back(r); + } + } + } + + if (!classes.empty()) { + for (auto cls : kAllClasses) { + if (std::find(classes.begin(), classes.end(), static_cast(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; @@ -50,7 +99,15 @@ void CharacterCreateScreen::reset() { 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 @@ -81,20 +138,36 @@ 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(); - game::Race race = allRaces[raceIndex]; - for (auto cls : allClasses) { - if (game::isValidRaceClassCombo(race, cls)) { - availableClasses.push_back(cls); + if (availableRaces_.empty() || raceIndex >= static_cast(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(availableClasses.size())) { @@ -123,7 +196,7 @@ void CharacterCreateScreen::updatePreviewIfNeeded() { hairColorId = static_cast(hairColor); } preview_->loadCharacter( - allRaces[raceIndex], + availableRaces_[raceIndex], static_cast(genderIndex), static_cast(skin), static_cast(face), @@ -167,7 +240,7 @@ void CharacterCreateScreen::updateAppearanceRanges() { auto dbc = assetManager_->loadDBC("CharSections.dbc"); if (!dbc) return; - uint32_t targetRaceId = static_cast(allRaces[raceIndex]); + uint32_t targetRaceId = static_cast(availableRaces_[raceIndex]); uint32_t targetSexId = (genderIndex == 1) ? 1u : 0u; const auto* csL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr; @@ -320,41 +393,46 @@ void CharacterCreateScreen::render(game::GameHandler& /*gameHandler*/) { ImGui::Spacing(); - // Race selection + // Race selection (filtered by expansion) + int raceCount = static_cast(availableRaces_.size()); ImGui::Text("Race:"); ImGui::Spacing(); ImGui::Indent(10.0f); - ImGui::TextColored(ImVec4(0.3f, 0.5f, 1.0f, 1.0f), "Alliance:"); - ImGui::SameLine(); - for (int i = 0; i < ALLIANCE_COUNT; ++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(allRaces[i]))) { - if (raceIndex != i) { - raceIndex = i; - classIndex = 0; - skin = face = hairStyle = hairColor = facialHair = 0; - updateAvailableClasses(); + 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 (selected) ImGui::PopStyleColor(); } - ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Horde:"); - ImGui::SameLine(); - for (int i = ALLIANCE_COUNT; i < RACE_COUNT; ++i) { - if (i > ALLIANCE_COUNT) 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(allRaces[i]))) { - if (raceIndex != i) { - raceIndex = i; - classIndex = 0; - skin = face = hairStyle = hairColor = facialHair = 0; - updateAvailableClasses(); + 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(); } - if (selected) ImGui::PopStyleColor(); } ImGui::Unindent(10.0f); @@ -460,9 +538,10 @@ void CharacterCreateScreen::render(game::GameHandler& /*gameHandler*/) { setStatus("No valid class for this race.", true); } else { setStatus("Creating character...", false); + createTimer_ = 0.0f; game::CharCreateData data; data.name = name; - data.race = allRaces[raceIndex]; + data.race = availableRaces_[raceIndex]; data.characterClass = availableClasses[classIndex]; data.gender = currentGender; data.useFemaleModel = (genderIndex == 2 && bodyTypeIndex == 1); // Nonbinary + Feminine