mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: implement barber shop UI with hair/facial customization
Adds a functional barber shop window triggered by SMSG_ENABLE_BARBER_SHOP. Players can adjust hair style, hair color, and facial features using sliders bounded by race/gender max values. Sends CMSG_ALTER_APPEARANCE on confirm; server result closes the window on success. Escape key also closes the barber shop.
This commit is contained in:
parent
8dfd916fe4
commit
64fd7eddf8
6 changed files with 160 additions and 1 deletions
|
|
@ -1219,6 +1219,12 @@ public:
|
||||||
uint32_t getPetUnlearnCost() const { return petUnlearnCost_; }
|
uint32_t getPetUnlearnCost() const { return petUnlearnCost_; }
|
||||||
void confirmPetUnlearn();
|
void confirmPetUnlearn();
|
||||||
void cancelPetUnlearn() { petUnlearnPending_ = false; }
|
void cancelPetUnlearn() { petUnlearnPending_ = false; }
|
||||||
|
|
||||||
|
// Barber shop
|
||||||
|
bool isBarberShopOpen() const { return barberShopOpen_; }
|
||||||
|
void closeBarberShop() { barberShopOpen_ = false; }
|
||||||
|
void sendAlterAppearance(uint32_t hairStyle, uint32_t hairColor, uint32_t facialHair);
|
||||||
|
|
||||||
/** True when ghost is within 40 yards of corpse position (same map). */
|
/** True when ghost is within 40 yards of corpse position (same map). */
|
||||||
bool canReclaimCorpse() const;
|
bool canReclaimCorpse() const;
|
||||||
/** Seconds remaining on the PvP corpse-reclaim delay, or 0 if the reclaim is available now. */
|
/** Seconds remaining on the PvP corpse-reclaim delay, or 0 if the reclaim is available now. */
|
||||||
|
|
@ -2983,6 +2989,9 @@ private:
|
||||||
uint64_t activeCharacterGuid_ = 0;
|
uint64_t activeCharacterGuid_ = 0;
|
||||||
Race playerRace_ = Race::HUMAN;
|
Race playerRace_ = Race::HUMAN;
|
||||||
|
|
||||||
|
// Barber shop
|
||||||
|
bool barberShopOpen_ = false;
|
||||||
|
|
||||||
// ---- Phase 5: Loot ----
|
// ---- Phase 5: Loot ----
|
||||||
bool lootWindowOpen = false;
|
bool lootWindowOpen = false;
|
||||||
bool autoLoot_ = false;
|
bool autoLoot_ = false;
|
||||||
|
|
|
||||||
|
|
@ -2796,5 +2796,12 @@ public:
|
||||||
static network::Packet build(int32_t titleBit);
|
static network::Packet build(int32_t titleBit);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** CMSG_ALTER_APPEARANCE – barber shop: change hair style, color, facial hair.
|
||||||
|
* Payload: uint32 hairStyle, uint32 hairColor, uint32 facialHair. */
|
||||||
|
class AlterAppearancePacket {
|
||||||
|
public:
|
||||||
|
static network::Packet build(uint32_t hairStyle, uint32_t hairColor, uint32_t facialHair);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
} // namespace wowee
|
} // namespace wowee
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,7 @@ private:
|
||||||
void renderQuestOfferRewardWindow(game::GameHandler& gameHandler);
|
void renderQuestOfferRewardWindow(game::GameHandler& gameHandler);
|
||||||
void renderVendorWindow(game::GameHandler& gameHandler);
|
void renderVendorWindow(game::GameHandler& gameHandler);
|
||||||
void renderTrainerWindow(game::GameHandler& gameHandler);
|
void renderTrainerWindow(game::GameHandler& gameHandler);
|
||||||
|
void renderBarberShopWindow(game::GameHandler& gameHandler);
|
||||||
void renderStableWindow(game::GameHandler& gameHandler);
|
void renderStableWindow(game::GameHandler& gameHandler);
|
||||||
void renderTaxiWindow(game::GameHandler& gameHandler);
|
void renderTaxiWindow(game::GameHandler& gameHandler);
|
||||||
void renderLogoutCountdown(game::GameHandler& gameHandler);
|
void renderLogoutCountdown(game::GameHandler& gameHandler);
|
||||||
|
|
@ -543,6 +544,15 @@ private:
|
||||||
uint32_t vendorConfirmPrice_ = 0;
|
uint32_t vendorConfirmPrice_ = 0;
|
||||||
std::string vendorConfirmItemName_;
|
std::string vendorConfirmItemName_;
|
||||||
|
|
||||||
|
// Barber shop UI state
|
||||||
|
int barberHairStyle_ = 0;
|
||||||
|
int barberHairColor_ = 0;
|
||||||
|
int barberFacialHair_ = 0;
|
||||||
|
int barberOrigHairStyle_ = 0;
|
||||||
|
int barberOrigHairColor_ = 0;
|
||||||
|
int barberOrigFacialHair_ = 0;
|
||||||
|
bool barberInitialized_ = false;
|
||||||
|
|
||||||
// Trainer search filter
|
// Trainer search filter
|
||||||
char trainerSearchFilter_[128] = "";
|
char trainerSearchFilter_[128] = "";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2681,8 +2681,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
case Opcode::SMSG_ENABLE_BARBER_SHOP:
|
case Opcode::SMSG_ENABLE_BARBER_SHOP:
|
||||||
// Sent by server when player sits in barber chair — triggers barber shop UI
|
// Sent by server when player sits in barber chair — triggers barber shop UI
|
||||||
// No payload; we don't have barber shop UI yet, so just log
|
|
||||||
LOG_INFO("SMSG_ENABLE_BARBER_SHOP: barber shop available");
|
LOG_INFO("SMSG_ENABLE_BARBER_SHOP: barber shop available");
|
||||||
|
barberShopOpen_ = true;
|
||||||
break;
|
break;
|
||||||
case Opcode::SMSG_FEIGN_DEATH_RESISTED:
|
case Opcode::SMSG_FEIGN_DEATH_RESISTED:
|
||||||
addUIError("Your Feign Death was resisted.");
|
addUIError("Your Feign Death was resisted.");
|
||||||
|
|
@ -4902,6 +4902,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
uint32_t result = packet.readUInt32();
|
uint32_t result = packet.readUInt32();
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
addSystemChatMessage("Hairstyle changed.");
|
addSystemChatMessage("Hairstyle changed.");
|
||||||
|
barberShopOpen_ = false;
|
||||||
} else {
|
} else {
|
||||||
const char* msg = (result == 1) ? "Not enough money for new hairstyle."
|
const char* msg = (result == 1) ? "Not enough money for new hairstyle."
|
||||||
: (result == 2) ? "You are not at a barber shop."
|
: (result == 2) ? "You are not at a barber shop."
|
||||||
|
|
@ -19180,6 +19181,13 @@ void GameHandler::confirmTalentWipe() {
|
||||||
talentWipeCost_ = 0;
|
talentWipeCost_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameHandler::sendAlterAppearance(uint32_t hairStyle, uint32_t hairColor, uint32_t facialHair) {
|
||||||
|
if (state != WorldState::IN_WORLD || !socket) return;
|
||||||
|
auto pkt = AlterAppearancePacket::build(hairStyle, hairColor, facialHair);
|
||||||
|
socket->send(pkt);
|
||||||
|
LOG_INFO("sendAlterAppearance: hair=", hairStyle, " color=", hairColor, " facial=", facialHair);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Phase 4: Group/Party
|
// Phase 4: Group/Party
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -5878,5 +5878,14 @@ network::Packet SetTitlePacket::build(int32_t titleBit) {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
network::Packet AlterAppearancePacket::build(uint32_t hairStyle, uint32_t hairColor, uint32_t facialHair) {
|
||||||
|
// CMSG_ALTER_APPEARANCE: uint32 hairStyle + uint32 hairColor + uint32 facialHair
|
||||||
|
network::Packet p(wireOpcode(Opcode::CMSG_ALTER_APPEARANCE));
|
||||||
|
p.writeUInt32(hairStyle);
|
||||||
|
p.writeUInt32(hairColor);
|
||||||
|
p.writeUInt32(facialHair);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
} // namespace wowee
|
} // namespace wowee
|
||||||
|
|
|
||||||
|
|
@ -733,6 +733,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
renderQuestOfferRewardWindow(gameHandler);
|
renderQuestOfferRewardWindow(gameHandler);
|
||||||
renderVendorWindow(gameHandler);
|
renderVendorWindow(gameHandler);
|
||||||
renderTrainerWindow(gameHandler);
|
renderTrainerWindow(gameHandler);
|
||||||
|
renderBarberShopWindow(gameHandler);
|
||||||
renderStableWindow(gameHandler);
|
renderStableWindow(gameHandler);
|
||||||
renderTaxiWindow(gameHandler);
|
renderTaxiWindow(gameHandler);
|
||||||
renderMailWindow(gameHandler);
|
renderMailWindow(gameHandler);
|
||||||
|
|
@ -2763,6 +2764,8 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
||||||
gameHandler.closeGossip();
|
gameHandler.closeGossip();
|
||||||
} else if (gameHandler.isVendorWindowOpen()) {
|
} else if (gameHandler.isVendorWindowOpen()) {
|
||||||
gameHandler.closeVendor();
|
gameHandler.closeVendor();
|
||||||
|
} else if (gameHandler.isBarberShopOpen()) {
|
||||||
|
gameHandler.closeBarberShop();
|
||||||
} else if (gameHandler.isBankOpen()) {
|
} else if (gameHandler.isBankOpen()) {
|
||||||
gameHandler.closeBank();
|
gameHandler.closeBank();
|
||||||
} else if (gameHandler.isTrainerWindowOpen()) {
|
} else if (gameHandler.isTrainerWindowOpen()) {
|
||||||
|
|
@ -16806,6 +16809,119 @@ void GameScreen::renderEscapeMenu() {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Barber Shop Window
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void GameScreen::renderBarberShopWindow(game::GameHandler& gameHandler) {
|
||||||
|
if (!gameHandler.isBarberShopOpen()) {
|
||||||
|
barberInitialized_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* ch = gameHandler.getActiveCharacter();
|
||||||
|
if (!ch) return;
|
||||||
|
|
||||||
|
uint8_t race = static_cast<uint8_t>(ch->race);
|
||||||
|
game::Gender gender = ch->gender;
|
||||||
|
game::Race raceEnum = ch->race;
|
||||||
|
|
||||||
|
// Initialize sliders from current appearance
|
||||||
|
if (!barberInitialized_) {
|
||||||
|
barberOrigHairStyle_ = static_cast<int>((ch->appearanceBytes >> 16) & 0xFF);
|
||||||
|
barberOrigHairColor_ = static_cast<int>((ch->appearanceBytes >> 24) & 0xFF);
|
||||||
|
barberOrigFacialHair_ = static_cast<int>(ch->facialFeatures);
|
||||||
|
barberHairStyle_ = barberOrigHairStyle_;
|
||||||
|
barberHairColor_ = barberOrigHairColor_;
|
||||||
|
barberFacialHair_ = barberOrigFacialHair_;
|
||||||
|
barberInitialized_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxHairStyle = static_cast<int>(game::getMaxHairStyle(raceEnum, gender));
|
||||||
|
int maxHairColor = static_cast<int>(game::getMaxHairColor(raceEnum, gender));
|
||||||
|
int maxFacialHair = static_cast<int>(game::getMaxFacialFeature(raceEnum, gender));
|
||||||
|
|
||||||
|
auto* window = core::Application::getInstance().getWindow();
|
||||||
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||||
|
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
||||||
|
float winW = 300.0f;
|
||||||
|
float winH = 220.0f;
|
||||||
|
ImGui::SetNextWindowPos(ImVec2((screenW - winW) / 2.0f, (screenH - winH) / 2.0f), ImGuiCond_Appearing);
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(winW, winH), ImGuiCond_Appearing);
|
||||||
|
|
||||||
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse;
|
||||||
|
bool open = true;
|
||||||
|
if (ImGui::Begin("Barber Shop", &open, flags)) {
|
||||||
|
ImGui::Text("Choose your new look:");
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Spacing();
|
||||||
|
|
||||||
|
ImGui::PushItemWidth(-1);
|
||||||
|
|
||||||
|
// Hair Style
|
||||||
|
ImGui::Text("Hair Style");
|
||||||
|
ImGui::SliderInt("##HairStyle", &barberHairStyle_, 0, maxHairStyle,
|
||||||
|
"%d");
|
||||||
|
|
||||||
|
// Hair Color
|
||||||
|
ImGui::Text("Hair Color");
|
||||||
|
ImGui::SliderInt("##HairColor", &barberHairColor_, 0, maxHairColor,
|
||||||
|
"%d");
|
||||||
|
|
||||||
|
// Facial Hair / Piercings / Markings
|
||||||
|
const char* facialLabel = (gender == game::Gender::FEMALE) ? "Piercings" : "Facial Hair";
|
||||||
|
// Some races use "Markings" or "Tusks" etc.
|
||||||
|
if (race == 8 || race == 6) facialLabel = "Features"; // Trolls, Tauren
|
||||||
|
ImGui::Text("%s", facialLabel);
|
||||||
|
ImGui::SliderInt("##FacialHair", &barberFacialHair_, 0, maxFacialHair,
|
||||||
|
"%d");
|
||||||
|
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Show whether anything changed
|
||||||
|
bool changed = (barberHairStyle_ != barberOrigHairStyle_ ||
|
||||||
|
barberHairColor_ != barberOrigHairColor_ ||
|
||||||
|
barberFacialHair_ != barberOrigFacialHair_);
|
||||||
|
|
||||||
|
// OK / Reset / Cancel buttons
|
||||||
|
float btnW = 80.0f;
|
||||||
|
float totalW = btnW * 3 + ImGui::GetStyle().ItemSpacing.x * 2;
|
||||||
|
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - totalW) / 2.0f);
|
||||||
|
|
||||||
|
if (!changed) ImGui::BeginDisabled();
|
||||||
|
if (ImGui::Button("OK", ImVec2(btnW, 0))) {
|
||||||
|
gameHandler.sendAlterAppearance(
|
||||||
|
static_cast<uint32_t>(barberHairStyle_),
|
||||||
|
static_cast<uint32_t>(barberHairColor_),
|
||||||
|
static_cast<uint32_t>(barberFacialHair_));
|
||||||
|
// Keep window open — server will respond with SMSG_BARBER_SHOP_RESULT
|
||||||
|
}
|
||||||
|
if (!changed) ImGui::EndDisabled();
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (!changed) ImGui::BeginDisabled();
|
||||||
|
if (ImGui::Button("Reset", ImVec2(btnW, 0))) {
|
||||||
|
barberHairStyle_ = barberOrigHairStyle_;
|
||||||
|
barberHairColor_ = barberOrigHairColor_;
|
||||||
|
barberFacialHair_ = barberOrigFacialHair_;
|
||||||
|
}
|
||||||
|
if (!changed) ImGui::EndDisabled();
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Cancel", ImVec2(btnW, 0))) {
|
||||||
|
gameHandler.closeBarberShop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
gameHandler.closeBarberShop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Pet Stable Window
|
// Pet Stable Window
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue