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.
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)
WotLK inserts a uint32 tradeId between isSelf and slotCount, and
appends uint32 createPlayedTime at the end of each slot (52-byte
trail vs 48 for Classic/TBC). Without the expansion check, Classic
and TBC parsers consumed tradeId as part of slotCount, resulting in
a bogus slot count and corrupted trade window item display.
Now gates the tradeId read and adjusts SLOT_TRAIL size based on
isActiveExpansion("wotlk").
handleMonsterMoveTransport() read a server-supplied pointCount without
any bounds check before iterating. A malformed packet with
pointCount=0xFFFFFFFF would loop billions of times. All other parsers
(MonsterMoveParser::parse, TBC parseMonsterMove) cap at 1000 or 16384.
Added kMaxTransportSplinePoints=1000 cap with a LOG_WARNING, matching
the limit used by MonsterMoveParser::parse() in world_packets.cpp.
Items with startQuestId != 0 were calling useItemBySlot()/useItemInBag()
which sends CMSG_USE_ITEM — but quest-starting items have no on-use spell,
so the server silently ignored the packet and no quest dialog appeared.
Fix:
- offerQuestFromItem(itemGuid, questId): sends CMSG_QUESTGIVER_QUERY_QUEST
with the item's own GUID as the questgiver GUID. The server responds with
SMSG_QUESTGIVER_QUEST_DETAILS which handleQuestDetails() already picks up
and opens the Accept/Decline dialog with full rewards/description.
- getBagItemGuid(bagIndex, slotIndex): resolves the per-slot item GUID from
the bag's containerContents_ map (mirrors the logic inside useItemInBag).
- inventory_screen.cpp right-click handler: checks item.startQuestId != 0
before the equip/use branch; if set, resolves item GUID and calls
offerQuestFromItem. Works for both backpack slots and bag slots.
Parses the pet talent wipe confirm packet (petGuid + cost), shows a
confirmation dialog matching the player talent reset UX, and sends
CMSG_PET_UNLEARN_TALENTS on confirmation. Completes the pet talent
respec flow for Hunters/Warlocks on WotLK servers.
When the server sends SMSG_PET_RENAMEABLE (after taming a pet for the first
time), the pet rename modal now automatically opens so the player can name
their new pet without needing to right-click the pet frame.
Replaces the silent consume with full packet parsing: reads two lists of
(guid, x, y) positions (typically ally and horde flag carriers) and stores
them in bgPlayerPositions_. Renders each as a colored diamond on the minimap
(blue=group0, red=group1) with a "Flag carrier" tooltip showing the player's
name when available.
When 'Auto Repair' is enabled in Settings > Gameplay, all damaged
equipment is automatically repaired when opening any armorer vendor
(canRepair=true). The repair is skipped when no items are actually
damaged to avoid a pointless server round-trip. A system chat message
confirms the repair. Setting persists to ~/.wowee/settings.cfg as
auto_repair.
When 'Auto Sell Greys' is enabled in Settings > Gameplay, all grey
(ItemQuality::POOR) items in the backpack and extra bags are sold
automatically when opening a vendor window. Items with no sell price
are skipped. A system chat message reports the number of items sold
and total gold received. The setting persists to ~/.wowee/settings.cfg
under the key auto_sell_grey.
The player's own cast bar now uses spell-school-based colors for quick
identification: Fire=orange-red, Frost=icy blue, Shadow=purple,
Arcane=violet, Nature=green, Holy=golden, Physical=gold. Channels
remain blue regardless of school. Adds getSpellSchoolMask() using the
already-loaded Spell.dbc cache (schoolMask field, covering all
expansions including Classic SchoolEnum→bitmask conversion).
Load AttributesEx from Spell.dbc for all expansions (Classic/TBC/WotLK/
Turtle). Check SPELL_ATTR_EX_NOT_INTERRUPTIBLE (bit 4 = 0x10) to classify
each cast as interruptible or not when SMSG_SPELL_START arrives.
Target frame and nameplate cast bars now use:
- Green: spell can be interrupted by Kick/Counterspell/Pummel etc.
- Red: spell is immune to interrupt (boss abilities, instant-cast effects)
Both colors pulse faster at >80% completion to signal the closing window.
Adds GameHandler::isSpellInterruptible() and UnitCastState::interruptible.
Add GLANCING (hitInfo 0x800) and CRUSHING (hitInfo 0x1000) as distinct
combat text types so players see mechanics feedback they expect from
Classic/TBC content:
- Glancing: shown as "~{amount}" in muted yellow/red; "glances for N" in
the combat log
- Crushing: shown as "{amount}!" in bright orange/red; "crushes for N!"
in the combat log
Both types are counted toward DPS meter accumulation. AttackerStateUpdateData
gains isGlancing()/isCrushing() helpers alongside the existing isCrit()/isMiss().
loadSpellVisualDbc() now builds two distinct maps:
spellVisualCastPath_ — visualId → M2 via SpellVisual.CastKit chain
spellVisualImpactPath_ — visualId → M2 via SpellVisual.ImpactKit chain
playSpellVisual() accepts useImpactKit=false (default, cast) / true (impact).
SMSG_PLAY_SPELL_IMPACT passes useImpactKit=true so impact effects (explosions,
debuff indicators) use the ImpactKit model instead of the CastKit model.
Added ImpactKit field to all four dbc_layouts.json files.
SMSG_PLAY_SPELL_IMPACT has a different wire format from SMSG_PLAY_OBJECT_SOUND:
it carries uint64 targetGuid + uint32 visualId (same as SMSG_PLAY_SPELL_VISUAL),
not uint32 soundId + uint64 sourceGuid.
Previously both were handled together, causing the target GUID low-bytes to be
misread as a sound ID and the visualId to be missed entirely.
Now each handler parses its own format correctly. SMSG_PLAY_SPELL_IMPACT resolves
the target entity position and calls playSpellVisual() to spawn the M2 impact effect
at that location.
Parse SMSG_PLAY_SPELL_VISUAL (casterGuid + visualId) and spawn a
transient M2 spell effect at the caster's world position.
DBC chain: SpellVisual.dbc → SpellVisualKit.dbc → SpellVisualEffectName.dbc
Lookup priority: CastKit.SpecialEffect0, fallback to MissileModel.
Models are lazy-loaded and cached by path; instances auto-expire after 3.5s.
DBC layouts added to all four expansion layout files (Classic/TBC/WotLK/Turtle).
Add addUIError() for remaining error-only chat-message cases:
- SMSG_BARBER_SHOP_RESULT non-zero result (not enough money, wrong
location, must stand up)
- SMSG_NPC_WONT_TALK ("That creature can't talk to you right now")
- SMSG_LFG_AUTOJOIN_FAILED and SMSG_LFG_AUTOJOIN_FAILED_NO_PLAYER
Completes the UIError improvement pass: all server-reported failure
events now surface as the red on-screen overlay, not chat-only.
Add addUIError() alongside addSystemChatMessage() for:
- SMSG_CHAT_WRONG_FACTION / SMSG_CHAT_NOT_IN_PARTY / SMSG_CHAT_RESTRICTED
- SMSG_LFG_JOIN_RESULT failure, LFG proposal failure (state=0), LFG
role check missing-role failure
- SMSG_AUCTION_COMMAND_RESULT error cases (bid/post/cancel/buyout)
- SMSG_PLAYERBINDERROR (hearthstone not bound / bind failed)
- SMSG_READ_ITEM_FAILED
- SMSG_PET_NAME_INVALID
Consistent with the rest of the error-overlay pass: players now see
these failures as the red on-screen overlay text, not just in chat.
- SMSG_TRANSFER_ABORTED: all zone/instance portal rejection reasons shown as UIError
(expansion required, instance full, too many instances, zone in combat, etc.)
- handleGroupUninvite: "You have been removed from the group." now shown as UIError
- handlePartyCommandResult: all party errors (group full, not leader, wrong faction,
ignoring you, etc.) now also shown as UIError overlay
- Fishing bobber splash: "A fish is on your line!" shown as UIError (time-critical)
- SMSG_BATTLEFIELD_PORT_DENIED: shown as UIError
- SMSG_INSTANCE_RESET_FAILED: failure reason shown as UIError
- SMSG_GROUP_DESTROYED: "Party disbanded" shown as UIError
- SMSG_CORPSE_NOT_IN_INSTANCE: shown as UIError
- SMSG_ZONE_UNDER_ATTACK: "[Zone] is under attack!" shown as UIError
- SMSG_AREA_TRIGGER_MESSAGE: zone/area entry messages shown as UIError
- SMSG_TRAINER_BUY_FAILED: "Cannot learn [spell]" now appears as red overlay
- SMSG_RESURRECT_FAILED: all resurrection failure reasons shown as UIError
- SMSG_BINDZONEREPLY error: "Too far from innkeeper" shown as UIError
- SMSG_CHANGEPLAYER_DIFFICULTY_RESULT error: reason shown as UIError
- SMSG_INVENTORY_CHANGE_FAILURE case 1: level-gated equip error now calls
addUIError before the early break, matching all other inventory error paths
SMSG_BUY_FAILED ("Not enough money", "Sold out", etc.) and
SMSG_SELL_ITEM non-zero results now call addUIError() so the error
appears on screen alongside the chat message.
Several server-reported action failures were posting to chat only
without firing the red on-screen UIError overlay:
- SMSG_INVENTORY_CHANGE_FAILURE: bag full, wrong slot, can't equip, etc.
- SMSG_MOUNTRESULT / SMSG_DISMOUNTRESULT: mount denied errors
- SMSG_QUESTLOG_FULL: quest log at capacity
- SMSG_SOCKET_GEMS_RESULT: gem socketing failure
All now call addUIError() in addition to addSystemChatMessage() so
players see the error immediately on screen without looking at chat.
handleCastFailed was only posting to chat; now also calls addUIError
so mid-cast server rejections (e.g. "Interrupted") show the same red
on-screen overlay as SMSG_CAST_RESULT failures already did.
Show '+X Honor' floating text in gold when SMSG_PVP_CREDIT is received,
matching WoW's native behavior. Also add HONOR_GAIN to the combat log
panel for a complete record. Previously only a chat message was added.
- Add MT/MA/Asst badges to party frames (matching raid frame treatment)
- Clear learnedTalents_ on SMSG_TALENTS_INVOLUNTARILY_RESET so the
talent screen stays accurate after a server-side talent wipe
Parse group type, member flags, and leader GUID instead of silently
discarding the packet. Updates partyData so group frames reflect
role changes and leadership transitions in real time.
Add buildItemLink() helper that formats |cff...|Hitem:...|h[Name]|h|r links so
the chat renderer draws item names in their quality colour (grey/white/green/
blue/purple/orange) with a small icon and tooltip on hover.
Applied to: loot received (SMSG_ITEM_PUSH_RESULT), looted from corpse
(handleLootRemoved), quest item count updates, and all three auction
house notifications (sold, outbid, expired).