feat: implement equipment set save, update, and delete

Add saveEquipmentSet() and deleteEquipmentSet() methods that send
CMSG_EQUIPMENT_SET_SAVE and CMSG_DELETEEQUIPMENT_SET packets. The save
packet captures all 19 equipment slot GUIDs via packed GUID encoding.
The Outfits tab now always shows (not just when sets exist), with an
input field to create new sets and Update/Delete buttons per set.
This commit is contained in:
Kelsi 2026-03-20 04:43:46 -07:00
parent f88d90ee88
commit ae56f2eb80
3 changed files with 82 additions and 14 deletions

View file

@ -1535,6 +1535,9 @@ public:
};
const std::vector<EquipmentSetInfo>& 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);

View file

@ -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, 018)
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;

View file

@ -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<int>(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();