SMSG_SPELL_GO packets with unreasonably high miss counts (48, 118, 241)
were causing the entire packet to be discarded, losing all combat hit
data. Now salvage the successfully-parsed hit targets (needed for combat
text, health bars, animations) instead of discarding everything. Also
add spellId/hitCount to truncation warnings for easier diagnosis.
M2Renderer::removeInstance() was calling rebuildSpatialIndex() for every
single removal, causing 25-90ms frame hitches during entity despawns.
Now uses O(1) lookup via instanceIndexById, incremental spatial grid
cell removal, and swap-remove from the instance vector. The auxiliary
index vectors are rebuilt cheaply since they're small.
WotLK and TBC parsers were reading uint32+uint8 (5 bytes) for
SPELL_MISS_REFLECT entries, but the server only sends uint8
reflectResult (1 byte). This caused a 4-byte misalignment after every
reflected spell, corrupting subsequent miss entries and SpellCastTargets
parsing. Classic parser was already correct.
Right-clicking a locked container (e.g. Dead-Tooth's Strong Box) was
sending CMSG_USE_ITEM with spellId=0, which the server rejects. Locked
containers (itemClass==1, inventoryType==0) now send CMSG_OPEN_ITEM
instead, letting the server auto-check the keyring for the required key.
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.
'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.
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.
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.
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.
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.
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
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.
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.
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.
- /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)
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.
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.
- 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
- 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
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.
- 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.
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.
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.
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.
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
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.
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
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
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.
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.
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.
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.
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
- 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).
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.
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).
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).
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