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)
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.
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").
Added skipClassicSpellCastTargets() and skipTbcSpellCastTargets() calls
in parseSpellGo() for both expansions, matching the same fix applied to
WotLK SpellGoParser and both SpellStartParsers. Prevents packet stream
misalignment for ground-targeted and AoE spells (Blizzard, Rain of
Fire, Flamestrike, etc.) where the server appends DEST_LOCATION or
other target fields after the hit/miss lists.
Applied the same SpellCastTargets fix from SpellStartParser (dd64724)
to SpellGoParser: after parsing hit/miss target lists, now reads the
full target section (UNIT/UNIT_MINIPET/CORPSE/GAMEOBJECT packed GUID,
ITEM/TRADE_ITEM packed GUID, SOURCE/DEST PackedGuid+3floats, null-
terminated STRING). Also adds targetGuid field to SpellGoData so
callers can read the primary target. Prevents stream misalignment on
ground-targeted AoE spells (e.g. Blizzard, Rain of Fire).
Replaced partial UNIT/OBJECT-only flag handling with full WotLK
SpellCastTargets layout: UNIT/UNIT_MINIPET/CORPSE/GAMEOBJECT share
one PackedGuid, ITEM/TRADE_ITEM share one PackedGuid, SOURCE_LOCATION
and DEST_LOCATION are each PackedGuid+3floats (transport-relative),
STRING is null-terminated. Prevents byte-stream corruption on
ground-targeted AoE and similar multi-field target packets.
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.
The minimap had a comment "skip self (already drawn as arrow)" but no
code that actually drew the arrow. Players had no visual indication of
which direction they were facing on the minimap.
Draws a chevron-shaped white/gold arrow at the minimap center:
- On fixed-north minimap: arrow rotates to match camera compass bearing
(computed from camera forward vector: atan2(-fwd.x, fwd.y))
- On rotating minimap: arrow points straight up because the minimap
already rotates to put camera-forward at the top
- Style: two filled triangles (tip+left half, tip+right half) with dark
outline for readability against all map backgrounds
- Rendered last so it sits on top of all other minimap markers
The funcList_ dispatchers were populated by initializeModule() but the
public tick(), generateRC4Keys(), and unload() methods had their actual
call sites commented out as TODOs.
- tick(): now calls funcList_.tick(deltaMs) so the emulated module can
run its internal periodic scheduler.
- generateRC4Keys(): now calls funcList_.generateRC4Keys(packet) so
the Warden crypto stream is re-keyed as the module expects.
- unload(): now calls funcList_.unload(nullptr) before freeing module
memory, allowing the module to clean up its own state.
All three paths already guard on !loaded_ || !funcList_.<fn> so they
are no-ops when the module is not loaded or Unicorn is unavailable.
- CameraController::resetAngles(): new method that only resets yaw/pitch
without teleporting the player. R key now calls resetAngles() instead
of reset() so pressing R no longer moves the character to spawn.
The full reset() (position + angles) is still used on world-entry and
respawn via application.cpp.
- packet_parsers_classic: parseSpellStart now calls
skipClassicSpellCastTargets() to consume all target payload bytes
(UNIT, ITEM, SOURCE_LOCATION, DEST_LOCATION, etc.) instead of only
handling UNIT/OBJECT. Prevents packet-read corruption for ground-
targeted AoE spells.
- packet_parsers_tbc: added skipTbcSpellCastTargets() static helper
(uint32 targetFlags, full payload coverage including TRADE_ITEM and
STRING targets). parseSpellStart now uses it.
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.
Previously initializeModule() read the 4 WardenFuncList function addresses
from emulated memory, logged them, then discarded them — funcList_ was never
populated, so tick(), generateRC4Keys(), and processCheckRequest() were
permanently no-ops even when the Unicorn emulator successfully ran the module.
Changes:
- initializeModule() now wraps each non-null emulated function address in a
std::function lambda that marshals args to/from emulated memory via
emulator_->writeData/callFunction/freeMemory
- generateRC4Keys: copies 4-byte seed to emulated space, calls function
- unload: calls function with NULL (module saves own RC4 state)
- tick: direct uint32_t(deltaMs) dispatch, returns emulated EAX
- packetHandler: 2-arg variant for generic callers
- Stores emulatedPacketHandlerAddr_ for full 4-arg call in processCheckRequest
- processCheckRequest() now calls the emulated PacketHandler with the proper
4-argument stdcall convention: (data, size, responseOut, responseSizeOut),
reads back the response size and bytes, returns them in responseOut
- unload() resets emulatedPacketHandlerAddr_ to 0 for clean re-initialization
- Remove dead no-op renderObjectiveTracker() (no call sites, superseded)
Previously hookAPI() allocated a stub address and registered a C++ handler
but never stored the handler or wrote any executable code to the stub
region, meaning any Warden module call to a Windows API would execute zeros
and crash or silently return garbage.
Changes:
- Store ApiHookEntry {argCount, handler} per stub address in apiHandlers_
- Write RET (0xC3) to stub memory as a safe fallback
- Register UC_HOOK_CODE over the API stub address range during initialize()
- hookCode() now detects stub addresses, reads args from the emulated stack,
dispatches to the C++ handler, then simulates stdcall epilogue by setting
EAX/ESP/EIP so Unicorn returns cleanly to the caller
- Convert static-local nextStubAddr to instance member nextApiStubAddr_
so re-initialization resets the allocator correctly
- Known arg counts for all 7 registered Windows APIs (VirtualAlloc,
VirtualFree, GetTickCount, Sleep, GetCurrentThreadId,
GetCurrentProcessId, ReadProcessMemory)
Items with startQuestId != 0 now show:
- Gold outer glow border (2px) around the item icon
- Gold "!" badge in the top-right corner of the icon
- "Begins a Quest" label in gold on the second text line
Matches WoW's visual convention for quest-pickup items in loot rolls.
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.
Implements renderSkillsWindow() showing all player skills grouped by
DBC category (Professions, Secondary Skills, Class Skills, Weapon Skills,
Armor, Languages) with value/max progress bars and a bonus breakdown tooltip.
Hooked up to the TOGGLE_SKILLS keybinding (K by default).
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.
Shows current local time in HH:MM format in a small dimmed label just
below the coordinate display near the minimap. Uses localtime_r (POSIX)
with a _WIN32 fallback. The clock complements the existing coordinate
and zone name overlays, matching the WoW default UI minimap area.
The ToT (target-of-target) cast bar was still using a fixed orange-yellow
color regardless of spell interruptibility. Now uses the same green/red
scheme as the target frame and nameplate cast bars: green = interruptible
(can Kick/Counterspell), red = not interruptible, both pulse at >80%.
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).
When the player dies and releases spirit, the world map now renders a
bone-white X cross at the corpse's location (matching the existing
minimap skull marker). The marker appears only when the player is a
ghost with an unclaimed corpse on the same map, and shows a "Your
corpse" tooltip on hover. Implemented via setCorpsePos() on WorldMap,
called from renderWorldMap() using getCorpseCanonicalPos().
When a hostile unit has UNIT_FIELD_TARGET pointing to the local player,
highlight its nameplate with an orange border so players can immediately
see which enemies are attacking them vs. attacking group members.
Priority: gold=selected, orange=targeting you, dark=default.
Boss encounter frames were still using the old fixed orange/red cast bar
color. Update them to match the target frame: green = interruptible,
red = SPELL_ATTR_EX_NOT_INTERRUPTIBLE, both pulse at >80% completion.
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.
The zone label above the minimap now preferentially uses the zone/area
name from getWorldStateZoneId() (populated via SMSG_INIT_WORLD_STATES)
rather than the renderer's map-level zone name. This means the label
correctly shows "Ironforge", "Wailing Caverns", etc. instead of always
showing the parent continent zone name.
Previously the "Entering: [Zone]" overlay only triggered when the terrain
renderer loaded a new map. Now it also fires whenever worldStateZoneId_
changes (sent by the server via SMSG_INIT_WORLD_STATES on each zone
crossing), giving correct "Entering: Ironforge", "Entering: Wailing
Caverns" etc. display for sub-zones and dungeon entries without requiring
a full map reload.
- Added lastKnownWorldStateZoneId_ to track server-reported zone changes
- renderZoneText() now takes GameHandler& to access getWorldStateZoneId()
and getWhoAreaName() for name lookup via WorldMapArea.dbc cache
- Renderer zone name still checked as a fallback for map-level transitions
- Both sources de-duplicate to avoid triggering the same text twice
Aura icons on the player buff bar and the target frame now display a
WoW-style dark fan overlay that sweeps clockwise as the buff/debuff
elapses, providing instant visual feedback on remaining duration.
The sweep uses AuraSlot::maxDurationMs / getRemainingMs() — the same
data that already drives the numeric countdown — so no new state is
required. Only temporary auras (maxDurationMs > 0) show a sweep;
permanent buffs remain unaffected.
Add gold diamond markers for every flight master the player has already
discovered (knownTaxiMask_), read from TaxiNodes.dbc and filtered to the
current continent/map being displayed:
- WorldMapTaxiNode struct carries canonical WoW coords + known flag
- WorldMap::setTaxiNodes() accepts the per-frame list from game_screen
- renderImGuiOverlay() projects each known node to UV, draws a gold
diamond (AddQuadFilled) with a dark outline, and shows the node name
as a tooltip on hover
- GameHandler::isKnownTaxiNode(id) checks knownTaxiMask_[] efficiently
- Markers update live — newly discovered nodes appear without reopening
the map
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.