Parses reward money, guaranteed items, and choice items from SMSG_QUEST_QUERY_RESPONSE fixed header for both Classic/TBC (40-field) and WotLK (55-field) layouts. Rewards are shown in the quest details pane below objective progress with icons, names, and counts.
Chests (and lockboxes, coffers, etc.) failed to open because CMSG_LOOT
was only sent on Classic/Turtle expansions, and only when GO type was
already cached as type 3. Fix: always send CMSG_LOOT after
CMSG_GAMEOBJ_USE (server silently ignores it for non-lootable objects).
Also broaden CMSG_GAMEOBJ_REPORT_USE to all non-mailbox WotLK GOs.
Latency meter: record pingTimestamp_ in sendPing() and compute RTT in
handlePong(); add toggleable "Show Latency Meter" checkbox in Interface
settings (saved to settings.cfg).
The delayed-opening logic conflicted with quest details' use of the same
questDetailsOpenTime variable, causing the reward dialog to never appear.
Reverted to immediately opening the window. Item info queries are still
triggered, but will populate asynchronously with placeholders shown initially.
Add 100ms delay before opening the quest offer reward dialog, giving item
info queries time to complete. Prevents "Item X" placeholders where players
can't see item names or icons needed to choose rewards. Reuses the existing
questDetailsOpenTime mechanism with delayed flag check in isQuestOfferRewardOpen().
Quest reward items (both in details and offer-reward windows) were showing as "Item {itemId}"
placeholders because the window opened immediately after receiving SMSG_QUESTGIVER_QUEST_DETAILS,
before the item query responses from pre-fetched queries had time to arrive.
Solution: Delay opening the quest details window by 100ms to allow item queries to complete
and be cached before the window first renders. Uses std::chrono::steady_clock for timing.
- Add questDetailsOpenTime field to track delayed opening timestamp
- Modify isQuestDetailsOpen() to check timer and open window when time expires
- Reset timer whenever quest details window closes
- Updated comment to clarify pre-fetch benefits both details and offer-reward windows
Add upfront validation to setPlayerOnTransport to ensure the transport
GUID is registered in transportGuids_ before attaching the player. This
prevents transport desyncs when movement packets reference transports
that haven't been spawned/registered yet.
Implement quick-access quality presets (Low, Medium, High, Ultra) that adjust multiple graphics settings at once for better user experience. Each preset configures:
- Shadow rendering and distance
- Anti-aliasing (MSAA) level
- Normal mapping and parallax mapping
- Ground clutter density
The system automatically detects when settings deviate from a preset and marks them as "Custom". Presets are persisted to settings.cfg for consistency across sessions. Users can quickly switch between performance and quality modes or tweak individual settings as needed.
Move ShadowParamsUBO from 5 separate shadow rendering functions (2 in
m2_renderer, 1 in terrain_renderer, 1 in wmo_renderer) into shared
vk_frame_data.hpp header. Eliminates 5 identical local struct definitions
and improves consistency across all shadow pass implementations. Structure
layout matches shader std140 uniform buffer requirements.
Move envSizeMBOrDefault and envSizeOrDefault from 4 separate rendering
modules (character_renderer, m2_renderer, terrain_renderer, wmo_renderer)
into shared vk_utils.hpp header as inline functions. Use the most robust
version which includes overflow checking for MB-to-bytes conversion. This
eliminates 7 identical local function definitions and improves consistency
across all rendering modules.
Move ShadowPush from 4 separate rendering modules (character_renderer,
m2_renderer, terrain_renderer, wmo_renderer) into shared vk_frame_data.hpp
header. This eliminates 4 identical local struct definitions and ensures
consistency across all shadow rendering passes. Add vk_frame_data.hpp include
to character_renderer.cpp.
Consolidated duplicate attachment point resolution code used by both
attachWeapon() and getAttachmentTransform(). New findAttachmentBone()
helper encapsulates the complete lookup chain: attachment by ID, fallback
scan, key-bone fallback, and validation. Eliminates ~55 lines of duplicate
code while improving maintainability and consistency.
- Add showMinimap_ and showRaidFrames_ visibility flags to GameScreen
- Wire up TOGGLE_MINIMAP (M key) to toggle minimap visibility
- Wire up TOGGLE_RAID_FRAMES (F key) to toggle party/raid frame visibility
- Conditional rendering of minimap markers and party frames
- Completes keybinding manager integration for all 15 customizable actions
- Add TOGGLE_BAGS action to keybinding manager (B key default)
- Update inventory_screen.cpp to use keybinding manager for bag and character toggles
- Maintain consistent keybinding system across all UI windows
Implement showWorldMap_ state variable and TOGGLE_WORLD_MAP keybinding
integration to allow players to customize the W key binding for opening/
closing the World Map, consistent with other window toggles like Nameplates
(V key) and Guild Roster (O key).
- Add World Map (W), Nameplates (V), Raid Frames (R), Quest Log (Q) to
KeybindingManager enum with customizable default bindings
- Replace hard-coded V key check for nameplate toggle with
KeybindingManager::isActionPressed() to support customization
- Update config file persistence to handle new bindings
- Infrastructure in place for implementing visibility toggles on other
windows (World Map, Raid Frames, Quest Log) with future UI refactoring
- Extended KeybindingManager enum with TOGGLE_GUILD_ROSTER (O) and
TOGGLE_DUNGEON_FINDER (J) to replace hard-coded key checks
- Added Controls tab in Settings UI for rebinding all 10 customizable actions
- Implemented real-time key capture and binding with visual feedback
- Integrated keybinding persistence with main settings.cfg file
- Replaced hard-coded O key (Guild Roster) and I key (Dungeon Finder) checks
with KeybindingManager::isActionPressed() calls
- Added Reset to Defaults button for restoring original keybindings
Implement KeybindingManager singleton class to support:
- Storing and loading keybinding configuration from ini files
- Querying whether an action's keybinding was pressed
- Runtime rebinding of actions to different keys
- Default keybinding set: C=Character, I=Inventory, S=Spellbook, K=Talents,
L=Quests, M=Minimap, Esc=Settings, Enter=Chat
This is the foundation for user-customizable keybindings. Integration with
UI controls and replacement of hard-coded ImGui::IsKeyPressed calls will
follow in subsequent improvements.
Classic 1.12 uses uint16 spellId + uint16 slot (4 bytes/spell); TBC and WotLK
use uint32 spellId + uint16 unknown (6 bytes/spell). The old size-based heuristic
could misdetect TBC packets that happened to fit both layouts. Add a vanillaFormat
parameter to InitialSpellsParser::parse and override parseInitialSpells in
ClassicPacketParsers to always pass true, eliminating the ambiguity.
Classic 1.12 SMSG_CAST_RESULT uses an enum starting at 0=AFFECTING_COMBAT
(no SUCCESS entry), while WotLK starts at 0=SUCCESS, 1=AFFECTING_COMBAT.
Without this override, Classic result codes were handled by TBC's
parseCastResult which passed them unshifted, causing result 0
(AFFECTING_COMBAT) to be silently treated as success with no error shown.
This applies the same +1 shift used in parseCastFailed so all Classic
spell failure codes map correctly to getSpellCastResultString.
Adds dedicated CombatTextEntry::Type entries for ABSORB (miss type 7)
and RESIST (miss type 8), replacing the generic MISS display. Updates
missTypes arrays in SMSG_SPELLLOGMISS and SMSG_SPELL_GO, and adds
light-blue "Absorb" and grey "Resist" rendering in the combat text overlay.
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.
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.
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.
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
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().
- 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)
Replace the text-only "/join to enter" message with an interactive
popup that shows the BG name, a live countdown progress bar, and
Enter/Leave Queue buttons.
- Parse STATUS_WAIT_JOIN timeout from SMSG_BATTLEFIELD_STATUS
- Store inviteReceivedTime (steady_clock) on the queue slot
- BgQueueSlot moved to public section so UI can read invite details
- Add declineBattlefield() that sends CMSG_BATTLEFIELD_PORT(action=0)
- acceptBattlefield() optimistically sets statusId=3 to dismiss popup
- renderBgInvitePopup: colored countdown bar (green→yellow→red),
named BG (Alterac Valley, Warsong Gulch, etc.), auto-dismisses on expiry
- Parse SMSG_ALL_ACHIEVEMENT_DATA on login to populate earnedAchievements_ set
- Pass achievement name through callback so toast shows name instead of ID
- Add renderItemTooltip(ItemQueryResponseData) overload for loot/non-inventory contexts
- Loot window now shows full item tooltip on hover (stats, sell price, bind type, etc.)
Camera controller / sitting:
- Any movement key (WASD/QE/Space) pressed while sitting now clears the
sitting flag immediately, matching WoW's sit-to-stand-on-move behaviour
- Added StandUpCallback: when the player stands up via local input the
callback fires setStandState(0) → CMSG_STAND_STATE_CHANGE(STAND) so
the server releases the sit lock and restores normal movement
- Fixes character getting stuck in sit state after accidentally
right-clicking a chair GO in Goldshire Inn (or similar)
Nameplates:
- Use getRenderPositionForGuid() (renderer visual position) as primary
source for nameplate anchor, falling back to entity X/Y/Z only when
no render instance exists yet; keeps health bars in sync with the
rendered model instead of the parallel entity interpolator
Expose SpellbookScreen::renderSpellInfoTooltip() as a public method,
then use it in the action bar slot tooltip. Action bar spell tooltips
now show the same full tooltip as the spellbook: spell school (colored),
mana/rage/energy cost, cast time, range, cooldown, and description.
Falls back to a plain spell name if DBC data is not yet loaded.
Hearthstone location note is appended after the rich body.
Cooldown text moved inside each branch for consistent styling.
Load SchoolMask (TBC/WotLK bitmask) or SchoolEnum (Classic/Turtle 0-6
enum, converted to mask via 1<<N) from Spell.dbc into SpellInfo.
renderSpellTooltip now shows the spell school name (Holy/Fire/Nature/
Frost/Shadow/Arcane) in the appropriate school color between the spell
rank and resource cost. Physical school is suppressed as it is the
implied default. Multi-school spells display both names separated by /.
WotLK DBC fallback path uses field 225 for SchoolMask.
Parse and store reward items (choice and fixed) from SMSG_QUESTGIVER_QUEST_DETAILS
in both WotLK (QuestDetailsParser) and TBC/Classic (TbcPacketParsers) parsers.
Show item icons, names, and counts in the quest acceptance dialog alongside XP/money.
Move QuestRewardItem before QuestDetailsData in header to fix forward-reference.
Items that begin a quest (like quest starter drop items) now show
"Begins a Quest" in the tooltip.
All three expansion parsers (WotLK/TBC/Classic) now read the
PageText/LanguageID/PageMaterial/StartQuest fields after Description.
startQuestId is propagated through all 5 inventory rebuild paths and
stored in ItemDef.