Files
WoWee/tests/test_inventory_sort.cpp
Kelsi e08b64c88e feat: bank window sort, contiguous view, and bag-slot prices
Add a Sort button that orders the main bank and bank bags by quality/itemID/
stack (mirroring the backpack sort, one CMSG_SWAP_ITEM per frame), a
"Combine bags" toggle that renders every bank slot as one continuous grid,
and per-slot purchase prices from BankBagSlotPrices.dbc on the bank-bag row
(only the next slot in sequence is buyable; later ones are locked).
2026-07-23 17:53:10 -07:00

146 lines
5.4 KiB
C++

#include "catch_amalgamated.hpp"
#include "game/inventory.hpp"
using namespace wowee::game;
namespace {
ItemDef makeItem(uint32_t id, ItemQuality quality, uint32_t stack = 1) {
ItemDef def;
def.itemId = id;
def.quality = quality;
def.stackCount = stack;
def.maxStack = 20;
return def;
}
} // namespace
TEST_CASE("sortBags orders backpack by quality then itemId", "[inventory]") {
Inventory inv;
inv.setBackpackSlot(0, makeItem(500, ItemQuality::COMMON));
inv.setBackpackSlot(3, makeItem(100, ItemQuality::RARE));
inv.setBackpackSlot(7, makeItem(200, ItemQuality::UNCOMMON));
inv.sortBags();
CHECK(inv.getBackpackSlot(0).item.itemId == 100); // rare first
CHECK(inv.getBackpackSlot(1).item.itemId == 200); // then uncommon
CHECK(inv.getBackpackSlot(2).item.itemId == 500); // then common
CHECK(inv.getBackpackSlot(3).empty());
}
TEST_CASE("sortBags skips special containers", "[inventory]") {
Inventory inv;
// Bag 0: quiver with arrows in slots 0 and 2
inv.setBagSize(0, 6);
inv.setBagSpecial(0, true);
inv.setBagSlot(0, 0, makeItem(2512, ItemQuality::COMMON, 200)); // arrows
inv.setBagSlot(0, 2, makeItem(2512, ItemQuality::COMMON, 50));
// Bag 1: normal bag with one item
inv.setBagSize(1, 4);
inv.setBagSlot(1, 3, makeItem(300, ItemQuality::EPIC));
// Backpack: one item so bag contents have room to move forward
inv.setBackpackSlot(5, makeItem(400, ItemQuality::COMMON));
inv.sortBags();
// Quiver contents untouched, including the gap between them
CHECK(inv.getBagSlot(0, 0).item.itemId == 2512);
CHECK(inv.getBagSlot(0, 0).item.stackCount == 200);
CHECK(inv.getBagSlot(0, 1).empty());
CHECK(inv.getBagSlot(0, 2).item.itemId == 2512);
CHECK(inv.getBagSlot(0, 2).item.stackCount == 50);
// Normal bag + backpack items pooled and sorted into the backpack
CHECK(inv.getBackpackSlot(0).item.itemId == 300); // epic first
CHECK(inv.getBackpackSlot(1).item.itemId == 400);
CHECK(inv.getBagSlot(1, 3).empty());
}
TEST_CASE("computeSortSwaps never addresses special containers", "[inventory]") {
Inventory inv;
inv.setBagSize(0, 6);
inv.setBagSpecial(0, true);
inv.setBagSlot(0, 0, makeItem(2512, ItemQuality::COMMON, 200));
inv.setBagSize(1, 4);
inv.setBagSlot(1, 0, makeItem(100, ItemQuality::RARE));
inv.setBackpackSlot(0, makeItem(500, ItemQuality::COMMON));
auto swaps = inv.computeSortSwaps();
// Wire address of bag 0 is FIRST_BAG_EQUIP_SLOT + 0 = 19
const uint8_t quiverBag = static_cast<uint8_t>(Inventory::FIRST_BAG_EQUIP_SLOT);
for (const auto& op : swaps) {
CHECK(op.srcBag != quiverBag);
CHECK(op.dstBag != quiverBag);
}
// The rare item from the normal bag should still be moved (sort not a no-op)
CHECK(!swaps.empty());
}
TEST_CASE("sortBank orders main bank slots by quality then itemId", "[inventory]") {
Inventory inv;
inv.setBankSlot(0, makeItem(500, ItemQuality::COMMON));
inv.setBankSlot(4, makeItem(100, ItemQuality::RARE));
inv.setBankSlot(9, makeItem(200, ItemQuality::UNCOMMON));
inv.sortBank(28);
CHECK(inv.getBankSlot(0).item.itemId == 100); // rare first
CHECK(inv.getBankSlot(1).item.itemId == 200); // then uncommon
CHECK(inv.getBankSlot(2).item.itemId == 500); // then common
CHECK(inv.getBankSlot(3).empty());
}
TEST_CASE("sortBank pools bank bag contents into the main bank", "[inventory]") {
Inventory inv;
// Main bank has one low-quality item; a bank bag holds a higher-quality one.
inv.setBankSlot(2, makeItem(400, ItemQuality::COMMON));
inv.setBankBagSize(0, 6);
inv.setBankBagSlot(0, 3, makeItem(100, ItemQuality::EPIC));
inv.sortBank(28);
// Everything pools and refills main bank first: epic, then common.
CHECK(inv.getBankSlot(0).item.itemId == 100);
CHECK(inv.getBankSlot(1).item.itemId == 400);
CHECK(inv.getBankBagSlot(0, 3).empty());
}
TEST_CASE("sortBank respects the main slot count for Classic", "[inventory]") {
Inventory inv;
// Only 24 main slots exist on Classic — item in slot 25 must not seed the sort,
// and the sort must never write past slot 23.
inv.setBankSlot(0, makeItem(500, ItemQuality::COMMON));
inv.setBankSlot(1, makeItem(100, ItemQuality::RARE));
inv.sortBank(24);
CHECK(inv.getBankSlot(0).item.itemId == 100);
CHECK(inv.getBankSlot(1).item.itemId == 500);
CHECK(inv.getBankSlot(24).empty());
CHECK(inv.getBankSlot(27).empty());
}
TEST_CASE("computeBankSortSwaps addresses bank slots and bags correctly", "[inventory]") {
Inventory inv;
inv.setBankSlot(0, makeItem(500, ItemQuality::COMMON));
inv.setBankSlot(1, makeItem(100, ItemQuality::RARE));
inv.setBankBagSize(0, 4);
inv.setBankBagSlot(0, 0, makeItem(300, ItemQuality::EPIC));
auto swaps = inv.computeBankSortSwaps(28);
CHECK(!swaps.empty());
// Every address is either the main bank (0xFF, slot >= BANK_SLOT_START) or a bank
// bag container (>= BANK_BAG_CONTAINER_START).
for (const auto& op : swaps) {
bool srcOk = (op.srcBag == 0xFF && op.srcSlot >= Inventory::BANK_SLOT_START) ||
(op.srcBag >= Inventory::BANK_BAG_CONTAINER_START);
bool dstOk = (op.dstBag == 0xFF && op.dstSlot >= Inventory::BANK_SLOT_START) ||
(op.dstBag >= Inventory::BANK_BAG_CONTAINER_START);
CHECK(srcOk);
CHECK(dstOk);
}
}