From 6df8c72cf7a3ae0eff4149df94002cda7504879b Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Mar 2026 17:48:08 -0700 Subject: [PATCH] feat: add WotLK equipment set UI to character screen - Expose equipment sets via public EquipmentSetInfo getter - Populate equipmentSetInfo_ from handleEquipmentSetList() - Implement useEquipmentSet() sending CMSG_EQUIPMENT_SET_USE - Add "Outfits" tab in character screen listing saved sets with Equip button --- include/game/game_handler.hpp | 11 +++++++++++ src/game/game_handler.cpp | 19 +++++++++++++++++++ src/ui/inventory_screen.cpp | 28 ++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index ae4ed3fd..037a0ea4 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1252,6 +1252,16 @@ public: void sendLootRoll(uint64_t objectGuid, uint32_t slot, uint8_t rollType); // rollType: 0=need, 1=greed, 2=disenchant, 96=pass + // Equipment Sets (WotLK): saved gear loadouts + struct EquipmentSetInfo { + uint64_t setGuid = 0; + uint32_t setId = 0; + std::string name; + std::string iconName; + }; + const std::vector& getEquipmentSets() const { return equipmentSetInfo_; } + void useEquipmentSet(uint32_t setId); + // NPC Gossip void interactWithNpc(uint64_t guid); void interactWithGameObject(uint64_t guid); @@ -2840,6 +2850,7 @@ private: std::array itemGuids{}; }; std::vector equipmentSets_; + std::vector equipmentSetInfo_; // public-facing copy // ---- Forced faction reactions (SMSG_SET_FORCED_REACTIONS) ---- std::unordered_map forcedReactions_; // factionId -> reaction tier diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index c7396f8f..485d96aa 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -7914,6 +7914,14 @@ void GameHandler::sendRequestVehicleExit() { vehicleId_ = 0; // Optimistically clear; server will confirm via SMSG_PLAYER_VEHICLE_DATA(0) } +void GameHandler::useEquipmentSet(uint32_t setId) { + if (state != WorldState::IN_WORLD) return; + // CMSG_EQUIPMENT_SET_USE: uint32 setId + network::Packet pkt(wireOpcode(Opcode::CMSG_EQUIPMENT_SET_USE)); + pkt.writeUInt32(setId); + socket->send(pkt); +} + void GameHandler::sendMinimapPing(float wowX, float wowY) { if (state != WorldState::IN_WORLD) return; @@ -20633,6 +20641,17 @@ void GameHandler::handleEquipmentSetList(network::Packet& packet) { } equipmentSets_.push_back(std::move(es)); } + // Populate public-facing info + equipmentSetInfo_.clear(); + equipmentSetInfo_.reserve(equipmentSets_.size()); + for (const auto& es : equipmentSets_) { + EquipmentSetInfo info; + info.setGuid = es.setGuid; + info.setId = es.setId; + info.name = es.name; + info.iconName = es.iconName; + equipmentSetInfo_.push_back(std::move(info)); + } LOG_INFO("SMSG_EQUIPMENT_SET_LIST: ", equipmentSets_.size(), " equipment sets received"); } diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index acce3a27..1c029217 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -1342,6 +1342,34 @@ void InventoryScreen::renderCharacterScreen(game::GameHandler& gameHandler) { ImGui::EndTabItem(); } + // Equipment Sets tab (WotLK only) + const auto& eqSets = gameHandler.getEquipmentSets(); + if (!eqSets.empty()) { + if (ImGui::BeginTabItem("Outfits")) { + ImGui::Spacing(); + ImGui::TextDisabled("Saved Equipment Sets"); + ImGui::Separator(); + ImGui::BeginChild("##EqSetsList", ImVec2(0, 0), false); + for (const auto& es : eqSets) { + ImGui::PushID(static_cast(es.setId)); + // Icon placeholder or name + const char* displayName = es.name.empty() ? "(Unnamed)" : es.name.c_str(); + ImGui::Text("%s", displayName); + if (!es.iconName.empty()) { + ImGui::SameLine(); + ImGui::TextDisabled("(%s)", es.iconName.c_str()); + } + ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60.0f); + if (ImGui::SmallButton("Equip")) { + gameHandler.useEquipmentSet(es.setId); + } + ImGui::PopID(); + } + ImGui::EndChild(); + ImGui::EndTabItem(); + } + } + ImGui::EndTabBar(); }