mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 01:23:51 +00:00
feat: server-synced bag sort, fix world map continent bounds, update docs
Inventory sort: clicking "Sort Bags" now generates CMSG_SWAP_ITEM packets to move items server-side (one swap per frame to avoid race conditions). Client-side sort runs immediately for visual preview; server swaps follow. New Inventory::computeSortSwaps() computes minimal swap sequence using selection-sort permutation on quality→itemId→stackCount comparator. World map: fix continent bounds derivation that used intersection (max/min) instead of union (min/max) of child zone bounds, causing continent views to display zoomed-in/clipped. Update README.md and docs/status.md with current features, release info, and known gaps (v1.8.2-preview, 664 opcode handlers, NPC voices, bag independence, CharSections auto-detect, quest GO server limitation).
This commit is contained in:
parent
62e99da1c2
commit
c18720f0f0
7 changed files with 145 additions and 22 deletions
|
|
@ -224,6 +224,92 @@ void Inventory::sortBags() {
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<Inventory::SwapOp> Inventory::computeSortSwaps() const {
|
||||
// Build a flat list of (bag, slot, item) entries matching the same traversal
|
||||
// order as sortBags(): backpack first, then equip bags in order.
|
||||
struct Entry {
|
||||
uint8_t bag; // WoW bag address: 0xFF=backpack, 19+i=equip bag i
|
||||
uint8_t slot; // WoW slot address: 23+i for backpack, slotIndex for bags
|
||||
uint32_t itemId;
|
||||
ItemQuality quality;
|
||||
uint32_t stackCount;
|
||||
};
|
||||
|
||||
std::vector<Entry> entries;
|
||||
entries.reserve(BACKPACK_SLOTS + NUM_BAG_SLOTS * MAX_BAG_SIZE);
|
||||
|
||||
for (int i = 0; i < BACKPACK_SLOTS; ++i) {
|
||||
entries.push_back({0xFF, static_cast<uint8_t>(23 + i),
|
||||
backpack[i].item.itemId, backpack[i].item.quality,
|
||||
backpack[i].item.stackCount});
|
||||
}
|
||||
for (int b = 0; b < NUM_BAG_SLOTS; ++b) {
|
||||
for (int s = 0; s < bags[b].size; ++s) {
|
||||
entries.push_back({static_cast<uint8_t>(19 + b), static_cast<uint8_t>(s),
|
||||
bags[b].slots[s].item.itemId, bags[b].slots[s].item.quality,
|
||||
bags[b].slots[s].item.stackCount});
|
||||
}
|
||||
}
|
||||
|
||||
// Build a sorted index array using the same comparator as sortBags().
|
||||
int n = static_cast<int>(entries.size());
|
||||
std::vector<int> sortedIdx(n);
|
||||
for (int i = 0; i < n; ++i) sortedIdx[i] = i;
|
||||
|
||||
// Separate non-empty items and empty slots, then sort the non-empty items.
|
||||
// Items are sorted by quality desc -> itemId asc -> stackCount desc.
|
||||
// Empty slots go to the end.
|
||||
std::stable_sort(sortedIdx.begin(), sortedIdx.end(), [&](int a, int b) {
|
||||
bool aEmpty = (entries[a].itemId == 0);
|
||||
bool bEmpty = (entries[b].itemId == 0);
|
||||
if (aEmpty != bEmpty) return bEmpty; // non-empty before empty
|
||||
if (aEmpty) return false; // both empty: preserve order
|
||||
// Both non-empty: same comparator as sortBags()
|
||||
if (entries[a].quality != entries[b].quality)
|
||||
return static_cast<int>(entries[a].quality) > static_cast<int>(entries[b].quality);
|
||||
if (entries[a].itemId != entries[b].itemId)
|
||||
return entries[a].itemId < entries[b].itemId;
|
||||
return entries[a].stackCount > entries[b].stackCount;
|
||||
});
|
||||
|
||||
// sortedIdx[targetPos] = sourcePos means the item currently at sourcePos
|
||||
// needs to end up at targetPos. We use selection-sort-style swaps to
|
||||
// permute current positions into sorted order, tracking where items move.
|
||||
|
||||
// posOf[i] = current position of the item that was originally at index i
|
||||
std::vector<int> posOf(n);
|
||||
for (int i = 0; i < n; ++i) posOf[i] = i;
|
||||
|
||||
// invPos[p] = which original item index is currently sitting at position p
|
||||
std::vector<int> invPos(n);
|
||||
for (int i = 0; i < n; ++i) invPos[i] = i;
|
||||
|
||||
std::vector<SwapOp> swaps;
|
||||
|
||||
for (int target = 0; target < n; ++target) {
|
||||
int need = sortedIdx[target]; // original index that should be at 'target'
|
||||
int cur = invPos[target]; // original index currently at 'target'
|
||||
if (cur == need) continue; // already in place
|
||||
|
||||
// Skip swaps between two empty slots
|
||||
if (entries[cur].itemId == 0 && entries[need].itemId == 0) continue;
|
||||
|
||||
int srcPos = posOf[need]; // current position of the item we need
|
||||
|
||||
// Emit a swap between position srcPos and position target
|
||||
swaps.push_back({entries[srcPos].bag, entries[srcPos].slot,
|
||||
entries[target].bag, entries[target].slot});
|
||||
|
||||
// Update tracking arrays
|
||||
posOf[cur] = srcPos;
|
||||
posOf[need] = target;
|
||||
invPos[srcPos] = cur;
|
||||
invPos[target] = need;
|
||||
}
|
||||
|
||||
return swaps;
|
||||
}
|
||||
|
||||
void Inventory::populateTestItems() {
|
||||
// Equipment
|
||||
{
|
||||
|
|
|
|||
|
|
@ -363,10 +363,10 @@ void WorldMap::loadZonesFromDBC() {
|
|||
cont.locTop = z.locTop; cont.locBottom = z.locBottom;
|
||||
first = false;
|
||||
} else {
|
||||
cont.locLeft = std::max(cont.locLeft, z.locLeft);
|
||||
cont.locRight = std::min(cont.locRight, z.locRight);
|
||||
cont.locTop = std::max(cont.locTop, z.locTop);
|
||||
cont.locBottom = std::min(cont.locBottom, z.locBottom);
|
||||
cont.locLeft = std::min(cont.locLeft, z.locLeft);
|
||||
cont.locRight = std::max(cont.locRight, z.locRight);
|
||||
cont.locTop = std::min(cont.locTop, z.locTop);
|
||||
cont.locBottom = std::max(cont.locBottom, z.locBottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1138,12 +1138,30 @@ void InventoryScreen::renderBagWindow(const char* title, bool& isOpen,
|
|||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
// Sort Bags button — client-side reorder by quality/type
|
||||
if (ImGui::SmallButton("Sort Bags")) {
|
||||
inventory.sortBags();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Sort all bag slots by quality (highest first),\nthen by item ID, then by stack size.");
|
||||
// Sort Bags button — compute swaps, apply client-side preview, queue server packets
|
||||
{
|
||||
bool sorting = !sortSwapQueue_.empty();
|
||||
if (sorting) ImGui::BeginDisabled();
|
||||
if (ImGui::SmallButton(sorting ? "Sorting..." : "Sort Bags")) {
|
||||
// Compute the swap operations before modifying local state
|
||||
auto swaps = inventory.computeSortSwaps();
|
||||
// Apply local preview immediately
|
||||
inventory.sortBags();
|
||||
// Queue server-side swaps (one per frame)
|
||||
for (auto& s : swaps)
|
||||
sortSwapQueue_.push_back(s);
|
||||
}
|
||||
if (sorting) ImGui::EndDisabled();
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
||||
ImGui::SetTooltip("Sort all bag slots by quality (highest first),\nthen by item ID, then by stack size.");
|
||||
}
|
||||
|
||||
// Process one queued swap per frame
|
||||
if (!sortSwapQueue_.empty() && gameHandler_) {
|
||||
auto op = sortSwapQueue_.front();
|
||||
sortSwapQueue_.pop_front();
|
||||
gameHandler_->swapContainerItems(op.srcBag, op.srcSlot, op.dstBag, op.dstSlot);
|
||||
}
|
||||
}
|
||||
|
||||
if (moneyCopper > 0) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue