Commit graph

698 commits

Author SHA1 Message Date
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
Kelsi
27d18b2189 physics: implement player-controlled flying mount physics
When CAN_FLY + FLYING movement flags are both set (flying mounts, Druid
Flight Form), the CameraController now uses 3D pitch-following movement
instead of ground physics:
- Forward/back follows the camera's 3D look direction (ascend when
  looking up, descend when looking down)
- Space = ascend vertically, X (while mounted) = descend
- No gravity, no grounding, no jump coyote time
- Fall-damage checks suppressed (grounded=true)

Also wire up all remaining server movement state flags to CameraController:
- Feather Fall: cap terminal velocity at -2 m/s
- Water Walk: clamp to water surface, skip swim entry
- Flying: 3D movement with no gravity

All states synced each frame from GameHandler via isPlayerFlying(),
isFeatherFalling(), isWaterWalking(), isGravityDisabled().
2026-03-10 13:23:38 -07:00
Kelsi
1853e8aa56 physics: implement Water Walk movement state tracking and surface clamping
SMSG_MOVE_WATER_WALK / SMSG_MOVE_LAND_WALK now correctly set/clear
WATER_WALK (0x00008000) in movementInfo.flags, ensuring the flag is
included in movement ACKs sent to the server.

In CameraController, when waterWalkActive_ is set and the player is
at or above the water surface (within 0.5 units), clamp them to the
water surface and mark as grounded — preventing water entry and allowing
them to walk across the water surface as the spell intends.
2026-03-10 13:18:04 -07:00
Kelsi
0b99cbafb2 physics: implement feather fall and water walk movement flag tracking
Feather Fall (SMSG_MOVE_FEATHER_FALL / SMSG_MOVE_NORMAL_FALL):
- Add FEATHER_FALL = 0x00004000 to MovementFlags enum
- Fix handlers to set/clear the flag instead of passing flag=0
- Cap downward terminal velocity at -2.0 m/s in CameraController when
  feather fall is active (Slow Fall, Parachute, etc.)

All three handlers now correctly propagate server movement state flags
that were previously acknowledged without updating any local state.
2026-03-10 13:14:52 -07:00
Kelsi
701cb94ba6 physics: apply server walk and swim speed overrides to CameraController
serverWalkSpeed_ and serverSwimSpeed_ were stored in GameHandler but
never exposed or synced to the camera controller. The controller used
hardcoded WOW_WALK_SPEED and speed*SWIM_SPEED_FACTOR regardless of
server-sent speed changes.

Add getServerWalkSpeed()/getServerSwimSpeed() accessors, walkSpeedOverride_
and swimSpeedOverride_ fields in CameraController, and sync all three
server speeds each frame. Both swim speed sites (main and camera-collision
path) now use the override when set. This makes Slow debuffs (walk speed),
Swim Form, and Engineering fins actually affect movement speed.
2026-03-10 13:11:50 -07:00
Kelsi
f2337aeaa7 physics: disable gravity when server sends SMSG_MOVE_GRAVITY_DISABLE
SMSG_MOVE_GRAVITY_DISABLE/ENABLE now correctly set/clear the LEVITATING
movement flag instead of passing flag=0. GameHandler::isGravityDisabled()
reads the LEVITATING bit and is synced to CameraController each frame.

When gravity is disabled the physics loop bleeds off downward velocity
and skips gravity accumulation, so Levitate and similar effects actually
float the player rather than letting them fall through the world.
2026-03-10 13:07:34 -07:00
Kelsi
21604461fc physics: block client-side movement when server roots the player
When SMSG_FORCE_MOVE_ROOT sets ROOT in movementInfo.flags, the
camera controller was not aware and continued to accept directional
input. This caused position desync (client moves, server sees player
as rooted).

- Add movementRooted_ flag to CameraController with setter/getter.
- Block nowForward/nowBackward/nowStrafe when movementRooted_ is set.
- Sync isPlayerRooted() from GameHandler to CameraController each
  frame alongside the existing run-speed sync in application.cpp.
- Add GameHandler::isPlayerRooted() convenience accessor.
2026-03-10 13:01:44 -07:00
Kelsi
ea291179dd gameplay: fix talent reset and ignore list population on login
- SMSG_IGNORE_LIST was silently consumed; now parses guid+name pairs to
  populate ignoreCache so /unignore works correctly for pre-existing
  ignores loaded at login.

- MSG_TALENT_WIPE_CONFIRM was discarded without responding; now parses
  the NPC GUID and cost, shows a confirm dialog, and sends the required
  response packet when the player confirms. Without this, talent reset
  via Talent Master NPC was completely broken.
2026-03-10 12:53:05 -07:00
Kelsi
4cf73a6def movement: track fallTime and jump fields in movement packets
Previously movementInfo.fallTime was always 0 and jumpVelocity/jumpSinAngle/
jumpCosAngle/jumpXYSpeed were never populated.  The server reads fallTime
unconditionally from every movement packet and uses it to compute fall damage
and anti-cheat heuristics; the jump fields are required when FALLING is set.

Changes:
- Add isFalling_ / fallStartMs_ to track fall state across packets
- MSG_MOVE_JUMP: set isFalling_=true, record fallStartMs_, populate jump fields
  (jumpVelocity=7.96, direction from facing angle, jumpXYSpeed from server
  run speed or walk speed when WALKING flag is set)
- MSG_MOVE_FALL_LAND: clear all fall/jump fields
- sendMovement: update movementInfo.fallTime = (time - fallStartMs_) each call
  so every heartbeat and position packet carries the correct elapsed fall time
- World entry: reset all fall/jump fields alongside the flag reset
2026-03-10 12:36:56 -07:00
Kelsi
c622fde7be physics: implement knockback simulation from SMSG_MOVE_KNOCK_BACK
Previously the handler ACKed with current position and ignored the
velocity fields entirely (vcos/vsin/hspeed/vspeed were [[maybe_unused]]).
The server expects the client to fly through the air on knockback — without
simulation the player stays in place while the server models them as airborne,
causing position desync and rubberbanding.

Changes:
- CameraController: add applyKnockBack(vcos, vsin, hspeed, vspeed)
  that sets knockbackHorizVel_ and launches verticalVelocity = -vspeed
  (server sends vspeed as negative for upward launches, matching TrinityCore)
- Physics loop: each tick adds knockbackHorizVel_ to targetPos then applies
  exponential drag (KNOCKBACK_HORIZ_DRAG=4.5/s) until velocity < 0.05 u/s
- GameHandler: parse all four fields, add KnockBackCallback, call it for
  the local player so the camera controller receives the impulse
- Application: register the callback — routes server knockback to physics

The existing ACK path is unchanged; the server gets position confirmation
as before while the client now actually simulates the trajectory.
2026-03-10 12:28:11 -07:00
Kelsi
dd3f9e5b9e wmo: enable portal culling by default after AABB transform fix
The AABB transform bug (direct min/max transform was wrong for rotated
WMOs) was fixed in a prior commit. Portal culling now uses the correct
world-space AABB computed from all 8 corners, so frustum intersection
is valid.

The AABB-based test is conservative (no portal plane-side check): a
visible portal can only be incorrectly INCLUDED, never EXCLUDED. This
means no geometry can disappear, and any overdraw is handled by the
z-buffer. Enable by default to get the performance benefit inside WMOs
and dungeons.
2026-03-10 12:11:13 -07:00
Kelsi
8856af6b2d lfg: implement CMSG_LFG_SET_BOOT_VOTE and vote-to-kick UI
CMSG_LFG_SET_BOOT_VOTE was defined in the opcode table but never sent.
- Add GameHandler::lfgSetBootVote(bool) which sends the packet
- Fix handleLfgBootProposalUpdate() to set lfgState_=Boot while the
  vote is in progress and return to InDungeon when it ends
- Add Yes/No vote buttons to the Dungeon Finder window when in Boot state
2026-03-10 12:08:58 -07:00
Kelsi
acbfe99401 anim: also trigger animation update on walk/run transitions for creatures
Extend the locomotion state-change detection to include the WALKING
movement flag. Previously a creature that switched from walking to
running (or vice versa) while staying in the moving state would keep
playing the wrong animation because only the moving/idle transition
was tracked.

Add creatureWasWalking_ alongside creatureWasSwimming_ and
creatureWasFlying_; guard the walking check with isMovingNow to avoid
spurious triggers when the flag flips while the creature is idle.
Clear and erase the new map at world reset and creature/player despawn.
2026-03-10 12:04:59 -07:00
Kelsi
2717018631 anim: fix creature animation not updating on swim/fly state transitions
Previously, the animation update for other entities (creatures, players)
was only triggered when the moving/idle state changed. This meant a
creature landing while still moving would stay in FlyForward instead of
switching to Run, and a flying-idle creature touching down would keep
the FlyIdle animation instead of returning to Stand.

Fix: track creatureWasSwimming_ and creatureWasFlying_ alongside
creatureWasMoving_, and fire the animation update whenever any of the
three locomotion flags change. Clean up the new maps on world reset and
on per-creature despawn.
2026-03-10 12:03:33 -07:00
Kelsi
30a65320fb anim: add flying state tracking and Fly/FlyIdle animation selection for entities
Previously the move-flags callback only tracked SWIMMING and WALKING,
so flying players/mounts always played Run(5) or Stand(0) animations
instead of Fly(61)/FlyIdle(60).

Changes:
- Add creatureFlyingState_ (mirroring creatureSwimmingState_) set by
  the FLYING flag (0x01000000) in unitMoveFlagsCallback_.
- Update animation selection: moving+flying → 61 (Fly/FlyForward),
  idle+flying → 60 (FlyIdle/hover). Flying takes priority over swim
  in the priority chain: fly > swim > walk > run.
- Clear creatureFlyingState_ on world reset.
2026-03-10 11:56:50 -07:00
Kelsi
84558fda69 net: ack SMSG_MOVE_SET/UNSET_CAN_TRANSITION_SWIM_FLY and SMSG_MOVE_SET_COLLISION_HGT
These three server-push opcodes were silently consumed without sending
the required client acks, causing the server to stall waiting for
confirmation before granting the capability.

- SMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY →
  CMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY_ACK (via handleForceMoveFlagChange)
- SMSG_MOVE_UNSET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY →
  same ack opcode (no separate unset ack exists in WotLK 3.3.5a)
- SMSG_MOVE_SET_COLLISION_HGT → CMSG_MOVE_SET_COLLISION_HGT_ACK via new
  handleMoveSetCollisionHeight() which appends the float height after the
  standard movement block (required by server-side ack validation)
2026-03-10 11:40:46 -07:00
Kelsi
863ea742f6 net/game: initialise entity swim/walk state from spawn packet and SPLINE_MOVE opcodes
UpdateBlock now stores moveFlags from the LIVING movement block so the
cold-join problem is fixed: entities already swimming or walking when the
client joins get their animation state correctly initialised from the
SMSG_UPDATE_OBJECT CREATE_OBJECT packet rather than waiting for the next
MSG_MOVE_* heartbeat.

Additionally, SMSG_SPLINE_MOVE_START_SWIM, SMSG_SPLINE_MOVE_STOP_SWIM,
SMSG_SPLINE_MOVE_SET_WALK_MODE, SMSG_SPLINE_MOVE_SET_RUN_MODE, and
SMSG_SPLINE_MOVE_SET_FLYING now fire unitMoveFlagsCallback_ with
synthesised flags so explicit server-driven mode transitions update
animation state immediately without waiting for a heartbeat.
2026-03-10 11:14:58 -07:00
Kelsi
d7ebc5c8c7 game/rendering: drive Walk(4) and swim state from movement flags
Add UnitMoveFlagsCallback fired on every MSG_MOVE_* with the raw
movement flags field. Application.cpp uses it to update swimming
and walking state from any packet, not just explicit START_SWIM/
STOP_SWIM opcodes — fixing cold-join cases where a player is already
swimming when we enter the world.

Per-frame animation sync now selects Walk(4) when the WALKING flag is
set, Run(5) otherwise, and Swim(42)/SwimIdle(41) when swimming.
UnitAnimHintCallback is simplified to jump (38=JumpMid) only.
2026-03-10 10:55:23 -07:00
Kelsi
333ada8eb6 rendering/game: track per-entity swim state for correct water animations
- Add creatureSwimmingState_ map to track which units are swimming
- unitAnimHintCallback with animId=42 (Swim): marks entity as swimming
- unitAnimHintCallback with animId=0 (MSG_MOVE_STOP_SWIM): clears swim state
- Per-frame sync: uses Swim(42)/SwimIdle(41) when swimming, Run(5)/Stand(0) otherwise
  — creatures/players now show SwimIdle when standing still in water
- Clear creatureSwimmingState_ on creature/player despawn and world reset
2026-03-10 10:36:45 -07:00
Kelsi
14c2bc97b1 rendering/game: fix other-player movement animations and add jump/swim hints
- MSG_MOVE_STOP/STOP_STRAFE/STOP_TURN/STOP_SWIM/FALL_LAND: snap entity to
  stop position (duration=0) and pass durationMs=0 to renderer so the
  Run-animation flash is suppressed; per-frame sync plays Stand on next frame
- MSG_MOVE_JUMP: fire new UnitAnimHintCallback with anim 38 (JumpMid) so
  other players and NPCs visually leave the ground during jumps
- MSG_MOVE_START_SWIM: fire UnitAnimHintCallback with anim 42 (Swim)
- Wire up UnitAnimHintCallback in application.cpp; skips Death (anim 1)
2026-03-10 10:30:50 -07:00
Kelsi
137b25f318 rendering: fix NPC movement animation IDs and remove redundant player anim
The renderer's CharAnimState machine already drives player character
animations (Run=5, Walk=4, Jump, Swim, etc.) — remove the conflicting
camera controller code added in the previous commit.

Fix creature movement animations to use the correct WoW M2 IDs:
4=Walk, 5=Run. Both the per-frame sync loop and the SMSG_MONSTER_MOVE
spline callback now use Run (5) for NPC movement.
2026-03-10 10:19:13 -07:00
Kelsi
4e137c4061 rendering: drive Run/Stand animations from actual movement state
CameraController now transitions the player character to Run (anim 4)
on movement start and back to Stand (anim 0) on stop, guarded by a
prevPlayerMoving_ flag so animation time is not reset every frame.
Death animation (anim 1) is never overridden.

Application creature sync similarly switches creature models to Run (4)
when they move between server positions and Stand (0) when they stop,
with per-guid creatureWasMoving_ tracking to avoid per-frame resets.
2026-03-10 10:06:56 -07:00
Kelsi
c8d9d6b792 rendering/game: make player model semi-transparent in ghost form
Add GhostStateCallback to GameHandler, fired when PLAYER_FLAGS_GHOST
transitions on or off in UPDATE_OBJECT / login detection. Add
setInstanceOpacity() to CharacterRenderer to directly set opacity
without disturbing fade-in state. Application wires the callback to
set opacity 0.5 on ghost entry and 1.0 on resurrect.
2026-03-10 09:57:24 -07:00
Kelsi
366321042f rendering/game: sync camera sit state from server-confirmed stand state
Add CameraController::setSitting() and call it from the StandStateCallback
so the camera blocks movement when the server confirms the player is
sitting or kneeling (stand states 1-6, 8). This prevents the player
from sliding across the ground after sitting.

Death (state 7) deliberately leaves sitting=false so the player can
still respawn/move after death without input being blocked.
2026-03-10 09:51:15 -07:00
Kelsi
9f3c236c48 game/rendering: drive player stand-state animation from SMSG_STANDSTATE_UPDATE
Add StandStateCallback to GameHandler, fired when the server confirms
a stand state change (SMSG_STANDSTATE_UPDATE). Connect in Application
to map the WoW stand state (0-8) to M2 animation IDs on the player
character model:
  - 0 = Stand → anim 0 (Stand)
  - 1-6 = Sit variants → anim 27 (SitGround)
  - 7 = Dead → anim 1 (Death)
  - 8 = Kneel → anim 72 (Kneel)

Sit and Kneel animations are looped so the held-pose frame stays
visible; Death stays on the final frame.
2026-03-10 09:46:46 -07:00
Kelsi
59c50e3beb game/rendering: play SpellCast animation during SMSG_SPELL_START
Add SpellCastAnimCallback to GameHandler, triggered on SMSG_SPELL_START
(start=true) and cleared on SMSG_SPELL_GO / SMSG_SPELL_FAILURE
(start=false) for both the player and other units.

Connect the callback in Application to play animation 3 (SpellCast) on
the player character, NPCs, and other players when they begin a cast.
The cast animation is one-shot (loop=false) so it auto-returns to Stand
when complete via the existing return-to-idle logic.

Also fire stop-cast on spell failure to cancel any stuck cast pose.
2026-03-10 09:42:17 -07:00
Kelsi
b2dccca58c rendering: re-enable WMO camera collision with asymmetric smoothing
Previously disabled because the per-frame raycast caused erratic zoom
snapping at doorway transitions.  Re-enable using an asymmetrically-
smoothed collision limit: pull-in reacts quickly (τ≈60 ms) to prevent
the camera from ever visibly clipping through walls, while recovery is
slow (τ≈400 ms) so walking through a doorway zooms back out gradually
instead of snapping.

Uses wmoRenderer->raycastBoundingBoxes() which already has strict wall
filters (|normal.z|<0.20, surface-alignment check, ±0.9 height band)
to ignore floors, ramps, and arch geometry.
2026-03-10 09:13:31 -07:00
Kelsi
54246345bb game: fix NPCs not spawning on reconnect to same map
On disconnect/reconnect to the same map, entityManager was not cleared
and creatureInstances_ still held old entries from the previous session.
When the server re-sent CREATE_OBJECT for the same GUIDs, the spawn
callback's early-return guard (creatureInstances_.count(guid)) silently
dropped every NPC re-spawn, leaving the world empty.

Fixes:
- disconnect() now calls entityManager.clear() to purge stale entities
- WorldEntryCallback gains a bool isInitialEntry parameter (true on first
  login or reconnect, false on in-world teleport/flight landing)
- Same-map optimization path skipped when isInitialEntry=true, so
  loadOnlineWorldTerrain runs its full cleanup and properly despawns old
  creature/player instances before the server refreshes them
2026-03-10 08:35:36 -07:00
Kelsi
0a157d3255 game: add area name cache from WorldMapArea.dbc for /who zone display and exploration messages
- Load WorldMapArea.dbc lazily on first use to build areaId→name lookup
- /who results now show [Zone Name] alongside level: 'Name - Level 70 [Stormwind City]'
- SMSG_EXPLORATION_EXPERIENCE now shows 'Discovered Elwynn Forest! Gained X experience.'
  instead of generic 'Discovered new area!' message when the zone name is available
- Cache is populated once per session and shared across both callsites
2026-03-10 08:06:21 -07:00
Kelsi
03c4d59592 game: fix talent state not resetting across login sessions
Replace static-local firstSpecReceived with talentsInitialized_ member
variable, reset in handleLoginVerifyWorld alongside other per-session
state. Also clear learnedTalents_, unspentTalentPoints_, and
activeTalentSpec_ at world entry so reconnects and character switches
start from a clean talent state instead of carrying over stale data.
2026-03-10 07:41:27 -07:00
Kelsi
2a9d26e1ea game,ui: add rest state tracking and rested XP bar overlay
- Track PLAYER_REST_STATE_EXPERIENCE update field for all expansions
  (WotLK=636, Classic=718, TBC=928, Turtle=718)
- Set isResting_ flag from SMSG_SET_REST_START packet
- XP bar shows rested bonus as a lighter purple overlay extending
  beyond the current fill to (currentXp + restedXp) position
- Tooltip text changes to "%u / %u XP  (+%u rested)" when bonus exists
- "zzz" indicator shown at bar right edge while resting
2026-03-10 07:35:30 -07:00
Kelsi
0ea8e55ad4 ui,game,pipeline: player nameplates always-on, level-up ring effect, vanilla tile fallback, warden null guard
- Nameplates: player names always rendered regardless of V-key toggle;
  separate cull distance 40u (players/target) vs 20u (NPCs); cyan name
  color for other players; fade alpha scales with cull distance
- Level-up: add expanding golden ring burst (3 staggered waves, 420u
  max radius) + full-screen flash to renderDingEffect(); M2 LevelUp.m2
  is still attempted as a bonus on top
- Vanilla tile loading: add AssetManager::setBaseFallbackPath() so that
  when the primary manifest is an expansion-specific DBC-only subset
  (e.g. Data/expansions/vanilla/), world terrain files fall back to
  the base Data/ extraction; wired in Application::initialize()
- Warden: map a null guard page at address 0x0 in the Unicorn emulator
  so NULL-pointer reads in the module don't crash with UC_ERR_MAP;
  execution continues past the NULL read for better diagnostics
2026-03-10 07:25:04 -07:00
Kelsi
3cdaf78369 game,warden,assets: fix unknown player names, warden heap overlap, and Vanilla Item.dbc
- game: clear pendingNameQueries on player out-of-range and DESTROY_OBJECT so
  re-entering players get a fresh name query instead of being silently skipped
- game: add 5s periodic name resync scan that re-queries players with empty names
  and no pending query, recovering from dropped CMSG_NAME_QUERY responses
- warden: fix UC_ERR_MAP by moving HEAP_BASE from 0x200000 to 0x20000000; the old
  heap [0x200000, 0x1200000) overlapped the module at 0x400000, causing Unicorn to
  reject the heap mapping and abort emulator initialisation
- warden: add early overlap check between module and heap regions to catch future
  layout bugs at init time
- assets: add loadDBCOptional() which logs at DEBUG level when a DBC is absent,
  for files that are not distributed on all expansions
- assets: use loadDBCOptional for Item.dbc (absent on Vanilla 1.12 clients) and
  fall back to server-sent itemInfoCache displayInfoId for NPC weapon resolution
2026-03-10 07:00:43 -07:00
Kelsi
dc2aab5e90 perf: limit NPC composite texture processing to 2ms per frame
processAsyncNpcCompositeResults() had no per-frame budget cap, so when
many NPCs finished async skin compositing simultaneously (e.g. right
after world load), all results were finalized in a single frame causing
up to 284ms frame stalls. Apply the same 2ms budget pattern used by
processAsyncCreatureResults. Load screen still processes all pending
composites without the cap (unlimited=true).
2026-03-10 06:47:33 -07:00
Kelsi
ea9c7e68e7 rendering,ui: sync selection circle to renderer instance position
The selection circle was positioned using the entity's game-logic
interpolator (entity->getX/Y/Z), while the actual M2 model is
positioned by CharacterRenderer's independent interpolator (moveInstanceTo).
These two systems can drift apart during movement, causing the circle
to appear under the wrong position relative to the visible model.

Fix: add CharacterRenderer::getInstancePosition / Application::getRenderPositionForGuid
and use the renderer's inst.position for XY (with footZ override for Z)
so the circle always tracks the rendered model exactly. Falls back to
the entity game-logic position when no CharacterRenderer instance exists.
2026-03-10 06:33:44 -07:00
Kelsi
a2dd8ee5b5 game,ui: implement MSG_RAID_TARGET_UPDATE and display raid marks
Parse the full and single-update variants of MSG_RAID_TARGET_UPDATE to
track which guid carries each of the 8 raid icons (Star/Circle/Diamond/
Triangle/Moon/Square/Cross/Skull). Marks are cleared on world transfer.

The target frame now shows the Unicode symbol for the target's raid mark
in its faction color to the left of the name. Nameplates show the same
symbol to the left of the unit name for all nearby marked units.
2026-03-10 06:10:29 -07:00
Kelsi
90b8cccac5 ui,game: add second action bar (Shift+1-12 keybinds, slots 12-23)
Expand action bar from 12 to 24 slots (2 bars × 12). Bar 2 is rendered
above bar 1 and loaded from SMSG_ACTION_BUTTONS slots 12-23. Pressing
Shift+number activates the corresponding bar-2 slot. Drag-and-drop,
cooldown overlays, and tooltips work identically on both bars. Bar 2
fades slightly when all its slots are empty to minimize visual noise.
2026-03-10 06:04:43 -07:00
Kelsi
7cd8e86d3b game,ui: add ContactEntry struct and Friends tab in social frame
Store structured friend data (online status, level, area, class) that
was previously discarded in handleFriendList/handleContactList. New
ContactEntry struct lives in game_handler.hpp; getContacts() exposes it.

UI: the O-key Social window (formerly guild-only) now has a Friends tab.
- Shows online/offline status dot, name, level, and AFK/DND label
- Pressing O when not in a guild opens Social directly on the Friends tab
- The window title changed from "Guild" to "Social" for accuracy
- Non-guild players no longer get a "not in a guild" rejection on O press
2026-03-10 05:46:03 -07:00