mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-25 13:03:50 +00:00
feat: add item stack splitting via Shift+right-click
Implements CMSG_SPLIT_ITEM (0x10E) with a slider popup for choosing split count. Auto-finds empty destination slot across backpack and bags. Shift+right-click on stackable items (count > 1) opens split dialog; non-stackable items still get the destroy confirmation.
This commit is contained in:
parent
17d652947c
commit
9b32a328c3
6 changed files with 122 additions and 15 deletions
|
|
@ -2029,6 +2029,7 @@ public:
|
||||||
void openItemBySlot(int backpackIndex);
|
void openItemBySlot(int backpackIndex);
|
||||||
void openItemInBag(int bagIndex, int slotIndex);
|
void openItemInBag(int bagIndex, int slotIndex);
|
||||||
void destroyItem(uint8_t bag, uint8_t slot, uint8_t count = 1);
|
void destroyItem(uint8_t bag, uint8_t slot, uint8_t count = 1);
|
||||||
|
void splitItem(uint8_t srcBag, uint8_t srcSlot, uint8_t count);
|
||||||
void swapContainerItems(uint8_t srcBag, uint8_t srcSlot, uint8_t dstBag, uint8_t dstSlot);
|
void swapContainerItems(uint8_t srcBag, uint8_t srcSlot, uint8_t dstBag, uint8_t dstSlot);
|
||||||
void swapBagSlots(int srcBagIndex, int dstBagIndex);
|
void swapBagSlots(int srcBagIndex, int dstBagIndex);
|
||||||
void useItemById(uint32_t itemId);
|
void useItemById(uint32_t itemId);
|
||||||
|
|
|
||||||
|
|
@ -2046,6 +2046,13 @@ public:
|
||||||
static network::Packet build(uint8_t dstBag, uint8_t dstSlot, uint8_t srcBag, uint8_t srcSlot);
|
static network::Packet build(uint8_t dstBag, uint8_t dstSlot, uint8_t srcBag, uint8_t srcSlot);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** CMSG_SPLIT_ITEM packet builder */
|
||||||
|
class SplitItemPacket {
|
||||||
|
public:
|
||||||
|
static network::Packet build(uint8_t srcBag, uint8_t srcSlot,
|
||||||
|
uint8_t dstBag, uint8_t dstSlot, uint8_t count);
|
||||||
|
};
|
||||||
|
|
||||||
/** CMSG_SWAP_INV_ITEM packet builder */
|
/** CMSG_SWAP_INV_ITEM packet builder */
|
||||||
class SwapInvItemPacket {
|
class SwapInvItemPacket {
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,14 @@ private:
|
||||||
uint8_t destroyCount_ = 1;
|
uint8_t destroyCount_ = 1;
|
||||||
std::string destroyItemName_;
|
std::string destroyItemName_;
|
||||||
|
|
||||||
|
// Stack split popup state
|
||||||
|
bool splitConfirmOpen_ = false;
|
||||||
|
uint8_t splitBag_ = 0xFF;
|
||||||
|
uint8_t splitSlot_ = 0;
|
||||||
|
int splitMax_ = 1;
|
||||||
|
int splitCount_ = 1;
|
||||||
|
std::string splitItemName_;
|
||||||
|
|
||||||
// Pending chat item link from shift-click
|
// Pending chat item link from shift-click
|
||||||
std::string pendingChatItemLink_;
|
std::string pendingChatItemLink_;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21177,6 +21177,40 @@ void GameHandler::destroyItem(uint8_t bag, uint8_t slot, uint8_t count) {
|
||||||
socket->send(packet);
|
socket->send(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameHandler::splitItem(uint8_t srcBag, uint8_t srcSlot, uint8_t count) {
|
||||||
|
if (state != WorldState::IN_WORLD || !socket) return;
|
||||||
|
if (count == 0) return;
|
||||||
|
|
||||||
|
// Find a free slot for the split destination: try backpack first, then bags
|
||||||
|
int freeBp = inventory.findFreeBackpackSlot();
|
||||||
|
if (freeBp >= 0) {
|
||||||
|
uint8_t dstBag = 0xFF;
|
||||||
|
uint8_t dstSlot = static_cast<uint8_t>(23 + freeBp);
|
||||||
|
LOG_INFO("splitItem: src(bag=", (int)srcBag, " slot=", (int)srcSlot,
|
||||||
|
") count=", (int)count, " -> dst(bag=0xFF slot=", (int)dstSlot, ")");
|
||||||
|
auto packet = SplitItemPacket::build(srcBag, srcSlot, dstBag, dstSlot, count);
|
||||||
|
socket->send(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Try equipped bags
|
||||||
|
for (int b = 0; b < inventory.NUM_BAG_SLOTS; b++) {
|
||||||
|
int bagSize = inventory.getBagSize(b);
|
||||||
|
for (int s = 0; s < bagSize; s++) {
|
||||||
|
if (inventory.getBagSlot(b, s).empty()) {
|
||||||
|
uint8_t dstBag = static_cast<uint8_t>(19 + b);
|
||||||
|
uint8_t dstSlot = static_cast<uint8_t>(s);
|
||||||
|
LOG_INFO("splitItem: src(bag=", (int)srcBag, " slot=", (int)srcSlot,
|
||||||
|
") count=", (int)count, " -> dst(bag=", (int)dstBag,
|
||||||
|
" slot=", (int)dstSlot, ")");
|
||||||
|
auto packet = SplitItemPacket::build(srcBag, srcSlot, dstBag, dstSlot, count);
|
||||||
|
socket->send(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addSystemChatMessage("Cannot split: no free inventory slots.");
|
||||||
|
}
|
||||||
|
|
||||||
void GameHandler::useItemBySlot(int backpackIndex) {
|
void GameHandler::useItemBySlot(int backpackIndex) {
|
||||||
if (backpackIndex < 0 || backpackIndex >= inventory.getBackpackSize()) return;
|
if (backpackIndex < 0 || backpackIndex >= inventory.getBackpackSize()) return;
|
||||||
const auto& slot = inventory.getBackpackSlot(backpackIndex);
|
const auto& slot = inventory.getBackpackSlot(backpackIndex);
|
||||||
|
|
|
||||||
|
|
@ -4358,6 +4358,17 @@ network::Packet SwapItemPacket::build(uint8_t dstBag, uint8_t dstSlot, uint8_t s
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
network::Packet SplitItemPacket::build(uint8_t srcBag, uint8_t srcSlot,
|
||||||
|
uint8_t dstBag, uint8_t dstSlot, uint8_t count) {
|
||||||
|
network::Packet packet(wireOpcode(Opcode::CMSG_SPLIT_ITEM));
|
||||||
|
packet.writeUInt8(srcBag);
|
||||||
|
packet.writeUInt8(srcSlot);
|
||||||
|
packet.writeUInt8(dstBag);
|
||||||
|
packet.writeUInt8(dstSlot);
|
||||||
|
packet.writeUInt8(count);
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
network::Packet SwapInvItemPacket::build(uint8_t srcSlot, uint8_t dstSlot) {
|
network::Packet SwapInvItemPacket::build(uint8_t srcSlot, uint8_t dstSlot) {
|
||||||
network::Packet packet(wireOpcode(Opcode::CMSG_SWAP_INV_ITEM));
|
network::Packet packet(wireOpcode(Opcode::CMSG_SWAP_INV_ITEM));
|
||||||
packet.writeUInt8(srcSlot);
|
packet.writeUInt8(srcSlot);
|
||||||
|
|
|
||||||
|
|
@ -871,6 +871,35 @@ void InventoryScreen::render(game::Inventory& inventory, uint64_t moneyCopper) {
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stack split popup
|
||||||
|
if (splitConfirmOpen_) {
|
||||||
|
ImVec2 mousePos = ImGui::GetIO().MousePos;
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(mousePos.x - 80.0f, mousePos.y - 20.0f), ImGuiCond_Always);
|
||||||
|
ImGui::OpenPopup("##SplitStack");
|
||||||
|
splitConfirmOpen_ = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopup("##SplitStack", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar)) {
|
||||||
|
ImGui::Text("Split %s", splitItemName_.c_str());
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::SetNextItemWidth(120.0f);
|
||||||
|
ImGui::SliderInt("##splitcount", &splitCount_, 1, splitMax_ - 1);
|
||||||
|
ImGui::Spacing();
|
||||||
|
if (ImGui::Button("OK", ImVec2(55, 0))) {
|
||||||
|
if (gameHandler_ && splitCount_ > 0 && splitCount_ < splitMax_) {
|
||||||
|
gameHandler_->splitItem(splitBag_, splitSlot_, static_cast<uint8_t>(splitCount_));
|
||||||
|
}
|
||||||
|
splitItemName_.clear();
|
||||||
|
inventoryDirty = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Cancel", ImVec2(55, 0))) {
|
||||||
|
splitItemName_.clear();
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
// Draw held item at cursor
|
// Draw held item at cursor
|
||||||
renderHeldItem();
|
renderHeldItem();
|
||||||
}
|
}
|
||||||
|
|
@ -2302,9 +2331,25 @@ void InventoryScreen::renderItemSlot(game::Inventory& inventory, const game::Ite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift+right-click: open destroy confirmation for non-quest items
|
// Shift+right-click: split stack (if stackable >1) or destroy confirmation
|
||||||
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right) &&
|
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right) &&
|
||||||
!holdingItem && ImGui::GetIO().KeyShift && item.itemId != 0 && item.bindType != 4) {
|
!holdingItem && ImGui::GetIO().KeyShift && item.itemId != 0) {
|
||||||
|
if (item.stackCount > 1 && item.maxStack > 1) {
|
||||||
|
// Open split popup for stackable items
|
||||||
|
splitConfirmOpen_ = true;
|
||||||
|
splitItemName_ = item.name;
|
||||||
|
splitMax_ = static_cast<int>(item.stackCount);
|
||||||
|
splitCount_ = splitMax_ / 2;
|
||||||
|
if (splitCount_ < 1) splitCount_ = 1;
|
||||||
|
if (kind == SlotKind::BACKPACK && backpackIndex >= 0) {
|
||||||
|
splitBag_ = 0xFF;
|
||||||
|
splitSlot_ = static_cast<uint8_t>(23 + backpackIndex);
|
||||||
|
} else if (kind == SlotKind::BACKPACK && isBagSlot) {
|
||||||
|
splitBag_ = static_cast<uint8_t>(19 + bagIndex);
|
||||||
|
splitSlot_ = static_cast<uint8_t>(bagSlotIndex);
|
||||||
|
}
|
||||||
|
} else if (item.bindType != 4) {
|
||||||
|
// Destroy confirmation for non-quest, non-stackable items
|
||||||
destroyConfirmOpen_ = true;
|
destroyConfirmOpen_ = true;
|
||||||
destroyItemName_ = item.name;
|
destroyItemName_ = item.name;
|
||||||
destroyCount_ = static_cast<uint8_t>(std::clamp<uint32_t>(
|
destroyCount_ = static_cast<uint8_t>(std::clamp<uint32_t>(
|
||||||
|
|
@ -2320,6 +2365,7 @@ void InventoryScreen::renderItemSlot(game::Inventory& inventory, const game::Ite
|
||||||
destroySlot_ = static_cast<uint8_t>(equipSlot);
|
destroySlot_ = static_cast<uint8_t>(equipSlot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Right-click: bank deposit (if bank open), vendor sell (if vendor mode), or auto-equip/use
|
// Right-click: bank deposit (if bank open), vendor sell (if vendor mode), or auto-equip/use
|
||||||
// Note: InvisibleButton only tracks left-click by default, so use IsItemHovered+IsMouseClicked
|
// Note: InvisibleButton only tracks left-click by default, so use IsItemHovered+IsMouseClicked
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue