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.
Previously only the 5 primary stats (Str/Agi/Sta/Int/Spi) were stored,
discarding hit rating, crit, haste, attack power, spell power, resilience,
expertise, armor penetration, MP5, and many others.
Changes:
- Add ItemDef::ExtraStat and ItemQueryResponseData::ExtraStat arrays
- All three expansion parsers (WotLK/TBC/Classic) now capture non-primary
stat type/value pairs into extraStats instead of silently dropping them
- All 5 rebuildOnlineInventory paths propagate extraStats to ItemDef
- Tooltip now renders each extra stat on its own line with a name lookup
covering all common WotLK stat types (hit, crit, haste, AP, SP, etc.)
- Also fix Classic/TBC bag-content and bank-bag paths that were missing
bindType, description propagation from previous commits
- Parse Bonding and Description fields from SMSG_ITEM_QUERY_SINGLE_RESPONSE
(read after the 5 spell slots: bindType uint32, then description cstring)
- Add bindType and description to ItemQueryResponseData and ItemDef
- Propagate bindType and description through all 5 rebuildOnlineInventory paths
- Tooltip now shows: "Binds when picked up/equipped/used/quest item"
- Tooltip now shows weapon damage range ("X - Y Damage") and speed ("Speed 2.60")
on same line, plus DPS in parentheses below
- Tooltip now shows spell effects ("Use: <SpellName>", "Equip: <SpellName>",
"Chance on Hit: ...") using existing getSpellName() lookup
- Tooltip now shows item flavor/lore description in italic-style yellow text
- Add itemLevel/requiredLevel fields to ItemQueryResponseData (parsed
from SMSG_ITEM_QUERY_SINGLE_RESPONSE) and ItemDef
- Propagate through all 5 rebuildOnlineInventory() paths
- Show "Item Level N" and "Requires Level N" in item tooltip in
standard WoW order (below item name, above required level/stats)
- Add ITEM_FIELD_DURABILITY (60) and ITEM_FIELD_MAXDURABILITY (61) to
update_field_table.hpp enum and wotlk/update_fields.json
- Add curDurability/maxDurability to OnlineItemInfo and ItemDef structs
- Parse durability fields in OBJECT_CREATE and OBJECT_VALUES handlers;
preserve existing values on partial updates (fixes stale durability
being reset to 0 on stack-count-only updates)
- Propagate durability to ItemDef in all 5 rebuildOnlineInventory() paths
- Implement GameHandler::repairItem() and repairAll() via CMSG_REPAIR_ITEM
(itemGuid=0 repairs all equipped items per WotLK protocol)
- Add canRepair flag to ListInventoryData; set it when player selects
GOSSIP_OPTION_ARMORER in gossip window
- Show "Repair All" button in vendor window header when canRepair=true
- Display color-coded durability in item tooltip (green >50%, yellow
>25%, red <=25%)
Action bars:
- Expand from 2 bars (24 slots) to 4 bars (48 slots)
- Bar 2: right-edge vertical bar (slots 24-35), off by default
- Bar 3: left-edge vertical bar (slots 36-47), off by default
- New "Interface" settings tab with toggles and offset sliders for all bars
- XP bar Y position now tracks bar 2 visibility and vertical offset
HUD resize fix:
- All HUD elements (action bars, bag bar, XP bar, cast bar, mirror timers)
now use ImGui::GetIO().DisplaySize instead of window->getWidth/Height()
- DisplaySize is always in sync with the current frame — eliminates the
one-frame lag that caused bars to misalign after window resize
Player nameplates:
- Show player name only on nameplate (no level number clutter)
- Fall back to "Player (level)" while name query is pending
- NPC nameplates unchanged (still show "level Name")
XP bar rest state:
- isResting_ now set from PLAYER_BYTES_2 byte 3 bit 0 (rest state flag)
on both CREATE and VALUES update object handlers
- playerRestedXp_ was missing from VALUES handler — now tracked there too
- Eliminates dependency on SMSG_SET_REST_START (wrong in WotLK opcodes.json)
Interface settings:
- New "Interface" tab in Settings window
- "Show Second Action Bar" toggle (default: on)
- Horizontal/vertical position offset sliders for bar 2
- Settings persisted to/from save file
- Add BG_SYSTEM_NEUTRAL/ALLIANCE/HORDE chat types (0x52-0x54) and reclassify
them as SYSTEM in the parser — prevents bogus [Say] prefix on arena/BG
system messages
- Remove fallback [TypeName] bracket for sender-less SAY/YELL/WHISPER messages;
only group-channel types (Party/Guild/Raid/BG) show brackets without a sender
- Remove factionTemplate != 0 guard — units with FT=0 now get setHostile() like
any other unit (defaulting to hostile from the map default), fixing NPCs that
appeared friendly due to unset faction template
- Enable CMSG_LOOT for WotLK type=3 (chest) game objects in addition to
CMSG_GAMEOBJ_USE — fixes Milly's Harvest and other quest gather objects on
AzerothCore WotLK servers
When SMSG_MESSAGECHAT arrives before the entity has spawned or its
name is cached, senderName is empty and messages fell through to the
generic '[Say] message' branch. Fix:
- GameHandler::lookupName(guid): checks playerNameCache then entity
manager (Unit subclass cast) at call time
- Chat display: resolves senderName via lookupName() at render time
so messages show "Name says: msg" even if the name was unavailable
when the packet was first parsed
- wmo_renderer: pass character position (not camera position) to portal
visibility traversal — the 3rd-person camera can orbit outside a WMO
while the character is inside, causing interior groups to cull; render()
now accepts optional viewerPos that defaults to camPos for compatibility
- renderer: pass &characterPosition to wmoRenderer->render() at both
main and single-threaded call sites; reflection pass keeps camPos
- renderer: apply mount pitch/roll to rider during all flight, not just
taxiFlight_ (fixes zero rider tilt during player-controlled flying)
- game_screen: format SAY/YELL/WHISPER/EMOTE using WoW-style "Name says:"
instead of "[SAY] Name:" bracket prefix
- Add setMovementPitch() and isSwimming() to GameHandler
- In the per-frame sync block, derive the pitch angle from the camera's
forward vector (asin of the Z component) and write it to movementInfo.pitch
whenever FLYING or SWIMMING flags are set — the server includes the pitch
field in those packets, so sending 0 made other players see the character
flying perfectly flat even when the camera was pitched
- Also tilt the mount model (setMountPitchRoll) to match the flight direction
during player-controlled flight, and reset to 0 when not flying
When flyingActive_, detect Space/X key transitions and emit proper flight
vertical movement opcodes so the server (and other players) see the
correct ascending/descending animation state:
- MSG_MOVE_START_ASCEND (Space pressed while flying) → sets ASCENDING flag
- MSG_MOVE_STOP_ASCEND (Space released while flying) → clears ASCENDING flag
- MSG_MOVE_START_DESCEND (X pressed while flying) → clears ASCENDING flag
- MSG_MOVE_STOP_ASCEND (X released while flying) → clears vertical state
Track wasAscending_/wasDescending_ member state to detect transitions.
Also clear lingering vertical state when leaving flight mode.
- Add getServerTurnRate() accessor and turnRateOverride_ field so the
keyboard turn speed respects SMSG_FORCE_TURN_RATE_CHANGE from server
- Convert rad/s → deg/s before applying to camera yaw logic
- Fix SMSG_SPLINE_SET_RUN_BACK/SWIM/FLIGHT/FLIGHT_BACK/SWIM_BACK/WALK/
TURN_RATE handlers: all previously discarded the value; now update the
corresponding serverXxxSpeed_ / serverTurnRate_ field when GUID matches
playerGuid (camera controller syncs these every frame)
SMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE was already ACK'd and stored in
serverFlightBackSpeed_, but the value was never accessible or synced
to the CameraController. Backward flight movement always used forward
flight speed (flightSpeedOverride_), making it faster than the server
intended.
- Add getServerFlightBackSpeed() accessor in GameHandler
- Add flightBackSpeedOverride_ field and setter in CameraController
- Apply it in the fly movement block: backward-only flight uses the
back speed; forward or strafing uses the forward speed as WoW does
- Fallback: 50% of forward flight speed when override is unset
- Sync per-frame in application.cpp alongside the other speed overrides
Backward swimming was using 50% of forward swim speed as a hardcoded
fallback. Wire up the server-authoritative swim back speed so Warlock
Dark Pact, buffs, and server-forced speed changes all apply correctly
when swimming backward.
- game_handler.hpp: add getServerSwimBackSpeed() accessor
- camera_controller.hpp: add swimBackSpeedOverride_ field + setter
- camera_controller.cpp: apply swimBackSpeedOverride_ when player
swims backward without forward input; fall back to 50% of swim speed
- application.cpp: sync swim back speed each frame
When the server sets MovementFlags::HOVER (SMSG_MOVE_SET_HOVER), the
player now floats 4 yards above the nearest ground surface instead of
standing on it. Uses the existing floor-snap path with a HOVER_HEIGHT
offset applied to the snap target.
- game_handler.hpp: add isHovering() accessor (reads HOVER flag from
movementInfo.flags, which is already set by handleForceMoveFlagChange)
- camera_controller.hpp: add hoverActive_ field and setHoverActive()
- camera_controller.cpp: apply HOVER_HEIGHT = 4.0f offset at floor snap
- application.cpp: sync hover state each frame alongside other movement
states (gravity, feather fall, water walk, flying)