Commit graph

735 commits

Author SHA1 Message Date
Kelsi
cda703b0f4 refactor: consolidate duplicate ShadowPush structure definition
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.
2026-03-11 11:32:08 -07:00
Kelsi
3202c1392d refactor: extract shared attachment lookup logic into helper function
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.
2026-03-11 11:15:06 -07:00
Kelsi
1808d98978 feat: implement TOGGLE_MINIMAP and TOGGLE_RAID_FRAMES keybindings
- 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
2026-03-11 09:24:37 -07:00
Kelsi
332c2f6d3f feat: add TOGGLE_BAGS action and integrate inventory screen with keybinding manager
- 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
2026-03-11 09:02:15 -07:00
Kelsi
a3e0d36a72 feat: add World Map visibility toggle with keybinding support
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).
2026-03-11 07:38:08 -07:00
Kelsi
0d9404c704 feat: expand keybinding system with 4 new customizable actions
- 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
2026-03-11 07:19:54 -07:00
Kelsi
f7a79b436e feat: integrate keybinding customization UI into Settings window
- 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
2026-03-11 06:51:48 -07:00
Kelsi
e6741f815a feat: add keybinding manager for customizable action shortcuts
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.
2026-03-11 06:26:57 -07:00
Kelsi
ed48a3c425 fix: replace fragile heuristic in SMSG_INITIAL_SPELLS with explicit Classic format flag
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.
2026-03-11 04:38:30 -07:00
Kelsi
750b270502 fix: use expansion-aware item size in LootResponseParser for Classic/TBC
The previous per-iteration heuristic (remaining >= 22 → 22 bytes, >= 14 → 14 bytes)
incorrectly parsed Classic/TBC multi-item loots: 2+ items × 14 bytes would
trigger the 22-byte WotLK path for the first item, corrupting subsequent items.

Classic 1.12 and TBC 2.4.3 use 14 bytes/item (slot+itemId+count+displayInfo+slotType).
WotLK 3.3.5a uses 22 bytes/item (adds randomSuffix+randomPropertyId).

Add isWotlkFormat bool parameter to LootResponseParser::parse and pass
isActiveExpansion('wotlk') from handleLootResponse.
2026-03-11 04:01:07 -07:00
Kelsi
d6e398d814 fix: add Classic parseCastResult override with result enum +1 shift
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.
2026-03-11 03:53:18 -07:00
Kelsi
e902375763 feat: add ABSORB and RESIST combat text types for spell misses
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.
2026-03-11 03:23:01 -07:00
Kelsi
7c77c4a81e Fix per-frame particle descriptor set leak in M2 renderer
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.
2026-03-11 02:01:23 -07:00
Kelsi
06facc0060 feat: implement trade window UI with item slots and gold offering
Previously trade only showed an accept/decline popup with no way to
actually offer items or gold. This commit adds the complete trade flow:

Packets:
- CMSG_SET_TRADE_ITEM (tradeSlot, bag, bagSlot) — add item to slot
- CMSG_CLEAR_TRADE_ITEM (tradeSlot) — remove item from slot
- CMSG_SET_TRADE_GOLD (uint64 copper) — set gold offered
- CMSG_UNACCEPT_TRADE — unaccept without cancelling
- SMSG_TRADE_STATUS_EXTENDED parser — updates trade slot/gold state

State:
- TradeSlot struct: itemId, displayId, stackCount, bag, bagSlot
- myTradeSlots_/peerTradeSlots_ arrays (6 slots each)
- myTradeGold_/peerTradeGold_ (copper)
- resetTradeState() helper clears all state on cancel/complete/close

UI (renderTradeWindow):
- Two-column layout: my offer | peer offer
- Each column shows 6 item slots with item names
- Double-click own slot to remove; right-click empty slot to open
  backpack picker popup
- Gold input field (copper, Enter to set)
- Accept Trade / Cancel buttons
- Window close button triggers cancel trade
2026-03-11 00:44:07 -07:00
Kelsi
6f5bdb2e91 feat: implement WotLK quest POI query to show objective locations on minimap
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.
2026-03-11 00:18:23 -07:00
Kelsi
73439a4457 feat: restore quest kill counts from update fields using parsed 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.
2026-03-10 23:52:18 -07:00
Kelsi
7e55d21cdd feat: read quest completion state from update fields on login and mid-session
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).
2026-03-10 23:33:38 -07:00
Kelsi
3a7ff71262 fix: use expansion-aware explored zone count to prevent fog-of-war corruption
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.
2026-03-10 23:18:16 -07:00
Kelsi
99de1fa3e5 feat: track UNIT_FIELD_STAT0-4 from server update fields for accurate character stats
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.
2026-03-10 23:08:15 -07:00
Kelsi
d95abfb607 feat: propagate OBJECT_FIELD_SCALE_X through creature and GO spawn pipeline
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
2026-03-10 22:45:47 -07:00
Kelsi
00a939a733 fix: world map exploration fallback when server mask is unavailable
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.
2026-03-10 22:27:00 -07:00
Kelsi
6928b8ddf6 feat: desaturate quest markers for trivial (gray) quests
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.
2026-03-10 22:26:56 -07:00
Kelsi
19eb7a1fb7 fix: animation stutter, resolution crash, memory cap, spell tooltip hints, GO collision
- 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
2026-03-10 22:26:50 -07:00
Kelsi
8f2974b17c feat: add standalone LFG group-found popup (renderLfgProposalPopup)
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().
2026-03-10 21:40:21 -07:00
Kelsi
4a445081d8 feat: latency indicator, BG queue status, and ToT improvements
- 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
2026-03-10 21:19:42 -07:00
Kelsi
8ab83987f1 feat: focus target frame with health/power bars and cast bar
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)
2026-03-10 21:15:24 -07:00
Kelsi
a7a559cdcc feat: battleground invitation popup with countdown timer
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
2026-03-10 21:12:28 -07:00
Kelsi
6275a45ec0 feat: achievement name in toast, parse earned achievements, loot item tooltips
- 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.)
2026-03-10 20:53:21 -07:00
Kelsi
564a286282 fix: stand-up-on-move and nameplate position tracking
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
2026-03-10 19:49:33 -07:00
Kelsi
caf0d18393 feat: show rich spell tooltip on action bar hover
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.
2026-03-10 19:31:46 -07:00
Kelsi
bae3477c94 feat: display spell school in spellbook tooltip
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.
2026-03-10 19:22:48 -07:00
Kelsi
1ff48259cc feat: display quest reward items in quest details acceptance window
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.
2026-03-10 19:05:34 -07:00
Kelsi
62b7622f75 feat: parse and display StartQuest field from item query response
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.
2026-03-10 17:05:04 -07:00
Kelsi
321aaeae54 feat: capture and display all item stat types in tooltips
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
2026-03-10 17:00:24 -07:00
Kelsi
76bd6b409e feat: enhance item tooltips with binding, description, speed, and spell effects
- 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
2026-03-10 16:47:55 -07:00
Kelsi
1793549550 feat: parse and display item level and required level in tooltips
- 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)
2026-03-10 16:26:20 -07:00
Kelsi
0afa41e908 feat: implement item durability tracking and vendor repair
- 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%)
2026-03-10 16:21:09 -07:00
Kelsi
bee4dde9b7 ui: add side action bars, fix resize positioning, and fix player nameplates
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")
2026-03-10 15:56:41 -07:00
Kelsi
ec5e7c66c3 fix: derive rest state from PLAYER_BYTES_2 and add action bar 2 settings
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
2026-03-10 15:45:35 -07:00
Kelsi
1a370fef76 fix: chat prefix, hostile faction display, and game object looting
- 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
2026-03-10 15:32:04 -07:00
Kelsi
942df21c66 ui: resolve chat sender names at render time to fix [Say] prefix
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
2026-03-10 15:18:00 -07:00
Kelsi
60ebb565bb rendering: fix WMO portal culling and chat message format
- 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
2026-03-10 14:59:02 -07:00
Kelsi
920d6ac120 physics: sync camera pitch to movement packets and mount tilt during flight
- 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
2026-03-10 14:46:17 -07:00
Kelsi
132598fc88 physics: send MSG_MOVE_START/STOP_ASCEND and START_DESCEND during flight
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.
2026-03-10 14:32:30 -07:00
Kelsi
a9ddfe70c2 physics: sync server turn rate and fix SPLINE speed handlers
- 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)
2026-03-10 14:18:25 -07:00
Kelsi
e2f65dfc59 physics: add server flight-back speed override to CameraController
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
2026-03-10 14:05:50 -07:00
Kelsi
a33f635490 physics: add server swim-back speed override to CameraController
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
2026-03-10 13:51:47 -07:00
Kelsi
23293d6453 physics: implement HOVER movement flag physics in CameraController
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)
2026-03-10 13:39:23 -07:00
Kelsi
56ec49f837 physics: sync all server movement speeds to CameraController
Previously only run speed was synced. Now all server-driven movement
speeds are forwarded to the camera controller each frame:
- runSpeedOverride_: server run speed (existing)
- walkSpeedOverride_: server walk speed (Ctrl key movement)
- swimSpeedOverride_: swim speed (Swim Form, Engineering fins)
- flightSpeedOverride_: flight speed (epic vs normal flying mounts)
- runBackSpeedOverride_: back-pedal speed

Each uses the server value when non-zero/sane, falling back to the
hardcoded WoW default constant otherwise.
2026-03-10 13:28:53 -07:00
Kelsi
a1ee9827d8 physics: apply server flight speed to flying mount movement
serverFlightSpeed_ (from SMSG_FORCE_FLIGHT_SPEED_CHANGE) was stored but
never synced to CameraController. Add getServerFlightSpeed() accessor,
flightSpeedOverride_ field, and use it in the flying physics path so
normal vs epic flying mounts actually move at their correct speeds.
2026-03-10 13:25:10 -07:00