IMMUNE misses (spell miss type 5) were shown as generic MISS text in both
spell cast feedback handlers. Now consistently shows IMMUNE combat text
to match the fix already applied to SMSG_ATTACKERSTATEUPDATE.
SMSG_ATTACKERSTATEUPDATE victimState values 5 (EVADE), 6 (IMMUNE), and
7 (DEFLECT) were previously falling through to the damage display path,
showing incorrect damage numbers instead of the proper miss/immune feedback.
Now correctly shows MISS for evade/deflect and IMMUNE for immune hits.
handleLearnedSpell, handleRemovedSpell, handleSupercededSpell, and
handleUnlearnSpells all lacked size checks before reading packet fields.
Also implements SMSG_SPELLSTEALLOG (previously silently consumed) with
proper player feedback showing the stolen spell name when the local player
is the caster, matching the same expansion-conditional packed-guid format
as SPELLDISPELLOG.
Each dispelled spell entry is uint32(spellId) + uint8(isPositive) = 5 bytes,
not uint32 + uint32 = 8 bytes as the loop previously assumed.
The incorrect stride caused the second and subsequent entries to be read at
wrong offsets, potentially showing the wrong spell name for multi-dispels.
SMSG_COOLDOWN_EVENT in WotLK appends an 8-byte unit guid after the spellId.
The handler was reading without a size check and not consuming the trailing
guid, which could misalign subsequent reads.
Both opcodes use packed GUIDs in WotLK 3.3.5a but were reading full uint64,
causing incorrect GUID parsing and potentially matching wrong player entities.
SMSG_PROCRESIST: caster + victim guids (packed in WotLK, uint64 in TBC/Classic)
SMSG_TOTEM_CREATED: totem guid (packed in WotLK, uint64 in TBC/Classic)
WotLK sends spellId(4) + packed_guid caster + packed_guid victim, while
TBC/Classic sends full uint64 caster + uint64 victim + spellId(4).
The previous handler assumed TBC format unconditionally, causing incorrect
reads in WotLK mode.
Also use the spell name cache to display "Purge failed to dispel." rather
than a raw "Dispel failed! (spell N)" message.
All expansions send spellId(4) before the caster guid — the previous
handler was missing this field entirely, causing the caster guid read
to consume spellId bytes and corrupt all subsequent parsing.
Additionally, in WotLK mode, victim guids inside the per-miss loop are
packed guids (not full uint64), matching the caster guid format.
Also handle the REFLECT (missInfo=11) extra payload in WotLK: the server
appends reflectSpellId(4) + reflectResult(1) for reflected spells, which
previously caused the following loop entries to be mis-parsed.
Same DBC-driven physical school detection replaces the brittle hardcoded
warrior spell list in the pre-cast range check, so rogue, DK, paladin,
feral druid, and hunter melee abilities get correct range/facing enforcement.
Replace the brittle warrior-only hardcoded spell ID list for melee ability
detection with a DBC-driven check: physical school mask (1) from spellNameCache_
covers warrior, rogue, DK, paladin, feral druid, and all other physical-school
instant abilities generically. Instant detection: spellId != currentCastSpellId.
Replace generic 'Transfer aborted' message with WotLK TRANSFER_ABORT_*
reason codes: difficulty, expansion required, instance full, too many
instances, zone in combat, etc.
WotLK 3.3.5a format uses packed guids (not full uint64) for victim and caster,
and adds an absorbed(4) field before schoolMask. Classic/TBC use full uint64 guids.
Previously the handler always read full uint64 guids, causing misparse on WotLK
(e.g. Thorns and Shield Spike damage shield combat text was garbled/wrong).
Add PERIODIC_ENERGIZE (91) and OBS_MOD_POWER (46) handling so mana/energy/rage
restore ticks from common WotLK auras (Replenishment, Mana Spring Totem, Divine
Plea, etc.) appear as ENERGIZE in floating combat text. Also handle PERIODIC_MANA_LEECH
(98) to properly consume its 12 bytes instead of halting mid-event parse.
MSG_SET_DUNGEON_DIFFICULTY sends 4 or 12 bytes (difficulty + optional
isInGroup + savedBool) while SMSG_INSTANCE_DIFFICULTY sends 8 bytes
(difficulty + heroic). The previous guard of < 8 caused the handler
to silently return for 4-byte variants, leaving instanceDifficulty_
unchanged. Now reads as much as available and infers heroic flag from
the field count.
Pre-allocate one stable VkDescriptorSet per particle emitter at model
upload time (particleTexSets[]) instead of allocating a new set from
materialDescPool_ every frame for each particle group. The per-frame
path exhausted the 8192-set pool in ~14 s at 60 fps with 10 active
particle emitters, causing GPU device-lost crashes. The old path is
kept as an explicit fallback but should never be reached in practice.
- character_renderer: playAnimation now prefers the primary variation
(variationIndex==0) when multiple sequences share the same animation ID;
this fixes hitching on human female run where a variation sequence was
selected first before the base cycle
- character_renderer: move the compositeWithRegions size-mismatch warning
inside the else branch so it only fires when sizes genuinely don't match,
not for every successful 1:1 or scaled blit
- terrain_renderer: add FREE_DESCRIPTOR_SET_BIT flag and vkFreeDescriptorSets
in destroyChunkGPU so material descriptor sets are returned to the pool;
prevents GPU device lost from pool exhaustion near populated areas
- game_screen: fix projectToMinimap to use the exact inverse of the minimap
shader transform so quest objective markers appear at the correct position
and orientation regardless of camera bearing
- inventory_screen: fix item comparison tooltip to not compare equipped items
against themselves (character screen); add item level diff line; show (=)
indicator when stats are equal rather than bare value which looked identical
to the item's own tooltip
After parsing the peer's trade window state, query item info for all
occupied slots so item names display immediately rather than showing
'Item 12345' until the cache is populated on the next frame.
Replace the raw area ID in the zone-under-attack message with the
resolved area name from the zone manager, matching retail WoW behavior
('Hillsbrad Foothills is under attack!' instead of 'area 267').
Previously only displayed a chat message without updating the quest
tracker. Now parses the full packet (guid+questId+count+reqCount),
stores progress under entry-key 0 in killCounts, and shows a progress
message matching the format used for creature kills.
Handles both WotLK (4-field) and Classic (3-field, no reqCount) variants
with fallback to the existing killCounts or killObjectives for reqCount.
Yellow crossed-swords icon appears to the right of the unit name when
the creature's entry is an incomplete kill objective in a tracked quest.
Updated icon is suppressed once the kill count is satisfied.
Uses unit->getEntry() (Unit subclass method) rather than the base
Entity pointer, matching how questKillEntries keys are stored.
Quest POI markers are map-specific. Clearing gossipPois_ on world entry
prevents stale markers from previous maps being displayed on the new map.
Quest POIs will be re-fetched as the quest log re-queries on the new map.
When abandonQuest() removes a quest from the log, also remove any
gossipPoi markers tagged with that questId (data field) so stale
objective markers don't linger on the minimap.
Remove existing POI markers for a quest before adding new ones (using the
data field as questId tag) so repeated CMSG_QUEST_POI_QUERY calls don't
accumulate duplicate markers. Also fix LOG_DEBUG to appear before the move.
Send CMSG_QUEST_POI_QUERY alongside each CMSG_QUEST_QUERY (WotLK only,
gated by questLogStride == 5 and opcode availability). Parse the response
to extract POI region centroids and add them as GossipPoi markers so the
existing minimap rendering shows quest objective locations as cyan diamonds.
Each quest POI region is reduced to its centroid point; markers for the
current map only are shown. This gives players visual guidance for where
to go for active quests directly on the minimap.
- SMSG_QUESTUPDATE_ADD_KILL: use absolute value of npcOrGoId when looking
up required count from killObjectives (negative values = game objects)
- applyPackedKillCountsFromFields: same fix — use abs(npcOrGoId) as map key
so GO objective counts are stored with the correct entry key
- SMSG_QUESTUPDATE_ADD_ITEM: also match quests via itemObjectives when
requiredItemCounts is not yet populated (race at quest accept time)
- Quest log and minimap sidebar: fall back to GO name cache for entries
that return empty from getCachedCreatureName (interact/loot objectives)
SMSG_QUEST_QUERY_RESPONSE uses 40 fixed uint32 fields + 4 strings for both
Classic/Turtle and TBC, but the isClassicLayout flag was only set for stride-3
expansions (Classic/Turtle). TBC (stride 4) was incorrectly using the WotLK
55-field path, causing objective parsing to fail.
- Extend isClassicLayout to cover stride <= 4 (includes TBC)
- Refactor extractQuestQueryObjectives to try both layouts with fallback,
matching the robustness of pickBestQuestQueryTexts
- Pre-fetch creature/GO/item name queries when quest objectives are parsed
so names are ready before the player opens the quest log
- Quest log detail view: show creature names instead of raw entry IDs for
kill objectives, and show required count (x/y) for item objectives
Parse kill/item objectives from SMSG_QUEST_QUERY_RESPONSE binary data:
- extractQuestQueryObjectives() scans past the fixed integer header and
variable-length strings to reach the 4 entity + 6 item objective entries
(using known offsets: 40 fields for Classic/TBC, 55 for WotLK)
- Objectives stored in QuestLogEntry.killObjectives / itemObjectives arrays
- After storing, applyPackedKillCountsFromFields() reads 6-bit packed counts
from update-field slots (stride+2 / stride+3) and populates killCounts
using the parsed creature/GO entry IDs as keys
This means on login, quests that were in progress show correct kill count
progress (e.g. "2/5 Defias Bandits killed") without waiting for the first
server SMSG_QUESTUPDATE_ADD_KILL notification.
resyncQuestLogFromServerSlots now reads the state field (slot*stride+1)
alongside the quest ID field, and marks quest.complete=true when the
server reports QuestStatus=1 (complete/ready-to-turn-in). Previously,
quests that were already complete before login would remain incorrectly
marked as incomplete until SMSG_QUESTUPDATE_COMPLETE fired, which only
happens when objectives are NEWLY completed during the session.
applyQuestStateFromFields() is a lightweight companion called from both
the CREATE and VALUES update handlers that applies the same state-field
check to already-tracked quests mid-session, catching the case where
the last objective completes via an update-field delta rather than the
dedicated quest-complete packet.
Works across all expansion strides (Classic stride=3, TBC stride=4,
WotLK stride=5); guarded against stride<2 (no state field available).
Classic 1.12 and Turtle WoW have only 64 PLAYER_EXPLORED_ZONES uint32
fields (zone IDs pack into 2048 bits). TBC and WotLK use 128 (needed for
Outland/Northrend zone IDs up to bit 4095).
The hardcoded PLAYER_EXPLORED_ZONES_COUNT=128 caused extractExploredZoneFields
to read 64 extra fields beyond the actual zone block in Classic/Turtle —
consuming PLAYER_REST_STATE_EXPERIENCE, PLAYER_FIELD_COINAGE, and character-
points fields as zone flags. On the world map, this could mark zones as
explored based on random bit patterns in those unrelated fields.
Add `exploredZonesCount()` virtual method to PacketParsers (default=128,
Classic/Turtle override=64) and use it in extractExploredZoneFields to
limit reads to the correct block and zero-fill remaining slots.
The REST_STATE_EXPERIENCE field was erroneously set to the same index as
PLAYER_SKILL_INFO_START in all four expansion JSON files, causing the
rested XP tracker to read the first skill slot ID as the rested XP value.
Correct indices derived from layout: EXPLORED_ZONES_START + 128 zone
fields (or 64 for Classic) immediately precede PLAYER_FIELD_COINAGE, with
REST_STATE_EXPERIENCE in the one slot between them.
- WotLK: 636 → 1169 (1041 + 128 = 1169, before COINAGE=1170)
- Classic: 718 → 1175 (1111 + 64 = 1175, before COINAGE=1176)
- TBC: 928 → 1440 (1312 + 128 = 1440, before COINAGE=1441)
- Turtle: 718 → 1175 (same as Classic layout)
Add UNIT_FIELD_STAT0-4 (STR/AGI/STA/INT/SPI) to the UF enum and wire up
per-expansion indices in all four expansion JSON files (WotLK: 84-88,
Classic/Turtle: 138-142, TBC: 159-163). Read the values in both CREATE
and VALUES player update handlers and store in playerStats_[5].
renderStatsPanel now uses the server-authoritative totals when available,
falling back to the previous 20+level estimate only if the server hasn't
sent UNIT_FIELD_STAT* yet. Item-query bonuses are still shown as (+N)
alongside the server total for both paths.
Reads OBJECT_FIELD_SCALE_X (field 4, cross-expansion) from CREATE_OBJECT
update fields and passes it through the full creature and game object spawn
chain: game_handler callbacks → pending spawn structs → async load results
→ createInstance() calls. This gives boss giants, gnomes, children, and
other non-unit-scale NPCs correct visual size, and ensures scaled GOs
(e.g. large treasure chests, oversized plants) render at the server-specified
scale rather than always at 1.0.
- Added OBJECT_FIELD_SCALE_X to UF enum and all expansion update_fields.json
- Added float scale to CreatureSpawnCallback and GameObjectSpawnCallback
- Propagated scale through PendingCreatureSpawn, PreparedCreatureModel,
PendingGameObjectSpawn, PreparedGameObjectWMO
- Used scale in charRenderer/m2Renderer/wmoRenderer createInstance() calls
- Sanity-clamped raw float to [0.01, 100.0] range before use
Display 'Requires Level N' in red when the player does not meet the
item's level requirement, and in normal colour when they do. Applies
to both equipped-item and bag-item tooltip paths.
When the server has not sent SMSG_INIT_WORLD_STATES or the mask is
empty, fall back to locally-accumulated explored zones tracked by
player position. The local set is cleared when a real server mask
arrives so it doesn't persist stale data.
Trivial/low-level quests now show gray '!' / '?' markers instead of
yellow, matching the in-game distinction between available and trivial
quests. Add grayscale parameter to QuestMarkerRenderer::setMarker and
the push-constant block; application sets grayscale=1.0 for trivial
markers and 0.0 for all others.
- Animation stutter: skip playAnimation(Run) for the local player in the
server movement callback — the player renderer state machine already manages
it; resetting animTime on every movement packet caused visible stutter
- Resolution crash: reorder swapchain recreation so old swapchain is only
destroyed after confirming the new build succeeded; add null-swapchain
guard in beginFrame to survive the retry window
- Memory cap: reduce cache budget from 80% uncapped to 50% hard-capped at
16 GB to prevent excessive RAM use on high-memory systems
- Spell tooltip: suppress "Drag to action bar / Double-click to cast" hints
when the tooltip is shown from the action bar (showUsageHints=false)
- M2 collision: add watermelon/melon/squash/gourd to foliage (no-collision);
exclude chair/bench/stool/seat/throne from smallSolidProp so invisible chair
bounding boxes no longer trap the player
Shows a centered modal when LfgState::Proposal is active regardless of
whether the Dungeon Finder window is open, matching WoW behaviour where
the accept/decline prompt always appears over the game world.
Mirrors the BG invite popup pattern; buttons call lfgAcceptProposal().
On Windows arm64, wchar_t is unsigned so 'wc >= 0' is always true
and GCC/Clang emit -Wtype-limits. Drop the redundant lower bound
check — only the upper bound 'wc <= 0x7f' is needed.
- Buff bar was at Y=140 which overlaps the minimap (Y=10 to Y=210);
moved to Y=215 (just below minimap bottom edge) with 8 icons per row
- Quest tracker moved from Y=200 (inside minimap area) to Y=320 to
leave space for up to 3 rows of buffs between minimap and tracker
- Both are right-anchored and no longer conflict with the minimap or
each other in typical usage (up to ~20 active auras)
- Relocates buff bar from top-left Y=145 (overlapping party frames) to
top-right (screenW - barW - 10, 140) where it doesn't conflict with
party/raid frames anchored on the left side
- Increases max shown auras from 16 to 40 (WotLK supports 48 slots)
- Two-pass rendering: buffs shown first, debuffs below with a spacing gap
between them; both still use green/red borders for visual distinction
- Widens row to 12 icons for better horizontal use of screen space
Right-clicking a party member name in the 5-man party frame opens
a context menu with: Target, Set Focus, Whisper, Trade, Inspect.
- Whisper switches chat type to WHISPER and pre-fills the target name
- Trade calls GameHandler::initiateTrade(guid)
- Inspect sets target then calls GameHandler::inspectTarget()
- Uses BeginPopupContextItem tied to the Selectable widget
- Party frames: gold star prefix and gold name color for group leader;
LFG role badges [T]/[H]/[D] shown inline after member name
- Raid frames: leader name rendered in gold with a corner star marker;
role letter (T/H/D) drawn in bottom-right corner of each compact cell;
uses partyData.leaderGuid already present in the function scope
- Minimap party dots already use gold for leader (unchanged)
- Add latency indicator below minimap (color-coded: green/yellow/orange/red)
using the lastLatency value measured via CMSG_PING/SMSG_PONG
- Add BG queue status indicator below minimap when in WAIT_QUEUE
(abbreviated name: AV/WSG/AB/EotS etc.)
- Target-of-Target frame: add level display and click-to-target support
- Expose getLatencyMs() accessor on GameHandler
Add a compact focus target frame on the right side of the screen
when the player has a focus target set via /focus.
- Shows [Focus] label, name (colored by hostility/level diff), level
- HP bar with green→yellow→red coloring; power bar with type colors
- Cast bar showing spell name and remaining time when focus is casting
- Clicking the frame targets the focus entity
- Clears automatically when focus is lost (/clearfocus)