SMSG_PET_CAST_FAILED: Classic/TBC omit the castCount byte (matching
SMSG_CAST_FAILED pattern). Without this fix, TBC parsing reads garbage.
SMSG_LFG_ROLE_CHOSEN: surface role selection messages in chat during
dungeon finder role checks.
Parse SMSG_PETITION_QUERY_RESPONSE, SMSG_PETITION_SHOW_SIGNATURES,
and SMSG_PETITION_SIGN_RESULTS. Add UI to view signatures, sign
petitions, and turn in completed charters. Send CMSG_PETITION_SIGN
and CMSG_TURN_IN_PETITION packets.
Store team name and type (2v2/3v3/5v5) from SMSG_ARENA_TEAM_QUERY_RESPONSE.
Display proper team labels instead of raw IDs. Add Load/Refresh roster
buttons and CMSG_ARENA_TEAM_ROSTER request support.
PLAYER_BYTES and PLAYER_BYTES_2 changes in SMSG_UPDATE_OBJECT now
update the Character struct's appearanceBytes and facialFeatures,
and fire an appearance-changed callback that resets the inventory
screen preview so it reloads with the new hair/face values.
Arena and BG type IDs now match actual 3.3.5a BattlemasterList.dbc:
Nagrand Arena=4, Blade's Edge=5, Ruins of Lordaeron=8, Dalaran
Sewers=10, Ring of Valor=11, Isle of Conquest=30, Random BG=32.
Eye of the Storm uses bgTypeId 7 (from BattlemasterList.dbc), not 6.
BG invite popup now uses the stored bgName from the queue slot instead
of re-deriving the name with a duplicate switch statement.
Adds a functional barber shop window triggered by SMSG_ENABLE_BARBER_SHOP.
Players can adjust hair style, hair color, and facial features using
sliders bounded by race/gender max values. Sends CMSG_ALTER_APPEARANCE
on confirm; server result closes the window on success. Escape key
also closes the barber shop.
Implements CMSG_SPLIT_ITEM (0x10E) with a slider popup for choosing
split count. Auto-finds empty destination slot across backpack and bags.
Shift+right-click on stackable items (count > 1) opens split dialog;
non-stackable items still get the destroy confirmation.
Play UI error sound on SMSG_CAST_FAILED for consistent audio feedback,
matching other error handlers (vendor, inventory, trainer).
Auto-reply to incoming whispers with AFK/DND message when player has
set /afk or /dnd status.
Store the voteMask from SMSG_LOOT_START_ROLL and use it to conditionally
show Need/Greed/Disenchant/Pass buttons. Previously all four buttons were
always shown regardless of the server's allowed roll types.
SMSG_SPELL_MISS_LOG REFLECT entries include a reflectSpellId field that
was parsed but discarded. Now store it in SpellMissLogEntry and pass it
to addCombatText, so floating combat text shows the actual reflected
spell name instead of the original cast spell.
Combat text (damage, heals, misses, crits, etc.) now floats above the
target entity in 3D space instead of appearing at fixed screen positions.
Text rises upward from the entity's head, with random horizontal stagger
to prevent stacking. HUD-only types (XP, Honor, Procs) and entries
without a valid entity anchor fall back to the original screen overlay.
Read PLAYER_GUILDID from entity update fields (UNIT_END + 3) and query
guild names via CMSG_GUILD_QUERY. Cache results in guildNameCache_ so
each guild ID is queried only once. Display <Guild Name> in grey below
the player name on nameplates. Fix handleGuildQueryResponse to not
overwrite the local player's guild data when querying other guilds.
The WotLK periodic damage format includes an isCrit byte after resisted
(21 bytes total, not 20). Missing this byte caused parse misalignment
for multi-effect periodicauralog packets. Also use the already-read
isCrit on periodic heals to display critical HoT ticks distinctly.
Death, respawn, and melee swing callbacks only checked
creatureInstances_, so online players never played death animation when
killed, never returned to idle on resurrect, and never showed attack
swings. Extended all three callbacks to also check playerInstances_.
Also extended the game_handler death/respawn callback triggers to fire
for PLAYER entities (not just UNIT), and added spawn-time death
detection for players that are already dead when first seen.
Both CREATE_OBJECT and MOVEMENT update paths called
setPlayerOnTransport(guid, vec3(0)) then immediately overwrote
playerTransportOffset_ on the next line. This left a one-frame window
where the composed world position used (0,0,0) as the local offset,
causing the player to visually snap to the transport origin. Compute the
canonical offset first and pass it directly.
Change WotLK MonsterMove pointCount > 1000 from cap-to-1000 to return
false. Capping caused the parser to read only 1000 of N points, leaving
the remaining point data unread and misaligning subsequent reads.
Also correct misleading loot response comment: Classic/TBC DO include
randomSuffix and randomPropertyId (22 bytes/item, same as WotLK). The
only WotLK difference is the quest item list appended after regular
items.
Check remaining packet data before reading update type, GUIDs, object
type, and block count in parseUpdateBlock and parseUpdateFields. Prevents
silent garbage reads when the parser reaches the end of a truncated or
misaligned packet.
Complete the parser hardening across all expansions. Check remaining
bytes before every conditional read in the WotLK base
UpdateObjectParser::parseMovementBlock: LIVING entry (66-byte minimum),
transport, pitch, fall time, jumping, spline elevation, speeds,
POSITION, STATIONARY, and all tail flags (HAS_TARGET, TRANSPORT,
VEHICLE, ROTATION, LOWGUID, HIGHGUID). Prevents silent garbage reads
when Packet::readUInt8/readFloat return 0 past EOF.
Same hardening as the Classic and Turtle parsers: check remaining bytes
before every conditional read in TbcPacketParsers::parseMovementBlock.
Change spline pointCount > 256 to return false instead of capping to
zero (which silently consumed wrong bytes for the endPoint).
Mirror the Turtle parser hardening: check remaining bytes before every
conditional read in ClassicPacketParsers::parseMovementBlock. Prevents
silent garbage reads (readUInt8 returns 0 past EOF) that corrupt
subsequent update fields and lose NPC data in multi-block packets.
The Turtle parseMovementBlock had no bounds checking on any reads.
Since Packet::readUInt8() returns 0 past the end without failing, the
parser could "succeed" with all-zero garbage data, then subsequent
parseUpdateFields would read from wrong positions, producing
"truncated field value" and "truncated update mask" errors.
Added bounds checks before every conditional read section (transport,
swimming pitch, fall time, jumping, spline elevation, speeds, spline
data, tail flags). Also removed the WotLK movement block fallback from
the Turtle parser chain — WotLK format is fundamentally incompatible
(uint16 flags, 9 speeds) and false-positive parses corrupt NPC data.
Also changed spline pointCount > 256 from cap-to-zero to return false
so the parser correctly fails instead of silently dropping waypoints.
The spline parser assumed WotLK format (durationMod, durationModNext,
conditional PARABOLIC fields) for all expansions. Classic/Turtle has a
simpler layout: timePassed+duration+splineId+pointCount directly.
Reading WotLK-specific fields from Classic data consumed wrong bytes,
causing pointCount to read garbage and the entire update block to fail
— losing dozens of NPC spawns in multi-block packets.
Now tries Classic format first (pointCount at offset 12), then WotLK
(offset 20+), then compact fallback. Also fixes WotLK SMSG_SPELL_GO
hit/miss targets to use full uint64 GUIDs instead of PackedGuid, which
was the root cause of garbage missCount values (46, 64, 241).
The legacy UPDATE_OBJECT spline path was reading verticalAccel (float)
and effectStartTime (uint32) unconditionally, but these 8 bytes are
only present when SPLINEFLAG_PARABOLIC (0x00000800) is set. Without
the flag, the extra reads shifted the stream by 8 bytes, causing
pointCount to read garbage (e.g. 3323328650) and failing the entire
update block parse.
When SMSG_SPELL_GO reads a suspiciously high missCount (>20), log
the surrounding packet bytes, castFlags, and position for debugging
the persistent offset error causing garbage miss counts (46, 48, 241).
Servers may reject CMSG_GAMEOBJ_USE or cancel the resulting pickup
spell cast if movement flags are still active. Now sends MSG_MOVE_STOP
to clear directional movement before the interaction packet. Also adds
diagnostic logging for GO interactions to help trace collection issues.
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.
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.
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.
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.
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.
- 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.
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 (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).
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
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.
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.