Commit graph

2311 commits

Author SHA1 Message Date
Kelsi
2fb7901cca feat: enable water refraction by default
The VK_ERROR_DEVICE_LOST crash on AMD/Mali GPUs (barrier srcAccessMask)
was fixed in 2026-03-18. Enable refraction for new sessions so players
get the improved water visuals without needing to touch Settings.
Existing saved configs that explicitly disabled it are preserved.
2026-03-18 05:44:59 -07:00
Kelsi
fabcde42a5 fix: clarify death dialog — auto-release label and resurrection hint
'Release in X:XX' implied a client-enforced forced release; renamed to
'Auto-release in X:XX' (server-driven) and added 'Or wait for a player
to resurrect you.' hint so players know they can stay dead without
clicking Release Spirit.
2026-03-18 05:39:42 -07:00
Kelsi
90843ea989 fix: don't set releasedSpirit_ optimistically in releaseSpirit()
Setting releasedSpirit_=true immediately on CMSG_REPOP_REQUEST raced
with PLAYER_FLAGS field updates that arrive from the server before it
processes the repop: the PLAYER_FLAGS handler saw wasGhost=true /
nowGhost=false and fired the 'ghost cleared' path, wiping corpseMapId_
and corpseGuid_ — so the minimap skull marker and the Resurrect from
Corpse dialog never appeared.

Ghost state is now driven entirely by the server-confirmed PLAYER_FLAGS
GHOST bit (and the login-as-ghost path), eliminating the race.
2026-03-18 05:35:23 -07:00
Kelsi
d0f544395e feat: add mounted/group/channeling/casting/vehicle macro conditionals
Extends evaluateMacroConditionals() with [mounted], [nomounted],
[group], [nogroup], [raid], [channeling], [nochanneling],
[channeling:SpellName], [casting], [nocasting], [vehicle], [novehicle].
2026-03-18 05:23:32 -07:00
Kelsi
4e13a344e8 feat: add buff:/nobuff:/debuff:/nodebuff: macro conditionals
Macro conditions now support checking aura presence:
  [buff:Power Word: Fortitude]  — player has the named buff
  [nobuff:Frost Armor]          — player does NOT have the named buff
  [debuff:Faerie Fire]          — target has the named debuff
  [nodebuff:Hunter's Mark]      — target does NOT have the named debuff

Name matching is case-insensitive. When a target override (@target etc.)
is active the check uses that unit's aura list instead of the player's.
2026-03-18 05:20:15 -07:00
Kelsi
a802e05091 feat: add /mark slash command for setting raid target icons
Adds /mark [icon], /marktarget, and /raidtarget slash commands that
set a raid mark on the current target. Accepts icon names (star,
circle, diamond, triangle, moon, square, cross, skull), numbers 1-8,
or "clear"/"none" to remove the mark. Defaults to skull when no
argument is given.
2026-03-18 05:16:14 -07:00
Kelsi
e7fe35c1f9 feat: add right-click pet spell autocast toggle via CMSG_PET_SPELL_AUTOCAST
Right-clicking a castable pet ability (actionId > 6) in the pet action
bar now sends CMSG_PET_SPELL_AUTOCAST to toggle the spell's autocast
state. The local petAutocastSpells_ set is updated optimistically and
the tooltip shows the current state with a right-click hint.
2026-03-18 05:08:10 -07:00
Kelsi
586408516b fix: correct character geoset group ranges for other-player equipment rendering
setOnlinePlayerEquipment used wrong geoset ID ranges for boots (402+ instead
of 501+), gloves (301+ instead of 401+), and chest/sleeves (501+ instead of
801+), and was missing bare-shin (502), bare-wrist (801), and bare-leg (1301)
defaults. This caused other players to render with missing shin/wrist geometry
and wrong geosets when wearing equipment (the "shin mesh" gap in status.md).

Now mirrors the CharacterPreview::applyEquipment logic exactly:
- Group 4 (4xx) forearms/gloves: default 401, equipment 401+gg
- Group 5 (5xx) shins/boots:    default 502, equipment 501+gg
- Group 8 (8xx) wrists/sleeves: default 801, equipment 801+gg
- Group 13 (13xx) legs/pants:   default 1301, equipment 1301+gg
2026-03-18 04:42:21 -07:00
Kelsi
5f3bc79653 feat: show queued spell icon in cast bar and expose getQueuedSpellId()
When a spell is queued in the 400ms window before the current cast ends,
render its icon dimmed (0.8 alpha) to the right of the cast bar progress,
with a "Queued: <name>" tooltip. The progress bar shrinks to accommodate
the icon when one is present.

Also exposes getQueuedSpellId() as a public const accessor on GameHandler
so the UI can observe the spell queue state without friend access.
2026-03-18 04:34:36 -07:00
Kelsi
277a26b351 feat: flash action bar button red when spell cast fails
Add SpellCastFailedCallback to GameHandler, fired from SMSG_CAST_RESULT
when result != 0. GameScreen registers the callback and records each failed
spellId in actionFlashEndTimes_ (keyed by spell ID, value = expiry time).

During action bar rendering, if a slot's spell has an active flash entry,
an AddRectFilled overlay is drawn over the button with alpha proportional
to remaining time (1.0→0.0 over 0.5 s), giving the same error-red flash
visual feedback as the original WoW client.
2026-03-18 04:30:33 -07:00
Kelsi
c1765b6b39 fix: defer loot item notification until item name is known from server query
When SMSG_ITEM_PUSH_RESULT arrives for an item not yet in the cache, store
a PendingItemPushNotif and fire the 'Received: [item]' chat message only
after SMSG_ITEM_QUERY_SINGLE_RESPONSE resolves the name and quality, so the
notification always shows a proper item link instead of 'item #12345'.

Notifications that are already cached emit immediately as before; multiple
pending notifs for the same item are all flushed on the single response.
2026-03-18 04:25:37 -07:00
Kelsi
09b0bea981 feat: add /stopmacro support and low durability warning for equipped items
- /stopmacro [conditions] halts remaining macro commands; supports all existing
  macro conditionals ([combat], [nocombat], [mod:shift], etc.) via the sentinel
  action trick on evaluateMacroConditionals
- macroStopped_ flag in GameScreen; executeMacroText resets and checks it after
  each command so /stopmacro mid-macro skips all subsequent lines
- Emit a "X is about to break!" UI error + system chat when an equipped item's
  durability drops below 20% via SMSG_UPDATE_OBJECT field delta; warning fires
  once per threshold crossing (prevDur >= maxDur/5, newDur < maxDur/5)
2026-03-18 04:14:44 -07:00
Kelsi
d7c377292e feat: show socket gems and consolidate enchant name DBC cache in item tooltips
Extends OnlineItemInfo to track gem enchant IDs (socket slots 2-4) from item
update fields; socket display now shows inserted gem name inline (e.g.
"Red Socket: Bold Scarlet Ruby"). Consolidates redundant SpellItemEnchantment
DBC loads into one shared static per tooltip variant.
2026-03-18 04:04:23 -07:00
Kelsi
167e710f92 feat: add /equipset macro command for saved equipment set switching
/equipset <name> equips a saved set by case-insensitive prefix match;
/equipset with no argument lists available sets in chat.
2026-03-18 03:53:59 -07:00
Kelsi
1fd3d5fdc8 feat: display permanent and temporary enchants in item tooltips for equipped items
Tracks ITEM_ENCHANTMENT_SLOT 0 (permanent) and 1 (temporary) from item update
fields in OnlineItemInfo, then looks up names from SpellItemEnchantment.dbc and
renders them in both ItemDef and ItemQueryResponseData tooltip variants.
2026-03-18 03:50:24 -07:00
Kelsi
4025e6576c feat: implement /castsequence macro command
Supports: /castsequence [conds] [reset=N/target/combat] Spell1, Spell2, ...
Cycles through the spell list on successive button presses. State is keyed
by spell list so the same sequence shared across macros stays in sync.
2026-03-18 03:36:05 -07:00
Kelsi
df7150503b feat: /assist now accepts name and macro conditional arguments
/assist TankName targets whoever TankName is targeting; /assist [target=focus]
assists your focus target. Mirrors /target and /focus conditional support.
2026-03-18 03:31:40 -07:00
Kelsi
5d4b0b0f04 feat: show target-of-focus with health bar in focus frame
Healers and tanks can now see who their focus target is targeting,
with a compact percentage health bar — mirrors the ToT in the target frame.
2026-03-18 03:29:48 -07:00
Kelsi
a151531a2a feat: show health bar on target-of-target in target frame
The ToT health bar gives healers immediate % health readout of whoever
the target is attacking, without needing to click-through to that unit.
2026-03-18 03:28:06 -07:00
Kelsi
11c07f19cb feat: add macro conditional support to /cleartarget and /startattack
/cleartarget [dead] now clears target only when it meets conditions;
/startattack [harm,nodead] respects conditionals including target=mouseover.
2026-03-18 03:25:34 -07:00
Kelsi
6cd3c613ef feat: add macro conditional support to /target and /focus commands
/target [target=mouseover], /target [mod:shift] BossName; DefaultMob,
/focus [target=mouseover], and /focus PlayerName all now evaluate WoW
macro conditionals and resolve named/mouseover targets correctly.
2026-03-18 03:21:27 -07:00
Kelsi
e2a484256c feat: show spell icon on macro buttons via #showtooltip directive
- getMacroShowtooltipArg() parses the #showtooltip [SpellName] directive
- Action bar macro buttons now display the named spell's icon when
  #showtooltip SpellName is present at the top of the macro body
- For bare #showtooltip (no argument), derives the icon from the first
  /cast line in the macro (stripping conditionals and rank suffixes)
- Falls back to "Macro" text label only when no spell can be resolved
2026-03-18 03:16:05 -07:00
Kelsi
28d7d3ec00 feat: track mouseover on party frames; fix /cast !spell; update macro editor hint 2026-03-18 03:11:34 -07:00
Kelsi
7967bfdcb1 feat: implement [target=mouseover] macro conditional via nameplate/raid hover
- Adds mouseoverGuid_ to GameHandler (set/cleared each frame by UI)
- renderNameplates() sets mouseoverGuid when the cursor is inside a
  nameplate's hit region; resets to 0 at frame start
- Raid frame cells set mouseoverGuid while hovered (IsItemHovered)
- evaluateMacroConditionals() resolves @mouseover / target=mouseover to
  the hover GUID; returns false (skip alternative) when no unit is hovered

This enables common healer macros like:
  /cast [target=mouseover,help,nodead] Renew; Renew
2026-03-18 03:09:43 -07:00
Kelsi
d2b2a25393 feat: extend macro conditionals to /use command 2026-03-18 03:06:23 -07:00
Kelsi
30513d0f06 feat: implement WoW macro conditional evaluator for /cast
Adds evaluateMacroConditionals() which parses the [cond1,cond2] Spell;
[cond3] Spell2; Default syntax and returns the first matching
alternative. Supported conditions:

- mod:shift/ctrl/alt, nomod  — keyboard modifier state
- target=player/focus/target, @player/@focus/@target — target override
- help / harm (noharm / nohelp)  — target faction check
- dead / nodead                  — target health check
- exists / noexists              — target presence check
- combat / nocombat              — player combat state
- noform / nostance / form:0     — shapeshift/stance state
- Unknown conditions are permissive (true) to avoid false negatives.

/cast now resolves conditionals before spell lookup and routes
castSpell() to the [target=X] override GUID when specified.
isHostileFaction() exposed as isHostileFactionPublic() for UI use.
2026-03-18 03:04:45 -07:00
Kelsi
ed3bca3d17 fix: escape newlines in macro cfg persistence; execute all macro lines
- Macro text is now escaped (\\n, \\\\) on save and unescaped on load,
  fixing multiline macros silently truncating after the first line in
  the character config file.
- executeMacroText() runs every non-comment line of a macro body in
  sequence (WoW behaviour), replacing the firstMacroCommand() approach
  that only fired the first actionable line. The server still enforces
  one spell-cast per click; non-cast commands (target, equip, pet, etc.)
  now all execute correctly in the same macro activation.
2026-03-18 02:44:28 -07:00
Kelsi
c676d99fc2 feat: add /petattack, /petfollow, /petstay, /petpassive, /petaggressive macro commands
Adds the standard WoW pet control slash commands used in macros:
- /petattack     — attack current target
- /petfollow     — follow player
- /petstay / /pethalt — stop and hold position
- /petpassive    — set passive react mode
- /petdefensive  — set defensive react mode
- /petaggressive — set aggressive react mode
- /petdismiss    — dismiss the pet

All commands also appear in Tab-autocomplete.
2026-03-18 02:32:49 -07:00
Kelsi
ae3e57ac3b feat: add /cancelform, /cancelshapeshift, /cancelaura slash commands
These are standard WoW macro commands:

- /cancelform / /cancelshapeshift: exits current shapeshift form by
  cancelling the first permanent aura (flag 0x20) on the player
- /cancelaura <name|#id>: cancels a specific player buff by spell name
  or numeric ID (e.g. /cancelaura Stealth, /cancelaura #1784)

Also expand the Tab-autocomplete command list to include /cancelaura,
/cancelform, /cancelshapeshift, /dismount, /sit, /stand, /startattack,
/stopcasting, /target, and other commands that were previously missing.
2026-03-18 02:30:35 -07:00
Kelsi
c3be43de58 fix: skip #showtooltip and other # directives when executing macros
Macros often start with a #showtooltip or #show directive line; these
should not be executed as chat commands.  The firstMacroCommand() helper
now scans forward through the macro text, skipping blank lines and any
line starting with '#', and executes the first actual command line.

Applies to all three execution paths: left-click, keyboard shortcut,
and right-click Execute menu item.
2026-03-18 02:27:34 -07:00
Kelsi
db0f868549 feat: extend /use command to support bag/slot notation and equip slot numbers
Adds WoW macro-standard /use argument forms alongside the existing
item-name search:

- /use 0 <slot>   — backpack slot N (1-based, bag 0)
- /use 1-4 <slot> — equipped bag slot N (1-based bag index)
- /use <N>        — equip slot N (1-based, e.g. /use 16 = main hand)

These are the standard forms used in macros like:
  #showtooltip
  /use 13          (trinket 1)
  /cast Arcane Blast
2026-03-18 02:23:47 -07:00
Kelsi
b236a85454 docs: update status.md — water refraction fix, date 2026-03-18 2026-03-18 02:20:59 -07:00
Kelsi
fa3a5ec67e fix: correct water refraction barrier srcAccessMask to prevent VK_ERROR_DEVICE_LOST
The captureSceneHistory barrier was using srcAccessMask=0 with
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT when transitioning the swapchain
image from PRESENT_SRC_KHR to TRANSFER_SRC_OPTIMAL.  This does not
flush the GPU's color attachment write caches, causing VK_ERROR_DEVICE_LOST
on strict drivers (AMD, Mali) that require explicit cache invalidation
before transfer reads.

Fix: use VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT + COLOR_ATTACHMENT_OUTPUT
as the source mask so color writes are properly made visible to the
transfer unit before the image copy begins.

Also remove the now-unnecessary "requires FSR" restriction in the
settings UI — water refraction can be enabled independently of FSR.
2026-03-18 02:20:35 -07:00
Kelsi
8abb65a813 feat: execute macros via keyboard shortcuts; support numeric /cast spell IDs
Two companion improvements for the macro system:

- Keyboard shortcut handler now executes MACRO slots (1-0 keys) by running
  the first line of their text as a command, same as left-click
- /cast now accepts a numeric spell ID or #ID prefix (e.g. /cast 133,
  /cast #133) in addition to spell names — enables standard WoW macro
  syntax and direct spell ID testing
2026-03-18 02:14:10 -07:00
Kelsi
2c86fb4fa6 feat: implement client-side macro text storage and execution
Macros in WoW are client-side — the server sends only a macro index via
SMSG_ACTION_BUTTONS, never the text. This commit adds local storage and
a UI so macro slots are actually usable.

- GameHandler: getMacroText/setMacroText accessors backed by macros_ map;
  text is persisted to the character .cfg file as macro_N_text= entries
- Action bar left-click: MACRO slot executes first line of macro text as
  a chat/slash command (same path as /cast, /use, etc.)
- Context menu: "Execute" and "Edit" items for MACRO slots; "Edit" opens
  a multiline modal editor (320×80 px, up to 255 chars) with Save/Cancel
- Tooltip: shows macro text body below the index; hints "right-click to
  Edit" when no text is set yet
2026-03-18 02:07:59 -07:00
Kelsi
1588c1029a fix: add user feedback for ATTACKSWING_NOTSTANDING and CANT_ATTACK
Both handlers silently cleared state with no visible message, leaving the
player unsure why their attack failed.  Split the shared case block:

- NOTSTANDING: show "You need to stand up to fight." (rate-limited to 1.25s
  via the existing autoAttackRangeWarnCooldown_ guard), keep auto-attack
  active so it fires once the player stands.

- CANT_ATTACK: call stopAutoAttack() to end the attack loop (target is a
  critter, civilian, or already dead — no point retrying), then show "You
  can't attack that." with the same rate limiter.
2026-03-18 01:46:19 -07:00
Kelsi
36158ae3e3 fix: show macro ID in action bar tooltip and context menu header
Macro slots stored from SMSG_ACTION_BUTTONS had no tooltip and no context
menu header — hovering or right-clicking gave a blank result.  Add an
"else if MACRO" branch to both the tooltip and the popup-context-item so
that "Macro #N" is displayed in both places.  Clearing via right-click
still works via the existing "Clear Slot" item which was already outside
the type branches.
2026-03-18 01:42:07 -07:00
Kelsi
7a0c7241ba fix: parse macro action bar slots from SMSG_ACTION_BUTTONS
Macro slots (type 0x40 / 64) were silently dropped by the default branch
of the SMSG_ACTION_BUTTONS type switch, leaving the bar empty for any slot
a player had set to a macro.  ActionBarSlot::MACRO already existed and the
UI already rendered it; only the parser was missing the case.  Add
case 0x40 to map to ActionBarSlot::MACRO for Classic (type=64), TBC, and
WotLK formats, which all share the same 0x40 encoding for macros.
2026-03-18 01:35:39 -07:00
Kelsi
5801af41bc fix: correct Turtle WoW SMSG_INIT_WORLD_STATES format and remove dead minRepeatMs branch
Turtle WoW is Classic 1.12-based and uses the Classic packet format for
SMSG_INIT_WORLD_STATES (no areaId uint32 field before count), not WotLK
format.  Including it in the WotLK branch caused the parser to consume 4
bytes of the count+first-key as a phantom areaId, misaligning all world
state key/value pairs (BG scores, zone events, flag states).

Also remove the dead `turtleMode ? 150 : 150` branch in
performGameObjectInteractionNow — both arms were identical so the ternary
had no effect; replace with a constexpr constant.
2026-03-18 01:30:20 -07:00
Kelsi
57b44d2347 fix: clear craft queue on spell failure and all cast reset paths
craftQueueSpellId_ and craftQueueRemaining_ were already cleared in
cancelCast(), stopCasting(), and SMSG_CAST_RESULT failure, but were
missing from five other cast-abort paths:

- SMSG_SPELL_FAILURE (mid-cast interrupt): queue persisted after
  combat interruption, risking a ghost re-cast on the next SMSG_SPELL_GO
- handleCastFailed() (SMSG_CAST_FAILED): queue persisted if the server
  rejected a craft before it started
- Player login state reset: leftover queue from prior session survived
  into the new world session
- Same-map resurrection (SMSG_NEW_WORLD): queue persisted through
  spirit-healer resurrection teleport
- Regular world transfer (SMSG_NEW_WORLD): queue persisted across zone
  changes and dungeon portals
2026-03-18 01:15:04 -07:00
Kelsi
6be695078b fix: clear spell queue in stopCasting; fix SMSG_SPELL_DELAYED castTimeTotal; clear cast on same-map res
- stopCasting() (invoked by /stopcasting) now clears queuedSpellId_/
  queuedSpellTarget_ and craftQueueSpellId_/craftQueueRemaining_ so a
  queued spell cannot fire silently after the player explicitly cancels.
- SMSG_SPELL_DELAYED now extends castTimeTotal alongside castTimeRemaining
  for the local player, matching the existing other-unit handling and
  keeping the cast bar progress percentage accurate after server-imposed
  cast delays.
- Same-map resurrection path (SMSG_NEW_WORLD same-map) now resets casting,
  castIsChannel, currentCastSpellId, castTimeRemaining, and the spell queue
  as a defensive measure (player is dead and cannot be casting, but this
  ensures state is clean on respawn).
2026-03-18 00:59:15 -07:00
Kelsi
76ba428b87 fix: /target command selects nearest matching entity
Previously used arbitrary map-iteration order (last match), meaning
'/target Kobold' might target a far-away enemy instead of the closest.

Now computes squared distance for every prefix-matching entity and
keeps the nearest one, matching WoW's own /target behaviour.
2026-03-18 00:39:32 -07:00
Kelsi
60d5edf97f fix: cancel timed cast immediately on movement start
When the player starts moving (forward/backward/strafe/jump) while a
timed non-channeled cast is in progress, call cancelCast() before
sending the movement packet.  Previously the cast bar kept counting
down until the server sent SMSG_SPELL_FAILED, causing a visible lag.

Channeled spells are excluded (server ends those via MSG_CHANNEL_UPDATE).
Turning opcodes are excluded (turning while casting is allowed in WoW).
2026-03-18 00:25:04 -07:00
Kelsi
4907f4124b feat: implement spell queue window (400ms pre-cast)
When castSpell() is called while a timed cast is in progress and
castTimeRemaining <= 0.4s, store the spell in queuedSpellId_ instead
of silently dropping it.  handleSpellGo() fires the queued spell
immediately after clearing the cast state, matching the ~400ms spell
queue window in Blizzlike WoW clients.

Queue is cleared on all cancel/interrupt paths: cancelCast(),
handleCastFailed(), SMSG_CAST_RESULT failure, SMSG_SPELL_FAILED,
world-teardown, and worldport ACK.  Channeled casts never queue
(cancelling a channel should remain explicit).
2026-03-18 00:21:46 -07:00
Kelsi
0f8852d290 fix: clear selfResAvailable_ when player releases spirit 2026-03-18 00:09:22 -07:00
Kelsi
5a5c2dcda3 feat: implement self-resurrection (Reincarnation/Twisting Nether)
SMSG_PRE_RESURRECT was silently discarded; Shamans with Reincarnation
and Warlocks with Twisting Nether could never see or use the self-res
ability. Now:

- SMSG_PRE_RESURRECT sets selfResAvailable_ flag when addressed to the
  local player
- Death dialog gains a "Use Self-Resurrection" button (blue, shown above
  Release Spirit) when the flag is set
- Clicking it sends CMSG_SELF_RES (empty body) and clears the flag
- selfResAvailable_ is cleared on all resurrection and session-reset
  paths so it never bleeds across deaths or logins
2026-03-18 00:06:39 -07:00
Kelsi
395a8f77c4 fix: clear corpse reclaim delay on world reset and resurrection
Reset corpseReclaimAvailableMs_ to 0 in both world-teardown/re-login
and ghost-flag-cleared paths so the PvP delay countdown never bleeds
into subsequent deaths or sessions.
2026-03-17 23:57:47 -07:00
Kelsi
b0046fa777 feat: track PvP corpse-reclaim delay and show countdown in UI
SMSG_CORPSE_RECLAIM_DELAY is now stored as an absolute expiry timestamp
(steady_clock ms) instead of being discarded after a chat message.

GameHandler::getCorpseReclaimDelaySec() returns remaining seconds (0 when
reclaim is available). The "Resurrect from Corpse" button now:
- Disables and shows the remaining seconds when a PvP delay is active
- Shows the usual "Corpse: N yards" helper text when available
Also resets corpseReclaimAvailableMs_ on world/session teardown.
2026-03-17 23:52:45 -07:00
Kelsi
2acab47eee fix: correct corpse reclaim — SMSG_DEATH_RELEASE_LOC is graveyard, not corpse
Two bugs prevented "Resurrect from Corpse" from working:

1. SMSG_DEATH_RELEASE_LOC was overwriting corpseX_/Y_/Z_/MapId_ with the
   graveyard spawn point (where the ghost appears after releasing spirit),
   not the actual corpse location.  canReclaimCorpse() was therefore comparing
   the ghost's distance to the graveyard instead of the real corpse, so the
   button never appeared when the ghost returned to the death position.
   Fix: read and log the packet but leave corpseX_/Y_/Z_ untouched.

2. reclaimCorpse() fell back to playerGuid when corpseGuid_ == 0.
   CMSG_RECLAIM_CORPSE requires the corpse object's own GUID; the server
   looks it up by GUID and silently rejects an unknown one.
   Fix: gate reclaimCorpse() on corpseGuid_ being known (set when the
   corpse object arrives in SMSG_UPDATE_OBJECT), and add canReclaimCorpse()
   guard for the same.

Corpse position is now sourced only from:
  - Health-drop detection (primary, fires immediately on death)
  - SMSG_UPDATE_OBJECT CORPSE type (updates when object enters view range)
2026-03-17 23:44:55 -07:00
Kelsi
d99fe8de0f feat: add Sort Bags button to backpack window
Adds Inventory::sortBags() which collects all items from the backpack
and equip bags, sorts them client-side by quality descending → item ID
ascending → stack count descending, then writes them back. A "Sort Bags"
SmallButton is rendered in the backpack footer with a tooltip explaining
the sort order.

The sort is purely local (no server packets) since the WoW protocol has
no sort-bags opcode; it provides an instant, session-persistent visual
reorder.
2026-03-17 23:29:50 -07:00