Commit graph

1158 commits

Author SHA1 Message Date
Kelsi
7b4fdaa277 refactor: name memory/taxi constants, add camera jitter why-comment
- memory_monitor: extract kOneGB and kFallbackRAM constants from 6
  duplicated 1024*1024*1024 expressions; name kFieldPrefixLen for
  /proc/meminfo "MemAvailable:" offset (was bare 13)
- camera: add why-comment on projection matrix jitter — column 2 holds
  NDC x/y offset for TAA/FSR2 sub-pixel sampling
- movement_handler: name kMaxTaxiNodeId (384) with why-comment —
  WotLK TaxiNodes.dbc has 384 entries, bitmask is 12 × uint32
2026-03-30 15:07:55 -07:00
Kelsi
8c7db3e6c8 refactor: name FNV-1a/transport constants, fix dead code, add comments
- vk_context: name FNV-1a hash constants (kFnv1aOffsetBasis/kFnv1aPrime)
  with why-comment on algorithm choice for sampler cache
- transport_manager: collapse redundant if/else that both set
  looping=false into single unconditional assignment, add why-comment
  explaining the time-closed path design
- transport_manager: hoist duplicate kMinFallbackZOffset constants out
  of separate if-blocks, add why-comment on icebreaker Z clamping
- entity: expand velocity smoothing comment — explain 65/35 EMA ratio
  and its tradeoff (jitter suppression vs direction change lag)
2026-03-30 14:48:06 -07:00
Kelsi
683e171fd1 fix: VkTexture move/destroy ownsSampler_ flag, extract finalizeSampler
- Fix move constructor and move assignment: set other.ownsSampler_ to
  false after transfer (was incorrectly set to true, leaving moved-from
  object claiming ownership of a null sampler)
- Fix destroy(): reset ownsSampler_ to false after clearing sampler
  handle (was set to true, inconsistent with null handle state)
- Extract finalizeSampler() from 3 duplicated cache-or-create blocks
  in createSampler() overloads and createShadowSampler() (-24 lines)
- Add SPIR-V alignment why-comment in vk_shader.cpp
2026-03-30 14:24:41 -07:00
Kelsi
28e5cd9281 refactor: replace magic bag slot offset 19 with FIRST_BAG_EQUIP_SLOT
- Add Inventory::FIRST_BAG_EQUIP_SLOT = 19 constant with why-comment
  explaining WoW equip slot layout (bags occupy slots 19-22)
- Replace all 19 occurrences of magic number 19 in bag slot calculations
  across inventory_handler, spell_handler, inventory, and game_handler
- Add UNIT_FIELD_FLAGS / UNIT_FLAG_PVP comment in combat_handler
- Add why-comment on network packet budget constants (prevent server
  data bursts from starving the render loop)
2026-03-30 14:20:39 -07:00
Kelsi
4acebff65c refactor: extract fallback textures, add why-comments, name WMO constant
- character_renderer: extract duplicated fallback texture creation
  (white/transparent/flat-normal) into createFallbackTextures() — was
  copy-pasted between initialize() and clear()
- wmo_renderer: replace magic 8192 with kMaxRetryTracked constant,
  add why-comment explaining the fallback-retry set cap (Dalaran has
  2000+ unique WMO groups)
- quest_handler: add why-comment on reqCount=0 fallback — escort/event
  quests can report kill credit without objective counts in query response
2026-03-30 14:06:30 -07:00
Kelsi
f313eec24e refactor: replace magic slot offset 23 with NUM_EQUIP_SLOTS, simplify channel search
- Replace all 11 occurrences of magic number 23 in backpack slot
  calculations with Inventory::NUM_EQUIP_SLOTS across inventory_handler,
  spell_handler, and inventory.cpp
- Add why-comment to NUM_EQUIP_SLOTS explaining WoW slot layout
  (equipment 0-22, backpack starts at 23 in bag 0xFF)
- Add why-comment on 0x80000000 bit mask in item query response
  (high bit flags negative/missing entry response)
- Replace manual channel membership loops with std::find in
  chat_handler.cpp (YOU_JOINED and PLAYER_ALREADY_MEMBER cases)
- Add why-comment on PLAYER_ALREADY_MEMBER reconnect edge case
2026-03-30 14:01:34 -07:00
Kelsi
a9ce22f315 refactor: extract findOnUseSpellId helper, add warden hash comment
- spell_handler: extract duplicated item on-use spell lookup into
  findOnUseSpellId() — was copy-pasted in useItemBySlot and useItemInBag
- warden_handler: add why-comment explaining the door model HMAC-SHA1
  hash table (wall-hack detection for unmodified 3.3.5a client data)
2026-03-30 13:56:45 -07:00
Kelsi
76d29ad669 fix: address PR #31 and #32 review findings
- Dockerfile: fix LLVM apt repo codename (jammy → noble) for ubuntu:24.04
- build-linux.sh: add missing mkdir -p /wowee-build-src before tar extraction
- Dockerfile: remove dead ENV OSXCROSS_VERSION=1.5 and its unset
- CMakeLists: scope -undefined dynamic_lookup to wowee target only
- GameServices: remove redundant game:: qualifier inside namespace game
- application.cpp: zero out gameServices_ after gameHandler reset in shutdown
2026-03-30 13:40:40 -07:00
Paul
a86efaaa18 [refactor] Break Application::getInstance() from GameHandler
Introduce `GameServices` struct — an explicit dependency bundle that
`Application` populates and passes to `GameHandler` at construction time.
Eliminates all 47 hidden `Application::getInstance()` calls in
`src/game/*.cpp`, completing SOLID-D (dependency-inversion) cleanup.

Changes:
- New `include/game/game_services.hpp` — `struct GameServices` carrying
  pointers to `Renderer`, `AssetManager`, `ExpansionRegistry`, and two
  taxi-mount display IDs
- `GameHandler(GameServices&)` replaces default constructor; exposes
  `services() const` accessor for domain handlers
- `Application` holds `game::GameServices gameServices_`; populates it
  after all subsystems are created, then constructs `GameHandler`
  (fixes latent init-order bug: `GameHandler` was previously created
  before `AssetManager` / `ExpansionRegistry`)
- `game_handler.cpp`: duplicate `isActiveExpansion` / `isClassicLikeExpansion` /
  `isPreWotlk` anonymous-namespace helpers removed; `game_utils.hpp`
  included instead
- All domain handlers (`InventoryHandler`, `SpellHandler`, `MovementHandler`,
  `CombatHandler`, `QuestHandler`, `SocialHandler`, `WardenHandler`) replace
  `Application::getInstance().getXxx()` with `owner_.services().xxx`
2026-03-30 09:17:42 +03:00
Kelsi
bbb560f93c fix: data race on collision query profiling counters
queryTimeMs and queryCallCount on WMORenderer and M2Renderer were plain
mutable doubles/uint32s written by getFloorHeight (dispatched on async
threads from CameraController) and read by the main thread. This is
undefined behavior per C++ — thread sanitizer would flag it. Changed to
std::atomic with relaxed ordering (adequate for diagnostics) and updated
QueryTimer to use atomic fetch_add/compare_exchange.
2026-03-29 21:26:11 -07:00
Kelsi
fa15a3de1f fix: transport elapsed time lost millisecond precision after ~4.5 hours
elapsedTime_ was float (32-bit, ~7 significant digits). At 16384
seconds the float can only represent integers, so elapsedTime_*1000
jumps in 1-second steps — ships and elevators visibly jerk. Changed to
double (53-bit mantissa) which maintains sub-millisecond precision for
~285 million years. Also changed lastServerUpdate to double to match.
2026-03-29 20:14:45 -07:00
Kelsi
f2237c5531 perf: switch 3 spawn queues from vector to deque
pendingPlayerSpawns_, deferredEquipmentQueue_, and
pendingGameObjectSpawns_ are consumed from the front via erase(begin()),
which is O(n) on vector (shifts all elements). With many spawns queued
(entering a city), this made the processing loop O(n²). deque supports
O(1) front erasure. pendingCreatureSpawns_ already used deque.
2026-03-29 19:51:26 -07:00
Kelsi
ed63b029cd fix: getRemainingSize() underflowed when readPos exceeded data size
Both operands are size_t (unsigned), so if readPos > data.size() the
subtraction wrapped to ~0 instead of returning 0. This could happen
via setReadPos() which has no bounds check. Downstream hasRemaining()
was already safe but getRemainingSize() callers (e.g. hasFullPackedGuid)
would see billions of bytes available.
2026-03-29 19:36:41 -07:00
Kelsi
5b9b8b59ba refactor: extract buildItemDef from 4 copy-pasted blocks in rebuildOnlineInventory
The same 25-line block copying ~20 fields from itemInfoCache_ into
ItemDef was duplicated for equipment, backpack, keyring, and bag slots.
Extracted into buildItemDef() so new fields only need adding once.
Net -100 lines.
2026-03-29 19:16:36 -07:00
Kelsi
bf63d8e385 fix: static lastSpellCount shared across SpellHandler instances
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
The spellbook tab dirty check used a function-local static, meaning
switching to a character with the same spell count would skip the
rebuild and return the previous character's tabs. Changed to an
instance member so each SpellHandler tracks its own count.
2026-03-29 19:08:58 -07:00
Kelsi
7462fdd41f refactor: extract buildForceAck from 5 duplicated force-ACK blocks
All five force-ACK handlers (speed, root, flag, collision-height,
knockback) repeated the same ~25-line GUID+counter+movementInfo+coord-
conversion+send sequence. Extracted into buildForceAck() which returns
a ready-to-send packet with the movement payload already written.

This also fixes a transport coordinate conversion bug: the collision-
height handler was the only one that omitted the ONTRANSPORT check,
causing position desync when riding boats/zeppelins. buildForceAck
handles transport coords uniformly for all callers.

Net -80 lines.
2026-03-29 19:08:42 -07:00
Kelsi
2e1f0f15ea fix: auction house refresh failed after browse-all (empty name search)
The auto-refresh after successful bid/buyout was gated on
lastAuctionSearch_.name.length() > 0, so a browse-all search (empty
name) would never refresh. Replaced with a hasAuctionSearch_ flag
that's set on any search regardless of the name filter.
2026-03-29 19:01:06 -07:00
Kelsi
961af04b36 fix: gossip banker sent CMSG_BANKER_ACTIVATE twice; deduplicate quest icons
Icon==6 and text=="GOSSIP_OPTION_BANKER" both sent BANKER_ACTIVATE
independently. Banking NPCs match both, so the packet was sent twice —
some servers toggle the bank window open then closed. Added sentBanker
guard so only one packet is sent.

Also extracts classifyGossipQuests() from two identical 30-line blocks
in handleGossipMessage and handleQuestgiverQuestList. The icon→status
mapping (5/6/10=completable, 3/4=incomplete, 2/7/8=available) is now
in one place with a why-comment explaining these are protocol-defined.
2026-03-29 18:53:30 -07:00
Kelsi
3f37ffcea3 refactor: extract SpellHandler::resetAllState from selectCharacter
selectCharacter had 30+ if(spellHandler_) guards reaching into
SpellHandler internals (knownSpells_, spellCooldowns_, playerAuras_,
etc.) to clear per-character state. Consolidated into resetAllState()
so SpellHandler owns its own reset logic and new state fields don't
require editing GameHandler.
2026-03-29 18:46:42 -07:00
Kelsi
2ae14d5d38 fix: RX silence 15s warning fired ~30 times per window
The 10s silence warning used a one-shot bool guard, but the 15s warning
used a 500ms time window — firing every frame (~30 times at 60fps).
Added rxSilence15sLogged_ guard consistent with the 10s pattern.
2026-03-29 18:46:25 -07:00
Kelsi
6f6571fc7a fix: pet opcodes shared unlearn handler despite incompatible formats
SMSG_PET_GUIDS, SMSG_PET_DISMISS_SOUND, and SMSG_PET_ACTION_SOUND were
registered with the same handler as SMSG_PET_UNLEARN_CONFIRM. Their
different formats (GUID lists, sound IDs with position) were misread as
unlearn cost, potentially triggering a bogus unlearn confirmation dialog.

Also extracts resetWardenState() from 13 lines duplicated verbatim
between connect() and disconnect().
2026-03-29 18:39:38 -07:00
Kelsi
4e0e234ae9 fix: MSG_MOVE_START_DESCEND never set DESCENDING flag
Only ASCENDING was cleared — the DESCENDING flag was never toggled,
so outgoing movement packets during flight descent had incorrect flags.
Also clears DESCENDING on start-ascend and stop-ascend for symmetry.

Replaces static heartbeat log counter with member variable (was shared
across instances and not thread-safe) and demotes to LOG_DEBUG.
2026-03-29 18:28:49 -07:00
Kelsi
fc2526fc18 fix: env damage alias overwrote handler that preserved damage type
SMSG_ENVIRONMENTALDAMAGELOG (alias) registration at line 173 silently
overwrote the canonical SMSG_ENVIRONMENTAL_DAMAGE_LOG handler at line
108. The alias handler discarded envType (fall/lava/drowning), so the
UI couldn't differentiate environmental damage sources. Removed the
dead alias handler and its method; the canonical inline handler with
envType forwarding is now the sole registration.
2026-03-29 18:20:51 -07:00
Kelsi
35b952bc6f fix: SMSG_IGNORE_LIST read phantom string field after each GUID
The packet only contains uint8 count + count×uint64 GUIDs, but the
handler called readString() after each GUID. This consumed raw bytes of
subsequent GUIDs as a string, corrupting all entries after the first.
Now stores GUIDs in ignoreListGuids_ and resolves names asynchronously
via SMSG_NAME_QUERY_RESPONSE, matching the friends list pattern.

Also fixes unsafe static_pointer_cast in ready check (no type guard)
and removes redundant packetHasRemaining wrapper (duplicates Packet API).
2026-03-29 18:11:29 -07:00
Kelsi
298974ebc2 refactor: extract markPlayerDead to deduplicate death/corpse caching
Both the health==0 and dynFlags UNIT_DYNFLAG_DEAD paths duplicated the
same corpse-position caching and death-state logic with a subtle
asymmetry (only health path called stopAutoAttack). Extracted into
markPlayerDead() so coordinate swapping and state changes happen in one
place. stopAutoAttack remains at the health==0 call site since the
dynFlags path doesn't need it.
2026-03-29 17:59:44 -07:00
Kelsi
dc500fede9 refactor: consolidate buildItemLink into game_utils.hpp
Three identical copies (game_handler.cpp, spell_handler.cpp,
quest_handler.cpp) plus two forward declarations (inventory_handler.cpp,
social_handler.cpp) replaced with a single inline definition in
game_utils.hpp. All affected files already include this header, so
quality color table changes now propagate from one source of truth.
2026-03-29 17:57:05 -07:00
Kelsi
020e016853 fix: quest reward items stuck as 'Item #ID' due to stale pending queries
Two fixes for item name resolution:

1. Clear entry from pendingItemQueries_ even when response parsing fails.
   Previously a malformed response left the entry stuck in pending forever,
   blocking all retries so the UI permanently showed "Item 12345".

2. Add 5-second periodic cleanup of pendingItemQueries_ so lost/dropped
   responses don't permanently block item info resolution.
2026-03-29 17:44:46 -07:00
Kelsi
209c257745 fix: wire SpellHandler::updateTimers and remove stale cast state members
SpellHandler::updateTimers() was never called after PR #23 extraction,
so cast bar timers, spell cooldowns, and unit cast state timers never
ticked. Also removes duplicate cast/queue/spell members left in
GameHandler that shadowed the SpellHandler versions, and fixes
MovementHandler writing to those stale members on world portal.

Demotes SMSG_SPELL_START/CAST_RESULT debug logs to LOG_DEBUG.
2026-03-29 16:49:17 -07:00
Kelsi
d32b35c583 fix: restore Classic aura flag normalization and clean up EntityController
- Restore 0x02→0x80 Classic harmful-to-WotLK debuff bit mapping in
  syncClassicAurasFromFields so downstream checks work across expansions
- Extract handleDisplayIdChange helper to deduplicate identical logic
  in onValuesUpdateUnit and onValuesUpdatePlayer
- Remove unused newItemCreated parameter from handleValuesUpdate
- Fix indentation on PLAYER_DEAD/PLAYER_ALIVE/PLAYER_UNGHOST emit calls
2026-03-29 16:29:56 -07:00
Paul
b0a07c2472 refactor(game): apply SOLID phases 2-6 to EntityController
- split applyUpdateObjectBlock into handleCreateObject,
  handleValuesUpdate, handleMovementUpdate
-  extract concern helpers — createEntityFromBlock,
  applyPlayerTransportState, applyUnitFieldsOnCreate/OnUpdate,
  applyPlayerStatFields, dispatchEntitySpawn, trackItemOnCreate,
  updateItemOnValuesUpdate, syncClassicAurasFromFields,
  detectPlayerMountChange, updateNonPlayerTransportAttachment
- UnitFieldIndices, PlayerFieldIndices, UnitFieldUpdateResult
  structs with static resolve() — eliminate repeated fieldIndex() calls
- IObjectTypeHandler strategy interface; concrete handlers
  UnitTypeHandler, PlayerTypeHandler, GameObjectTypeHandler,
  ItemTypeHandler, CorpseTypeHandler registered in typeHandlers_ map;
  handleCreateObject and handleValuesUpdate now dispatch via
  getTypeHandler() — adding a new object type requires zero changes
  to existing handler methods
- PendingEvents member bus; all 27 inline owner_.fireAddonEvent()
  calls in the update path replaced with pendingEvents_.emit(); events
  flushed via flushPendingEvents() at the end of each handler, decoupling
  field-parse logic from the addon callback system

entity_controller.cpp: 1520-line monolith → longest method ~200 lines,
cyclomatic complexity ~180 → ~5; zero duplicated CREATE/VALUES blocks
2026-03-29 14:42:38 +03:00
Paul
f5757aca83 refactor(game): extract EntityController from GameHandler (step 1.3)
Moves entity lifecycle, name/creature/game-object caches, transport GUID
tracking, and the entire update-object pipeline out of GameHandler into a
new EntityController class (friend-class pattern, same as CombatHandler
et al.).

What moved:
- applyUpdateObjectBlock() — 1,520-line core of all entity creation,
  field updates, and movement application
- processOutOfRangeObjects() / finalizeUpdateObjectBatch()
- handleUpdateObject() / handleCompressedUpdateObject() / handleDestroyObject()
- handleNameQueryResponse() / handleCreatureQueryResponse()
- handleGameObjectQueryResponse() / handleGameObjectPageText()
- handlePageTextQueryResponse()
- enqueueUpdateObjectWork() / processPendingUpdateObjectWork()
- playerNameCache, playerClassRaceCache_, pendingNameQueries
- creatureInfoCache, pendingCreatureQueries
- gameObjectInfoCache_, pendingGameObjectQueries_
- transportGuids_, serverUpdatedTransportGuids_
- EntityManager (accessed by other handlers via getEntityManager())

8 opcodes re-registered by EntityController::registerOpcodes():
  SMSG_UPDATE_OBJECT, SMSG_COMPRESSED_UPDATE_OBJECT, SMSG_DESTROY_OBJECT,
  SMSG_NAME_QUERY_RESPONSE, SMSG_CREATURE_QUERY_RESPONSE,
  SMSG_GAMEOBJECT_QUERY_RESPONSE, SMSG_GAMEOBJECT_PAGETEXT,
  SMSG_PAGE_TEXT_QUERY_RESPONSE

Other handler files (combat, movement, social, spell, inventory, quest,
chat) updated to access EntityManager via getEntityManager() and the
name cache via getPlayerNameCache() — no logic changes.

Also included:
- .clang-tidy: add modernize-use-nodiscard,
  modernize-use-designated-initializers; set -std=c++20 in ExtraArgs
- test.sh: prepend clang's own resource include dir before GCC's to
  silence xmmintrin.h / ia32intrin.h conflicts during clang-tidy runs

Line counts:
  entity_controller.hpp  147 lines  (new)
  entity_controller.cpp  2172 lines (new)
  game_handler.cpp       8095 lines (was 10143, −2048)

Build: 0 errors, 0 warnings.
2026-03-29 08:21:27 +03:00
Kelsi
d8c768701d fix: visible item field base 284→283 (confirmed by raw field dump)
RAW FIELDS dump shows item entries at odd indices: 283, 285, 287, 289...
With base=283, stride=2: 17 of 19 slots have valid item IDs (14200,
12020, 14378, etc). Slots 12-13 (trinkets) correctly empty.

With base=284: only 5 entries, and values are enchant IDs (913, 905, 904)
— these are the field AFTER each entry, confirming base was off by 1.
2026-03-28 16:12:31 -07:00
Kelsi
91c6eef967 fix: suspend gravity for 10s after world entry to prevent WMO fall-through
Stormwind WMO collision takes 25+ seconds to fully load. The warmup
ground check couldn't detect the WMO floor because collision data
wasn't finalized yet. Player spawned and immediately fell through
the unloaded WMO floor into the terrain below (Dun Morogh).

New approach: suspendGravityFor(10s) after world entry. Gravity is
disabled (Z position frozen) until either:
1. A floor is detected by the collision system (gravity resumes instantly)
2. The 10-second timer expires (gravity resumes as fallback)

This handles the case where WMO collision loads during the first few
seconds of gameplay — the player hovers at spawn Z until the floor
appears, then lands normally.

Also fixes faction language for chat (ORCISH for Horde, COMMON for
Alliance) and adds SMSG_MESSAGECHAT diagnostic logging.
2026-03-28 15:50:13 -07:00
Kelsi
6edcad421b fix: group invite popup never showing (hasPendingGroupInvite stale getter)
hasPendingGroupInvite() and getPendingInviterName() were inline getters
reading GameHandler's stale copies. SocialHandler owns the canonical
pendingGroupInvite/pendingInviterName state. Players were auto-added to
groups without seeing the accept/decline popup.

Now delegates to socialHandler_.
2026-03-28 15:29:19 -07:00
Kelsi
11571c582b fix: hearthstone from action bar, far teleport loading screen
Action bar hearthstone: the slot was type SPELL (spell 8690) not ITEM.
castSpell sends CMSG_CAST_SPELL which the server rejects for item-use
spells. Now detects item-use spells via getItemIdForSpell() and routes
through useItemById() instead, sending CMSG_USE_ITEM correctly.

Far same-map teleport: hearthstone on the same continent (e.g., Westfall
→ Stormwind on Azeroth) skipped the loading screen, so the player fell
through unloaded terrain. Now triggers a full world reload with loading
screen for teleports > 500 units, with the warmup ground check ensuring
WMO floors are loaded before spawning.
2026-03-28 14:55:58 -07:00
Kelsi
b81c616785 fix: delegate gossip/quest detail getters to QuestHandler (NPC dialog broken)
4 more stale getters from PR #23 split:
- isGossipWindowOpen() — QuestHandler owns gossipWindowOpen_
- getCurrentGossip() — QuestHandler owns currentGossip_
- isQuestDetailsOpen() — QuestHandler owns questDetailsOpen_
- getQuestDetails() — QuestHandler owns currentQuestDetails_

Also fix GameHandler::update() distance-close checks to use delegating
getters instead of stale member variables for vendor/gossip/taxi/trainer.

Map state (currentMapId_, worldStateZoneId_, exploredZones_) confirmed
NOT stale — domain handlers write via owner_. reference to GameHandler's
members. Those getters are correct as-is.
2026-03-28 12:43:44 -07:00
Kelsi
ee02faa183 fix: delegate all 113 stale GameHandler getters to domain handlers
PR #23 split GameHandler into 8 domain handlers but left 113 inline
getters reading stale duplicate member variables. Every feature that
relied on these getters was silently broken (showing empty/stale data):

InventoryHandler (32): bank, mail, auction house, guild bank, trainer,
  loot rolls, vendor, buyback, item text, master loot candidates
SocialHandler (43): guild roster, battlegrounds, LFG, duels, petitions,
  arena teams, instance lockouts, ready check, who results, played time
SpellHandler (10): talents, craft queue, GCD, pet unlearn, queued spell
QuestHandler (13): quest log, gossip POIs, quest offer/request windows,
  tracked quests, shared quests, NPC quest statuses
MovementHandler (15): all 8 server speeds, taxi state, taxi nodes/data

All converted from inline `{ return member_; }` to out-of-line
delegations: `return handler_ ? handler_->getter() : fallback;`
2026-03-28 12:18:14 -07:00
Kelsi
f37994cc1b fix: trade accept dialog not showing (stale state from domain handler split)
GameHandler::hasPendingTradeRequest() and all trade getters were reading
GameHandler's own tradeStatus_/tradeSlots_ which are never written after
the PR #23 split. InventoryHandler owns the canonical trade state.

Delegate all trade getters to InventoryHandler:
- getTradeStatus, hasPendingTradeRequest, isTradeOpen, getTradePeerName
- getMyTradeSlots, getPeerTradeSlots, getMyTradeGold, getPeerTradeGold

Also fix InventoryHandler::isTradeOpen() to include Accepted state.
2026-03-28 11:58:02 -07:00
Kelsi
99ac31987f fix: add missing <algorithm> include for std::clamp (Windows build) 2026-03-28 11:51:34 -07:00
Kelsi
b9ac3de498 tweak: camera defaults stiffness=30, pivot height=1.6 2026-03-28 11:45:21 -07:00
Kelsi
416e091498 feat: add Camera Stiffness and Pivot Height settings for motion comfort
Camera Stiffness (default 20, range 5-100): controls how tightly the
camera follows the player. Higher values = less sway/lag. Users who
experience motion sickness can increase this to reduce floaty camera.

Camera Pivot Height (default 1.8, range 0-3): height of the camera
orbit point above the player's feet. Lower values reduce the
"detached/floating" feel that can cause nausea. Setting to 0 puts the
pivot at foot level (ground-locked camera).

Both settings saved to settings file and applied via sliders in the
Gameplay tab of the Settings window.
2026-03-28 11:39:37 -07:00
Kelsi
15f6aaadb2 fix: revert stride to 2 (correct for WotLK visible items), add re-emit tracing
Stride 4 was wrong — the raw dump shows entries at 284, 288, 292 which
are slots 0, 2, 4 with stride 2 (slot 1=NECK is zero because necks are
invisible). Stride 2 with base 284 correctly maps 19 equipment slots.

Added WARNING-level log when item query responses trigger equipment
re-emit for other players, to confirm the re-emit chain works.

The falling-through-world issue is likely terrain chunks not loading
fast enough — the terrain streaming stalls are still present.
2026-03-28 11:07:17 -07:00
Kelsi
05ab9922c4 fix: visible item stride 2→4 (confirmed from raw field dump)
RAW FIELDS dump shows equipment entries at indices 284, 288, 292, 296, 300
— stride 4, not 2. Each visible item slot occupies 4 fields (entry +
enchant + 2 padding), not 2 as previously assumed.

Field dump evidence:
  [284]=3817(Reinforced Buckler) [288]=3808(Double Mail Boots)
  [292]=3252 [296]=3823 [300]=3845 [312]=3825 [314]=3827

With stride 2, slots 0-18 read indices 284,286,288,290... which interleaves
entries with enchant/padding values, producing mostly zeros for equipment.
With stride 4, slots correctly map to entry-only fields.
2026-03-28 11:04:29 -07:00
Kelsi
f70beba07c fix: visible item field base 408→286 (computed from INV_SLOT_HEAD=324)
PLAYER_VISIBLE_ITEM_1_ENTRYID = PLAYER_FIELD_INV_SLOT_HEAD(324) - 19*2
= 286. The previous value of 408 landed far past inventory slots in
string/name data, producing garbage entry IDs (ASCII fragments like
"mant", "alk ", "ryan") that the server rejected as invalid items.

Derivation: 19 visible item slots × 2 fields (entry + enchant) = 38
fields immediately before PLAYER_FIELD_INV_SLOT_HEAD at index 324.
2026-03-28 10:58:34 -07:00
Kelsi
f4a2a631ab fix: visible item field base 284→408 (was reading quest log, not equipment)
PLAYER_VISIBLE_ITEM_1_ENTRYID for WotLK 3.3.5a is at UNIT_END(148) + 260
= field index 408 with stride 2. The previous default of 284 (UNIT_END+136)
was in the quest log field range, causing item IDs like "Lesser Invisibility
Potion" and "Deathstalker Report" to be read as equipment entries.

This was the root cause of other players appearing naked — item queries
returned valid responses but for the WRONG items (quest log entries instead
of equipment), so displayInfoIds were consumable/quest item appearances.

The heuristic auto-detection still overrides for Classic/TBC (different
stride per expansion), so this only affects the WotLK default before
detection runs.

Also filter addon whispers (GearScore GS_*, DBM, oRA, BigWigs, tab-prefixed)
from chat display — these are invisible in the real WoW client.
2026-03-28 10:49:00 -07:00
Paul
888a78d775 fixin critical bugs, non critical bugs, sendmail implementation 2026-03-28 11:35:10 +03:00
Paul
b2710258dc refactor(game): split GameHandler into domain handlers
Extract domain-specific logic from the monolithic GameHandler into
dedicated handler classes, each owning its own opcode registration,
state, and packet parsing:

- CombatHandler: combat, XP, kill, PvP, loot roll (~26 methods)
- SpellHandler: spells, auras, pet stable, talent (~3+ methods)
- SocialHandler: friends, guild, groups, BG, RAF, PvP AFK (~14+ methods)
- ChatHandler: chat messages, channels, GM tickets, server messages,
               defense/area-trigger messages (~7+ methods)
- InventoryHandler: items, trade, loot, mail, vendor, equipment sets,
                    read item (~3+ methods)
- QuestHandler: gossip, quests, completed quest response (~5+ methods)
- MovementHandler: movement, follow, transport (~2 methods)
- WardenHandler: Warden anti-cheat module

Each handler registers its own dispatch table entries via
registerOpcodes(DispatchTable&), called from
GameHandler::registerOpcodeHandlers(). GameHandler retains core
orchestration: auth/session handshake, update-object parsing,
opcode routing, and cross-handler coordination.

game_handler.cpp reduced from ~10,188 to ~9,432 lines.

Also add a POST_BUILD CMake step to symlink Data/ next to the
executable so expansion profiles and opcode tables are found at
runtime when running from build/bin/.
2026-03-28 09:42:37 +03:00
Kelsi
e61b23626a perf: entity/skill/DBC/warden maps to unordered_map; fix 3x contacts scan
Entity storage: std::map<uint64_t, shared_ptr<Entity>> → unordered_map for
O(1) entity lookups instead of O(log n). No code depends on GUID ordering.

Player skills: std::map<uint32_t, PlayerSkill> → unordered_map.
DBC ID cache: std::map<uint32_t, uint32_t> → unordered_map.
Warden: apiHandlers_ and allocations_ → unordered_map (freeBlocks_ kept
as std::map since its coalescing logic requires ordered iteration).

Contacts: handleFriendStatus() did 3 separate O(n) find_if scans per
packet. Consolidated to single find_if with iterator reuse. O(3n) → O(n).
2026-03-27 18:28:36 -07:00
Kelsi
2af3594ce8 perf: eliminate per-frame heap allocs in M2 renderer; UI polish and report
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
M2 renderer: move 3 per-frame local containers to member variables:
- particleGroups_ (unordered_map): reuse bucket structure across frames
- ribbonDraws_ (vector): reuse draw call buffer
- shadowTexSetCache_ (unordered_map): reuse descriptor cache
Eliminates ~3 heap allocations per frame in particle/ribbon/shadow passes.

UI polish:
- Nameplate hover tooltip showing level, class (players), guild name
- Bag window titles show slot counts: "Backpack (12/16)"

Player report: CMSG_COMPLAIN packet builder and reportPlayer() method.
"Report Player" option in target frame right-click menu for other players.
Server response handler (SMSG_COMPLAIN_RESULT) was already implemented.
2026-03-27 18:21:47 -07:00
Kelsi
cccd52b32f fix: equipment visibility (remove layout verification gate), follow uses run speed
Equipment: removed the visibleItemLayoutVerified_ gate from
updateOtherPlayerVisibleItems(). The default WotLK field layout (base=284,
stride=2) is correct and should be used immediately. The verification
heuristic was silently blocking ALL other-player equipment rendering by
queuing for auto-inspect (which doesn't return items in WotLK anyway).

Follow: auto-follow now uses run speed (autoRunning) instead of walk speed.
Also uses squared distance for the distance checks.

Commands: /quit, /exit aliases for /logout; /difficulty normal/heroic/25/25heroic
sends CMSG_CHANGEPLAYER_DIFFICULTY.
2026-03-27 18:05:42 -07:00