diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index cb34d462..67e88844 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1535,6 +1535,9 @@ public: }; const std::vector& getEquipmentSets() const { return equipmentSetInfo_; } void useEquipmentSet(uint32_t setId); + void saveEquipmentSet(const std::string& name, const std::string& iconName = "INV_Misc_QuestionMark", + uint64_t existingGuid = 0, uint32_t setIndex = 0xFFFFFFFF); + void deleteEquipmentSet(uint64_t setGuid); // NPC Gossip void interactWithNpc(uint64_t guid); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 89002564..e542dbca 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -10668,6 +10668,49 @@ void GameHandler::useEquipmentSet(uint32_t setId) { socket->send(pkt); } +void GameHandler::saveEquipmentSet(const std::string& name, const std::string& iconName, + uint64_t existingGuid, uint32_t setIndex) { + if (state != WorldState::IN_WORLD) return; + // CMSG_EQUIPMENT_SET_SAVE: uint64 setGuid + uint32 setIndex + string name + string iconName + // + 19 × PackedGuid itemGuid (one per equipment slot, 0–18) + if (setIndex == 0xFFFFFFFF) { + // Auto-assign next free index + setIndex = 0; + for (const auto& es : equipmentSets_) { + if (es.setId >= setIndex) setIndex = es.setId + 1; + } + } + network::Packet pkt(wireOpcode(Opcode::CMSG_EQUIPMENT_SET_SAVE)); + pkt.writeUInt64(existingGuid); // 0 = create new, nonzero = update + pkt.writeUInt32(setIndex); + pkt.writeString(name); + pkt.writeString(iconName); + for (int slot = 0; slot < 19; ++slot) { + uint64_t guid = getEquipSlotGuid(slot); + MovementPacket::writePackedGuid(pkt, guid); + } + socket->send(pkt); + LOG_INFO("CMSG_EQUIPMENT_SET_SAVE: name=\"", name, "\" guid=", existingGuid, " index=", setIndex); +} + +void GameHandler::deleteEquipmentSet(uint64_t setGuid) { + if (state != WorldState::IN_WORLD || setGuid == 0) return; + // CMSG_DELETEEQUIPMENT_SET: uint64 setGuid + network::Packet pkt(wireOpcode(Opcode::CMSG_DELETEEQUIPMENT_SET)); + pkt.writeUInt64(setGuid); + socket->send(pkt); + // Remove locally so UI updates immediately + equipmentSets_.erase( + std::remove_if(equipmentSets_.begin(), equipmentSets_.end(), + [setGuid](const EquipmentSet& es) { return es.setGuid == setGuid; }), + equipmentSets_.end()); + equipmentSetInfo_.erase( + std::remove_if(equipmentSetInfo_.begin(), equipmentSetInfo_.end(), + [setGuid](const EquipmentSetInfo& es) { return es.setGuid == setGuid; }), + equipmentSetInfo_.end()); + LOG_INFO("CMSG_DELETEEQUIPMENT_SET: guid=", setGuid); +} + void GameHandler::sendMinimapPing(float wowX, float wowY) { if (state != WorldState::IN_WORLD) return; diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index bee298c9..3597dc68 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -1438,32 +1438,54 @@ 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(); + // Equipment Sets tab (WotLK — always show so player can create sets) + if (ImGui::BeginTabItem("Outfits")) { + ImGui::Spacing(); + + // Save current gear as new set + static char newSetName[64] = {}; + ImGui::SetNextItemWidth(160.0f); + ImGui::InputTextWithHint("##newsetname", "New set name...", newSetName, sizeof(newSetName)); + ImGui::SameLine(); + bool canSave = (newSetName[0] != '\0'); + if (!canSave) ImGui::BeginDisabled(); + if (ImGui::SmallButton("Save Current Gear")) { + gameHandler.saveEquipmentSet(newSetName); + newSetName[0] = '\0'; + } + if (!canSave) ImGui::EndDisabled(); + + ImGui::Separator(); + + const auto& eqSets = gameHandler.getEquipmentSets(); + if (eqSets.empty()) { + ImGui::TextDisabled("No saved equipment sets."); + } else { 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); + float btnAreaW = 150.0f; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - btnAreaW + ImGui::GetCursorPosX()); if (ImGui::SmallButton("Equip")) { gameHandler.useEquipmentSet(es.setId); } + ImGui::SameLine(); + if (ImGui::SmallButton("Update")) { + gameHandler.saveEquipmentSet(es.name, es.iconName, es.setGuid, es.setId); + } + ImGui::SameLine(); + if (ImGui::SmallButton("Delete")) { + gameHandler.deleteEquipmentSet(es.setGuid); + ImGui::PopID(); + break; // Iterator invalidated + } ImGui::PopID(); } ImGui::EndChild(); - ImGui::EndTabItem(); } + ImGui::EndTabItem(); } ImGui::EndTabBar();