Commit graph

38 commits

Author SHA1 Message Date
Pavel Okhlopkov
6ba0edc2fb change weapon for ranged skills
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
2026-04-10 23:01:16 +03:00
Pavel Okhlopkov
b79d9b8fea feat(rendering): implement spell visual effects with bone-tracked ribbons and particles
Add complete spell visual pipeline resolving the DBC chain
(Spell → SpellVisual → SpellVisualKit → SpellVisualEffectName → M2)
with precast/cast/impact phases, bone-attached positioning, and
automatic dual-hand mirroring.

Ribbon rendering fixes:
- Parse visibility track as uint8 (was read as float, suppressing
  all ribbon edges due to ~1.4e-45 failing the >0.5 check)
- Filter garbage emitters with bone=UINT_MAX unconditionally
- Guard against NaN spine positions from corrupt bone data
- Resolve ribbon textures via direct index, not textureLookup table
- Fall back to bone 0 when ribbon bone index is out of range

Particle rendering fixes:
- Reduce spell particle scale from 5x to 1.5x (was oversized)
- Exempt spell effect instances from position-based deduplication

Spell handler integration:
- Trigger precast visuals on SMSG_SPELL_START with server castTimeMs
- Trigger cast/impact visuals on SMSG_SPELL_GO
- Cancel precast visuals on cast interrupt/failure/movement

M2 classifier expansion:
- Add AmbientEmitterType enum for sound system integration
- Add 20+ foliage tokens, 4 spell effect tokens, isSmallFoliage flag
- Add markModelAsSpellEffect() to override disableAnimation

DBC layouts:
- Add SpellVisualID field to Spell.dbc for all expansion configs

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
2026-04-07 11:27:59 +03:00
Pavel Okhlopkov
97106bd6ae fix(render): code quality cleanup
Magic number elimination:
- Create protocol_constants.hpp, warden_constants.hpp,
  render_constants.hpp, ui_constants.hpp
- Replace ~55 magic numbers across game_handler, warden_handler,
  m2_renderer_render

Reduce nesting depth:
- Extract 5 parseEffect* methods from handleSpellLogExecute
  (max indent 52 → 16 cols)
- Extract resolveSpellSchool/playSpellCastSound/playSpellImpactSound
  from 3× duplicate audio blocks in handleSpellGo
- Flatten SMSG_INVENTORY_CHANGE_FAILURE with early-return guards
- Extract drawScreenEdgeVignette() for 3 duplicate vignette blocks

DRY extract patterns:
- Replace 12 compound expansion checks with isPreWotlk() across
  movement_handler (9), chat_handler (1), social_handler (1)

const to constexpr:
- Promote 23+ static const arrays/scalars to static constexpr across
  12 source files

Error handling:
- Convert PIN auth from exceptions to std::optional<PinProof>
- Add [[nodiscard]] to 15+ initialize/parse methods
- Wrap ~20 unchecked initialize() calls with LOG_WARNING/LOG_ERROR

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
2026-04-06 22:43:13 +03:00
Kelsi
069dd36698 fix(parsing): bail on suspicious maskBlockCount in CREATE_OBJECT blocks
When spline parsing consumes the wrong number of bytes, the subsequent
blockCount read lands on garbage data (e.g. 71 instead of ~5 for UNIT).
Previously the parser logged a warning but continued, reading garbage
mask/field data until hitting truncation. Now it returns false for
CREATE_OBJECT blocks with suspicious counts, letting the block loop
skip cleanly to the next entity.

Also downgrade ~44 diagnostic LOG_WARNING messages to LOG_DEBUG across
17 files (equipment, transport, DBC, heartbeat, chat, GO raypick, etc.)
to reduce log noise and make real warnings visible.
2026-04-05 20:12:17 -07:00
Paul
34c0e3ca28 chore(refactor): god-object decomposition and mega-file splits
Split all mega-files by single-responsibility concern and
partially extracting AudioCoordinator and
OverlaySystem from the Renderer facade. No behavioral changes.

Splits:
- game_handler.cpp (5,247 LOC) → core + callbacks + packets (3 files)
- world_packets.cpp (4,453 LOC) → economy/entity/social/world (4 files)
- game_screen.cpp  (5,786 LOC) → core + frames + hud + minimap (4 files)
- m2_renderer.cpp  (3,343 LOC) → core + instance + particles + render (4 files)
- chat_panel.cpp   (3,140 LOC) → core + commands + utils (3 files)
- entity_spawner.cpp (2,750 LOC) → core + player + processing (3 files)

Extractions:
- AudioCoordinator: include/audio/ + src/audio/ (owned by Renderer)
- OverlaySystem: include/rendering/ + src/rendering/overlay_system.*

CMakeLists.txt: registered all 17 new translation units.
Related handler/callback files: minor include fixups post-split.
2026-04-05 19:30:44 +03:00
Paul
b4989dc11f feat(animation): decompose AnimationController into FSM-based architecture
Replace the 2,200-line monolithic AnimationController (goto-driven,
single class, untestable) with a composed FSM architecture per
refactor.md.

New subsystem (src/rendering/animation/ — 16 headers, 10 sources):
- CharacterAnimator: FSM composer implementing ICharacterAnimator
- LocomotionFSM: idle/walk/run/sprint/jump/swim/strafe
- CombatFSM: melee/ranged/spell cast/stun/hit reaction/charge
- ActivityFSM: emote/loot/sit-down/sitting/sit-up
- MountFSM: idle/run/flight/taxi/fidget/rear-up (per-instance RNG)
- AnimCapabilitySet + AnimCapabilityProbe: probe once at model load,
  eliminate per-frame hasAnimation() linear search
- AnimationManager: registry of CharacterAnimator by GUID
- EmoteRegistry: DBC-backed emote command → animId singleton
- FootstepDriver, SfxStateDriver: extracted from AnimationController

animation_ids.hpp/.cpp moved to animation/ subdirectory (452 named
constants); all include paths updated.

AnimationController retained as thin adapter (~400 LOC): collects
FrameInput, delegates to CharacterAnimator, applies AnimOutput.

Priority order: Mount > Stun > HitReaction > Spell > Charge >
Melee/Ranged > CombatIdle > Emote > Loot > Sit > Locomotion.
STAY_IN_STATE policy when all FSMs return valid=false.

Bugs fixed:
- Remove static mt19937 in mount fidget (shared state across all
  mounted units) — replaced with per-instance seeded RNG
- Remove goto from mounted animation branch (skipped init)
- Remove per-frame hasAnimation() calls (now one probe at load)
- Fix VK_INDEX_TYPE_UINT16 → UINT32 in shadow pass

Tests (4 new suites, all ASAN+UBSan clean):
- test_locomotion_fsm: 167 assertions
- test_combat_fsm: 125 cases
- test_activity_fsm: 112 cases
- test_anim_capability: 56 cases

docs/ANIMATION_SYSTEM.md added (architecture reference).
2026-04-05 12:27:35 +03:00
Paul
e58f9b4b40 feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.

Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.

Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.

Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.

Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.

Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
Kelsi
100394a743 fix(rendering,game): init bone SSBO to identity; stop movement before cast
Bone SSBO buffers were allocated for MAX_BONES (240) entries but only
the first numBones were written. Uninitialized GPU memory in the
remaining slots caused vertex spikes when any bone index exceeded the
model's actual bone count.

Also send MSG_MOVE_STOP before spell casts so the server doesn't reject
cast-time spells (e.g. hearthstone) with "can't do that while moving".
2026-04-04 00:03:19 -07:00
Paul
5af9f7aa4b chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.

AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
  emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()

Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
  directly, grouped by access pattern:
  - UIServices: settings_panel, game_screen, toast_manager, chat_panel,
    combat_ui, window_manager
  - GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
    social_handler, combat_handler
  - Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
  MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03: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
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
76f493f7d9 refactor: replace goto with structured control flow
- spell_handler.cpp: replace goto-done with do/while(false) for pet
  spell packet parsing — bail on truncated data while always firing
  events afterward
- water_renderer.cpp: replace goto-found_neighbor with immediately
  invoked lambda to break out of nested neighbor search loops
2026-03-30 13:47:14 -07:00
Kelsi Rae Davis
fe080bed4b
Merge pull request #31 from ldmonster/chore/break-application-from-gamehandler
[chore] refactor: Break Application::getInstance() from GameHandler
2026-03-30 13:26:29 -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
169595433a debug: add GO interaction diagnostics at every decision point
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
Adds [GO-DIAG] WARNING-level logs at:
- Right-click dispatch (raypick hit / re-interact with target)
- interactWithGameObject entry + all BLOCKED paths
- SMSG_SPELL_GO (wasInTimedCast, lastGoGuid, pendingGoGuid state)
- SMSG_LOOT_RESPONSE (items, gold, guid)
- Raypick candidate GO positions (entity pos + hit center + radius)

These logs will pinpoint exactly where the interaction fails:
- No GO-DIAG lines = GOs not in entity manager / not visible
- Raypick GO pos=(0,0,0) = GO position not set from update block
- BLOCKED = guard condition preventing interaction
- SPELL_GO wasInTimedCast=false = timer race (already fixed)
2026-03-29 23:09:28 -07:00
Kelsi
cfbae93ce3 fix: client timer fallback re-sent CMSG_GAMEOBJ_USE and cleared loot guid
When the client-side cast timer expired slightly before SMSG_SPELL_GO
arrived, the fallback at update():1367 called performGameObjectInteraction
Now which sent a DUPLICATE CMSG_GAMEOBJ_USE to the server (confusing its
GO state machine), then resetCastState() cleared lastInteractedGoGuid_.
When SMSG_SPELL_GO finally arrived, the guid was gone so CMSG_LOOT was
never sent — quest chests produced no loot window.

Fix: the fallback no longer re-sends USE (server drives the interaction
via SMSG_SPELL_GO). resetCastState() no longer clears
lastInteractedGoGuid_ so the SMSG_SPELL_GO handler can still send LOOT.
2026-03-29 22:45:17 -07:00
Kelsi
785f03a599 fix: stale GO interaction guard broke future casts; premature LOOT interfered
Two remaining GO interaction bugs:

1. pendingGameObjectInteractGuid_ was never cleared after SMSG_SPELL_GO
   or SMSG_CAST_FAILED, leaving it stale. This suppressed CMSG_CANCEL_CAST
   for ALL subsequent spell casts (not just GO casts), causing the server
   to think the player was still casting when they weren't.

2. For chest-like GOs, CMSG_LOOT was sent simultaneously with
   CMSG_GAMEOBJ_USE. If the server starts a timed cast ("Opening"),
   the GO isn't lootable until the cast completes — the premature LOOT
   gets an empty response or is dropped, potentially corrupting the
   server's loot state. Now defers LOOT to handleSpellGo which sends it
   after the cast completes (via lastInteractedGoGuid_).
2026-03-29 22:36:30 -07:00
Kelsi
01ab2a315c fix: achievement message silently truncated by char[256] snprintf
Long achievement names combined with sender name could exceed 256
bytes, silently cutting the message mid-word in chat. Replaced with
std::string concatenation which grows dynamically.
2026-03-29 21:35:56 -07:00
Kelsi
294c91d84a fix: migrate 197 unsafe packet bounds checks to hasRemaining/getRemainingSize
All domain handler files used 'packet.getSize() - packet.getReadPos()'
which underflows to ~2^64 when readPos exceeds size (documented in
commit ed63b029). The game_handler.cpp and packet_parsers were migrated
to hasRemaining(N) in an earlier cleanup, but the domain handlers were
created after that migration by the PR #23 split, copying the old
unsafe patterns back in. Now uses hasRemaining(N) for comparisons and
getRemainingSize() for assignments across all 7 handler files.
2026-03-29 20:53:26 -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
3712e6c5c1 fix: operator precedence broke stabled pet parsing — only first pet shown
!packet.hasRemaining(4) + 4 + 4 evaluated as (!hasRemaining(4))+8
due to ! binding tighter than +, making the check always truthy and
breaking out of the loop after the first pet. Hunters with multiple
stabled pets would see only one in the stable master UI.
2026-03-29 18:46:15 -07:00
Kelsi
f681de0a08 refactor: use guidToUnitId() instead of inline 4-way GUID comparison
handleSpellStart and handleSpellGo duplicated the player/target/focus/
pet GUID-to-unitId mapping that already exists in guidToUnitId(). If a
new unit-id category is added (e.g. mouseover), these inline copies
would not pick it up.
2026-03-29 18:39:52 -07:00
Kelsi
b0aa4445a0 fix: cast/cooldown/unit-cast timers ticked twice per frame
SpellHandler::updateTimers() (added in 209c2577) already ticks down
castTimeRemaining_, unitCastStates_, and spellCooldowns_. But the
GameHandler::update() loop also ticked them manually — causing casts to
complete at 2x speed and cooldowns to expire twice as fast.

Removed the duplicate tick-downs from update(). The GO interaction
completion check remains (client-timed casts need this fallback).
Also uses resetCastState() instead of manually clearing 4 fields,
adds missing castTimeTotal_ reset, and adds loadSpellNameCache()
to getSpellName/getSpellRank (every other DBC getter had it).
2026-03-29 18:21:03 -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
ec24bcd910 fix: Warrior Charge sent 3x SET_FACING by falling through to generic facing
Charge already computed facing and sent SET_FACING, but then fell through
to both the melee-ability facing block and the generic targeted-spell
facing block — sending up to 3 SET_FACING + 1 HEARTBEAT per cast. Added
facingHandled flag so only one block sends facing, reducing redundant
network traffic that could trigger server-side movement validation.
2026-03-29 17:52:51 -07:00
Kelsi
309fd11a7b fix: cast bar invisible due to stale ImGui saved window position
The cast bar window used ImGuiCond_FirstUseEver for positioning, so
ImGui's .ini state restored a stale off-screen position from a prior
session. Switch to ImGuiCond_Always and add NoSavedSettings flag so
the bar always renders centered near the bottom of the screen.

Also demotes remaining diagnostic logs to LOG_DEBUG.
2026-03-29 17:20:02 -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
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
4f2a4e5520 debug: log SMSG_SPELL_START to diagnose missing cast bar
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
2026-03-28 17:00:24 -07:00
Kelsi
71cf3ab737 debug: log CMSG_CAST_SPELL packet size to verify format 2026-03-28 16:56:15 -07:00
Kelsi
d68bb5a831 fix: spell facing used atan2(dy,dx) but canonical convention is atan2(-dy,dx)
The canonical yaw convention (documented in coordinates.hpp) is
atan2(-dy, dx) where X=north, Y=west. North=0, East=+PI/2.

The spell facing code used atan2(dy, dx) (no negation on dy), producing
a yaw ~77° off from the correct server orientation. The server rejected
every cast with "unit not in front" because the sent orientation pointed
in the wrong direction.

Fixed in all 3 locations: charge facing, melee facing, and general
pre-cast facing.
2026-03-28 16:51:23 -07:00
Kelsi
7f3c7379b5 debug: log pre-cast facing orientation details 2026-03-28 16:45:57 -07:00
Kelsi
4ff59c6f76 debug: log castSpell calls and SMSG_CAST_RESULT at WARNING level 2026-03-28 16:41:15 -07:00
Kelsi
1aec1c6cf1 fix: send heartbeat after SET_FACING before cast to ensure server has orientation 2026-03-28 16:27:26 -07:00
Kelsi
9cb6c596d5 fix: face target before casting any targeted spell (not just melee)
Only melee abilities sent MSG_MOVE_SET_FACING before the cast packet.
Ranged spells like Smite used whatever orientation was in movementInfo
from the last movement, causing "target not in front" server rejection.

Now sends a facing update toward the target entity before ANY targeted
spell cast. The server checks a ~180° frontal arc for most spells.
2026-03-28 16:23:27 -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