Commit graph

1936 commits

Author SHA1 Message Date
Kelsi
01f4ef5e79 fix: clear lastInteractedGoGuid_ for non-lootable GO interactions
Mailboxes, doors, buttons, and other non-lootable GOs set shouldSendLoot=false
so no CMSG_LOOT is dispatched — but lastInteractedGoGuid_ was still set.
Without SMSG_LOOT_RESPONSE to clear it, a subsequent timed cast completion
(e.g. player buffs at the mailbox) would fire a spurious CMSG_LOOT for the
mailbox GUID.
2026-03-13 05:06:00 -07:00
Kelsi
6878f120e9 fix: clear lastInteractedGoGuid_ in handleCastFailed path
SMSG_CAST_FAILED is a direct rejection (e.g. insufficient range, no mana)
before the cast starts.  Missing this path meant a stale gather-node guid
could survive into the next timed cast if SMSG_CAST_FAILED fired instead
of SMSG_SPELL_FAILURE.
2026-03-13 05:03:50 -07:00
Kelsi
7a4347dbac fix: clear lastInteractedGoGuid_ on cast failure, cancel, and world reset
If a gather cast was interrupted by SMSG_SPELL_FAILURE (e.g. player took
damage during mining), lastInteractedGoGuid_ was left set.  A subsequent
timed cast completion would then fire CMSG_LOOT for the stale node even
though the gather never completed.

Clear lastInteractedGoGuid_ in all cast-termination paths:
- SMSG_SPELL_FAILURE (cast interrupted by server)
- SMSG_CAST_RESULT non-zero (cast rejected before it started)
- cancelCast() (player or system cancelled the cast)
- World reset / logout block (state-clear boundary)
2026-03-13 05:02:58 -07:00
Kelsi
cc2b413e22 fix: guard gather-node CMSG_LOOT dispatch against instant casts and proc spells
handleSpellGo fired lootTarget(lastInteractedGoGuid_) on ANY player spell
completion, including instant casts and proc/triggered spells that arrive
while the gather cast is still in flight.  Save the casting flag before
clearing it and only dispatch CMSG_LOOT when wasInTimedCast is true — this
ensures only the gather cast completion triggers the post-gather loot send,
not unrelated instant spells that also produce SMSG_SPELL_GO.
2026-03-13 04:59:05 -07:00
Kelsi
2c6902d27d fix: mining nodes no longer report invalid target and now open loot after gather
Two bugs fixed:
1. Retry logic (for Classic) re-sent CMSG_GAMEOBJ_USE at 0.15s while the
   gather cast was in-flight, causing SPELL_FAILED_BAD_TARGETS. Now clears
   pendingGameObjectLootRetries_ as soon as SMSG_SPELL_START shows the player
   started a cast (gather accepted).

2. CMSG_LOOT was sent immediately before the gather cast completed, then
   never sent again — so the loot window never opened. Now tracks the last
   interacted GO and sends CMSG_LOOT in handleSpellGo once the gather spell
   completes, matching how the real client behaves.
2026-03-13 04:37:36 -07:00
Kelsi
4272491d56 feat: send CMSG_SET_ACTION_BUTTON to server when action bar slot changes
Action bar changes (dragging spells/items) were only saved locally.
Now notifies the server via CMSG_SET_ACTION_BUTTON so the layout
persists across relogs. Supports Classic (5-byte) and TBC/WotLK
(packed uint32) wire formats.
2026-03-13 04:25:05 -07:00
Kelsi
8f08d75748 fix: cache player death position so corpse reclaim works in Classic
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Classic 1.12 does not send SMSG_DEATH_RELEASE_LOC, leaving corpseMapId_=0
and preventing the 'Resurrect from Corpse' button from appearing.

- When health reaches 0 via VALUES update, immediately cache movementInfo
  as corpse position (canonical->server axis swap applied correctly)
- Do the same on UNIT_DYNFLAG_DEAD set path
- Clear corpseMapId_ when ghost flag is removed (corpse reclaimed)
- Clear corpseMapId_ in same-map spirit-healer resurrection path

The CORPSE object detection (UPDATE_OBJECT) and SMSG_DEATH_RELEASE_LOC
(TBC/WotLK) will still override with exact server coordinates when received.
2026-03-13 04:04:38 -07:00
Kelsi
499638142e feat: make quest tracker movable, resizable, and right-edge-anchored
- Remove NoDecoration flag to allow ImGui drag/resize
- Store questTrackerRightOffset_ instead of absolute X so tracker
  stays pinned to the right edge when the window is resized
- Persist position (right offset + Y) and size in settings.cfg
- Clamp to screen bounds after drag
2026-03-13 04:04:29 -07:00
Kelsi
85767187b1 fix: clear gameObjectDisplayIdWmoCache_ on world transition, add stale-entry guard
gameObjectDisplayIdWmoCache_ was not cleared on world unload/transition,
causing stale WMO model IDs (e.g. 40006, 40003) to be looked up after
the renderer cleared its model list, resulting in "Cannot create instance
of unloaded WMO model" errors on zone re-entry.

Changes:
- Clear gameObjectDisplayIdWmoCache_ alongside other GO caches on world reset
- Add WMORenderer::isModelLoaded() for cache-hit validation
- Inline GO WMO path now verifies cached model is still renderer-resident
  before using it; evicts stale entries and falls back to reload
2026-03-13 03:43:55 -07:00
Kelsi
0487d2eda6 fix: check loadModel return before createInstance for WMO doodads
When the M2 model cache is full (>6000 entries), loadModel() returns
false and the model is never added to the GPU cache. The WMO instance
doodad path was calling createInstanceWithMatrix() unconditionally,
generating hundreds of "Cannot create instance: model X not loaded"
warnings on zone entry. Add the same guard already present in the
terrain doodad path.
2026-03-13 03:41:42 -07:00
Kelsi
863faf9b54 fix: correct talent rank indexing — store 1-indexed, fix prereq and learn checks
SMSG_TALENTS_INFO wire format sends 0-indexed ranks (0=has rank 1). Both
handlers were storing raw 0-indexed values, but handleSpellLearnedServer
correctly stored rank+1 (1-indexed). This caused:
 - getTalentRank() returning 0 for both "not learned" and "has rank 1",
   making pointsInTree always wrong and blocking tier access
 - Prereq check `prereqRank < DBC_prereqRank` always met when not learned
   (0 < 0 = false), incorrectly unlocking talents
 - Click handler sending wrong desiredRank to server

Fixes:
 - Both SMSG_TALENTS_INFO handlers: store rank+1u (1-indexed)
 - talent_screen.cpp prereq check: change < to <= (DBC is 0-indexed,
   storage is 1-indexed; must use > for "met", <= for "not met")
 - talent_screen.cpp click handler: send currentRank directly (1-indexed
   value equals what CMSG_LEARN_TALENT requestedRank expects)
 - Tooltip: display prereqRank+1 so "Requires 1 point" shows correctly
2026-03-13 03:32:45 -07:00
Kelsi
952f36b732 fix: correct minimap player arrow orientation (was 90° off, E/W appeared flipped) 2026-03-13 03:19:05 -07:00
Kelsi
ebd9cf5542 fix: handle MSG_MOVE_SET_*_SPEED opcodes to suppress unhandled opcode warnings 2026-03-13 03:13:29 -07:00
Kelsi
64439673ce fix: show repair button when vendor has NPC_FLAG_REPAIR (0x40) set
Vendors that open directly (without gossip menu) never triggered the
armorer gossip path, so canRepair was always false and the Repair button
was hidden. Now also check the NPC's unit flags for NPC_FLAG_REPAIR when
the vendor list arrives, fixing armorers accessed directly.
2026-03-13 03:06:45 -07:00
Kelsi
8f3f1b21af fix: check vertices before skin load so WotLK (v264) character M2s parse correctly
TBC races like Draenei use version-264 M2 files with no embedded skin;
indices come from a separate .skin file loaded after M2::load().
The premature isValid() check (which requires non-empty indices) always
failed for WotLK-format character models, making Draenei (and Blood Elf)
players invisible.

Fix: only check vertices.empty() right after load(), then validate fully
with isValid() after the skin file is loaded.
2026-03-13 02:58:42 -07:00
Kelsi
27213c1d40 fix: robust SMSG_ATTACKERSTATEUPDATE parsing for WotLK format
Two issues in the WotLK SMSG_ATTACKERSTATEUPDATE parser:

1. subDamageCount could read a school-mask byte when a packed GUID is
   off by one byte, producing values like 32/40/44/48 (shadow/frost/etc
   school masks) as the count. The parser then tried to read 32-48
   sub-damages before hitting EOF. Fix: silently clamp subDamageCount to
   floor(remaining/20) so we only attempt entries that actually fit.

2. After sub-damages, AzerothCore sends victimState(4)+unk1(4)+unk2(4)+
   overkill(4) (16 bytes), not the 8-byte victimState+overkill the
   parser was reading. Fix: consume unk1 and unk2 before reading overkill.
   Also handle the hitInfo-conditional HITINFO_BLOCK/RAGE_GAIN/FAKE_DAMAGE
   fields at the end of the packet.
2026-03-13 02:47:40 -07:00
Kelsi
1cd8e53b2f fix: handle SPLINEFLAG_ANIMATION in UPDATE_OBJECT legacy spline layout
When SPLINEFLAG_ANIMATION (0x00400000) is set, AzerothCore inserts 5 bytes
(uint8 animationType + int32 animTime) between durationModNext and
verticalAccel in the SMSG_UPDATE_OBJECT MoveSpline block. The parser was
not accounting for these bytes, causing verticalAccel, effectStartTime,
and pointCount to be read from the wrong offset.

This produced garbage pointCount values (e.g. 3322451254) triggering the
"Spline pointCount invalid (legacy+compact)" fallback path and breaking
UPDATE_OBJECT parsing for animated-spline entities, causing all subsequent
update blocks in the same packet to be dropped.
2026-03-13 02:38:53 -07:00
Kelsi
61adb4a803 fix: free terrain descriptor sets when unloading mid-finalization tiles
When unloadTile() was called for a tile still in finalizingTiles_
(mid-incremental-finalization), terrain chunks already uploaded to the
GPU (terrainMeshDone=true) were not being cleaned up. The early-return
path correctly removed water and M2/WMO instances but missed calling
terrainRenderer->removeTile(), causing descriptor sets to leak.

After ~20 minutes of play the VkDescriptorPool (MAX_MATERIAL_SETS=16384)
filled up, causing all subsequent terrain material allocations to fail
and the log to flood with "failed to allocate material descriptor set".

Fix: check fit->terrainMeshDone before the early return and call
terrainRenderer->removeTile() to free those descriptor sets.
2026-03-13 02:33:02 -07:00
Kelsi
862d743f87 fix: WMO culling dead-end group fallback and minimap arrow direction
wmo_renderer: when portal BFS starts from a group with no portal refs
(utility/transition group), the rest of the WMO becomes invisible because
BFS only adds the starting group. Fix: if cameraGroup has portalCount==0,
fall back to marking all groups visible (same as camera-outside behavior).

renderer: minimap player-orientation arrow was pointing the wrong direction.
The shader convention is arrowRotation=0→North, positive→clockwise (West),
negative→East. The correct mapping from canonical yaw is arrowRotation =
-canonical_yaw. Fixed both render paths (cameraController and gameHandler)
in both the FXAA and non-FXAA minimap render calls.

World-map W-key: already corrected in prior commit (13c096f); users with
stale ~/.wowee/settings.cfg should rebind via Settings > Controls.
2026-03-13 02:25:06 -07:00
Kelsi
d4bf8c871e fix: clear gameObjectDisplayIdFailedCache_ on world reset and zone change
The failed-model cache introduced in f855327 would persist across map
changes, permanently suppressing models that failed on one map but might
be valid assets on another (or after a client update). Clear it in the
world reset path alongside the existing gameObjectDisplayIdModelCache_
clear, so model loads get a fresh attempt on each zone change.
2026-03-13 01:53:59 -07:00
Kelsi
d58c55ce8d fix: allow ribbon-only M2 models to load and silence transport doodad load errors
Two follow-up fixes for the ribbon emitter implementation and the
transport-doodad stall fix:

1. loadModel() rejected any M2 with no vertices AND no particles, but
   ribbon-only spell-effect models (e.g. weapon trail or aura ribbons)
   have neither. These models were silently invisible even though the
   ribbon rendering pipeline added in 1108aa9 is fully capable of
   rendering them. Extended the guard to also accept models that have
   ribbon emitters, matching the particle-emitter precedent.

2. processPendingTransportDoodads() ignored the bool return of
   loadModel(), calling createInstance() even when the model was
   rejected, generating spurious "Cannot create instance: model X not
   loaded" warnings for every failed doodad path. Check the return
   value and continue to the next doodad on failure.
2026-03-13 01:49:22 -07:00
Kelsi
f855327054 fix: eliminate 490ms transport-doodad stall and GPU device-loss crash
Three root causes identified from wowee.log crash at frame 134368:

1. processPendingTransportDoodads() was doing N separate synchronous
   GPU uploads (vkQueueSubmit + vkWaitForFences per texture per doodad).
   With 30+ doodads × multiple textures, this caused the 489ms stall in
   the 'gameobject/transport queues' update stage. Fixed by wrapping the
   entire batch in beginUploadBatch()/endUploadBatch() so all texture
   layout transitions are submitted in a single async command buffer.

2. Game objects whose M2 model has no geometry/particles (empty or
   unsupported format) were retried every frame because loadModel()
   returns false without adding to gameObjectDisplayIdModelCache_.
   Added gameObjectDisplayIdFailedCache_ to permanently skip these
   display IDs after the first failure, stopping the per-frame spam.

3. renderM2Ribbons() only checked ribbonPipeline_ != null, not
   ribbonAdditivePipeline_. If additive pipeline creation failed, any
   ribbon with additive blending would call vkCmdBindPipeline with
   VK_NULL_HANDLE, causing VK_ERROR_DEVICE_LOST on the GPU side.
   Extended the early-return guard to cover both ribbon pipelines.
2026-03-13 01:45:31 -07:00
Kelsi
367b48af6b fix: handle short loot-failure response in LootResponseParser
Servers send a 9-byte packet (guid+lootType) with lootType=LOOT_NONE when
loot is unavailable (locked chest, another player looting, needs a key).
The previous parser required ≥14 bytes (guid+lootType+gold+itemCount) and
logged a spurious WARNING for every such failure response.

Now:
- Accept the 9-byte form; return false so the caller skips opening the
  loot window (correct behaviour for a failure/empty response).
- Log at DEBUG level instead of WARNING for the short form.
- Keep the original WARNING for genuinely malformed packets < 9 bytes.
2026-03-13 01:29:21 -07:00
Kelsi
13c096f3e9 fix: resolve keybinding conflicts for Q, M, and grave keys
- TOGGLE_QUEST_LOG: change default from Q to None — Q conflicts with
  strafe-left in camera_controller; quest log already accessible via
  TOGGLE_QUESTS (L, the standard WoW binding)
- Equipment Set Manager: remove hardcoded SDL_SCANCODE_GRAVE shortcut
  (~` should not be used for this)
- World map M key: remove duplicate SDL_SCANCODE_M self-handler from
  world_map.cpp::render() that was desync-ing with game_screen's
  TOGGLE_WORLD_MAP binding; game_screen now owns open/close, render()
  handles initial zone load and ESC-close signalling via isOpen()
2026-03-13 01:27:30 -07:00
Kelsi
1108aa9ae6 feat: implement M2 ribbon emitter rendering for spell trail effects
Parse M2RibbonEmitter data (WotLK format) from M2 files — bone index,
position, color/alpha/height tracks, edgesPerSecond, edgeLifetime,
gravity. Add CPU-side trail simulation per instance (edge birth at bone
world position, lifetime expiry, gravity droop). New m2_ribbon.vert/frag
shaders render a triangle-strip quad per emitter using the existing
particleTexLayout_ descriptor set. Supports both alpha-blend and additive
pipeline variants based on material blend mode. Fixes invisible spell
trail effects (~5-10%% of spell visuals) that were silently skipped.
2026-03-13 01:17:30 -07:00
Kelsi
022d387d95 fix: correct corpse retrieval coordinate mismatch and detect corpse objects
- canReclaimCorpse() and getCorpseDistance() compared canonical movementInfo
  (x=north=server_y, y=west=server_x) against raw server corpseX_/Y_ causing
  the proximity check to always report wrong distance even when standing on corpse
- Fix: use corpseY_ for canonical north and corpseX_ for canonical west
- Also detect OBJECT_TYPE_CORPSE update blocks owned by the player to set
  corpse coordinates at login-as-ghost (before SMSG_DEATH_RELEASE_LOC arrives)
2026-03-13 00:59:43 -07:00
Kelsi
acf99354b3 feat: add ghost mode grayscale screen effect
- FXAA path: repurpose _pad field as 'desaturate' push constant; when
  ghostMode_ is true, convert final pixel to grayscale with slight cool
  blue tint using luma(0.299,0.587,0.114) mix
- Non-FXAA path: apply a high-opacity gray overlay (rgba 0.5,0.5,0.55,0.82)
  over the scene for a washed-out look
- Both parallel (SEC_POST) and single-threaded render paths covered
- ghostMode_ flag set each frame from gameHandler->isPlayerGhost()
2026-03-13 00:59:36 -07:00
Kelsi
d3159791de fix: rewrite handleTalentsInfo with correct WotLK SMSG_TALENTS_INFO packet format
TalentsInfoParser used a completely wrong byte layout (expected big-endian
counts, wrong field order), causing unspentTalentPoints to always be misread.
This made canLearn always false so clicking talents did nothing.

New format matches the actual WoW 3.3.5a wire format:
  uint8 talentType, uint32 unspentTalents, uint8 groupCount, uint8 activeGroup,
  per-group: uint8 talentCount, [uint32 id + uint8 rank]×N, uint8 glyphCount, [uint16]×M

Matches the proven parsing logic in handleInspectResults.
2026-03-13 00:47:04 -07:00
Kelsi
e4fd4b4e6d feat: parse SMSG_SET_FLAT/PCT_SPELL_MODIFIER and apply talent modifiers to spell tooltips
Implements SMSG_SET_FLAT_SPELL_MODIFIER and SMSG_SET_PCT_SPELL_MODIFIER
(previously consumed silently). Parses per-group (uint8 groupIndex, uint8
SpellModOp, int32 value) tuples sent by the server after login and talent
changes, and stores them in spellFlatMods_/spellPctMods_ maps keyed by
(SpellModOp, groupIndex).

Exposes getSpellFlatMod(op)/getSpellPctMod(op) accessors and a static
applySpellMod() helper. Clears both maps on character login alongside
spellCooldowns. Surfaces talent-modified mana cost and cast time in the
spellbook tooltip via SpellModOp::Cost and SpellModOp::CastingTime lookups.
2026-03-12 23:59:38 -07:00
Kelsi
74d5984ee2 feat: parse arena header in MSG_PVP_LOG_DATA and show arena scoreboard
Previously the arena path in handlePvpLogData consumed the packet and
returned early with no data. Now the two-team header is parsed (rating
change, new rating, team name), followed by the same player list and
winner fields as battlegrounds.

The BgScoreboardData struct gains ArenaTeamScore fields (teamName,
ratingChange, newRating) populated when isArena=true.

The BG scoreboard UI is updated to:
- Use "Arena Score" window title for arenas
- Show each team's name and rating delta at the top
- Identify the winner by team name instead of faction label
2026-03-12 23:46:38 -07:00
Kelsi
de5c122307 feat: parse SMSG_SET_FACTION_ATWAR/VISIBLE and show at-war status in reputation panel
- Parse SMSG_SET_FACTION_ATWAR (uint32 repListId + uint8 set) to track
  per-faction at-war flags in initialFactions_ flags byte
- Parse SMSG_SET_FACTION_VISIBLE (uint32 repListId + uint8 visible) to
  track faction visibility changes from the server
- Add FACTION_FLAG_* constants (VISIBLE, AT_WAR, HIDDEN, etc.) to GameHandler
- Build repListId <-> factionId bidirectional maps when loading Faction.dbc
  (ReputationListID field 1); used to correlate flag packets with standings
- Fix Faction.dbc field layout comment: field 1=ReputationListID, field 23=Name
  (was incorrectly documented as field 22 with no ReputationListID field)
- Add isFactionAtWar(), isFactionVisible(), getFactionIdByRepListId(),
  getRepListIdByFactionId() accessors on GameHandler
- Reputation panel now shows watched faction at top, highlights at-war
  factions in red with "(At War)" label, and marks tracked faction in gold
2026-03-12 23:30:44 -07:00
Kelsi
1d9dc6dcae feat: parse SMSG_RESPOND_INSPECT_ACHIEVEMENTS and request on inspect
When the player inspects another player on WotLK 3.3.5a, also send
CMSG_QUERY_INSPECT_ACHIEVEMENTS so the server responds with
SMSG_RESPOND_INSPECT_ACHIEVEMENTS.  The new handler parses the
achievement-id/date sentinel-terminated block (same layout as
SMSG_ALL_ACHIEVEMENT_DATA but prefixed with a packed guid) and stores
the earned achievement IDs keyed by GUID in
inspectedPlayerAchievements_.  The new public getter
getInspectedPlayerAchievements(guid) exposes this data for the inspect
UI.  The cache is cleared on world entry to prevent stale data.
QueryInspectAchievementsPacket::build() handles the CMSG wire format
(uint64 guid + uint8 unk=0).
2026-03-12 23:23:02 -07:00
Kelsi
0089b3a160 feat: extend SMSG_SPELLLOGEXECUTE to parse power drain, health leech, interrupt cast, and feed pet effects
- Effect 10 (POWER_DRAIN): show PERIODIC_DAMAGE text on victim, ENERGIZE on caster;
  handles Drain Mana, Viper Sting, Fel Drain, etc.
- Effect 11 (HEALTH_LEECH): show SPELL_DAMAGE on victim, HEAL on caster;
  handles Drain Life, Death Coil, etc.
- Effect 24/114 (CREATE_ITEM/CREATE_ITEM2): existing profession crafting feedback
  extended to also cover CREATE_ITEM2 (engineering/enchanting recipes using alt effect)
- Effect 26 (INTERRUPT_CAST): clear the interrupted unit's cast bar from unitCastStates_
  so the cast bar dismisses immediately rather than waiting for the next update packet
- Effect 49 (FEED_PET): show "You feed your pet <item>." message for hunter pet feeding

All effects are expansion-aware: TBC/Classic use full uint64 GUIDs, WotLK uses packed GUIDs.
2026-03-12 23:09:04 -07:00
Kelsi
e029e8649f feat: parse SMSG_SPELLLOGEXECUTE CREATE_ITEM effects for profession crafting feedback
- Implement SMSG_SPELLLOGEXECUTE handler with expansion-aware caster GUID reading
  (packed_guid for WotLK/Classic, full uint64 for TBC)
- Parse effect type 24 (SPELL_EFFECT_CREATE_ITEM): show "You create <item> using
  <spell>." in chat when the player uses a profession or any create-item spell
- Look up item name via ensureItemInfo/getItemInfo and spell name via spellNameCache_
- Fall back to "You create: <item>." when the spell name is not cached
- Safely consume unknown effect types by stopping parse at first unrecognized effect
  to avoid packet misalignment on variable-length sub-records
- Adds visible crafting feedback complementary to SMSG_ITEM_PUSH_RESULT (which shows
  "Received:" for looted/obtained items) with a profession-specific "create" message
2026-03-12 22:53:33 -07:00
Kelsi
d52c49c9fa fix: FXAA sharpening and MSAA exclusion
- Post-FXAA unsharp mask: when FSR2 is active alongside FXAA, forward
  the FSR2 sharpness value (0–2) to the FXAA fragment shader via a new
  vec4 push constant. A contrast-adaptive sharpening step (unsharp mask
  scaled to 0–0.3) is applied after FXAA blending, recovering the
  crispness that FXAA's sub-pixel blend removes.  At sharpness=2.0 the
  output matches RCAS quality; at sharpness=0 the step is a no-op.

- MSAA guard: setFXAAEnabled() refuses to activate FXAA when hardware
  MSAA is in use. FXAA's role is to supplement FSR temporal AA, not to
  stack on top of MSAA which already resolves jaggies during the scene
  render pass.
2026-03-12 22:38:37 -07:00
Kelsi
b832940509 fix: separate SMSG_QUEST_POI_QUERY_RESPONSE from consume-only stubs; add SMSG_SERVERTIME, SMSG_KICK_REASON, SMSG_GROUPACTION_THROTTLED, SMSG_GMRESPONSE_RECEIVED handlers
- Fix bug where SMSG_REDIRECT_CLIENT, SMSG_PVP_QUEUE_STATS, SMSG_PLAYER_SKINNED, etc.
  were incorrectly falling through to handleQuestPoiQueryResponse instead of being
  silently consumed; add separate setReadPos break for those opcodes
- Implement SMSG_SERVERTIME: sync gameTime_ from server's unix timestamp
- Implement SMSG_KICK_REASON: show player a chat message with reason for group removal
- Implement SMSG_GROUPACTION_THROTTLED: notify player of rate-limit with wait time
- Implement SMSG_GMRESPONSE_RECEIVED: display GM ticket response in chat and UI error
- Implement SMSG_GMRESPONSE_STATUS_UPDATE: show ticket status changes in chat
- Silence voice chat, dance, commentator, and debug/cheat opcodes with explicit consume
  cases rather than falling to the unhandled-opcode warning log
2026-03-12 22:35:37 -07:00
Kelsi
c5a6979d69 feat: handle SMSG_BATTLEFIELD_MGR_* and SMSG_CALENDAR_* opcodes
Implements WotLK Outdoor Battlefield Manager (Wintergrasp/Tol Barad):
- Parse SMSG_BATTLEFIELD_MGR_ENTRY_INVITE, ENTERED, QUEUE_INVITE,
  QUEUE_REQUEST_RESPONSE, EJECT_PENDING, EJECTED, STATE_CHANGE
- Store bfMgrInvitePending_/bfMgrActive_/bfMgrZoneId_ state
- Send CMSG_BATTLEFIELD_MGR_ENTRY_INVITE_RESPONSE via acceptBfMgrInvite() /
  declineBfMgrInvite() accessors
- Add renderBfMgrInvitePopup() UI dialog with Enter/Decline buttons;
  recognises Wintergrasp (zone 4197) and Tol Barad (zone 5095) by name

Implements WotLK Calendar notifications:
- SMSG_CALENDAR_SEND_NUM_PENDING: track pending invite count
- SMSG_CALENDAR_COMMAND_RESULT: map 15 error codes to friendly messages
- SMSG_CALENDAR_EVENT_INVITE_ALERT: notify player of new event invite with title
- SMSG_CALENDAR_EVENT_STATUS: show per-event RSVP status changes (9 statuses)
- SMSG_CALENDAR_RAID_LOCKOUT_ADDED/REMOVED: log raid lockout calendar entries
- Remaining SMSG_CALENDAR_* packets safely consumed
- requestCalendar() sends CMSG_CALENDAR_GET_CALENDAR + GET_NUM_PENDING
2026-03-12 22:25:46 -07:00
Kelsi
dd38026b23 feat: parse SMSG_GMTICKET_GETTICKET/SYSTEMSTATUS and SMSG_SPELLINSTAKILLLOG
Previously SMSG_GMTICKET_GETTICKET and SMSG_GMTICKET_SYSTEMSTATUS were
silently consumed. Now both are fully parsed:
- SMSG_GMTICKET_GETTICKET decodes all four status codes (no ticket,
  open ticket, closed, suspended), extracts ticket text, age and
  server-estimated wait time, and stores them on GameHandler.
- SMSG_GMTICKET_SYSTEMSTATUS shows a chat message when GM support
  goes offline/online.
- Added requestGmTicket() (sends CMSG_GMTICKET_GETTICKET) called
  automatically when the GM Ticket UI window is opened, so the player
  sees their existing open ticket text and wait time on first open.
- GM Ticket UI window now shows current-ticket status bar, estimated
  wait time, and hides the Delete button when no ticket is active.

Also implements SMSG_SPELLINSTAKILLLOG (previously silently consumed):
parses caster/victim/spellId for all expansions and emits combat text
when the local player is involved in an instant-kill spell event (e.g.
Execute, Obliterate).
2026-03-12 22:14:46 -07:00
Kelsi
9b60108fa6 feat: handle SMSG_MEETINGSTONE, LFG timeout, SMSG_WHOIS, and SMSG_MIRRORIMAGE_DATA
Add handlers for 14 previously-unhandled server opcodes:

LFG error/timeout states (WotLK Dungeon Finder):
- SMSG_LFG_TIMEDOUT: invite timed out, shows message and re-opens LFG UI
- SMSG_LFG_OTHER_TIMEDOUT: another player's response timed out
- SMSG_LFG_AUTOJOIN_FAILED: auto-join failed with reason code
- SMSG_LFG_AUTOJOIN_FAILED_NO_PLAYER: no players available for auto-join
- SMSG_LFG_LEADER_IS_LFM: party leader is in LFM mode

Meeting Stone (Classic/TBC era group-finding feature):
- SMSG_MEETINGSTONE_SETQUEUE: shows zone and level range in chat
- SMSG_MEETINGSTONE_COMPLETE: group ready notification
- SMSG_MEETINGSTONE_IN_PROGRESS: search ongoing notification
- SMSG_MEETINGSTONE_MEMBER_ADDED: player name resolved and shown in chat
- SMSG_MEETINGSTONE_JOINFAILED: localized error message (4 reason codes)
- SMSG_MEETINGSTONE_LEAVE: queue departure notification

Other:
- SMSG_WHOIS: displays GM /whois result line-by-line in system chat
- SMSG_MIRRORIMAGE_DATA: parses WotLK mirror image unit display ID and
  applies it to the entity so mirror images render with correct appearance
2026-03-12 22:07:03 -07:00
Kelsi
ebaf95cc42 fix: remove Y-flip counter-hacks in FSR shaders; invert mouse by default; FSR1 disables MSAA
FSR EASU and FSR2 sharpen fragment shaders had a manual Y-flip to undo
the now-removed postprocess.vert flip.  Strip those since the vertex
shader no longer flips, making all postprocess paths consistent.

Also flip the default mouse Y-axis to match user expectation (mouse
down = look up / flight-sim style) and make FSR1 disable MSAA on
enable, matching FSR2 behaviour (FSR provides its own spatial AA).
2026-03-12 21:59:41 -07:00
Kelsi
f8f57411f2 feat: implement SMSG_BATTLEFIELD_LIST handler
Parse the battleground availability list sent by the server when the
player opens the BG finder.  Handles all three expansion wire formats:
- Classic: bgTypeId + isRegistered + count + instanceIds
- TBC:     adds isHoliday byte
- WotLK:   adds minLevel/maxLevel for bracket display

Stores results in availableBgs_ (public via getAvailableBgs()) so the
UI can show available battlegrounds and running instance counts without
an additional server round-trip.
2026-03-12 21:54:48 -07:00
Kelsi
793c2b5611 fix: remove incorrect Y-flip in postprocess vertex shader
postprocess.vert.glsl had `TexCoord.y = 1.0 - TexCoord.y` which
inverted the vertical sampling of the scene texture.  Vulkan textures
use v=0 at the top, matching framebuffer row 0, so no flip is needed.
The camera already flips the projection matrix (mat[1][1] *= -1) so
the scene is rendered correctly oriented; the extra inversion in the
postprocess pass flipped FXAA output upside down.

Fixes: FXAA shows camera upside down
Also fixes: FSR1 upscale and FSR3 sharpen passes (same vertex shader)
2026-03-12 21:52:00 -07:00
Kelsi
4c1bc842bc fix: normalize TBC SMSG_INIT_EXTRA_AURA_INFO_OBSOLETE harmful bit to WotLK debuff convention
TBC aura packets (SMSG_INIT_EXTRA_AURA_INFO_OBSOLETE / SMSG_SET_EXTRA_AURA_INFO_OBSOLETE)
use flag bit 0x02 for harmful (debuff) auras, same as Classic 1.12. The UI checks bit 0x80
for debuff display, following the WotLK SMSG_AURA_UPDATE convention. Without normalization,
all TBC debuffs were displayed in the buff bar instead of the debuff bar.

Normalize using (flags & 0x02) ? 0x80 : 0, matching the fix applied to Classic in 9b09278.
2026-03-12 21:39:22 -07:00
Kelsi
9b092782c9 fix: normalize Classic UNIT_FIELD_AURAFLAGS harmful bit to WotLK debuff convention
The buff/debuff bar uses 0x80 (WotLK convention) to identify debuffs.
Classic UNIT_FIELD_AURAFLAGS uses 0x02 for harmful auras instead.
Map Classic 0x02 → 0x80 during aura rebuild so the UI correctly
separates buffs from debuffs for Classic players.
2026-03-12 21:34:16 -07:00
Kelsi
18d0e6a252 feat: use UNIT_FIELD_AURAFLAGS to correctly classify Classic buffs vs debuffs
Classic WoW stores aura flags in UNIT_FIELD_AURAFLAGS (12 uint32 fields
packed 4 bytes per uint32, one byte per aura slot). Flag bit 0x02 = harmful
(debuff), 0x04 = helpful (buff).

- Add UNIT_FIELD_AURAFLAGS to update_field_table.hpp (Classic wire index 98)
- Add wire index 98 to Classic and Turtle WoW JSON update field tables
- Both Classic aura rebuild paths (CREATE_OBJECT and VALUES) now read the
  flag byte for each aura slot to populate AuraSlot.flags, enabling the
  buff/debuff bar to correctly separate buffs from debuffs on Classic
2026-03-12 21:33:19 -07:00
Kelsi
fb8c251a82 feat: implement SMSG_ACHIEVEMENT_DELETED, SMSG_CRITERIA_DELETED, SMSG_FORCED_DEATH_UPDATE
- SMSG_ACHIEVEMENT_DELETED: removes achievement from earnedAchievements_ and
  achievementDates_ so the achievements UI stays accurate after revocation
- SMSG_CRITERIA_DELETED: removes criteria from criteriaProgress_ tracking
- SMSG_FORCED_DEATH_UPDATE: sets playerDead_ when server force-kills the
  player (GM command, scripted events) instead of silently consuming
2026-03-12 21:28:24 -07:00
Kelsi
758ca76bd3 feat: parse MSG_INSPECT_ARENA_TEAMS and display in inspect window
Implements MSG_INSPECT_ARENA_TEAMS (WotLK): reads the inspected player's
arena team data (2v2/3v3/5v5 bracket, team name, personal rating,
week/season W-L) and stores it in InspectResult.arenaTeams.

The inspect window now shows an "Arena Teams" section below the gear list
when arena team data is available, displaying bracket, team name, rating,
and win/loss record.

Also implement SMSG_COMPLAIN_RESULT with user-visible feedback for
report-player results.
2026-03-12 21:27:02 -07:00
Kelsi
a1edddd1f0 feat: open dungeon finder UI when server sends SMSG_OPEN_LFG_DUNGEON_FINDER
Previously SMSG_OPEN_LFG_DUNGEON_FINDER was consumed silently with no UI
response. Now it fires an OpenLfgCallback wired to openDungeonFinder() on
the GameScreen, so the dungeon finder window opens as the server requests.
2026-03-12 21:24:42 -07:00
Kelsi
e68ffbc711 feat: populate Classic playerAuras from UNIT_FIELD_AURAS update fields
Classic WoW (1.12) does not use SMSG_AURA_UPDATE like WotLK or TBC.
Instead, active aura spell IDs are sent via 48 consecutive UNIT_FIELD_AURAS
slots in SMSG_UPDATE_OBJECT CREATE_OBJECT and VALUES blocks.

Previously these fields were only used for mount spell ID detection.
Now on CREATE_OBJECT and VALUES updates for the player entity (Classic
only), any changed UNIT_FIELD_AURAS slot triggers a full rebuild of
playerAuras from the entity's accumulated field state, enabling the
buff/debuff bar to display active auras for Classic players.
2026-03-12 21:19:17 -07:00
Kelsi
470421879a feat: implement SMSG_GROUP_SET_LEADER and BG player join/leave notifications
- SMSG_GROUP_SET_LEADER: parse leader name, update partyData.leaderGuid
  by name lookup, display system message announcing the new leader
- SMSG_BATTLEGROUND_PLAYER_JOINED: parse guid, show named entry message
  when player is in nameCache
- SMSG_BATTLEGROUND_PLAYER_LEFT: parse guid, show named exit message
  when player is in nameCache

Replaces three LOG_INFO/ignore stubs with functional packet handlers.
2026-03-12 21:08:40 -07:00