Commit graph

2944 commits

Author SHA1 Message Date
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
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
bed859d8db fix: buyback used hardcoded WotLK opcode 0x290 bypassing wireOpcode()
Both buyBackItem() and the retry path in handleBuyFailed constructed
packets with a raw opcode constant instead of using the expansion-aware
BuybackItemPacket::build(). This would silently break if any expansion's
CMSG_BUYBACK_ITEM wire mapping diverges from 0x290.
2026-03-29 18:29:07 -07:00
Kelsi
78e2e4ac4d fix: locomotionFlags missing SWIMMING and DESCENDING
The heartbeat throttle bitmask was missing SWIMMING and DESCENDING,
treating swimming/descending players as stationary and using a slower
heartbeat interval. The identical bitmask in movement_handler.cpp
already included SWIMMING — this inconsistency could cause the server
to miss position updates during swim combat.
2026-03-29 18:28:58 -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
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
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
0795430390 cleanup: remove dead pos=0 reassignment and demote chat logs to DEBUG
Quest log had a redundant pos=0 right after initialization. Chat handler
logged every incoming/outgoing message at WARNING level, flooding the
log and obscuring genuine warnings.
2026-03-29 18:11:49 -07:00
Kelsi
a30c7f4b1a fix: taxi recovery was dead code — flag cleared before check
taxiRecoverPending_ was unconditionally reset to false in the general
state cleanup, 39 lines before the recovery check that reads it. The
recovery block could never execute. Removed the premature clear so
mid-flight disconnect recovery can actually trigger.
2026-03-29 18:11:37 -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
0e814e9c4a refactor: replace 8 copy-pasted spline speed lambdas with factory
All spline speed opcodes share the same PackedGuid+float format,
differing only in which member receives the value. Replaced 8 identical
lambdas (~55 lines) with a makeSplineSpeedHandler factory that captures
a member pointer, cutting duplication and making it trivial to add new
speed types.
2026-03-29 17:59:51 -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
0aff4b155c fix: dismount cleared all indefinite auras instead of just mount aura
The dismount path wiped every aura with maxDurationMs < 0, which
includes racial passives, tracking, and zone buffs — not just the mount
spell. Now only clears the specific mountAuraSpellId_ so the buff bar
stays accurate without waiting for a server aura resync.
2026-03-29 17:56:59 -07:00
Kelsi
8993b8329e fix: isReadableQuestText rejected all non-ASCII UTF-8 text
The range check (c > 0x7E) rejected UTF-8 multi-byte sequences, so quest
titles on localized servers (French, German, Russian, etc.) were treated
as unreadable binary and replaced with 'Quest #ID' placeholders. Now
allows bytes >= 0x80 while still requiring at least one ASCII letter to
distinguish real text from binary garbage.
2026-03-29 17:56:52 -07:00
Kelsi
b3abf04dbb fix: misleading indentation on PLAYER_ALIVE/PLAYER_UNGHOST event emits
The emit calls were indented at a level suggesting they were outside the
if/else blocks, but braces placed them inside. Fixed to match the actual
control flow, preventing a future maintainer from "correcting" the
indentation and accidentally changing the logic.
2026-03-29 17:52:56 -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
b9ecc26f50 fix: misplaced brace included book handlers inside LOOT_CLEAR_MONEY loop
The for-loop over {SMSG_LOOT_CLEAR_MONEY} was missing its closing brace,
so SMSG_READ_ITEM_OK and SMSG_READ_ITEM_FAILED registrations were inside
the loop body. Works by accident (single iteration) but fragile and
misleading — future additions to the loop would re-register book handlers.
2026-03-29 17:52:43 -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
51da88b120 fix: SMSG_ITEM_PUSH_RESULT read extra byte causing wrong item count
The handler read an extra uint8 (bag) after bagSlot, shifting all
subsequent fields by 1 byte. This caused count to straddle the count
and countInInventory fields — e.g. count=1 read as 0x03000000 (50M).

Also removes cast bar diagnostic overlay and demotes debug logs.
2026-03-29 17:30:44 -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
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
Kelsi Rae Davis
1988b53619
Merge pull request #29 from ldmonster/chore/entity-controller-refactoring
[chore] EntityController refactoring (SOLID decomposition)
2026-03-29 16:25:50 -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
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
Kelsi
c58537e2b8 fix: load binary DBCs from Data/db/ fallback path
CreatureDisplayInfo.dbc (691KB, 24K+ entries) exists at Data/db/ but
the loader only checked DBFilesClient\ (MPQ manifest) and expansion CSV.
The CSV had only 13248 entries (malformed export), so TBC+ creatures
(Mana Wyrms, Blood Elf area) had no display data and were invisible.

Now checks Data/db/ as a fallback for binary DBCs. This path contains
pre-extracted DBCs shared across expansions. Binary DBCs have complete
record data including proper IDs.
2026-03-28 16:17:59 -07: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
fdca990209 debug: dump raw fields for first 3 players (lowered threshold to size>20) 2026-03-28 16:06:03 -07:00
Kelsi
f74b79f1f8 debug: log outgoing heartbeat coords and chat, fix BG filter for SAY type
Heartbeat: log canonical + wire coords every 30th heartbeat to detect
if we're sending wrong position (causing server to teleport us).

Chat: log outgoing messages at WARNING level to confirm packets are sent.

BG filter: announcer uses SAY (type=0) with color codes, not SYSTEM.
Match "BG Queue Announcer" in message body regardless of chat type.
2026-03-28 16:02:36 -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
b1e2b8866d fix: use faction-correct language for outgoing chat (COMMON vs ORCISH)
Chat was always sent with COMMON (7) language. For Horde players,
AzerothCore rejects COMMON and silently drops the message. Alliance
players nearby also couldn't see Horde messages.

Now detects player race and sends ORCISH (1) for Horde races, COMMON (7)
for Alliance. This matches what the real WoW client sends.
2026-03-28 15:42:01 -07:00
Kelsi
2f96bda6fa fix: far same-map teleport blocked packet handler for 41 seconds
loadOnlineWorldTerrain() was called directly from the worldEntryCallback
inside the packet handler, running the 20s warmup loop synchronously.
This blocked ALL packet processing and froze the game for 20-41 seconds.

Now defers the world reload to pendingWorldEntry_ which is processed on
the next frame, outside the packet handler. Position and camera snap
immediately so the player doesn't drift at the old location.

The /y respawn report was actually a server-initiated teleport (possibly
anti-spam or area trigger) that hit this 41-second blocking path.
2026-03-28 15:38:44 -07:00
Kelsi
9666b871f8 fix: BG announcer filter was suppressing ALL chat messages
The filter matched ALL chat types for patterns like "[H:" + "A:" which
are common in normal messages. Any SAY/WHISPER/GUILD message containing
both substrings was silently dropped. This broke all incoming chat.

Now only filters SYSTEM messages and only matches specific BG announcer
keywords: "Queue status", "BG Queue", "BGAnnouncer".
2026-03-28 15:31:16 -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
1af1c66b04 fix: SMSG_TRADE_STATUS_EXTENDED format (whichPlayer is uint32, add missing fields)
WotLK trade packet format was wrong in multiple ways:
- whichPlayer was read as uint8, actually uint32
- Missing tradeId field (we read tradeId as tradeCount)
- Per-slot size was 52 bytes, actually 64 (missing suffixFactor,
  randomPropertyId, lockId = 12 bytes)
- tradeCount is 8 (7 trade + 1 "will not be traded"), not capped at 7

Verified: header(4+4=8) + 8×(1+64=65) + gold(4) = 532 bytes matches
the observed packet size exactly.

Note: Classic trade format differs and will need its own parser.
2026-03-28 15:24:26 -07:00
Kelsi
df9dad952d fix: handle TRADE_STATUS_UNACCEPT (status=8), revert to Open state 2026-03-28 15:21:32 -07:00
Kelsi
ce54b196e7 fix: trade COMPLETE resets state before EXTENDED can populate items/gold
SMSG_TRADE_STATUS(COMPLETE) and SMSG_TRADE_STATUS_EXTENDED arrive in the
same packet batch. COMPLETE was calling resetTradeState() which cleared
all trade slots and gold BEFORE EXTENDED could write the final data.
The trade window showed "7c" (garbage gold) because the gold field read
from the wrong offset (slot size was also wrong: 60→52 bytes).

Now COMPLETE just sets status to None without full reset, preserving
trade state for EXTENDED to populate. The TRADE_CLOSED addon event
still fires correctly.
2026-03-28 15:18:33 -07:00
Kelsi
ed7cbccceb fix: trade slot size check 60→52 bytes, add trade diagnostic logging 2026-03-28 15:14:53 -07:00
Kelsi
615db79819 fix: skip all-zero equipment emit, broaden BG announcer filter
Equipment: the first emitOtherPlayerEquipment call fired before any item
queries returned, sending all-zero displayIds that stripped players naked.
Now skips the callback when resolved=0 (waiting for queries). Equipment
only applies once at least one item resolves, preventing the naked flash.

BG announcer: broadened filter to match ALL chat types (not just SYSTEM),
and added more patterns: "BGAnnouncer", "[H: N, A: N]" with spaces.

Also added diagnostic logging in setOnlinePlayerEquipment to trace
displayId counts reaching the renderer.
2026-03-28 15:09:52 -07:00
Kelsi
12f5aaf286 fix: filter BG queue announcer spam from system chat
ChromieCraft/AzerothCore BG queue announcer module floods chat with
SYSTEM messages like "Queue status for Alterac Valley [H: 12/40, A: 15/40]".
Now filtered by detecting common patterns: "Queue status", "BG Queue",
"Announcer]", and "[H:...A:..." format.

Equipment status: resolved items ARE rendering (head, shoulders, chest,
legs confirmed with displayIds). Remaining unresolved slots (weapons)
are item queries the server hasn't responded to yet — timing issue,
not a client bug. Items trickle in over ~5 seconds as queries return.
2026-03-28 15:01:25 -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