diff --git a/README.md b/README.md index d983133c..50a09bfa 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,14 @@ Protocol Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**. > **Legal Disclaimer**: This is an educational/research project. It does not include any Blizzard Entertainment assets, data files, or proprietary code. World of Warcraft and all related assets are the property of Blizzard Entertainment, Inc. This project is not affiliated with or endorsed by Blizzard Entertainment. Users are responsible for supplying their own legally obtained game data files and for ensuring compliance with all applicable laws in their jurisdiction. -## Status & Direction (2026-03-07) +## Status & Direction (2026-03-24) -- **Compatibility**: **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a** are all supported via expansion profiles and per-expansion packet parsers (`src/game/packet_parsers_classic.cpp`, `src/game/packet_parsers_tbc.cpp`). All three expansions are roughly on par — no single one is significantly more complete than the others. -- **Tested against**: AzerothCore, TrinityCore, Mangos, and Turtle WoW (1.17). -- **Current focus**: instance dungeons, visual accuracy (lava/water, shadow mapping, WMO interiors), and multi-expansion coverage. +- **Compatibility**: **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a** are all supported via expansion profiles and per-expansion packet parsers. All three expansions are roughly on par. +- **Tested against**: AzerothCore/ChromieCraft, TrinityCore, Mangos, and Turtle WoW (1.17). +- **Current focus**: gameplay correctness (quest/GO interaction, NPC visibility), rendering stability, and multi-expansion coverage. - **Warden**: Full module execution via Unicorn Engine CPU emulation. Decrypts (RC4→RSA→zlib), parses and relocates the PE module, executes via x86 emulation with Windows API interception. Module cache at `~/.local/share/wowee/warden_cache/`. -- **CI**: GitHub Actions builds for Linux (x86-64, ARM64), Windows (MSYS2), and macOS (ARM64). Security scans via CodeQL, Semgrep, and sanitizers. +- **CI**: GitHub Actions builds for Linux (x86-64, ARM64), Windows (MSYS2 x86-64 + ARM64), and macOS (ARM64). Security scans via CodeQL, Semgrep, and sanitizers. +- **Release**: v1.8.2-preview — 530+ WoW API functions, 140+ events, 664 opcode handlers. ## Features @@ -52,7 +53,7 @@ Protocol Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**. - **Movement** -- WASD movement, camera orbit, spline path following, transport riding (trams, ships, zeppelins), movement ACK responses - **Combat** -- Auto-attack, spell casting with cooldowns, damage calculation, death handling, spirit healer resurrection - **Targeting** -- Tab-cycling with hostility filtering, click-to-target, faction-based hostility (using Faction.dbc) -- **Inventory** -- 23 equipment slots, 16 backpack slots, drag-drop, auto-equip, item tooltips with weapon damage/speed +- **Inventory** -- 23 equipment slots, 16 backpack slots, drag-drop, auto-equip, item tooltips with weapon damage/speed, server-synced bag sort (quality/type/stack), independent bag windows - **Bank** -- Full bank support for all expansions, bag slots, drag-drop, right-click deposit (non-equippable items) - **Spells** -- Spellbook with specialty, general, profession, mount, and companion tabs; drag-drop to action bar; item use support - **Talents** -- Talent tree UI with proper visuals and functionality @@ -67,7 +68,8 @@ Protocol Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**. - **Chat** -- Tabs/channels, emotes, chat bubbles, clickable URLs, clickable item links with tooltips - **Party** -- Group invites, party list, out-of-range member health via SMSG_PARTY_MEMBER_STATS - **Pets** -- Pet tracking via SMSG_PET_SPELLS, action bar (10 slots with icon/autocast tinting/tooltips), dismiss button -- **Map Exploration** -- Subzone-level fog-of-war reveal matching retail behavior +- **Map Exploration** -- Subzone-level fog-of-war reveal, world map with continent/zone views, quest POI markers, taxi node markers, party member dots +- **NPC Voices** -- Race/gender-specific NPC greeting, farewell, vendor, pissed, aggro, and flee sounds for all playable races including Blood Elf and Draenei - **Warden** -- Warden anti-cheat module execution via Unicorn Engine x86 emulation (cross-platform, no Wine) - **UI** -- Loading screens with progress bar, settings window with graphics quality presets (LOW/MEDIUM/HIGH/ULTRA), shadow distance slider, minimap with zoom/rotation/square mode, top-right minimap mute speaker, separate bag windows with compact-empty mode (aggregate view) diff --git a/docs/status.md b/docs/status.md index fca68f19..bb1e9614 100644 --- a/docs/status.md +++ b/docs/status.md @@ -1,6 +1,6 @@ # Project Status -**Last updated**: 2026-03-18 +**Last updated**: 2026-03-24 ## What This Repo Is @@ -30,16 +30,17 @@ Implemented (working in normal use): - Target/focus frames: guild name, creature type, rank badges, combo points, cast bars - Map exploration: subzone-level fog-of-war reveal - Warden anti-cheat: full module execution via Unicorn Engine x86 emulation; module caching -- Audio: ambient, movement, combat, spell, and UI sound systems -- Bag UI: separate bag windows, open-bag indicator on bag bar, optional collapse-empty mode in aggregate bag view +- Audio: ambient, movement, combat, spell, and UI sound systems; NPC voice lines for all playable races (greeting/farewell/vendor/pissed/aggro/flee) +- Bag UI: independent bag windows (any bag closable independently), open-bag indicator on bag bar, server-synced bag sort, off-screen position reset, optional collapse-empty mode in aggregate view +- DBC auto-detection: CharSections.dbc field layout auto-detected at runtime (handles stock WotLK vs HD-textured clients) - Multi-expansion: Classic/Vanilla, TBC, WotLK, and Turtle WoW (1.17) protocol and asset variants -- CI: GitHub Actions for Linux (x86-64, ARM64), Windows (MSYS2), macOS (ARM64); container builds via Podman +- CI: GitHub Actions for Linux (x86-64, ARM64), Windows (MSYS2 x86-64 + ARM64), macOS (ARM64); container builds via Podman In progress / known gaps: - Transports: M2 transports (trams) working with position-delta riding; WMO transports (ships, zeppelins) working with path following; some edge cases remain +- Quest GO interaction: CMSG_GAMEOBJ_USE + CMSG_LOOT sent correctly, but some AzerothCore/ChromieCraft servers don't grant quest credit for chest-type GOs (server-side limitation) - Visual edge cases: some M2/WMO rendering gaps (some particle effects) -- Lava steam particles: sparse in some areas (tuning opportunity) - Water refraction: enabled by default; srcAccessMask barrier fix (2026-03-18) resolved prior VK_ERROR_DEVICE_LOST on AMD/Mali GPUs ## Where To Look diff --git a/include/game/inventory.hpp b/include/game/inventory.hpp index cf092ac4..6ae07a2d 100644 --- a/include/game/inventory.hpp +++ b/include/game/inventory.hpp @@ -129,6 +129,18 @@ public: // Purely client-side: reorders the local inventory struct without server interaction. void sortBags(); + // A single swap operation using WoW bag/slot addressing (for CMSG_SWAP_ITEM). + struct SwapOp { + uint8_t srcBag; + uint8_t srcSlot; + uint8_t dstBag; + uint8_t dstSlot; + }; + + // Compute the CMSG_SWAP_ITEM operations needed to reach sorted order. + // Does NOT modify the inventory — caller is responsible for sending packets. + std::vector computeSortSwaps() const; + // Test data void populateTestItems(); diff --git a/include/ui/inventory_screen.hpp b/include/ui/inventory_screen.hpp index dca0e5a5..d350f210 100644 --- a/include/ui/inventory_screen.hpp +++ b/include/ui/inventory_screen.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +196,9 @@ private: int splitCount_ = 1; std::string splitItemName_; + // Server-side bag sort swap queue (one swap per frame) + std::deque sortSwapQueue_; + // Pending chat item link from shift-click std::string pendingChatItemLink_; diff --git a/src/game/inventory.cpp b/src/game/inventory.cpp index a6de6dcb..83fcc5fe 100644 --- a/src/game/inventory.cpp +++ b/src/game/inventory.cpp @@ -224,6 +224,92 @@ void Inventory::sortBags() { } } +std::vector 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 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(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(19 + b), static_cast(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(entries.size()); + std::vector 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(entries[a].quality) > static_cast(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 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 invPos(n); + for (int i = 0; i < n; ++i) invPos[i] = i; + + std::vector 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 { diff --git a/src/rendering/world_map.cpp b/src/rendering/world_map.cpp index 6b1710c4..8fe955ca 100644 --- a/src/rendering/world_map.cpp +++ b/src/rendering/world_map.cpp @@ -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); } } } diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index a04895be..a5a1f9f4 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -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) {