mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +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 openItemInBag(int bagIndex, int slotIndex);
|
||||
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 swapBagSlots(int srcBagIndex, int dstBagIndex);
|
||||
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);
|
||||
};
|
||||
|
||||
/** 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 */
|
||||
class SwapInvItemPacket {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -187,6 +187,14 @@ private:
|
|||
uint8_t destroyCount_ = 1;
|
||||
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
|
||||
std::string pendingChatItemLink_;
|
||||
|
||||
|
|
|
|||
|
|
@ -21177,6 +21177,40 @@ void GameHandler::destroyItem(uint8_t bag, uint8_t slot, uint8_t count) {
|
|||
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) {
|
||||
if (backpackIndex < 0 || backpackIndex >= inventory.getBackpackSize()) return;
|
||||
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;
|
||||
}
|
||||
|
||||
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 packet(wireOpcode(Opcode::CMSG_SWAP_INV_ITEM));
|
||||
packet.writeUInt8(srcSlot);
|
||||
|
|
|
|||
|
|
@ -871,6 +871,35 @@ void InventoryScreen::render(game::Inventory& inventory, uint64_t moneyCopper) {
|
|||
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
|
||||
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) &&
|
||||
!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;
|
||||
destroyItemName_ = item.name;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue