SMSG_SPELL_START fires for all units, not just the player. Previously only
the player's own cast was tracked; now we also track when the current
target is casting, enabling interrupt decisions.
- GameHandler: track targetCasting_/targetCastSpellId_/targetCastTimeTotal_
/targetCastTimeRemaining_ — updated by SMSG_SPELL_START for the current
target and ticked down in the update loop each frame
- Target cast cleared when: target changes (setTarget), target's spell
lands (SMSG_SPELL_GO), or cast timer expires naturally
- game_screen: renderTargetFrame shows a red cast progress bar between
the power bar and distance line when the target is casting, with
spell name + remaining seconds
- Public accessors: isTargetCasting(), getTargetCastSpellId(),
getTargetCastProgress(), getTargetCastTimeRemaining()
Both SpellGoParser::parse (WotLK) and TbcPacketParsers::parseSpellGo
(TBC) read missCount but did not consume the per-miss (guid + missType)
entries that follow, leaving unread bytes in the packet and silently
corrupting any subsequent parsing of cast-flags–gated spell data.
- Add SpellGoMissEntry{targetGuid, missType} and missTargets vector
to SpellGoData
- WotLK parser now reads packed GUIDs + missType per miss entry
- TBC parser now reads full uint64 GUIDs + missType per miss entry
(9 bytes per entry, bounds-checked)
- handleSpellGo now shows MISS/DODGE/PARRY/BLOCK combat text
for each missed target when the local player cast the spell,
complementing the existing SMSG_SPELLLOGMISS path
- Remove unused foliageLikeModel variable in m2_renderer pass-2 loop
(fix unused-variable warning)
- Update smoke model comment in m2_renderer to reflect current state
SMSG_PET_SPELLS: Parse full packet — pet GUID, react/command state,
10 action bar slots, per-spell entries with autocast flags. Previously
only read the GUID.
SMSG_PET_MODE: Parse petGuid + mode uint32 (command low byte, react
high byte) to keep stance state in sync after server updates.
SMSG_PET_BROKEN: Clear pet state and show "Your pet has died." chat
message.
SMSG_PET_LEARNED_SPELL / SMSG_PET_UNLEARNED_SPELL: Maintain pet spell
list incrementally.
SMSG_PET_CAST_FAILED: Parse and log cast count + spell + reason.
New state accessors: getPetActionSlot(), getPetCommand(), getPetReact(),
getPetSpells(), isPetSpellAutocast().
CMSG_PET_ACTION: Add targetGuid (uint64) field — the wire format
requires petGuid(8)+action(4)+targetGuid(8). Was sending an 12-byte
packet instead of the required 20 bytes.
sendPetAction(): New method that builds and sends CMSG_PET_ACTION with
the correct target guid.
switchTalentSpec() was only updating local state without notifying the
server, leaving the server out of sync with the client's active talent
group. Now sends CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE (WotLK wire
opcode 0x4C3) with the target group index (0=primary, 1=secondary),
prompting the server to apply the spec swap and respond with a fresh
SMSG_TALENTS_INFO for the newly active group.
Also adds ActivateTalentGroupPacket::build() to world_packets for the
packet construction.
When a player releases spirit, the server sends SMSG_DEATH_RELEASE_LOC
with the corpse map and position. Store this so the ghost can reclaim.
New flow:
- SMSG_DEATH_RELEASE_LOC now stores corpseMapId_/corpseX_/Y_/Z_ instead
of logging and discarding
- canReclaimCorpse(): true when ghost is on same map within 40 yards of
stored corpse position
- reclaimCorpse(): sends CMSG_RECLAIM_CORPSE (no payload)
- renderReclaimCorpseButton(): shows "Resurrect from Corpse" button at
bottom-center when canReclaimCorpse() is true
Two bugs fixed:
1. acceptResurrect() was always sending CMSG_SPIRIT_HEALER_ACTIVATE even
for player-cast resurrections (Priest/Paladin/Druid). That opcode is
only the correct response to SMSG_SPIRIT_HEALER_CONFIRM. For
SMSG_RESURRECT_REQUEST the server expects CMSG_RESURRECT_RESPONSE
with accept=1. Added resurrectIsSpiritHealer_ to track which path
triggered the dialog and send the right packet per type.
2. The resurrect dialog showed a generic "Return to life?" string
regardless of who cast the resurrection. Parse the optional CString
name from SMSG_RESURRECT_REQUEST (or fall back to playerNameCache)
and display "X wishes to resurrect you." when the caster is known.
TBC 2.4.3 quest log update fields use 4 fields per slot
(questId, state, counts, timer) vs WotLK's 5 (extra counts field).
The wrong stride (5) caused all quest log reads to use wrong indices
beyond the first slot, breaking quest tracking on TBC servers.
TBC 2.4.3 CMSG_QUESTGIVER_QUERY_QUEST is guid(8) + questId(4) = 12
bytes. WotLK added a trailing isDialogContinued(u8) byte that TBC
servers don't expect; sending it caused quest details to not be sent
back on some emulators.
When the player starts casting Hearthstone (spell IDs 6948/8690),
trigger background terrain loading at the bind point so tiles are
ready when the teleport fires.
- Add HearthstonePreloadCallback to GameHandler, called from
handleSpellStart when a Hearthstone cast begins.
- Application callback enqueues a 5×5 tile grid around the bind
point via precacheTiles() (same-map) or starts a file-cache warm
via startWorldPreload() (cross-map) during the ~10 s cast time.
- On same-map teleport arrival, call processAllReadyTiles() to
GPU-upload any tiles that finished parsing during the cast before
the first frame at the new position.
Fixes: player landing in unloaded terrain and falling after Hearthstone.
TBC 2.4.3 SMSG_SPELL_START and SMSG_SPELL_GO send full uint64 GUIDs for
casterGuid/casterUnit and hit targets. WotLK uses packed (variable-length)
GUIDs. Using readPackedGuid() on a full uint64 reads the first byte as the
bitmask, consuming 1-8 wrong bytes, which shifts all subsequent fields
(spellId, castFlags, castTime) and causes:
- Cast bar to never show for the player's own spells
- Sound effects to use the wrong spell ID
- Hit/miss target tracking to be completely wrong
Additionally, TBC SMSG_SPELL_GO lacks the WotLK timestamp field after
castFlags.
Add TbcPacketParsers::parseSpellStart and ::parseSpellGo using full GUIDs,
add virtual base methods, and route both handlers through virtual dispatch.
TBC 2.4.3 SMSG_CAST_RESULT sends spellId(u32) + result(u8) = 5 bytes.
WotLK 3.3.5a added a castCount(u8) prefix making it 6 bytes. Without
this fix the WotLK parser was reading spellId[0] as castCount, then the
remaining 3 spellId bytes plus result byte as spellId (wrong), and then
whatever follows as result — producing incorrect failure messages and
potentially not clearing the cast bar on TBC.
Add TbcPacketParsers::parseCastResult override and a virtual base method,
then route SMSG_CAST_RESULT through virtual dispatch in the game handler.
Classic 1.12 SMSG_CREATURE_QUERY_RESPONSE has no iconName CString between
subName and typeFlags. The TBC/WotLK parser was reading the typeFlags
uint32 bytes as the iconName string, then reading the remaining bytes as
typeFlags — producing garbage creature type/family/rank values and
corrupting target frame display for all creatures on Classic servers.
Add ClassicPacketParsers::parseCreatureQueryResponse without the iconName
read, and route the game handler through virtual dispatch so the override
is called.
Add TbcPacketParsers::parseSpellHealLog override using full uint64 GUIDs
(TBC) instead of packed GUIDs (WotLK). Route handleAttackerStateUpdate,
handleSpellDamageLog, and handleSpellHealLog through the virtual
packetParsers_ interface so expansion-specific overrides are actually
called. Previously the game handler bypassed virtual dispatch with
direct static parser calls, making all three TBC overrides dead code.
TBC 2.4.3 SMSG_ATTACKERSTATEUPDATE and SMSG_SPELLNONMELEEDAMAGELOG send
full uint64 GUIDs for attacker/target, while WotLK 3.3.5a uses packed
(variable-length) GUIDs. Using the WotLK reader on TBC packets consumes
1-8 bytes where a fixed 8 are expected, shifting all subsequent reads
and producing completely wrong damage/absorbed/resisted values.
Add TbcPacketParsers overrides that read plain uint64 GUIDs. Also note
that TBC SMSG_SPELLNONMELEEDAMAGELOG lacks the WotLK overkill field.
TBC 2.4.3 differs from WotLK in four ways:
- Header: uint8 count only (WotLK: uint32 totalCount + uint8 shownCount),
so the WotLK parser was reading 4 garbage bytes before the count
- No extra unknown uint32 between itemTextId and stationery in each entry
- Attachment item GUID: full uint64 (WotLK uses uint32 low GUID)
- Attachment enchants: 7 × uint32 id only (WotLK: 7 × {id+duration+charges})
The resulting mis-parse would scramble subject/money/cod/flags for every
mail entry and corrupt all attachment reads. Add TbcPacketParsers::parseMailList
with the correct TBC 2.4.3 format.
SMSG_GOSSIP_MESSAGE quest entries in TBC 2.4.3 do not include
questFlags(u32) or isRepeatable(u8) that WotLK 3.3.5a added.
The WotLK default parser reads these 5 bytes, causing all quest titles
in gossip dialogs to be shifted/corrupted on TBC servers.
Add TbcPacketParsers::parseGossipMessage() which parses quest entries
without those fields, fixing NPC quest list display.
CMSG_CAST_SPELL: WotLK adds a castFlags(u8) byte after spellId that TBC
2.4.3 does not have. Add TbcPacketParsers::buildCastSpell() to omit it,
preventing every spell cast from being rejected by TBC servers.
CMSG_USE_ITEM: WotLK adds a glyphIndex(u32) field between itemGuid and
castFlags that TBC 2.4.3 does not have. Add buildUseItem() override.
SMSG_MONSTER_MOVE: WotLK adds a uint8 unk byte after the packed GUID
(MOVEMENTFLAG2_UNK7 toggle) that TBC 2.4.3 does not have. Add
parseMonsterMove() override to fix NPC movement parsing — without this,
all NPC positions, durations, and waypoints parse from the wrong byte
offset, making all NPC movement appear broken on TBC servers.
- Parse uint8 itemClass + uint32 subClassMask from SMSG_SET_PROFICIENCY
- Store weaponProficiency_ (itemClass=2) and armorProficiency_ (itemClass=4)
- Expose getWeaponProficiency(), getArmorProficiency(), canUseWeaponSubclass(n),
canUseArmorSubclass(n) on GameHandler for use by equipment UI
- Enables future equipment slot validation (grey out non-proficient items)
- SMSG_SPELLDAMAGESHIELD: parse victim/caster/damage fields and show SPELL_DAMAGE
combat text for player-relevant events (damage shields like Thorns)
- SMSG_SPELLORDAMAGE_IMMUNE: parse packed caster/victim guids and show new
IMMUNE combat text type when player is involved in an immunity event
- Add CombatTextEntry::IMMUNE type to spell_defines.hpp and render it as
white "Immune!" in the combat text overlay
- handleCompressedMoves: add MSG_MOVE_* routing so SMSG_MULTIPLE_MOVES
sub-packets (player movement batches) are dispatched to handleOtherPlayerMovement
instead of logged as unhandled; fix runtime-opcode lookup (non-static array)
Socket fixes (fixes Windows-only connection failure):
- WorldSocket::connect() now waits for non-blocking connect to complete with
select() before returning, preventing WSAENOTCONN on the first recv() call
on Windows (Linux handles this implicitly but Windows requires writability
poll after non-blocking connect)
- Add net::isConnectionClosed() helper: treats WSAENOTCONN/WSAECONNRESET/
WSAESHUTDOWN/WSAECONNABORTED as graceful peer-close rather than recv errors
- Apply isConnectionClosed() in both WorldSocket and TCPSocket recv loops
UI:
- Add renderBossFrames(): displays boss unit health bars in top-right corner
when SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT has active slots; supports
click-to-target and color-coded health bars (red→orange→yellow as HP drops)
- SMSG_RESUME_CAST_BAR: parse packed_guid caster/target + spellId + remainingMs +
totalMs; restores cast bar state when server re-syncs a cast in progress
- SMSG_THREAT_UPDATE: properly consume packed_guid host/target + threat entries
to suppress unhandled packet warnings
- SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT: track up to 5 boss encounter unit guids
per slot; expose via getEncounterUnitGuid(slot); clear on world transfer
These guids identify active boss units for raid/boss frame display.
Previously "Achievement earned! (ID 1234)" was the only message. Now
loadAchievementNameCache() lazily loads Achievement.dbc (field 4 = Title,
verified against WotLK 3.3.5a binary) on first earned event and shows
"Achievement earned: Level 10" or "Player has earned the achievement: ..."
Falls back to ID if DBC is unavailable or entry is missing.
Parse the flight-master POI status packet (guid + uint8 status) and cache
it per-NPC in taxiNpcHasRoutes_. Exposes taxiNpcHasRoutes(guid) accessor
for future nameplate/interaction indicators. Previously this packet was
silently consumed without any state tracking.
Previously all player spell casts played ARCANE school sounds regardless
of the actual spell school. Now loadSpellNameCache() reads SchoolMask
(bitmask, TBC/WotLK) or SchoolEnum (Vanilla/Classic) from Spell.dbc and
stores it in SpellNameEntry. handleSpellStart/handleSpellGo look up the
spell's school and select the correct MagicSchool for cast sounds.
DBC field indices: WotLK SchoolMask=225 (verified), TBC=215, Classic/Turtle
SchoolEnum=1 (Vanilla enum 0-6 converted to bitmask).
Parse SMSG_RESYNC_RUNES, SMSG_ADD_RUNE_POWER, and SMSG_CONVERT_RUNE to
track the state of all 6 DK runes (Blood/Unholy/Frost/Death type,
ready flag, and cooldown fraction). Render a six-square rune bar below
the Runic Power bar when the player is class 6, with per-type colors
(Blood=red, Unholy=green, Frost=blue, Death=purple) and client-side
fill animation so runes visibly refill over the 10s cooldown.
XP gain was previously shown as a HEAL entry (green +N) which conflates
it with actual healing. New XP_GAIN type renders as purple '+N XP' in the
outgoing column, matching WoW's floating XP style.
Parse SPELLENERGIZELOG (victim/caster packed GUIDs + spellId + powerType +
amount) and emit ENERGIZE combat text for mana/energy gains. Add ENERGIZE
to CombatTextEntry::Type enum (blue +N text).
Also add explicit renderCombatText cases for BLOCK, PERIODIC_DAMAGE,
PERIODIC_HEAL, and ENVIRONMENTAL — previously all fell through to the
colourless default handler.
Add PlayPositionalSoundCallback that carries both soundId and sourceGuid.
In Application, look up the source entity position and play via
AudioEngine::playSound3D(); fall back to playSound2D() when the entity
is unknown. Also read the 8-byte sourceGuid field from the packet
(previously the full 12-byte payload was ignored).
Add PlaySoundCallback to GameHandler (same pattern as PlayMusicCallback).
When SMSG_PLAY_SOUND arrives, resolve the soundId through SoundEntries.dbc
(fields 3-12 = files, field 23 = DirectoryBase) and play the first found
file as a 2-D sound effect via AudioEngine::playSound2D(). Previously the
opcode was parsed and dropped.
Add ZoneManager::enrichFromDBC() which walks AreaTable.dbc (field 8 = ZoneMusicId)
→ ZoneMusic.dbc (fields 6/7 = day/night SoundEntryIds) → SoundEntries.dbc
(fields 3-12 = files, field 23 = DirectoryBase) and appends MPQ music paths for
all zones in the DBC, covering ~2300+ areas vs the previous ~15 hardcoded entries.
Existing hardcoded paths are preserved as the primary pool; DBC paths are added
only if not already present. Called from Renderer::init() after initialize().
Add PlayMusicCallback to GameHandler so SMSG_PLAY_MUSIC (and the
vanilla 0x0103 alias) dispatch a soundId to the registered handler
instead of being silently consumed. Application.cpp registers the
callback, loads SoundEntries.dbc, resolves the first non-empty
Name+DirectoryBase into an MPQ path, and passes it to MusicManager
for non-looping playback. Resolves the TODO in the SMSG_PLAY_MUSIC
handler.
- Send CMSG_QUERY_QUESTS_COMPLETED on initial world entry so
completedQuests_ is populated from the server response
- Clear completedQuests_ on world entry to avoid stale data across sessions
- Add isQuestCompleted(questId) and getCompletedQuests() public accessors
to allow UI layers to filter NPC quest offers by completion state
- SMSG_BUY_ITEM: log successful purchase and clear pending buy state
- SMSG_CRITERIA_UPDATE: log achievement criteria progress (no UI yet)
- SMSG_BARBER_SHOP_RESULT: show success/failure message in chat
- SMSG_OVERRIDE_LIGHT: store zone light override id + transition time, expose via getOverrideLightId()/getOverrideLightTransMs()
- Add Reputation tab in character screen tab bar (Equipment/Stats/Reputation/Skills)
- Implement renderReputationPanel() showing all tracked factions sorted alphabetically
- Progress bars colored per WoW reputation tier: Hated/Hostile/Unfriendly/Neutral/Friendly/Honored/Revered/Exalted
- Add public getFactionNamePublic() backed by DBC name cache with lazy load
- Implement renderReadyCheckPopup() showing initiator name with Ready/Not Ready buttons
- Fix MSG_RAID_READY_CHECK fallback: use partyData.leaderGuid instead of non-existent isLeader field
- Add faction standing handler with loadFactionNameCache() (Faction.dbc field 22)
- Add gossip POI handler writing canonical WoW coords to gossipPois_ for minimap rendering
- SMSG_GOSSIP_POI: parse map POI markers (x/y/icon/name) from quest NPCs, render as
cyan diamonds on the minimap with hover tooltips for quest navigation
- SMSG_ATTACKSWING_DEADTARGET: clear auto-attack when target dies mid-swing
- SMSG_CANCEL_COMBAT: server-side combat reset (clears autoAttacking + target)
- SMSG_BREAK_TARGET / SMSG_CLEAR_TARGET: server-side targeting clears
- SMSG_DISMOUNT: server-forced dismount triggers mountCallback(0)
- SMSG_MOUNTRESULT / SMSG_DISMOUNTRESULT: mount feedback in system chat
- SMSG_LOOT_ALL_PASSED: "Everyone passed on [Item]" system message, clears loot roll
- SMSG_LOOT_ITEM_NOTIFY / SMSG_LOOT_SLOT_CHANGED: consumed
- SMSG_SPELLLOGMISS: decode miss/dodge/parry/block from spell casts into combat text
- SMSG_ENVIRONMENTALDAMAGELOG: environmental damage (drowning/lava/fall) in combat text
- GossipPoi struct + gossipPois_ vector in GameHandler with public getters/clearers
- SMSG_HEALTH_UPDATE / SMSG_POWER_UPDATE: update entity HP/power via entityManager
- SMSG_UPDATE_WORLD_STATE: single world state variable update (companion to INIT)
- SMSG_UPDATE_COMBO_POINTS: store comboPoints_/comboTarget_ in GameHandler
- SMSG_START_MIRROR_TIMER / SMSG_STOP_MIRROR_TIMER / SMSG_PAUSE_MIRROR_TIMER: breath/fatigue/feign timer state
- MirrorTimer struct + getMirrorTimer() public getter; renderMirrorTimers() draws colored breath/fatigue bars above cast bar
- SMSG_CAST_RESULT: WotLK extended cast result; clear cast bar and show reason on failure (result != 0)
- SMSG_SPELL_FAILED_OTHER / SMSG_PROCRESIST: consume silently
- SMSG_LOOT_START_ROLL: correct trigger for Need/Greed popup (replaces rollType=128 heuristic)
- SMSG_STABLE_RESULT: show pet stable feedback in system chat (store/retrieve/buy slot/error)
- SMSG_TITLE_EARNED: system chat notification for title earned/removed
- SMSG_PLAYERBOUND / SMSG_BINDER_CONFIRM: hearthstone binding notification
- SMSG_SET_PHASE_SHIFT: consume (WotLK phasing, no client action needed)
- SMSG_TOGGLE_XP_GAIN: system chat notification
- Parse SMSG_ITEM_TEXT_QUERY_RESPONSE (guid + isEmpty + string text),
store in itemText_ and open book window when non-empty
- queryItemText(guid) sends CMSG_ITEM_TEXT_QUERY for readable items
- renderItemTextWindow(): scrollable book window with parchment-toned
text, "Close" button; opens via isItemTextOpen() flag
- Parse SMSG_QUEST_CONFIRM_ACCEPT (questId + title + sharerGuid),
show chat notification with quest title and sharer name
- acceptSharedQuest() sends CMSG_QUEST_CONFIRM_ACCEPT with questId
- renderSharedQuestPopup(): shows sharer name, gold quest title,
Accept/Decline buttons (stacked below other social popups)
- Parse SMSG_LOOT_ROLL: if rollType==128 and it's our player, store
pending roll (itemId, slot, name from itemInfoCache_) and show popup;
otherwise show chat notification of another player's roll result
- Parse SMSG_LOOT_ROLL_WON: show winner announcement in chat with item
name and roll type/value
- sendLootRoll() sends CMSG_LOOT_ROLL (objectGuid+slot+rollType) and
clears pending roll state
- SMSG_LOOT_MASTER_LIST consumed silently (no UI yet)
- renderLootRollPopup(): ImGui window with Need/Greed/Disenchant/Pass
buttons; item name colored by quality (poor/common/uncommon/rare/epic/
legendary color scale)
- Parse SMSG_DUEL_REQUESTED: store challenger guid/name, set
pendingDuelRequest_ flag, show chat notification
- Parse SMSG_DUEL_COMPLETE: clear pending flag, notify on cancel
- Parse SMSG_DUEL_WINNER: show "X defeated Y in a duel!" chat message
- Handle SMSG_DUEL_OUTOFBOUNDS with warning message
- Add acceptDuel() method sending CMSG_DUEL_ACCEPTED (new builder)
- Wire forfeitDuel() to clear pendingDuelRequest_ on decline
- Add renderDuelRequestPopup() ImGui window (Accept/Decline buttons)
positioned near group invite popup; shown when challenge is pending
- Add DuelAcceptPacket builder to world_packets.hpp/cpp
- Parse SMSG_ACHIEVEMENT_EARNED (guid + achievementId + PackedTime date)
and fire AchievementEarnedCallback for self, chat notify for others
- Add renderAchievementToast() to GameScreen: slides in from right,
gold-bordered panel with "Achievement Earned!" title + ID, 5s duration
with 0.4s slide-in/out animation and fade at end
- Add triggerAchievementToast(uint32_t) public method on GameScreen
- Wire AchievementEarnedCallback in application.cpp
- Add playAchievementAlert() to UiSoundManager, loads
Sound\Interface\AchievementSound.wav with level-up fallback
- SMSG_ALL_ACHIEVEMENT_DATA silently consumed (no tracker UI yet)
- Add renderDungeonFinderWindow() with status display (not queued /
role check / queued+wait time / proposal / in dungeon / finished)
- Role checkboxes (Tank/Healer/DPS) and dungeon combo (25 entries
covering Vanilla, TBC, and WotLK including Random/Heroic)
- Accept/Decline buttons during Proposal state, Teleport button
while InDungeon, Leave Queue button while Queued/RoleCheck
- Store lfgProposalId_ on GameHandler so UI can pass it to
lfgAcceptProposal(); expose getLfgProposalId() and
getLfgTimeInQueueMs() getters
- Toggle window with I key (when chat input is not active)
Parse and store dungeon/raid lockout data sent on login:
- mapId, difficulty, resetTime (Unix timestamp), locked, extended flags
- Stored in instanceLockouts_ vector for UI / LFG / dungeon state queries
- Public InstanceLockout struct + getInstanceLockouts() accessor