diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index db702467..0e56ea59 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1230,6 +1230,11 @@ public: void setAutoLoot(bool enabled) { autoLoot_ = enabled; } bool isAutoLoot() const { return autoLoot_; } + // Master loot candidates (from SMSG_LOOT_MASTER_LIST) + const std::vector& getMasterLootCandidates() const { return masterLootCandidates_; } + bool hasMasterLootCandidates() const { return !masterLootCandidates_.empty(); } + void lootMasterGive(uint8_t lootSlot, uint64_t targetGuid); + // Group loot roll struct LootRollEntry { uint64_t objectGuid = 0; @@ -2493,6 +2498,7 @@ private: bool lootWindowOpen = false; bool autoLoot_ = false; LootResponseData currentLoot; + std::vector masterLootCandidates_; // from SMSG_LOOT_MASTER_LIST // Group loot roll state bool pendingLootRollActive_ = false; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 6bc076da..0670e917 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -3342,10 +3342,19 @@ void GameHandler::handlePacket(network::Packet& packet) { case Opcode::SMSG_LOOT_ROLL_WON: handleLootRollWon(packet); break; - case Opcode::SMSG_LOOT_MASTER_LIST: - // Master looter list — no UI yet; consume to avoid unhandled warning. - packet.setReadPos(packet.getSize()); + case Opcode::SMSG_LOOT_MASTER_LIST: { + // uint8 count + count * uint64 guid — eligible recipients for master looter + masterLootCandidates_.clear(); + if (packet.getSize() - packet.getReadPos() < 1) break; + uint8_t mlCount = packet.readUInt8(); + masterLootCandidates_.reserve(mlCount); + for (uint8_t i = 0; i < mlCount; ++i) { + if (packet.getSize() - packet.getReadPos() < 8) break; + masterLootCandidates_.push_back(packet.readUInt64()); + } + LOG_INFO("SMSG_LOOT_MASTER_LIST: ", (int)masterLootCandidates_.size(), " candidates"); break; + } case Opcode::SMSG_GOSSIP_MESSAGE: handleGossipMessage(packet); break; @@ -15585,6 +15594,7 @@ void GameHandler::lootItem(uint8_t slotIndex) { void GameHandler::closeLoot() { if (!lootWindowOpen) return; lootWindowOpen = false; + masterLootCandidates_.clear(); if (currentLoot.lootGuid != 0 && targetGuid == currentLoot.lootGuid) { clearTarget(); } @@ -15595,6 +15605,16 @@ void GameHandler::closeLoot() { currentLoot = LootResponseData{}; } +void GameHandler::lootMasterGive(uint8_t lootSlot, uint64_t targetGuid) { + if (state != WorldState::IN_WORLD || !socket) return; + // CMSG_LOOT_MASTER_GIVE: uint64 lootGuid + uint8 slotIndex + uint64 targetGuid + network::Packet pkt(wireOpcode(Opcode::CMSG_LOOT_MASTER_GIVE)); + pkt.writeUInt64(currentLoot.lootGuid); + pkt.writeUInt8(lootSlot); + pkt.writeUInt64(targetGuid); + socket->send(pkt); +} + void GameHandler::interactWithNpc(uint64_t guid) { if (state != WorldState::IN_WORLD || !socket) return; auto packet = GossipHelloPacket::build(guid); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 7429f04b..69cc6cef 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -12164,7 +12164,43 @@ void GameScreen::renderLootWindow(game::GameHandler& gameHandler) { // Process deferred loot pickup (after loop to avoid iterator invalidation) if (lootSlotClicked >= 0) { - gameHandler.lootItem(static_cast(lootSlotClicked)); + if (gameHandler.hasMasterLootCandidates()) { + // Master looter: open popup to choose recipient + char popupId[32]; + snprintf(popupId, sizeof(popupId), "##MLGive%d", lootSlotClicked); + ImGui::OpenPopup(popupId); + } else { + gameHandler.lootItem(static_cast(lootSlotClicked)); + } + } + + // Master loot "Give to" popups + if (gameHandler.hasMasterLootCandidates()) { + for (const auto& item : loot.items) { + char popupId[32]; + snprintf(popupId, sizeof(popupId), "##MLGive%d", item.slotIndex); + if (ImGui::BeginPopup(popupId)) { + ImGui::TextDisabled("Give to:"); + ImGui::Separator(); + const auto& candidates = gameHandler.getMasterLootCandidates(); + for (uint64_t candidateGuid : candidates) { + auto entity = gameHandler.getEntityManager().getEntity(candidateGuid); + auto* unit = entity ? dynamic_cast(entity.get()) : nullptr; + const char* cName = unit ? unit->getName().c_str() : nullptr; + char nameBuf[64]; + if (!cName || cName[0] == '\0') { + snprintf(nameBuf, sizeof(nameBuf), "Player 0x%llx", + static_cast(candidateGuid)); + cName = nameBuf; + } + if (ImGui::MenuItem(cName)) { + gameHandler.lootMasterGive(item.slotIndex, candidateGuid); + ImGui::CloseCurrentPopup(); + } + } + ImGui::EndPopup(); + } + } } if (loot.items.empty() && loot.gold == 0) {