The warmup loop waited up to 20 seconds for getHeightAt() to return a
terrain height within 15 units of spawn Z before accepting the ground
as ready. In practice, the terrain was loaded and the character was
visibly standing on it, but the height sample didn't match closely
enough (terrain LOD, chunk boundary, or server Z vs client height
mismatch).
Reduce the tile-count fallback timeout from 20s to 5s: if at least 4
tiles are loaded after 5 seconds, accept the ground as ready. The
exact height check still runs in the first 5 seconds for fast-path
cases where it does match.
ChromieCraft/AzerothCore tolerates no HASH_RESULT response (continues
session without Warden checks), but immediately kicks on a WRONG hash.
The previous commit sent a fallback SHA1 which the server rejected,
breaking login that was working before.
Restore the skip behavior for WotLK/TBC: stay silent on HASH_REQUEST
when no CR match exists, and advance to WAIT_CHECKS so the rest of the
session proceeds normally. Turtle/Classic servers still get the fallback
hash since they're lenient about wrong values.
Previously, WotLK/TBC servers with no CR match would skip the
HASH_REQUEST response entirely to "avoid account bans". This caused
a guaranteed kick-on-timeout for ALL WotLK servers including
permissive ones like ChromieCraft/AzerothCore.
Now sends a best-effort fallback hash (SHA1 of module image or raw
data) for all server types. Permissive servers accept this and
continue the session normally. Strict servers (Warmane) will reject
it but only kick — same outcome as the previous skip behavior, just
faster feedback.
For strict servers, the correct fix remains providing a .cr file
with pre-computed seed→reply entries for each module.
Suppress 9 unused parameter warnings in IObjectTypeHandler interface
methods and their overrides by commenting out parameter names:
- Base class: onCreate/onValuesUpdate/onMovementUpdate default empty
implementations (parameters intentionally unused in base)
- ItemTypeHandler::onCreate: entity param forwarded only to onCreateItem
which doesn't need it
- CorpseTypeHandler::onCreate: entity param not needed for corpse spawn
Build now produces zero warnings (excluding third-party stb headers).
Replace the incorrectly extracted RSA-2048 modulus (which contained
the exponent bytes embedded inside it) with the verified Blizzard
public key used across all pre-Cataclysm clients (1.12.1, 2.4.3,
3.3.5a).
Key confirmed against two independent sources:
- namreeb/WardenSigning ClientKey.hpp (72 verified sniffed modules)
- SkullSecurity wiki Warden_Modules documentation
The modulus starts with 0x6BCE F52D... and ends with ...03F4 AFC7.
Exponent remains 65537 (0x010001).
Verification algorithm: SHA1(module_data + "MAIEV.MOD"), 0xBB-padded
to 256 bytes, RSA verify-recover with raw (no-padding) mode.
Signature failures are non-fatal (log warning, continue loading) so
private-server modules signed with custom keys still work. This is
necessary because servers like ChromieCraft/AzerothCore may use their
own signing keys.
Also update warden_module.hpp status: all implementation items now ✅.
Complete the last major Warden stub — the import table parser that
resolves Windows API calls in loaded modules. This is the critical
missing piece for strict servers like Warmane.
Implementation:
- Parse Warden module import table from decompressed data (after
relocation entries): alternating libraryName\0 / functionName\0
pairs, terminated by null library name
- For each import, look up the emulator's pre-registered stub address
(VirtualAlloc, GetTickCount, ReadProcessMemory, etc.)
- Auto-stub unrecognized APIs with a no-op returning 0 — prevents
module crashes on unimplemented Windows functions
- Patch each IAT slot (sequential dwords at module image base) with
the resolved stub address
- Add WardenEmulator::getAPIAddress() public accessor for IAT lookups
- Fix initialization order: bindAPIs() now runs inside initializeModule()
after emulator setup but before entry point call
The full Warden pipeline is now: RC4 decrypt → RSA verify → zlib
decompress → parse executable → relocate → create emulator → register
API hooks → bind imports (IAT patch) → call entry point → extract
exported functions (packetHandler, tick, generateRC4Keys, unload).
Implement the three stubbed Warden module callbacks that were previously
TODO placeholders:
- **sendPacket**: Encrypts module output via WardenCrypto RC4 and sends
as CMSG_WARDEN_DATA through the game socket. Enables modules to send
responses back to the server (required for strict servers like Warmane).
- **validateModule**: Compares the module's provided 16-byte MD5 hash
against the hash received during download. Logs error on mismatch
(indicates corrupted module transit).
- **generateRC4**: Derives new encrypt/decrypt RC4 keys from a 16-byte
seed using SHA1Randx, then replaces the active WardenCrypto key state.
Handles mid-session re-keying requested by the module.
Architecture:
- Add setCallbackDependencies() to inject WardenCrypto* and socket send
function into WardenModule before load() is called
- Use thread_local WardenModule* so C function pointer callbacks (which
can't capture state) can reach the module's dependencies during init
- Wire dependencies from WardenHandler before module load
Also update warden_module.hpp status markers — RSA verification, zlib,
executable parsing, relocation, and Unicorn emulation are all implemented
(were incorrectly marked as TODO). Only API binding/IAT patching and
RSA modulus verification against real WoW.exe remain as gaps.
Remove all OpenGL/GLEW code and dependencies. The Vulkan renderer has
been the sole active backend for months; these files were dead code.
Deleted (8 files, 641 lines):
- rendering/mesh.cpp+hpp: OpenGL VAO/VBO/EBO wrapper (never instantiated)
- rendering/shader.cpp+hpp: OpenGL GLSL compiler (replaced by VkShaderModule)
- rendering/scene.cpp+hpp: Scene graph holding Mesh objects (created but
never populated — all rendering uses Vulkan sub-renderers directly)
- rendering/video_player.cpp+hpp: FFmpeg+GL texture uploader (never
included by any other file — login video feature can be re-implemented
with VkTexture when needed)
Cleaned up:
- renderer.hpp: remove Scene forward-decl, getScene() accessor, scene member
- renderer.cpp: remove scene.hpp/shader.hpp includes, Scene create/destroy
- application.cpp: remove stale "GL/glew.h removed" comment
- CMakeLists.txt: remove find_package(OpenGL/GLEW), source/header entries,
and target_link_libraries for OpenGL::GL and GLEW::GLEW
- PKGBUILD: remove glew dependency
- BUILD_INSTRUCTIONS.md: remove glew from all platform install commands
- Delete 4 legacy GLSL 330 shaders (basic.vert/frag, terrain.vert/frag)
left over from OpenGL→Vulkan migration — Vulkan equivalents exist as
*.glsl files compiled to SPIR-V by the build system
- Delete orphaned mpq_manager.hpp/cpp (694 lines) — not in CMakeLists,
not included by any file, unreferenced StormLib integration attempt
- Add comments to water.frag.glsl wave constants explaining the
multi-octave noise design: non-axis-aligned directions prevent tiling,
frequency increases and amplitude decreases per octave for natural
water appearance
Complete rewrite — the previous version extensively referenced OpenGL
(glClearColor, VAO/VBO/EBO, GLSL shaders) throughout all sections.
The project has used Vulkan exclusively for months.
Key changes:
- Replace all OpenGL references with Vulkan equivalents (VkContext,
VMA, descriptor sets, pipeline cache, SPIR-V shaders)
- Update system diagram to show actual sub-renderer hierarchy
(TerrainRenderer, WMORenderer, M2Renderer, CharacterRenderer, etc.)
- Document GameHandler SOLID decomposition (8 domain handlers +
EntityController + GameServices dependency injection)
- Add Warden 4-layer architecture section
- Add audio system section (miniaudio, 5 sound managers)
- Update opcode count from "100+" to 664+
- Update UI section: talent screen and settings are implemented (not TODO)
- Document threading model (async terrain, GPU upload queue, normal maps)
- Fix dependencies list (Vulkan SDK, VMA, vk-bootstrap, Unicorn, FFmpeg)
- Add container builds and CI platforms
- Remove stale "TODO" items for features that are complete
- CONTRIBUTING.md: C++17 → C++20 (matches CMakeLists.txt)
- TROUBLESHOOTING.md: fix log path (~/.wowee/logs/ → logs/wowee.log)
- docs/authentication.md: remove stale "next milestone" (char enum
and world entry have been working for months)
- docs/srp-implementation.md: update session key status (RC4 encryption
is implemented), fix file reference to actual src/auth/srp.cpp
- docs/packet-framing.md: remove stale "next steps" (realm list is
fully implemented), update status with tested servers
- docs/WARDEN_IMPLEMENTATION.md: fix file list — handler is in
warden_handler.cpp not game_handler.cpp, add warden_memory.hpp/cpp
- docs/WARDEN_QUICK_REFERENCE.md: fix header/source paths (include/
not src/), add warden_handler and warden_memory
- docs/quickstart.md: fix clone command (--recurse-submodules, WoWee
not wowee), remove obsolete manual ImGui clone step, fix log path
- docs/server-setup.md: update version to v1.8.9-preview, date to
2026-03-30, add all supported expansions
- assets/textures/README.md: remove broken doc references
(TURTLEHD_IMPORT.md, TEXTURE_MANIFEST.txt), update integration
status to reflect working PNG override pipeline
GETTING_STARTED.md:
- Fix keybinding table: T→N for talents, Q→L for quest log, W→M for
world map, add missing keys (C, I, O, J, Y, K), remove nonexistent
minimap toggle
- Fix extract_assets.ps1 example param (-WowDirectory → positional)
- Fix Data/ directory tree to match actual manifest layout
- Fix log path: ~/.wowee/logs/ → logs/wowee.log (local directory)
EXPANSION_GUIDE.md:
- Add Turtle WoW 1.17 to supported expansions
- Update code examples to use game_utils.hpp helpers
(isActiveExpansion/isClassicLikeExpansion/isPreWotlk) instead of
removed ExpansionProfile::getActive() and GameHandler::getInstance()
- Update packet parser references (WotLK is default in domain handlers,
not a separate packet_parsers_wotlk.cpp file)
- Update references section with game_utils.hpp
- README: update status date to 2026-03-30, version to v1.8.9-preview,
add container builds line, update current focus to code quality
- CHANGELOG: move v1.8.1 entries to their own section, add v1.8.2-v1.8.9
unreleased section covering architecture (GameHandler decomposition,
Docker cross-compilation), bug fixes (7 UB/overflow/safety fixes),
and code quality (30+ constants, 55+ comments, 8 DRY extractions)
- docs/status.md: update last-updated date to 2026-03-30
- Explain icon load deferral strategy: returning null without caching
allows retry next frame when budget resets, rather than permanently
blacklisting icons that were deferred due to rate-limiting
- Explain DBC field fallback logic: hard-coded WotLK indices are a
safety net when dbc_layouts.json is missing; fieldCount >= 200
distinguishes WotLK (234 fields) from Classic (148)
- Explain M2 version 264 threshold (WotLK stores submesh/bone data
in external .skin files; Classic/TBC embed it in the M2)
- Explain M2 texture types 1 and 6 (skin and hair/scalp; empty
filenames resolved via CharSections.dbc at runtime)
- Explain 0x20 anim flag (embedded data; when clear, keyframes live
in external {Model}{SeqID}-{Var}.anim files)
- Explain geoset ID encoding (group × 100 + variant from
ItemDisplayInfo.dbc; e.g. 801 = sleeves variant 1)
- packet_parsers_tbc: explain spline waypoint cap (DoS prevention),
spline compression flags (Catmull-Rom 0x80000 / linear 0x2000 use
uncompressed format, others use packed delta), spell hit target cap
(128 >> real AOE max of ~20), guild roster cap (1000 safety limit)
- ambient_sound_manager: explain 1.5s bell toll spacing — matches
retail WoW cadence, allows each toll to ring out before the next
- character_preview.hpp: explain 4:5 portrait aspect ratio for
full-body character display in creation/selection screen
- entity_controller: clamp block/dodge/parry/crit/rangedCrit percentage
fields to [0..100] after memcpy from update fields — guards against
NaN/Inf from corrupted packets reaching the UI renderer
- entity_controller: add why-comment on OBJECT_FIELD_SCALE_X raw==0
check — IEEE 754 0.0f is all-zero bits, so raw==0 means the field
was never populated; keeping default 1.0f prevents invisible entities
- inventory_screen: extract renderClassRestriction() and
renderRaceRestriction() from two identical 40-line blocks in quest
info and item info tooltips. Both used identical bitmask logic,
strncat formatting, and player-class/race validation (-49 lines net)
- world_map: add why-comment on AreaTable.dbc fallback field indices —
explains that incorrect indices silently return wrong data and why
the WotLK stock layout (ID=0, Parent=2, ExploreFlag=3) is chosen
as the safest default
- input: fix undefined behavior in SDL mouse button loop — SDL_BUTTON(0)
computes (1 << -1) which is UB. Start loop at 1 since SDL button
indices are 1-based (SDL_BUTTON_LEFT=1, RIGHT=3, MIDDLE=2)
- big_num: guard BN_bn2hex/BN_bn2dec against nullptr return on
OpenSSL allocation failure — previously constructed std::string
from nullptr which is undefined behavior
- Add bounds checks to readLE32/readLE16 — malformed Warden modules
could cause out-of-bounds reads on untrusted PE data
- Fix unsigned underflow in PE section loading: if rawDataOffset or
virtualAddr exceeds buffer size, the subtraction wrapped to a huge
uint32_t causing memcpy to read/write far beyond bounds. Now skips
the section entirely and uses std::min with pre-validated maxima
- world_packets: name kGuidTypeMask/kGuidTypePet/kGuidTypeVehicle
for chat receiver GUID type detection, with why-comment explaining
WoW's bits-48-63 entity type encoding and 0xF0FF mask purpose
- lua_engine: name kRoleTank/kRoleHealer/kRoleDamager (0x02/0x04/0x08)
for WotLK LFG role bitmask, add context on Leader bit (0x01) and
source packets (SMSG_GROUP_LIST / SMSG_LFG_ROLE_CHECK_UPDATE)
- Name kHalfMinutesPerDay (2880) replacing 8 bare literals across
time conversion, modulo clamping, and midnight wrap arithmetic.
Add why-comment: Light.dbc stores time-of-day as half-minutes
(24h × 60m × 2 = 2880 ticks per day cycle)
- Replace hardcoded 3.14159f with glm::two_pi<float>() in sun
direction angle calculations (2 occurrences)
- terrain_manager: extract kRand16Max (65535.0f) from 8 duplicated
random normalization expressions — 16-bit mask to [0..1] float
- terrain_manager: add static_assert verifying packed alpha unpacks
to full alpha map size (ALPHA_MAP_PACKED * 2 == ALPHA_MAP_SIZE)
- camera_controller: name kCameraClipEpsilon (0.1f) with why-comment
preventing character model clipping at near-minimum distance
- srp: name kEphemeralBytes (19 = 152 bits, matches Blizzard client)
and kMaxEphemeralAttempts (100) with why-comment explaining A != 0
mod N requirement and near-zero failure probability
- warden_module: add why-comment on 0x400000 module base (default
PE image base for 32-bit Windows executables)
- warden_module: name kRsaSignatureSize (256 = RSA-2048) with
why-comment explaining signature stripping (placeholder modulus
can't verify Blizzard's signatures)
- vk_pipeline: extract kColorWriteAll constant from 4 duplicated RGBA
bitmask expressions across blend mode functions, with why-comment
- frustum: name kMinNormalLenSq epsilon (1e-8) with why-comment —
prevents division by zero on degenerate planes
- dbc_loader: add why-comment on DBC field width validation — all
fields are fixed 4-byte uint32 per format spec
- pin_auth: replace 0x30 hex literal with '0' char constant, add
why-comment on ASCII encoding for server HMAC compatibility
- m2_loader: define kM2SeqFlagEmbeddedData (0x20) with why-comment —
when clear, keyframe data lives in external .anim files and M2 offsets
are file-relative (reading them from M2 produces garbage). Replaces
3 bare hex literals across parseAnimTrack and ribbon emitter parsing
- audio_engine: replace empty for-loop iterator advance with
std::advance() for clarity
- 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)
- weather: remove duplicate setZoneWeather(15) for Dustwallow Marsh —
second call silently overwrote the first with different parameters
- weather: replace duplicate static RNG in getRandomPosition() with
shared weatherRng() to avoid redundant generator state
- starfield: extract day/night cycle thresholds into named constants
(kDuskStart/kNightStart/kDawnStart/kDawnEnd/kFadeDuration)
- skybox: replace while-loop time wrapping with std::fmod — avoids
O(n) iterations on large time jumps
- asset_manager: add size guard before fsPath.substr(size-4) in
tryLoadPngOverride — resolveFile could theoretically return a
path shorter than the extension
- wmo_loader: name kDoodadNameIndexMask (0x00FFFFFF) with why-comment
explaining the 24-bit name index / 8-bit flags packing and MODN
string table reference
- window: add why-comment on LOG_WARNING usage during shutdown —
intentionally elevated so teardown progress is visible at default
log levels for crash diagnosis
- 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
- 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)
- Name portal spin wrap value as kTwoPi constant
- Name particle animTime wrap as kParticleWrapMs (3333ms) with
why-comment: covers longest known emission cycle (~3s torch/campfire)
while preventing float precision loss over hours of runtime
- Add FBlock interpolation documentation: explain what FBlocks are
(particle lifetime curves) and note that float/vec3 variants share
identical logic and must be updated together
- renderer: remove no-op assignment (mountAnims_.stand = 0 when already 0)
- renderer: add why-comments on blacksmith WMO ID 96048 (ambient forge
sounds) with TODO for other smithy buildings
- terrain_renderer: replace 1e30f sentinel with numeric_limits::max(),
name terrain view distance constant (1200 units ≈ 9 ADT tiles)
- social_handler: add missing LFG case 15, document case 0 nullptr
return (success = no error message), add enum name comments
- 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
- 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
- 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)
- 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
- 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
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`
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)
The client-side cast timer expires ~50-200ms before the server sends
SMSG_SPELL_GO (float precision + frame timing). Previously the fallback
called resetCastState() which set casting_=false and currentCastSpellId_
=0. When SMSG_SPELL_GO arrived moments later, wasInTimedCast evaluated
to false (false && spellId==0), so the loot path (CMSG_LOOT via
lastInteractedGoGuid_) was never taken. Quest chests never opened.
Now the fallback skips resetCastState() for GO interaction casts, letting
the cast bar sit at 100% until SMSG_SPELL_GO arrives and handles cleanup
properly with wasInTimedCast=true.
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.
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_).
pendingGameObjectInteractGuid_ was always cleared to 0 right before
the interaction, which defeated the cancel-protection guard in
cancelCast(). Any positional movement (WASD, jump) during a GO
interaction cast (e.g., "Opening" on a quest chest) sent
CMSG_CANCEL_CAST to the server, aborting the interaction and
preventing quest objective credit.
Now sets pendingGameObjectInteractGuid_ to the GO guid so:
1. cancelCast() skips CMSG_CANCEL_CAST for GO-triggered casts
2. The cast-completion fallback can re-trigger loot after timer expires
3. isGameObjectInteractionCasting() returns true during GO casts
CMSG_GAMEOBJ_REPORT_USE was only sent for non-chest GOs. Chest-type
(type=3) and name-matched chest-like GOs (Bundle of Wood, etc.) went
through a separate path that sent CMSG_GAMEOBJ_USE + CMSG_LOOT but
skipped REPORT_USE. On AzerothCore, REPORT_USE triggers the server-side
HandleGameobjectReportUse which calls GossipHello on the GO script —
this is where many quest objective scripts grant credit.
Restructured so CMSG_GAMEOBJ_USE is sent first for all GO types,
then chest-like GOs additionally send CMSG_LOOT, and REPORT_USE fires
for everything except mailboxes.
The click region for targeting via nameplates was bounded by the name
text (nameX to nameX+textSize.x). Short names like "Wolf" produced a
~30px clickable strip, while the health bar below was 80px wide. Clicks
on the bar outside the name text bounds were ignored. Now uses the wider
of name text or health bar for the horizontal hit area.
The Packet::skipAll() method was introduced to replace the verbose
setReadPos(getSize()) pattern. 186 instances were migrated earlier,
but 20 survived in domain handler files created after the migration.
Also removes a redundant single-element for-loop wrapper around
SMSG_LOOT_CLEAR_MONEY registration.
Same class of bug as inventory_handler fix b9ecc26f. The for-loop over
{SMSG_INSTANCE_DIFFICULTY, MSG_SET_DUNGEON_DIFFICULTY} was missing its
closing brace, so GUILD_DECLINE, RAF_EXPIRED, RAF_FAILURE, and
PVP_AFK_RESULT registrations executed inside the loop body — each
registered twice (once per opcode). Currently harmless since duplicate
registration is idempotent, but structurally wrong.
The while(true) loop retried av_read_frame after seeking to the start
on error. A corrupt file where read fails but seek succeeds would loop
forever, blocking the main thread. Bounded to 500 attempts with a
warning log on exhaustion.
If generateNormalHeightMapCPU threw (e.g., bad_alloc), the pending
counter was never decremented, causing shutdown() to block forever
waiting for a count that would never reach zero. Added try-catch to
guarantee the decrement. Also strengthened the increment from relaxed
to acq_rel so shutdown()'s acquire load sees the count before the
thread body begins executing.
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.
The two fireAddonEvent calls were indented as if conditional on
repChangeCallback_ but actually execute unconditionally (no braces).
Fixed indentation and added clarifying comment.
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.
std::future::get() re-throws any exception from the async task. The 6
call sites in the render pipeline (terrain/WMO/M2 workers + animation
worker) and 2 floor-query sites in camera_controller were unguarded,
so a single bad_alloc in any worker would terminate the process with
no recovery. Now wrapped in try-catch with error logging.
SDL2 requires video/window functions to be called from the main thread
(the one that called SDL_Init). The watchdog thread was calling
SDL_SetRelativeMouseMode, SDL_ShowCursor, and SDL_SetWindowGrab directly
on stall detection — undefined behavior on macOS (Cocoa requires main-
thread UI calls) and unsafe on other platforms.
Now the watchdog sets an atomic flag, and the main loop checks it at the
top of each iteration, executing the SDL calls on the correct thread.
zone_manager.cpp used std::rand() for music track selection with modulo
bias and global state. game_screen.cpp used std::rand() for rain/snow
particle positions. Both now use local std::mt19937 seeded from
random_device. Also removes the global srand(time(nullptr)) call since
no code depends on the C rand() seed anymore.
No std::rand() or srand() calls remain in the codebase.
8 rand() calls in weather.cpp used C rand() which defaults to seed 1.
Weather intensity rolls, cycle durations, and particle Y positions were
identical on every launch. Replaced with a file-local mt19937 seeded
from random_device, matching the RNG already present in getRandomPosition.
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.
All 8 rand() calls for animation time offsets and variation timers in
m2_renderer.cpp used C rand() which defaults to seed 1 without srand(),
producing identical sequences every launch. Trees, torches, and grass
all swayed in sync. Replaced with std::mt19937 seeded from
random_device. Same fix for 4 mount idle fidget/sound timer sites in
renderer.cpp which mixed rand() with the mt19937 already present.
offset + length was computed in uint32_t before comparing to size_t.
A crafted M2 with offset=0xFFFFFFFF, length=2 wraps to 1 in uint32,
passing the check and reading out of bounds. Now uses size_t arithmetic,
matching the readArray fix from an earlier round.
count * sizeof(T) was computed in uint32_t — a large count value from a
crafted WMO file could wrap to a small number, pass the bounds check,
then attempt a multi-GB allocation causing OOM/crash. Now uses 64-bit
arithmetic with a 64MB sanity cap, matching the M2 loader pattern.
All four functions read UNIT_FIELD_FLAGS instead of PLAYER_FLAGS.
- AFK (0x01) hit UNIT_FLAG_SERVER_CONTROLLED — vendors flagged as AFK
- DND (0x02) hit UNIT_FLAG_NON_ATTACKABLE — guards flagged as DND
- Ghost (0x100) hit UNIT_FLAG_IMMUNE_TO_PC — immune NPCs flagged as ghost
- FFA PvP (0x80000) hit UNIT_FLAG_PACIFIED — pacified mobs flagged FFA
All now correctly read PLAYER_FLAGS with the right bit masks (0x01,
0x02, 0x10, 0x80 respectively), matching entity_controller.cpp which
already uses the correct field.
Final sweep across mpq_manager, application, auth_screen, wmo_renderer,
character_renderer, and terrain_manager. All now use the unsigned char
cast pattern. No remaining bare ::tolower/::toupper or std::tolower(c)
calls on signed char in the codebase.
tavernTrackIndex was initialized to 0 but never modified, so the player
always heard TavernAlliance01.mp3. Added post-increment to rotate
through the 3 available tracks on each tavern entry.
std::strlen on raw MWMO chunk data has no upper bound if the chunk
lacks a null terminator (truncated/corrupt WDT file). Replaced with
strnlen bounded by chunkSize, matching the ADT parser fix in d776226f.
static int logCount/batchLogCount inside the per-group parse loop
accumulated globally, so after the first WMO with many sub-chunks
loaded, no subsequent WMO group would ever log. Changed to function-
local / loop-index-based throttle so each group gets its own window.
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.
vmaCreateBuffer return value was silently discarded in both loadTerrain
and loadTerrainIncremental. If allocation failed (OOM/fragmentation),
the chunk proceeded with a VK_NULL_HANDLE UBO, causing the GPU to read
from an invalid descriptor on the next draw call. Now checks the return
value and skips the chunk on failure.
The doodad set name read used raw memcpy(20 bytes) bypassing the safe
read<T> template that returns {} on OOB. A truncated WMO file would
read past the vector's storage. Added bounds check before the memcpy.
ARGB8888 decompression read pixelCount*4 bytes from mipData without
checking that mipSize was large enough — a truncated BLP caused heap
OOB reads. Also, 'int pixelCount = width * height' overflowed for
large dimensions (signed int UB). Now validates dimensions <= 4096,
uses uint32_t arithmetic, and checks mipSize >= required for ARGB8888.
std::toupper(int) and std::tolower(int) have undefined behavior when
passed a negative value. These sites passed raw signed char without
casting to unsigned char first, unlike the rest of the codebase which
already uses the correct pattern. Affects auth (account names), world
packets, and mount sound path matching.
1. MCNK sub-chunk bounds checks didn't account for the 8-byte header
skip, so parseMCVT/parseMCNR could read up to 8 bytes past the
validated buffer when sub-chunk headers are present (the common case).
2. parseMTEX/parseMMDX/parseMWMO used unbounded strlen on raw chunk
data. A truncated file without a null terminator would read past the
chunk boundary. Replaced with strnlen bounded by remaining size.
Also removes dead debug code: empty magic buffer copy, cathedral WMO
search, and Stormwind placement dump (which also had ::toupper UB).
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.
readArray was called inside the loop on every iteration, re-parsing the
entire flat key array via memcpy. For a model with 200 sequences and
10k keys this produced ~24MB of redundant copying. Now reads once before
the loop (matching how allTimestamps was already handled).
WotLK format is min(4)+max(4)+result(4)+guid(8)=20 bytes. The parser
read guid(8) first (treating min|max as a uint64), then targetGuid(8)
(non-existent field), then the actual values at wrong offsets. Every
/roll message showed garbled numbers and a bogus roller identity.
Also adds a hasRemaining guard for the 64 bytes of damage/armor/resist
fields in the item query parser — previously read past end with silent
zero-fill on truncated packets.
The comment would lead a maintainer to "fix" the working code to read
4-byte RGBA instead of 3-float C3Vector. Updated to match the actual
M2 particle FBlock color format (3 floats, values 0-255, per WoWDev).
std::tolower(int) has undefined behavior when passed a negative value,
which signed char produces for bytes > 127. The rest of the codebase
correctly casts to unsigned char first; these 3 sites were missed.
The golden tint rect was drawn before rendering with a hardcoded single-
line height. Multi-line wrapped messages only had the first line
highlighted. Now drawn after EndGroup() using GetItemRectMin/Max so the
rect covers all wrapped lines.
Also fixes std::tolower(char) UB at two call sites — negative char
values (extended ASCII) are undefined behavior without unsigned cast.
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.
A single send() that returned fewer bytes than requested was logged but
not retried, leaving the server with a truncated packet. This causes an
irreversible TCP framing desync (next header lands mid-payload) that
manifests as a disconnect under network pressure. Added a retry loop
that handles EWOULDBLOCK with a brief yield.
Also rejects payloads > 64KB instead of silently truncating the 16-bit
CMSG size field, which would have written a wrong header while still
appending all bytes.
The two emit calls were indented 12 spaces (suggesting a nested block)
instead of 8 (matching the enclosing if). Same class of maintenance
trap as the PLAYER_ALIVE/PLAYER_UNGHOST fix in b3abf04d.
1. Contradictory condition (!numStrings && numStrings >= 1) was always
false, so unknown guild event messages never included the server's
context string. Fixed to just numStrings >= 1.
2. leaveParty() only sent the packet without clearing partyData or
firing addon events, so /leave left party frames visible until the
server pushed an update. Now delegates to leaveGroup() which handles
both the packet and local state cleanup.
The cast falsely suggests reasonType is unused, but it's read on lines
3699-3702 for AFK/vote-kick differentiation. Same class of issue as
the (void)isPlayerTarget fix in commit 6731e584.
handleFriendStatus inserted into friendsCache with an empty playerName
when the name query hadn't resolved yet, creating a phantom "" entry.
Now guards with !playerName.empty().
removeIgnore erased from ignoreCache immediately without waiting for
server confirmation, desyncing the cache if the server rejected. Now
only clears the GUID set and lets the next SMSG_IGNORE_LIST rebuild
the cache, consistent with how removeFriend works.
The handler treated the second uint32 (auctionId) as itemEntry. The
real itemEntry is at byte 24 after auctionHouseId(4)+auctionId(4)+
bidderGuid(8)+bidAmount(4)+outbidAmount(4). Outbid chat messages always
referenced the wrong item.
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.
The per-tick GPU upload budget check ran before consuming async futures,
so after 1 upload ALL remaining ready results were deferred — including
permanent failures and cache hits that need zero GPU work. Moved the
budget gate after failure/cache-hit processing so only actual uploads
count. Re-queues over-budget results as pending spawns for next frame.
Callbacks and addons querying the current target during this event saw
the old (stale) target instead of null. setTarget correctly updates the
GUID before firing — clearTarget now does the same.
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.
Freshly spawned entities have maxHealth=0 before fields populate.
0/0 produces NaN which propagates through all geometry calculations
for that nameplate frame. Guard with a maxHealth>0 check.
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.
Loop iterated 20 hair geoset lookups for Human Male but the if-body
was empty — the LOG statement that was presumably there was removed
but the loop skeleton was left behind.
A function for taxi/movement cleanup was resetting 10 death-related
fields (playerDead_, releasedSpirit_, resurrectPending_, etc.), which
could cancel a pending resurrection or mark a dead player as alive
when called during taxi dismount. Death state is owned by
entity_controller and resurrect packet handlers, not movement cleanup.
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.
The character-list level update loop used 'return' instead of 'break',
exiting the handler lambda before the level-up chat message, sound
effect, callback, and PLAYER_LEVEL_UP event could fire. Since the
player GUID is always in the character list, the notification code
was effectively dead — players never saw "You have reached level N!".
The variable is used earlier in the function for hostile attacker
tracking, so the (void) cast falsely suggests it was unused. Leftover
from a prior refactor.
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.
If the server sent a NaN or out-of-range speed, the client echoed it
back in the ACK (confirming it to the server) but then rejected it
locally. This left the server believing the client accepted the speed
while the client used the old value — a desync only fixable by relog.
Moved validation before the ACK so bad speeds are rejected outright.
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.
reinterpret_cast<float*> on raw packet bytes is undefined behavior per
the C++ strict aliasing rule — compilers can optimize assuming uint8_t
and float never alias. Replaced with packet.readFloat() which uses
memcpy internally. Also switched to hasRemaining() for consistency.
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.
!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.
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.
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().
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.
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.
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.
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).
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 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
- 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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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".
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_.
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.
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.
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.
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.
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.
Some private servers (AzerothCore/ChromieCraft) send OFFICER chat type
to all guild members regardless of rank. The real WoW client checks the
GR_RIGHT_OFFCHATLISTEN (0x80) guild rank permission before displaying.
Now checks the player's guild rank rights from the roster data and
suppresses officer chat if the permission bit is not set.
Domain handlers were setting `owner_.gossipWindowOpen = false` directly
on GameHandler's stale member, but isGossipWindowOpen() delegates to
QuestHandler's copy. The gossip window stayed open because the
delegating getter never saw the close.
Fix: use owner_.closeGossip() / owner_.closeVendor() which properly
delegate to QuestHandler/InventoryHandler to close the canonical state.
Affected: InventoryHandler (3 sites: mail, trainer, bank opening),
MovementHandler (1 site: taxi opening), QuestHandler (2 sites: gossip
opening closes vendor).
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.
The TradeSlot structs differ between GameHandler (has bag/slot fields)
and InventoryHandler (no bag/slot). The reinterpret_cast was undefined
behavior that corrupted memory, potentially causing the teleport bug.
Now properly copies fields between the two struct layouts.
NOTE: 113 stale getters remain in GameHandler that read duplicate member
variables never updated by domain handlers. These need systematic fixing.
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.
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.
Stormwind players stand on WMO floors ~95m above terrain. The previous
check only tested if terrain existed at the spawn XY (it did — far below).
Now checks WMO floor first, then terrain, requiring the ground to be within
15 units of spawn Z. Falls back to tile count after 10s.
Also adds diagnostic logging for useItemBySlot (hearthstone debug).
Stormwind players stand on WMO floors, not terrain. The terrain-only
check passed immediately (terrain exists below the city) but the WMO
floor hadn't loaded yet, so the player fell through.
Now checks three ground sources in order:
1. Terrain height at spawn point
2. WMO floor height at spawn point (for cities/buildings)
3. After 8s, accepts if 4+ terrain tiles are loaded (fallback)
Won't exit warmup until at least one ground source returns valid height,
or the 25s hard cap is reached.
The terrain readiness check was using getCharacterPosition() which is
(0,0,0) during warmup — always returned a valid height and exited
immediately, causing the player to spawn before terrain loaded.
Now uses the server-provided spawn coordinates (x,y,z from world entry)
converted to render coords for the terrain query. Also logs when terrain
isn't ready after 5 seconds to show warmup progress.
Player spawn callbacks and equipment re-emit chain confirmed working.
Added terrain readiness check to the warmup exit condition: the loading
screen won't drop until getHeightAt(playerPos) returns a valid height,
ensuring the ground exists under the player's feet before spawning.
Also increased warmup hard cap from 15s to 25s to give terrain more time
to load in cities like Stormwind with dense WMO/M2 assets.
Equipment re-emit chain confirmed working: items resolve 3-4 seconds
after spawn and equipment is re-applied with valid displayIds.
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.
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.
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.
The Classic fallback silently succeeded on WotLK data by false-positive
matching, consuming wrong bytes and producing corrupt entity data that
was silently dropped — resulting in zero other players/NPCs visible.
Now tries 4 WotLK-only variants in order:
1. Full WotLK (durationMod+durationModNext+vertAccel+effectStart+compressed)
2. Full WotLK uncompressed
3. WotLK without parabolic fields (durationMod+durationModNext+points)
4. WotLK without parabolic, compressed
This covers servers that don't unconditionally send vertAccel+effectStart
(the MEMORY.md says AzerothCore does, but other cores may not).
The previous fix (b8a9efb7) that returned false on spline failure was too
aggressive — it aborted the ENTIRE UPDATE_OBJECT packet, not just one
block. Since many entity spawns (NPCs, other players) share the same
packet, a single spline parse failure killed ALL entities in the batch.
Restored the Classic-format fallback as a last resort after WotLK format
fails. The key difference from the original bug is that WotLK is now
tried FIRST (with proper position save/restore), and Classic only fires
if WotLK fails. This prevents the false-positive match that originally
caused corruption while still handling edge-case spline formats.
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.
SMSG_GAMEOBJECT_CUSTOM_ANIM with animId=0 on a fishing node (type 17)
was triggering "A fish is on your line!" for ALL fishing bobbers in
range, including other players'. Now checks OBJECT_FIELD_CREATED_BY
(fields 6-7) matches the local player GUID before showing the message.
When both WotLK compressed and uncompressed spline point parsing fail,
the parser was silently continuing with a corrupted read position (16
bytes of WotLK spline header already consumed). This caused the update
mask to read garbage (maskBlockCount=40), corrupting the current entity
AND all remaining blocks in the same UPDATE_OBJECT packet.
Now returns false on spline failure, cleanly aborting the single block
parse and allowing the remaining blocks to be recovered (if the parser
can resync). Also logs the failing GUID and spline flags for debugging.
This fixes:
- Entities spawning with displayId=0/entry=0 (corrupted parse)
- "Unknown update type: 128" errors from reading garbage
- Falling through the ground (terrain entities lost in corrupted batch)
- Phantom "fish on your line" from fishing bobber entity parse failure
Warden module download (18756 bytes, 38 chunks of 500 bytes) stalled at
32 chunks because the per-pump packet parse budget was 16 — after two
2ms pump cycles (32 packets), the TCP receive buffer filled and the
server stopped sending. Character list never arrived.
- kDefaultMaxParsedPacketsPerUpdate: 16 → 64
- kDefaultMaxPacketCallbacksPerUpdate: 6 → 48
Also adds WARNING-level diagnostic logs for auth pipeline packets and
Warden module download progress (previously DEBUG-only, invisible in
production logs).
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/.
CONTRIBUTING.md: code style, PR process, architecture pointers, packet
handler pattern, key files for new contributors.
CHANGELOG.md: grouped changes since v1.8.1-preview into Performance,
Bug Fixes, Features, Security, and Code Quality sections.
Chat parser: use stack-allocated std::array<char, 256> for typical chat
messages instead of heap-allocated std::string. Only falls back to heap
for messages > 256 bytes. Reduces allocator pressure on high-frequency
chat packet handling.
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).
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.
NPC and other-player melee swing callback was hardcoded to animation 16
(unarmed attack). Now tries 17 (1H weapon), 18 (2H weapon) first with
hasAnimation() check, falling back to 16 if neither exists on the model.
M2 renderer: skip bone matrix computation for instances beyond 150 units
(LOD 3 threshold). These models use minimal static geometry with no visible
skeletal animation. Last-computed bone matrices are retained for GPU upload.
Removes unnecessary float matrix operations for hundreds of distant NPCs
in crowded zones.
Water renderer: add per-surface AABB frustum culling before draw calls.
Computes tight AABB from surface corners and height range, tests against
camera frustum. Skips descriptor binding and vkCmdDrawIndexed for surfaces
outside the view. Handles both ADT and WMO water (rotated step vectors).
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.
Inspect: CMSG_INSPECT was writing full uint64 GUID instead of packed GUID.
Server silently rejected the malformed packet. Fixed both InspectPacket and
QueryInspectAchievementsPacket to use writePackedGuid().
Follow: was a no-op (only stored GUID). Added client-side auto-follow system:
camera controller walks toward followed entity, faces target, cancels on
WASD/mouse input, stops within 3 units, cancels at 40+ units distance.
Party commands:
- /lootmethod (ffa/roundrobin/master/group/nbg) sends CMSG_LOOT_METHOD
- /lootthreshold (0-5 or quality name) sets minimum loot quality
- /raidconvert converts party to raid (leader only)
Equipment diagnostic logging still active for debugging naked players.
Target frame: add Follow, Clear Target, and Set Raid Mark submenu to the
right-click context menu (Inspect, Trade, Duel were already present).
Equipment diagnostics: add LOG_INFO traces to updateOtherPlayerVisibleItems()
and emitOtherPlayerEquipment() to debug why other players appear naked.
Logs the visible item entry IDs received from the server and the resolved
displayIds from itemInfoCache. Check the log for "emitOtherPlayerEquipment"
to see if entries arrive as zeros (server not sending fields) or if
displayIds are zero (item templates not cached yet).
Shoulder pieces are M2 model attachments (like helmets), not body geosets.
Load left shoulder at attachment point 5, right shoulder at point 6.
Models resolved from ItemDisplayInfo.dbc LeftModel/RightModel fields,
with race/gender suffix variants tried first. Applied to both online
player and NPC equipment paths.
Other players previously appeared partially naked — only chest, legs, feet,
hands, cape, and tabard rendered. Now renders full equipment:
- Helmet M2 model: loads from ItemDisplayInfo.dbc with race/gender suffix,
attaches at head bone (point 0/11), hides hair geoset under helm
- Weapons: mainhand (attachment 1) and offhand (attachment 2) M2 models
loaded from ItemDisplayInfo, with Weapon/Shield path fallback
- Wrist/bracer geoset (group 8): applies when no chest sleeve overrides
- Belt/waist geoset (group 18): reads GeosetGroup1 from ItemDisplayInfo
- Shoulder M2 attachments deferred (separate bone attachment system)
Also applied same wrist/waist geosets to NPC and character preview paths.
Minimap: batch 9 individual vkUpdateDescriptorSets into single call.
Mail: change money/COD fields from uint32 to uint64 in CMSG_SEND_MAIL and
SMSG_MAIL_LIST_RESULT for WotLK 3.3.5a. Classic keeps uint32 on the wire.
Fixes money truncation and packet misalignment causing mail failures.
Other-player capes: add cape texture loading to setOnlinePlayerEquipment().
The cape geoset was enabled but no texture was loaded, leaving capes blank.
Now mirrors the local-player path: looks up ItemDisplayInfo.dbc, finds cape
texture candidates, applies via setGroupTextureOverride/setTextureSlotOverride.
Zone toasts: suppress duplicate zone toast when the zone text overlay is
already showing the same zone name. Fixes double "Entering: Stormwind City".
Network: enable TCP_NODELAY on both auth and world sockets after connect(),
disabling Nagle's algorithm to eliminate up to 200ms buffering delay on
small packets (movement, spell casts, chat).
Rendering: track material and bone descriptor sets in M2 renderer to skip
redundant vkCmdBindDescriptorSets calls between batches sharing same textures.
- Replace count()+operator[] double lookups with find() or try_emplace()
in gameObjectInstances_, playerTextureSlotsByModelId_, onlinePlayerAppearance_
- Add Entity::isUnit() helper; replace 5 dynamic_cast<Unit*> in per-frame
UI rendering (nameplates, combat text, pet frame) with isUnit()+static_cast
- Add constexpr kInv255 reciprocal for per-pixel normal map generation loops
in character_renderer and wmo_renderer
Spline parsing: remove Classic format fallback from the WotLK parser. The
PacketParsers hierarchy already dispatches to expansion-specific parsers
(Classic/TBC/WotLK/Turtle), so the WotLK parseMovementBlock should only
attempt WotLK spline format. The Classic fallback could false-positive when
durationMod bytes resembled a valid point count, corrupting downstream parsing.
Preload DBC caches: call loadSpellNameCache() and 5 other lazy DBC caches
during handleLoginVerifyWorld() on initial world entry. This moves the ~170ms
Spell.csv load from the first SMSG_SPELL_GO handler to the loading screen,
eliminating the mid-gameplay stall.
WMO portal culling: move per-instance portalVisibleGroups vector and
portalVisibleGroupSet to reusable member variables, eliminating heap
allocations per WMO instance per frame.
Spline auto-detection: try WotLK format before Classic to prevent false-positive
matches where durationMod float bytes resemble a valid Classic pointCount. This
caused the movement block to consume wrong byte count, corrupting the update mask
read (maskBlockCount=57/129/203 instead of ~5) and silently dropping NPC spawns.
Terrain latency: bound WMO liquid group loading to 4 groups per advanceFinalization
call. Large WMOs (e.g., Stormwind canals with 40+ liquid groups) previously loaded
all groups in one unbounded loop, blowing past the 8ms frame budget and causing
stalls up to 1300ms. Now yields back to processReadyTiles() after 4 groups so the
time budget check can break out.
- Hoist DBC field index lookups before loops in game_handler (7 DBC iteration loops)
- Cache getSkybox()/getPosition() calls instead of redundant per-frame queries
- Merge textureHasAlphaByPtr_ + textureColorKeyBlackByPtr_ into single map
- Add constexpr for DEG_TO_RAD, reciprocal constants, physics delta
- Add reserve() for WMO/M2 collision grid queries and portal BFS
- Frustum plane normalize: inversesqrt instead of length+divide
- M2 particle emission: inversesqrt for direction normalization
- Parse creature display IDs from query response
- UI: show spell names/IDs as fallback instead of "Unknown"
Squared distance optimizations across 30 files:
- Convert glm::length() comparisons to glm::dot() (no sqrt)
- Use glm::inversesqrt() for check-then-normalize patterns (1 rsqrt vs 2 sqrt)
- Defer sqrt to after early-out checks in collision/movement code
- Hottest paths: camera_controller (21), weather particles, WMO collision,
transport movement, creature interpolation, nameplate culling
Container and algorithm improvements:
- std::map<string> → std::unordered_map for asset/DBC/MPQ/warden caches
- std::mutex → std::shared_mutex for asset_manager and mpq_manager caches
- std::sort → std::partial_sort in lighting_manager (top-2 of N volumes)
- Double-lookup find()+operator[] → insert_or_assign in game_handler
- Add reserve() for per-frame vectors: weather, swim_effects, WMO/M2 collision
Threading and synchronization:
- Replace 1ms busy-wait polling with condition_variable in character_renderer
- Move timestamp capture before mutex in logger
- Use memory_order_acquire/release for normal map completion signaling
API additions:
- DBC getStringView()/getStringViewByOffset() for zero-copy string access
- Parse creature display IDs from SMSG_CREATURE_QUERY_SINGLE_RESPONSE
Add [[nodiscard]] to VkShaderModule::loadFromFile, Shader::loadFromFile/
loadFromSource, AssetManifest::load, DbcLoader::load — all return bool
indicating success/failure that callers should check.
Suppress with (void) at 17 call sites where validity is checked via
isValid() after loading rather than the return value (m2_renderer
recreatePipelines, swim_effects recreatePipelines).
loadTexture() is called from terrain worker threads, but the static
unordered_set dedup caches for missing-texture and decode-failure
warnings had no synchronization. Add std::mutex guards around both
log-dedup blocks to prevent data races.
Add kCastGreen (interruptible cast bar, 5 uses) and kQueueGreen
(queue status / talent met, 7 uses across game_screen + talent_screen).
Remove commented-out renderQuestMarkers call (replaced by 3D billboards).
terrain_manager: replace bare 4096/2048/0x80/0x7F with named constants
ALPHA_MAP_SIZE, ALPHA_MAP_PACKED, ALPHA_FILL_FLAG, ALPHA_COUNT_MASK
— documents the WoW alpha map RLE format.
character_renderer: replace bare 256/512 texture sizes with
kBaseTexSize/kUpscaleTexSize for NPC skin upscaling logic.
The findMemType/findMemoryType helper in auth_screen, loading_screen,
and vk_context returned 0 on failure — a valid memory type index.
Changed to return UINT32_MAX and log an error, so vkAllocateMemory
receives an invalid index and fails cleanly rather than silently
using the wrong memory type.
Add [[nodiscard]] to VkBuffer::uploadToGPU/createMapped and
VkContext::initialize/recreateSwapchain so callers that ignore
failure are flagged at compile time. Suppress with (void) cast at
3 call sites where failure is non-actionable (resize best-effort).
game_screen: fsrScales, fsrScaleFactors, kTotemInfo, kRaidMarks,
kTimerInfo, kNPMarks, kCellMarks, kPartyMarks, kMMMarks, kCatOrder
keybinding_manager: actionMap
All static const arrays in UI files are now constexpr where possible.
- Move itemKeys/spellKeys/thrKeys to shared kItemSetItemKeys/
kItemSetSpellKeys/kItemSetThresholdKeys in ui_colors.hpp, removing
5 identical local definitions across game_screen and inventory_screen
- Widen totem timer snprintf buffer from 8 to 16 bytes (defensive)
- Promote kStatTooltips to constexpr
Deduplicate class/race bitmask arrays (3 copies each → 1 shared) and
socket type definitions (3 copies → 1 shared). Eliminates ~80 lines of
repeated struct definitions across game_screen.cpp and inventory_screen.cpp.
The uncompressed spline skip loop used `pointCount - 1` in its bound
without guarding pointCount > 1. While pointCount==0 is already handled
by an early return, pointCount==1 would correctly iterate 0 times, but
the explicit guard makes the intent clearer and prevents future issues
if the early return is ever removed.
- Move kDispelNames to file-scope constexpr, removing 2 duplicate local
definitions in raid/party frame rendering
- Promote kTotemColors and kReactDimColors from static const to constexpr
- Replace std::to_string + string concat for ImGui widget IDs with
snprintf into stack buffers (avoids heap allocations in render loops)
Add kDarkRed, kSoftRed, kHostileRed, kMediumGray to ui_colors.hpp and
replace 31 inline ImVec4 literals across game_screen, character_screen,
inventory_screen, and performance_hud. Also replace local color aliases
in performance_hud with shared constants.
Wrap string-to-number conversions in try-catch where input comes from
external sources (realm address port, last_world.cfg, keybinding config,
ADT tile filenames) to prevent crashes on malformed data.
- Add kBrightGold, kPaleRed, kBrightRed, kLightBlue, kManaBlue, kCyan to ui_colors.hpp
- Replace 61 inline ImVec4 color literals across game_screen, inventory_screen,
talent_screen, and world_map with named constants
- Remove const_cast in character_renderer render loop by using non-const iteration
Replace remaining ImVec4(1.0f, 0.82f, 0.0f, 1.0f) gold color literals
in game_screen.cpp (19) and talent_screen.cpp (1) with the shared
colors::kTooltipGold constant. Zero inline gold literals remain.
Add colors::kTooltipGold to ui_colors.hpp and replace 14 inline
ImVec4(1.0f, 0.82f, 0.0f, 1.0f) literals in inventory_screen.cpp
for item set names, unique markers, and quest item indicators.
Add getInventorySlotName() and renderBindingType() to ui_colors.hpp,
replacing 3 copies of the 26-case slot name switch (2 inventory_screen
+ 1 game_screen) and 2 copies of the binding type switch. Removes ~80
lines of duplicate tooltip code.
Extract getEnchantmentNames() to share a single SpellItemEnchantment.dbc
cache between both renderItemTooltip overloads, replacing two identical
19-line lazy-load blocks with single-line references.
Fix extra-paren variants in world_packets and packet_parsers_tbc.
getRemainingSize() is now exclusively arithmetic across the entire
codebase — all bounds checks use hasRemaining().
Convert 33 remaining getRemainingSize() comparison patterns including
ternary expressions and extra-paren variants. getRemainingSize() is
now only used for arithmetic (byte counting), never for bounds checks.
Replace getRemainingSize()>=N with hasRemaining(N) and
getRemainingSize()<N with !hasRemaining(N) across all 4 packet files.
hasRemaining() is now the canonical bounds-check idiom with 680+ uses.
Replace verbose bounds checks with hasRemaining(N) in
packet_parsers_classic (7) and packet_parsers_tbc (40), completing
the migration across all packet-handling files.
Replace verbose getReadPos()+N>getSize() patterns in world_packets.cpp
with the existing Packet::hasRemaining(N) method, matching the style
already used in game_handler.cpp.
Extract renderSettingsInterfaceTab() (108 lines) from
renderSettingsWindow(). 6 of 7 tabs now have dedicated methods;
only Video remains inline (shares init state with parent).
Extract renderSettingsGameplayTab() (162 lines) and
renderSettingsControlsTab() (96 lines) from renderSettingsWindow().
5 of 7 settings tabs are now in dedicated methods; only Video and
Interface remain inline (they share resolution/display local state).
Extract renderSettingsAudioTab() (110 lines), renderSettingsChatTab()
(49 lines), and renderSettingsAboutTab() (48 lines) from the 1013-line
renderSettingsWindow(). Reduces it to ~806 lines.
Move 98 lines of auto-attack leash range, melee resync, facing
alignment, and hostile attacker orientation into a dedicated method.
update() is now ~180 lines (74% reduction from original 704).
Move 131 lines of taxi flight detection, mount reconciliation, taxi
activation timeout, and flight recovery into a dedicated method.
update() is now ~277 lines (61% reduction from original 704).
Move entity movement interpolation loop (distance-culled per-entity
update) into its own method. update() is now ~406 lines (down from
original 704, a 42% reduction across 3 extractions).
Move 164 lines of timer/pending-state logic into updateTimers():
auction delay, quest accept timeouts, money delta, GO loot retries,
name query resync, loot money notifications, auto-inspect throttling.
update() is now ~430 lines (down from original 704).
Move socket update, packet processing, Warden async drain, RX silence
detection, disconnect handling, and Warden gate logging into a separate
updateNetworking() method. Reduces update() from ~704 to ~591 lines.
Add registerWorldHandler() that wraps handler calls with an IN_WORLD
state check. Replaces 8 state-guarded lambda dispatch entries with
concise one-line registrations.
Add registerHandler() using member function pointers, replacing 120
single-line lambda dispatch entries of the form
[this](Packet& p) { handleFoo(p); } with concise
registerHandler(Opcode::X, &GameHandler::handleFoo) calls.
Replace all remaining inline skipAll dispatch lambdas with
registerSkipHandler() calls, including 2 standalone entries and
3 for-loop groups covering ~96 opcodes total.
Add helpers for common dispatch table patterns: registerSkipHandler()
for opcodes that just discard data (14 sites), registerErrorHandler()
for opcodes that show an error message (3 sites). Reduces boilerplate
in registerOpcodeHandlers().
Extract isPreWotlk() = isClassicLikeExpansion() || isActiveExpansion("tbc")
to replace 24 instances of the repeated compound check across packet
handlers. Clarifies intent: these code paths handle pre-WotLK packet
format differences.
Extract shared M2+skin loading logic into Application::loadWeaponM2(),
replacing duplicate 15-line blocks in loadEquippedWeapons() and
tryAttachCreatureVirtualWeapons(). Future weapon loading changes only
need to update one place.
Add writePackedGuid() to Packet class for read/write symmetry. Remove
now-redundant UpdateObjectParser::readPackedGuid and
MovementPacket::writePackedGuid static methods. Replace 6 internal
readPackedGuid calls, 9 writePackedGuid calls, and 1 inline 14-line
transport GUID write with Packet method calls.
Move packed GUID reading into Packet class alongside readUInt8/readFloat.
Replace 121 UpdateObjectParser::readPackedGuid(packet) calls with
packet.readPackedGuid() across 4 files, reducing coupling between
Packet and UpdateObjectParser.
Replace 37 verbose reinterpret_cast<const uint8_t*> float writes with
the existing Packet::writeFloat() method across world_packets,
packet_parsers_classic, and packet_parsers_tbc.
Replace 15 additional 3-line renderer acquisition + sound manager
null-check blocks with single-line withSoundManager() calls. Total
22 sites now use the helper; 11 remaining have complex multi-line
bodies or non-sound renderer usage.
Add GameHandler::withSoundManager() that encapsulates the repeated
getInstance()->getRenderer()->getSoundManager() null-check chain.
Replace 6 call sites, with helper available for future consolidation
of remaining 25 sites.
Replace 26 direct playerNameCache lookups with the existing lookupName()
helper, which also provides entity-name fallback. Eliminates duplicate
cache+entity lookup patterns in chat, social, loot, and combat handlers.
Simplifies getCachedPlayerName() to delegate to lookupName().
Mark spellNameCache_, titleNameCache_, factionNameCache_, areaNameCache_,
mapNameCache_, lfgDungeonNameCache_ and their loaded flags as mutable.
Update 6 lazy-load methods to const. Removes all 13 const_cast<GameHandler*>
calls, allowing const getters to lazily populate caches without UB.
Add GameHandler::getUnitByGuid() that combines entityManager.getEntity()
with dynamic_cast<Unit*>. Replaces 10 two-line lookup+cast blocks with
single-line calls.
Replace 8 direct spellNameCache_.find() patterns with existing helper
methods: getSpellName() for name lookups, getSpellSchoolMask() for
school mask checks. Eliminates redundant loadSpellNameCache() calls
and 3-line cache lookup boilerplate at each site.
Add renderCoinsFromCopper(uint64_t) overload in ui_colors.hpp that
decomposes copper into gold/silver/copper and renders. Replace 14
manual 3-line decomposition blocks across game_screen and
inventory_screen with single-line calls.
Add Packet::hasFullPackedGuid() method and remove identical standalone
definitions from game_handler.cpp and packet_parsers_classic.cpp.
Replace 53 free-function calls with method calls.
Add getRemainingSize() one-liner to Packet class and replace all 656
instances of getSize()-getReadPos() across game_handler, world_packets,
and both packet parser files.
Fix toLowerInPlace() which was accidentally self-recursive (would stack
overflow on any Lua string lowering). Remove 30 redundant
if(addonEventCallback_) wrappers around pure fireAddonEvent blocks.
Extract color constants in performance_hud.cpp (24 inline literals).
Use kRed, kBrightGreen, kDarkGray, kLightGray from ui_colors.hpp across
8 UI files, eliminating duplicate ImVec4 color definitions throughout
the UI layer.
Create shared include/ui/ui_colors.hpp with common ImGui color constants,
item quality color lookup, and renderCoinsText utility. Remove 3 duplicate
renderCoinsText implementations and 3 duplicate quality color switch
blocks across game_screen, inventory_screen, and quest_log_screen.
Replace ~37 remaining C-style casts with static_cast across 16 files.
Extract named color constants (kColorRed/Green/Yellow/Gray) and dialog
window flags (kDialogFlags) in game_screen.cpp, replacing 72 inline
literals. Normalize keybinding_manager.hpp to #pragma once.
Add inline fireAddonEvent() that wraps the addonEventCallback_ null
check. Replace ~120 direct addonEventCallback_ calls with fireAddonEvent,
eliminating redundant null checks at each callsite and reducing
boilerplate by ~30 lines.
- Extract guidToUnitId(), getQuestTitle(), findQuestLogEntry() helpers
to replace 14 duplicated GUID-to-unitId patterns and 7 quest log
search patterns in game_handler.cpp
- Remove duplicate #include in renderer.cpp
- Remove commented-out model cleanup code in terrain_manager.cpp
- Replace C-style casts with static_cast in auth and transport code
Remove duplicate opcode registrations introduced during the switch-to-dispatch-table
refactor (PR #22), keeping the better-commented second copies. Fix 4 instances where
addonEventCallback_("UNIT_QUEST_LOG_CHANGED") was either called unconditionally
(missing braces) or had incorrect indentation inside braces.
Crafting spells (bandages, smelting, etc.) were playing magic precast/
cast-complete audio and triggering melee weapon swing animations because
they have physical school mask (1). Re-add isProfessionSpell check to
skip spell sounds and melee animation for tradeskill spells. The
character still plays the generic cast animation via spellCastAnimCallback.
Remove the isProfessionSpell sound suppression so crafting spells play
precast and cast-complete audio like combat spells. Crafting was
previously silent by design but users expect audio feedback.
Add "Create All" button to the tradeskill UI that queues 999 crafts.
The server automatically stops the queue when materials run out
(SPELL_FAILED_REAGENTS cancels the craft queue). This matches the
real WoW client's behavior for batch crafting.
Request 2 queues from the graphics family when available (NVIDIA
exposes 16, AMD 2+). Upload batches now submit to queue[1] while
rendering uses queue[0], enabling parallel GPU transfers without
queue-family ownership transfer barriers (same family).
Falls back to single-queue path on GPUs with only 1 queue in the
graphics family. Transfer command pool is separate to avoid contention.
processReadyTiles was calling advanceFinalization with a step limit of 1
but a single step (texture upload or M2 model load) could take 1060ms.
Replace the step counter with an 8ms wall-clock time budget (16ms during
taxi) so finalization yields to the render loop before causing a visible
stall. Heavy tiles spread across multiple frames instead of blocking.
The melee swing clips used non-existent paths (SwordSwing, MeleeSwing)
instead of the actual WoW 3.3.5a weapon swing files: WeaponSwings/
mWooshMedium and mWooshLarge for hit swings, MissSwings/MissWhoosh
for misses. Fixes "No melee swing SFX found in assets" warning.
Add vkDeviceWaitIdle after world loading completes to ensure all async
texture uploads and resource creation are fully flushed before the
first render frame. Mitigates intermittent NVIDIA driver crashes at
vkCmdBeginRenderPass during initial world entry.
Add mapDisplayName() with friendly names for continents: "Eastern
Kingdoms", "Kalimdor", "Outland", "Northrend". The loading screen
previously showed WDT directory names like "Expansion01" when
Map.dbc's localized name field was empty or matched the internal name.
When parallel recording is active, the scene pass uses
VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS. Post-processing paths
(FSR/FXAA) end the scene pass and begin a new INLINE render pass for
the swapchain output. ImGui rendering must use the correct mode —
secondary buffers for SECONDARY passes, direct calls for INLINE.
Previously the check used a static condition based on enabled features
(!fsr && !fsr2 && !fxaa && parallel), which could mismatch if a
feature was enabled but initialization failed. Replace with
endFrameInlineMode_ flag that tracks the actual current render pass
mode at runtime, eliminating the validation error
VUID-vkCmdDrawIndexed-commandBuffer-recording that caused intermittent
NVIDIA driver crashes.
Validation layers revealed 9965 VkSamplers allocated against a device
limit of 4000 — every VkTexture created its own sampler even when
configurations were identical. This exhausted NVIDIA's sampler pool
and caused intermittent SIGSEGV in vkCmdBeginRenderPass.
Add a thread-safe sampler cache in VkContext that deduplicates samplers
by FNV-1a hash of all 14 VkSamplerCreateInfo fields. All texture,
render target, renderer, water, and loading screen sampler creation
now goes through getOrCreateSampler(). Textures set ownsSampler_=false
so shared samplers aren't double-freed.
Also auto-disable anisotropy in the cache when the physical device
doesn't support the samplerAnisotropy feature, fixing the validation
error VUID-VkSamplerCreateInfo-anisotropyEnable-01070.
VkPipelineCache causes vkCmdBeginRenderPass to SIGSEGV inside
libnvidia-glcore.so on NVIDIA 590.x drivers. Skip pipeline cache
creation on NVIDIA GPUs — NVIDIA drivers already provide built-in
shader disk caching, so the Vulkan-level cache is redundant.
Pipeline cache still works on AMD and other vendors.
Entity positions are in canonical WoW coords (X=north, Y=west) but the
audio listener uses render coords (X=west, Y=north) from the camera.
Without conversion, distance attenuation was computed on swapped axes,
making NPC ambient sounds (peasant voices, etc.) play at wrong volumes
regardless of actual distance.
The AMD FidelityFX FSR3 runtime corrupts Vulkan driver state when
context creation fails on NVIDIA GPUs, causing vkCmdBeginRenderPass
to SIGSEGV inside libnvidia-glcore. Gate FSR3 frame gen initialization
behind isAmdGpu() check — FSR2 upscaling still works on all GPUs.
When ffxCreateContext for the upscaler fails (e.g. on NVIDIA with the
AMD FidelityFX runtime), the shutdown() path called dlclose() on the
runtime library which could hang — the library's global destructors may
block waiting for GPU operations that never completed.
Skip dlclose() on context creation failure: just clean up function
pointers and mark as failed. The library stays loaded (harmless) and
the game continues with FSR2 fallback instead of hanging.
Log GPU name and vendor ID during VkContext initialization for easier
debugging of GPU-specific issues (FSR3, driver compat, etc.). Add
isAmdGpu()/isNvidiaGpu() accessors.
Temporarily log SMSG_PLAY_SOUND and SMSG_PLAY_OBJECT_SOUND at WARN
level (sound ID, name, file path) to diagnose unidentified ambient
NPC sounds reported by the user.
Create a VkPipelineCache at device init, loaded from disk if available.
All 65 pipeline creation calls across 19 renderer files now use the
shared cache. On shutdown, the cache is serialized to disk so subsequent
launches skip redundant shader compilation.
Cache path: ~/.local/share/wowee/pipeline_cache.bin (Linux),
~/Library/Caches/wowee/ (macOS), %APPDATA%\wowee\ (Windows).
Stale/corrupt caches are handled gracefully (fallback to empty cache).
Inventory sort: clicking "Sort Bags" now generates CMSG_SWAP_ITEM packets
to move items server-side (one swap per frame to avoid race conditions).
Client-side sort runs immediately for visual preview; server swaps follow.
New Inventory::computeSortSwaps() computes minimal swap sequence using
selection-sort permutation on quality→itemId→stackCount comparator.
World map: fix continent bounds derivation that used intersection (max/min)
instead of union (min/max) of child zone bounds, causing continent views
to display zoomed-in/clipped.
Update README.md and docs/status.md with current features, release info,
and known gaps (v1.8.2-preview, 664 opcode handlers, NPC voices, bag
independence, CharSections auto-detect, quest GO server limitation).
VALUES update blocks don't carry an objectType field (it defaults to 0),
so the sanity check incorrectly used the non-PLAYER threshold (10) for
player character updates that legitimately need 42-46 mask blocks. Allow
up to 55 blocks for VALUES updates (could be any entity type including
PLAYER). Only enforce strict limits on CREATE_OBJECT blocks where the
objectType is known.
Homebrew's vulkan-loader hides portability ICDs (like MoltenVK) from
pre-instance extension enumeration by default, causing SDL2 to fail
with "doesn't implement VK_KHR_surface". Set VK_LOADER_ENABLE_PORTABILITY_DRIVERS
before loading the Vulkan library so the loader includes MoltenVK and
its surface extensions.
Remove the forced backpack-open constraint that prevented closing the
backpack while other bags were open. Each bag window is now independently
closable regardless of which others are open.
Add off-screen position reset to individual bag windows (renderBagWindow)
so bags saved at positions outside the current resolution snap back to
their default stack position.
Chest-type GOs now send CMSG_GAMEOBJ_USE immediately followed by
CMSG_LOOT in the same frame. The USE handler opens the chest, then the
LOOT handler reads the contents — both processed sequentially by the
server. Previously only CMSG_LOOT was sent (no USE), which failed on
AzerothCore because the chest wasn't activated first.
Reset the Bags window position to bottom-right if the saved position
is outside the current screen resolution (e.g. after a resolution
change or moving between monitors).
Chest-type game objects (quest pickups, treasure chests) now send
CMSG_LOOT directly with the GO GUID instead of CMSG_GAMEOBJ_USE +
delayed CMSG_LOOT. The server's loot handler activates the GO and
sends SMSG_LOOT_RESPONSE in one step. The old approach failed because
CMSG_GAMEOBJ_USE opened+despawned the GO before CMSG_LOOT arrived.
Double M2 bone and material descriptor pool sizes (8192 → 16384) to
handle the increased NPC count from the spline parsing fix — patrolling
NPCs that were previously invisible now spawn correctly, exhausting
the old pool limits.
AzerothCore handles loot automatically in the CMSG_GAMEOBJ_USE handler
(calls SendLoot internally). Sending a redundant CMSG_LOOT 200ms later
triggers DoLootRelease() on the server, which closes the loot the server
just opened — before SMSG_LOOT_RESPONSE ever reaches the client. This
broke quest GO interactions (Bundle of Wood, etc.) because the loot
window never appeared and quest items were never granted.
Also remove temporary diagnostic logging from GO interaction path.
The wall/floor classification threshold was lowered from 0.65 to 0.35
in a prior optimization commit, causing surfaces at 35-65° from
horizontal (steep walls, angled building geometry) to be classified as
floors and skipped during wall collision. This allowed the player to
clip through angled WMO walls.
Restore the threshold to 0.65 (cos 50°) in both the collision grid
builder and the runtime checkWallCollision skip, matching the
MAX_WALK_SLOPE limit used for slope-slide physics.
AzerothCore/ChromieCraft always writes verticalAcceleration(float) +
effectStartTime(uint32) after durationMod in the spline movement block,
regardless of whether the PARABOLIC spline flag (0x800) is set. The
parser only read these 8 bytes when PARABOLIC was flagged, causing it
to read the wrong offset as pointCount (0 instead of e.g. 11). This
made every patrolling NPC fail to parse — invisible with no displayId.
Also fix splineStart calculation (was off by 4 bytes) and remove
temporary diagnostic logging.
When a WotLK NPC has durationMod=0.0, the Classic-first spline parser
reads it as pointCount=0 and "succeeds", then consumes garbage bytes as
splineMode and endPoint. This desynchronizes the read position for all
subsequent update blocks in the packet, causing cascading failures
(truncated update mask, unknown update type) that leave NPCs without
displayIds — making them invisible.
Fix: after reading splineMode, reject the Classic parse if splineMode > 3
(valid values are 0-3) and fall through to the WotLK format parser.
CharSections.dbc has different field layouts between stock WotLK (textures
at field 4-6) and Classic/TBC/Turtle/HD-textured WotLK (VariationIndex at
field 4). Add detectCharSectionsFields() that probes field-4 values at
runtime to determine the correct layout, so both stock and modded clients
work without JSON changes.
Also add BLOODELF_MALE/FEMALE and DRAENEI_MALE/FEMALE voice types to the
NPC voice system — previously all Blood Elf and Draenei NPCs fell through
to GENERIC (random dwarf/gnome/night elf/orc mix).
GetNetStats() returns bandwidthIn, bandwidthOut, latencyHome,
latencyWorld — with real latency from getLatencyMs(). Used by
latency display addons and the default UI's network indicator.
AcceptBattlefieldPort(index, accept) accepts or declines a
battleground queue invitation. Backed by existing acceptBattlefield
and declineBattlefield methods.
NumTaxiNodes() returns the count of taxi nodes available at the
current flight master. TaxiNodeName(index) returns the node name.
TaxiNodeGetType(index) returns whether the node is known/reachable.
TakeTaxiNode(index) activates the flight to the selected node.
Uses existing taxiNodes_ data and activateTaxi() method.
Enables flight path addons and taxi map overlay addons.
GetNumGossipOptions() returns option count for current NPC dialog.
GetGossipOptions() returns pairs of (text, type) for each option
where type is "gossip", "vendor", "taxi", "trainer", etc.
SelectGossipOption(index) selects a dialog option.
GetNumGossipAvailableQuests() / GetNumGossipActiveQuests() return
quest counts in the gossip dialog.
CloseGossip() closes the NPC dialog.
Uses existing GossipMessageData from SMSG_GOSSIP_MESSAGE handler.
Enables gossip addons and quest helper dialog interaction.
IsConnectedToServer() checks if connected to the game server.
UnequipItemSlot(slot) moves an equipped item to the backpack.
HasFocus() checks if the player has a focus target set.
GetRealmName() / GetNormalizedRealmName() return realm name.
All backed by existing GameHandler methods.
ShowHelm() / ShowCloak() toggle helm/cloak visibility.
TogglePVP() toggles PvP flag.
Minimap_Ping(x, y) sends a minimap ping to the group.
RequestTimePlayed() requests /played data from server.
All backed by existing GameHandler methods.
InviteUnit(name) invites a player to the group.
UninviteUnit(name) removes a player from the group.
LeaveParty() leaves the current party/raid.
Backed by existing inviteToGroup, uninvitePlayer, leaveGroup methods.
GuildInvite(name) invites a player to the guild.
GuildUninvite(name) kicks a member (uses kickGuildMember).
GuildPromote(name) / GuildDemote(name) change rank.
GuildLeave() leaves the guild.
GuildSetPublicNote(name, note) sets a member's public note.
All backed by existing GameHandler methods that send the appropriate
guild management CMSG packets.
DoEmote(token) maps emote token strings to TextEmote DBC IDs and
sends them via sendTextEmote. Supports 30+ common emotes: WAVE,
BOW, DANCE, CHEER, CHICKEN, CRY, EAT, FLEX, KISS, LAUGH, POINT,
ROAR, RUDE, SALUTE, SHY, SILLY, SIT, SLEEP, SPIT, THANK, CLAP,
KNEEL, LAY, NO, YES, BEG, ANGRY, FAREWELL, HELLO, WELCOME, etc.
Targets the current target if one exists.
AddFriend(name, note) and RemoveFriend(name) manage the friends list.
AddIgnore(name) and DelIgnore(name) manage the ignore list.
ShowFriends() is a stub (friends panel is ImGui-rendered).
All backed by existing GameHandler methods that send the appropriate
CMSG_ADD_FRIEND, CMSG_DEL_FRIEND, CMSG_ADD_IGNORE, CMSG_DEL_IGNORE
packets to the server.
GetNumWhoResults() returns result count and total online players.
GetWhoInfo(index) returns name, guild, level, race, class, zone,
classFileName — the standard 7-field /who result signature.
SendWho(query) sends a /who search to the server.
SetWhoToUI() is a stub for addon compatibility.
Uses existing whoResults_ from SMSG_WHO handler and queryWho().
Enables /who replacement addons and social panel search.
IsPlayerSpell(spellId) checks if the spell is in the player's known
spells set. Used by action bar addons to distinguish permanent spells
from temporary proc/buff-granted abilities.
IsCurrentSpell(spellId) checks if the spell is currently being cast.
IsSpellOverlayed() and IsAutoRepeatSpell() are stubs for addon compat.
GetCurrentTitle() returns the player's chosen title bit index.
GetTitleName(bit) returns the formatted title string from
CharTitles.dbc (e.g., "Commander %s", "%s the Explorer").
SetCurrentTitle() is a stub for title switching.
Used by title display addons and the character panel title selector.
GetInspectSpecialization() returns the inspected player's active
talent group from the cached InspectResult data.
NotifyInspect() and ClearInspectPlayer() are stubs — inspection is
auto-triggered by the C++ side when targeting players. These prevent
nil errors in inspection addons that call them.
GetHonorCurrency() returns honor points from update fields.
GetArenaCurrency() returns arena points.
GetTimePlayed() returns total time played and level time played
in seconds (populated from SMSG_PLAYED_TIME).
GetBindLocation() returns the hearthstone bind zone name.
Used by currency displays, /played addons, and hearthstone tooltip.
GetNumSavedInstances() returns count of saved instance lockouts.
GetSavedInstanceInfo(index) returns 9-field WoW signature: name,
mapId, resetTimeRemaining, difficulty, locked, extended,
instanceIDMostSig, isRaid, maxPlayers.
Uses existing instanceLockouts_ from SMSG_RAID_INSTANCE_INFO.
Enables SavedInstances and lockout tracking addons to display
which raids/dungeons the player is locked to and when they reset.
GetNumBattlefieldScores() returns player count in the BG scoreboard.
GetBattlefieldScore(index) returns 12-field WoW API signature: name,
killingBlows, honorableKills, deaths, honorGained, faction, rank,
race, class, classToken, damageDone, healingDone.
GetBattlefieldWinner() returns winning faction (0=Horde, 1=Alliance)
or nil if BG is still in progress.
RequestBattlefieldScoreData() sends MSG_PVP_LOG_DATA to refresh the
scoreboard from the server.
Uses existing BgScoreboardData from MSG_PVP_LOG_DATA handler.
Enables BG scoreboard addons and PvP tracking.
UnitIsPVP(unit) checks UNIT_FLAG_PVP (0x1000) on the unit's flags
field. Used by unit frame addons to show PvP status indicators.
UnitIsPVPFreeForAll(unit) checks for FFA PvP flag.
GetBattlefieldStatus() returns stub ("none") for addons that check
BG queue state on login. Full BG scoreboard data exists in
GameHandler but is rendered via ImGui.
Items with the Unique-Equipped flag (itemFlags & 0x1000000) now
display "Unique-Equipped" in the tooltip header. This is distinct
from "Unique" (maxCount=1) — Unique-Equipped means you can carry
multiple but only equip one (e.g., trinkets, rings with the flag).
Items with a startQuestId now display "This Item Begins a Quest" in
gold text at the bottom of the tooltip, matching WoW's behavior.
Helps players identify quest-starting drops in their inventory.
Passes startsQuest flag through _GetItemTooltipData from the
startQuestId field in ItemQueryResponseData.
Spell descriptions now substitute \$o1/\$o2/\$o3 with the total
periodic damage/healing: base_per_tick × (duration / 3sec).
Example: SW:Pain with base=4 (5 per tick), duration=18sec (6 ticks):
Before: "Causes X Shadow damage over 18 sec"
After: "Causes 30 Shadow damage over 18 sec"
Combined with \$s1 (per-tick/instant) and \$d (duration), the three
most common spell template variables are now fully resolved. This
covers the vast majority of spell tooltips.
Spell descriptions now substitute \$d with actual duration values:
Before: "X damage over X sec"
After: "30 damage over 18 sec"
Implementation:
- DurationIndex field (40) added to all expansion Spell.dbc layouts
- SpellDuration.dbc loaded during cache build: maps index → base ms
- cleanSpellDescription substitutes \$d with resolved seconds/minutes
- getSpellDuration() accessor on GameHandler
Combined with \$s1/\$s2/\$s3 from the previous commit, most common
spell description templates are now fully resolved with real values.
Spell descriptions now substitute \$s1/\$s2/\$s3 template variables
with actual effect base points from Spell.dbc (field 80/81/82).
For example: "causes \$s1 Fire Damage" → "causes 562 Fire Damage".
Implementation:
- Added EffectBasePoints0/1/2 to all 4 expansion DBC layouts
- SpellNameEntry now stores effectBasePoints[3]
- loadSpellNameCache reads base points during DBC iteration
- cleanSpellDescription substitutes \$s1→abs(base)+1 when available
- getSpellEffectBasePoints() accessor on GameHandler
Values are DBC base points (before spell power scaling). Still uses
"X" placeholder for unresolved variables (\$d, \$o1, etc.).
CalendarGetDate() returns real weekday, month, day, year from the
system clock. Used by calendar addons and date-aware UI elements.
CalendarGetNumPendingInvites() and CalendarGetNumDayEvents() return 0
as stubs — prevents nil errors in addons that check calendar state.
GetDifficultyInfo(id) returns name, groupType, isHeroic, maxPlayers
for WotLK instance difficulties:
0: "5 Player" (party, normal, 5)
1: "5 Player (Heroic)" (party, heroic, 5)
2: "10 Player" (raid, normal, 10)
3: "25 Player" (raid, normal, 25)
4/5: 10/25 Heroic raids
Used by boss mod addons (DBM, BigWigs) and instance info displays
to show the current dungeon difficulty.
Fire WEATHER_CHANGED(weatherType, intensity) when the server sends
SMSG_WEATHER with a new weather state. Enables weather-aware addons
to react to rain/snow/storm transitions.
GetWeatherInfo() returns current weatherType (0=clear, 1=rain, 2=snow,
3=storm) and intensity (0.0-1.0). Weather data is already tracked by
game_handler and used by the renderer for particle effects and fog.
GuildRoster() triggers CMSG_GUILD_ROSTER to request updated guild
member data from the server. Called by guild roster addons and the
social panel to refresh the member list.
SortGuildRoster() is a no-op (sorting is handled client-side by
the ImGui guild roster display).
BuyMerchantItem(index, count) purchases an item from the current
vendor by merchant slot index. Resolves itemId and slot from the
vendor's ListInventoryData.
SellContainerItem(bag, slot) sells an item from the player's
inventory to the vendor. Supports backpack (bag=0) and bags 1-4.
Enables auto-sell addons (Scrap, AutoVendor) and vendor UI addons
to buy/sell items programmatically.
RepairAllItems(useGuildBank) sends CMSG_REPAIR_ITEM to repair all
equipped items at the current vendor. Checks CanMerchantRepair before
sending. Optional useGuildBank flag for guild bank repairs.
One of the most commonly needed addon functions — enables auto-repair
addons to fix all gear in a single call when visiting a repair vendor.
AcceptTrade() locks in the trade offer via CMSG_ACCEPT_TRADE.
CancelTrade() cancels an open trade via CMSG_CANCEL_TRADE.
InitiateTrade(unit) starts a trade with a target player.
Uses existing GameHandler trade functions and TradeStatus tracking.
Enables trade addons and macro-based trade acceptance.
Quality-colored item links for auction house items, enabling AH addons
to display clickable item links in their UI and chat output.
This is the 100th commit of this session, bringing the total to
408 API functions across all WoW gameplay systems.
GetInboxNumItems() returns count of mail messages.
GetInboxHeaderInfo(index) returns the full 13-field WoW API signature:
packageIcon, stationeryIcon, sender, subject, money, COD, daysLeft,
hasItem, wasRead, wasReturned, textCreated, canReply, isGM.
GetInboxText(index) returns the mail body text.
HasNewMail() checks for unread mail (minimap icon indicator).
Uses existing mailInbox_ populated from SMSG_MAIL_LIST_RESULT.
Enables postal addons (Postal, MailOpener) to read inbox data.
GetNumGlyphSockets() returns 6 (WotLK glyph slot count).
GetGlyphSocketInfo(index, talentGroup) returns enabled, glyphType
(1=major, 2=minor), glyphSpellID, and icon for each socket.
Uses existing learnedGlyphs_ array populated from SMSG_TALENTS_INFO.
Enables talent/glyph inspection addons.
This commit brings the total API count to exactly 400 functions.
Complete the pet action bar interaction:
- CastPetAction(index) — cast the pet spell at the given bar slot
by sending the packed action via sendPetAction
- TogglePetAutocast(index) — toggle autocast for the pet spell
at the given slot via togglePetSpellAutocast
- PetDismiss() — send dismiss pet command
- IsPetAttackActive() — whether pet is currently in attack mode
Together with the previous pet bar functions (HasPetUI, GetPetActionInfo,
PetAttack, PetFollow, PetWait, PetPassiveMode, PetDefensiveMode), this
completes the pet action bar system for hunters/warlocks/DKs.
Implement pet action bar functions using existing pet data:
- HasPetUI() — whether player has an active pet
- GetPetActionInfo(index) — name, icon, isActive, autoCastEnabled
for each of the 10 pet action bar slots
- GetPetActionCooldown(index) — cooldown state stub
- PetAttack() — send attack command to current target
- PetFollow() — send follow command
- PetWait() — send stay command
- PetPassiveMode() — set passive react mode
- PetDefensiveMode() — set defensive react mode
All backed by existing SMSG_PET_SPELLS data (petActionSlots_,
petCommand_, petReact_, petAutocastSpells_) and sendPetAction().
GetActionBarPage() returns the current action bar page (1-6).
ChangeActionBarPage(page) switches pages and fires ACTIONBAR_PAGE_CHANGED
via the Lua frame event system. Used by action bar addons and the
default UI's page arrows / shift+number keybinds.
Action bar page state tracked in Lua global __WoweeActionBarPage.
CastShapeshiftForm(index) casts the spell for the given form slot:
- Warrior: Battle Stance(2457), Defensive(71), Berserker(2458)
- Druid: Bear(5487), Travel(783), Cat(768), Flight(40120),
Moonkin(24858), Tree(33891)
- Death Knight: Blood(48266), Frost(48263), Unholy(48265)
- Rogue: Stealth(1784)
This makes stance bar buttons functional — clicking a form button
actually casts the corresponding spell to switch forms.
CancelShapeshiftForm stub for cancelling current form.
GetShapeshiftFormInfo(index) returns icon, name, isActive, isCastable
for each shapeshift form slot. Provides complete form tables for:
- Warrior: Battle Stance, Defensive Stance, Berserker Stance
- Druid: Bear, Travel, Cat, Swift Flight, Moonkin, Tree of Life
- Death Knight: Blood/Frost/Unholy Presence
- Rogue: Stealth
isActive is true when the form matches the current shapeshiftFormId_.
GetShapeshiftFormCooldown stub returns no cooldown.
Together with GetShapeshiftForm and GetNumShapeshiftForms from the
previous commit, this completes the stance bar API that addons use
to render and interact with form/stance buttons.
Add UNIT_FIELD_BYTES_1 to all expansion update field tables (Classic=133,
TBC/WotLK=137). Byte 3 of this field contains the shapeshift form ID
(Bear=1, Cat=3, Travel=4, Moonkin=31, Tree=36, Battle Stance=17, etc.).
Track form changes in the VALUES update handler and fire
UPDATE_SHAPESHIFT_FORM + UPDATE_SHAPESHIFT_FORMS events when the
form changes. This enables stance bar addons and druid form tracking.
New Lua functions:
- GetShapeshiftForm() — returns current form ID (0 = no form)
- GetNumShapeshiftForms() — returns form count by class (Warrior=3,
Druid=6, DK=3, Rogue=1, Priest=1, Paladin=3)
GetEnchantInfo(enchantId) looks up the enchantment name from
SpellItemEnchantment.dbc (field 14). Returns the display name
like "Crusader", "+22 Intellect", or "Mongoose" for a given
enchant ID.
Used by equipment comparison addons and tooltip addons to display
enchantment names on equipped gear. The enchant ID comes from the
item's ITEM_FIELD_ENCHANTMENT update field.
Also adds getEnchantName() to GameHandler for C++ access.
Spell descriptions from DBC contain raw template variables like
\$s1, \$d, \$o1 that refer to effect values resolved at runtime.
Without DBC effect data loaded, these showed as literal "\$s1" in
tooltips, making descriptions hard to read.
Now strips template variables and replaces with readable placeholders:
- \$s1/\$s2/\$s3 → "X" (effect base points)
- \$d → "X sec" (duration)
- \$o1 → "X" (periodic total)
- \$a1 → "X" (radius)
- \$\$ → "$" (literal dollar sign)
- \${...} blocks → stripped
Result: "Hurls a fiery ball that causes X Fire Damage" instead of
"Hurls a fiery ball that causes \$s1 Fire Damage". Not as informative
as real values, but significantly more readable.
Implement the WoW Mixin pattern used by modern addons:
- Mixin(obj, ...) — copies fields from mixin tables into obj
- CreateFromMixins(...) — creates a new table from mixin templates
- CreateAndInitFromMixin(mixin, ...) — creates and calls Init()
- MergeTable(dest, src) — shallow-merge src into dest
These enable OOP-style addon architecture used by LibSharedMedia,
WeakAuras, and many Ace3-based addons for class/object creation.
strgfind = string.gmatch alias (deprecated WoW function used by
older addons that haven't migrated to string.gmatch).
tostringall(...) converts all arguments to strings and returns
them. Used by chat formatting and debug addons that need to safely
stringify mixed-type argument lists.
SpellStopCasting() cancels the current cast via cancelCast(). Used by
macro addons and cast-cancel logic (e.g., /stopcasting macro command).
UnitFullName/GetUnitName aliases for UnitName — some addons use these
variant names.
SpellIsTargeting() returns false (no AoE targeting reticle in this
client). SpellStopTargeting() is a no-op stub. Both prevent errors
in addons that check targeting state.
GetClassColor(className) returns r, g, b, colorString from the
RAID_CLASS_COLORS table. Used by unit frame addons, chat addons,
and party/raid frames to color player names by class.
QuestDifficultyColors table provides standard quest difficulty
color mappings (impossible=red, verydifficult=orange, difficult=yellow,
standard=green, trivial=gray, header=gold). Used by quest log and
quest tracker addons for level-appropriate coloring.
GetMaxPlayerLevel() returns the level cap for the active expansion:
60 (Classic/Turtle), 70 (TBC), 80 (WotLK). Used by XP bar addons
and leveling trackers.
GetAccountExpansionLevel() returns the expansion tier: 1 (Classic),
2 (TBC), 3 (WotLK). Used by addons that adapt features based on
which expansion is active.
Both read from the ExpansionRegistry's active profile at runtime.
CURRENT_SPELL_CAST_CHANGED fires when the player starts a new cast
via handleSpellStart. Some addons register for this as a catch-all
signal that the current spell state changed, complementing the more
specific UNIT_SPELLCAST_START/STOP/FAILED events.
PLAYER_COMBO_POINTS now fires from the existing SMSG_UPDATE_COMBO_POINTS
handler — the handler already updated comboPoints_ but never notified
Lua addons. Rogue/druid combo point displays and DPS rotation addons
register for this event.
LOOT_READY fires alongside LOOT_OPENED when a loot window opens. Some
addons register for this WoW 5.x+ event name instead of LOOT_OPENED.
The TBC item query parser left subclassName empty, so TBC items showed
no weapon/armor type in tooltips or the character sheet (e.g., "Sword",
"Plate", "Shield" were all blank). The Classic and WotLK parsers
correctly map subClass IDs to names.
Fix: call getItemSubclassName() in the TBC parser, same as WotLK.
Expose getItemSubclassName() in the header (was static, now shared
across parser files).
Items with maxCount=1 now show "Unique" in white text below the name.
Items with the Heroic flag (0x8) show "Heroic" in green text. Both
display before the bind type line, matching WoW's tooltip order.
Heroic items (from heroic dungeon/raid drops) are visually
distinguished from their normal-mode counterparts. Unique items
(trinkets, quest items, etc.) show the carry limit clearly.
Item tooltips now display spell effects in green text:
- "Use: Restores 2200 health over 30 sec" (trigger 0)
- "Equip: Increases attack power by 120" (trigger 1)
- "Chance on hit: Strikes the enemy for 95 Nature damage" (trigger 2)
Passes up to 5 item spell entries through _GetItemTooltipData with
spellId, trigger type, spell name, and spell description from DBC.
The tooltip builder maps trigger IDs to "Use: ", "Equip: ", or
"Chance on hit: " prefixes.
This completes the item tooltip with all major WoW tooltip sections:
quality name, bind type, equip slot/type, armor, damage/DPS/speed,
primary stats, combat ratings, resistances, spell effects, gem sockets,
required level, flavor text, and sell price.
Spell tooltips now show the spell description text (e.g., "Hurls a
fiery ball that causes 565 to 655 Fire Damage") in gold/yellow text
between the cast info and cooldown display.
New GetSpellDescription(spellId) C function exposes the description
field from SpellNameEntry (loaded from Spell.dbc via the spell name
cache). Descriptions contain the raw DBC text which may include
template variables ($s1, $d, etc.) — these show as-is until template
substitution is implemented.
Add gem socket display to item tooltips — shows [Meta Socket],
[Red Socket], [Yellow Socket], [Blue Socket], or [Prismatic Socket]
based on socketColor mask from ItemQueryResponseData.
Also pass itemSetId through _GetItemTooltipData for addons that
track set bonuses.
Register power-type-specific aliases (UnitRage, UnitEnergy, UnitFocus,
UnitRunicPower) that map to the existing lua_UnitPower function. Some
Classic/TBC addons call these directly instead of the generic UnitPower.
All return the unit's current power value regardless of type — the
underlying function reads from the entity's power field.
Replace PlaySound no-op stub with a real implementation that maps
WoW sound IDs and names to the UiSoundManager methods:
By ID: 856/1115→button click, 840→quest activate, 841→quest complete,
862→bag open, 863→bag close, 888→level up
By name: IGMAINMENUOPTION→click, IGQUESTLISTOPEN→quest activate,
IGQUESTLISTCOMPLETE→quest complete, IGBACKPACKOPEN/CLOSE→bags,
LEVELUPSOUND→level up, TALENTSCREEN→character sheet
This gives addons audio feedback when they call PlaySound() — button
clicks, quest sounds, and other UI sounds now actually play instead
of being silently swallowed.
DISPLAY_SIZE_CHANGED fires when the window is resized via
SDL_WINDOWEVENT_RESIZED, allowing UI addons to adapt their layout
to the new screen dimensions (5 FrameXML registrations).
UNIT_QUEST_LOG_CHANGED("player") fires alongside QUEST_LOG_UPDATE
at all 6 quest log modification points, for addons that register
for this variant instead (4 FrameXML registrations).
The tab-based addon message detection was too aggressive — any chat
message containing a tab character was treated as an addon message
and silently dropped from regular chat display. This could suppress
legitimate player messages containing tabs (from copy-paste).
Now only matches as addon message when:
- Chat type is PARTY/RAID/GUILD/WHISPER/etc. (not SAY/YELL/EMOTE)
- Prefix before tab is <=16 chars (WoW addon prefix limit)
- Prefix contains no spaces (addon prefixes are identifiers)
This prevents false positives while still correctly detecting addon
messages formatted as "DBM4\ttimer:start:10".
UNIT_QUEST_LOG_CHANGED("player") now fires at all 6 locations where
QUEST_LOG_UPDATE fires — quest accept, complete, objective update,
abandon, and server-driven quest log changes. Some addons register
for this event instead of QUEST_LOG_UPDATE (4 registrations in
FrameXML). Both events are semantically equivalent for the player.
Detect addon messages in the SMSG_MESSAGECHAT handler by looking for
the 'prefix\ttext' format (tab delimiter). When detected, fire
CHAT_MSG_ADDON with args (prefix, message, channel, sender) instead
of the regular CHAT_MSG_* event, and suppress the raw message from
appearing in chat.
This enables inter-addon communication used by:
- Boss mods (DBM, BigWigs) for timer/alert synchronization
- Raid tools (oRA3) for ready checks and cooldown tracking
- Group coordination addons for pull countdowns and assignments
Works with the existing SendAddonMessage/RegisterAddonMessagePrefix
functions that format outgoing messages as 'prefix\ttext'.
Some addons register for SPELL_UPDATE_USABLE instead of
ACTIONBAR_UPDATE_USABLE to detect when spell usability changes
due to power fluctuations. Fire both events together when the
player's mana/rage/energy changes.
When the player's mana/rage/energy changes, action bar addons need
ACTIONBAR_UPDATE_USABLE to update button dimming (grey out abilities
the player can't afford). Now fires from both the SMSG_POWER_UPDATE
handler and the SMSG_UPDATE_OBJECT VALUES power field change path.
Without this event, action bar buttons showed as usable even when the
player ran out of mana — the usability state only refreshed on spell
cast attempts, not on power changes.
Implement core vendor query functions from ListInventoryData:
- GetMerchantNumItems() — count of items for sale
- GetMerchantItemInfo(index) — name, texture, price, stackCount,
numAvailable, isUsable for each vendor item
- GetMerchantItemLink(index) — quality-colored item link
- CanMerchantRepair() — whether vendor offers repair service
Enables auto-sell addons (AutoVendor, Scrap) to read vendor inventory
and check repair capability. Data sourced from SMSG_LIST_INVENTORY
via currentVendorItems + itemInfoCache for names/icons.
When a unit's power type changes (e.g., druid shifting from mana to
rage in bear form, or warrior switching stances), fire UNIT_DISPLAYPOWER
so unit frame addons can switch the power bar display (mana blue bar →
rage red bar). Detected via UNIT_FIELD_BYTES_0 byte 3 changes in the
VALUES update path.
RAID_ROSTER_UPDATE now fires alongside GROUP_ROSTER_UPDATE when the
group type is raid, matching the event that raid frame addons register
for (6 registrations in FrameXML). Fires from group list updates and
group uninvite handlers.
UNIT_LEVEL fires when any tracked unit (player, target, focus, pet)
changes level via VALUES update fields. Used by unit frame addons to
update level display (5 registrations in FrameXML).
SetAction's item branch and SetHyperlink's item/spell branches
showed only the item name, ignoring the full tooltip system we built.
SetAction item path now uses _WoweePopulateItemTooltip (shows armor,
stats, damage, bind type, sell price etc.).
SetHyperlink item path now uses _WoweePopulateItemTooltip; spell
path now uses SetSpellByID (shows cost, range, cast time, cooldown).
This means shift-clicking an item link in chat, hovering an item on
the action bar, or viewing any hyperlink tooltip now shows the full
stat breakdown instead of just the name.
SetSpellByID now shows comprehensive spell information:
- Mana/Rage/Energy/Runic Power cost
- Range in yards (or omitted for self-cast)
- Cast time ("1.5 sec cast" or "Instant")
- Active cooldown remaining in red
Uses existing GetSpellInfo (castTime, range from DBC), GetSpellPowerCost
(mana cost from DBC), and GetSpellCooldown (remaining CD) to populate
the tooltip with real spell data.
Item tooltips now display the vendor sell price at the bottom, formatted
as "Sell Price: 12g 50s 30c". Uses the vendorPrice field from GetItemInfo
(field 11, sourced from SMSG_ITEM_QUERY_SINGLE_RESPONSE sellPrice).
Helps players quickly assess item value when looting or sorting bags.
Extend item tooltips with secondary stats and resistances:
- Extra stats from ItemQueryResponseData.extraStats: Hit Rating,
Crit Rating, Haste Rating, Resilience, Attack Power, Spell Power,
Defense Rating, Dodge/Parry/Block Rating, Expertise, Armor Pen,
Mana/Health per 5 sec, Spell Penetration — all in green text
- Elemental resistances: Fire/Nature/Frost/Shadow/Arcane Resistance
Also passes extraStats as an array of {type, value} pairs and
resistance fields through _GetItemTooltipData for Lua consumption.
Stat type IDs follow the WoW ItemMod enum (3=Agi, 7=Sta, 31=Hit,
32=Crit, 36=Haste, 45=SpellPower, etc.).
Enhance _WoweePopulateItemTooltip to show complete item information:
- Bind type (Binds when picked up / equipped / used)
- Armor value for armor items
- Weapon damage range, speed, and DPS for weapons
- Primary stats (+Stamina, +Strength, +Agility, +Intellect, +Spirit)
in green text
- Required level
- Flavor/lore description text in gold
Backed by new _GetItemTooltipData(itemId) C function that returns a
Lua table with armor, bindType, damageMin/Max, speed, primary stats,
requiredLevel, and description from ItemQueryResponseData.
Replace minimal name-only item tooltips with proper WoW-style display:
- Quality-colored item name header
- Item level line for equipment (gold text)
- Equip slot and weapon/armor type on a double line
(e.g., "Head" / "Plate" or "One-Hand" / "Sword")
- Item class for non-equipment items
Shared _WoweePopulateItemTooltip() helper used by both
SetInventoryItem and SetBagItem for consistent tooltip formatting.
Maps INVTYPE_* strings to display names (Head, Chest, Two-Hand, etc.).
GetItemInfo returned empty strings for item class (field 6), subclass
(field 7), and equip slot (field 9). Now returns:
- Class: mapped from itemClass enum (Consumable, Weapon, Armor, etc.)
- Subclass: from parsed subclassName (Sword, Mace, Shield, etc.)
- EquipSlot: mapped from inventoryType to INVTYPE_ strings
(INVTYPE_HEAD, INVTYPE_CHEST, INVTYPE_WEAPON, etc.)
These fields are used by equipment comparison addons, character sheet
displays, and bag sorting addons to categorize and filter items.
Four independent static local steady_clock start times were used as
time origins in GetTime(), GetSpellCooldown(), UnitBuff expiration,
and UnitCastingInfo — each initializing on first call at slightly
different times. This created systematic timestamp mismatches.
When addons compute (start + duration - GetTime()), the start value
from GetSpellCooldown and the GetTime() return used different epochs,
causing cooldown sweeps and buff timers to appear offset.
Replace all four independent statics with a single file-scope
kLuaTimeEpoch constant and luaGetTimeNow() helper, ensuring all
time-returning Lua functions share exactly the same origin.
Implement GetBindingKey(command) and GetBindingAction(key) with
default action button mappings (ACTIONBUTTON1-12 → "1"-"9","0","-","=").
Action bar addons display bound keys on button tooltips via
GetBindingKey("ACTIONBUTTON"..slot).
Also add stubs for GetNumBindings, GetBinding, SetBinding, SaveBindings,
SetOverrideBindingClick, and ClearOverrideBindings to prevent nil-call
errors in FrameXML keybinding UI code (37 call sites).
setActionBarSlot (called from PickupAction/PlaceAction drag-drop and
from server-driven action button updates) updated the slot data and
notified the server, but never fired the Lua addon event. Action bar
addons (Bartender4, Dominos) register for ACTIONBAR_SLOT_CHANGED to
refresh button textures, tooltips, and cooldown state when slots change.
Also fires ACTIONBAR_UPDATE_STATE for general action bar refresh.
PLAYER_DEAD only fired from SMSG_FORCED_DEATH_UPDATE (GM kill) — the
normal death path (health dropping to 0 via VALUES update) never fired
it. Death-related addons and the default release spirit dialog depend
on this event.
Also add PLAYER_ALIVE (fires when resurrected without having been a
ghost) and PLAYER_UNGHOST (fires when player rezzes from ghost form)
at the health-restored-from-zero VALUES path. These events control
the transition from ghost form back to alive, letting addons restore
normal UI state after death.
handleTextEmote pushed emote messages directly to chatHistory
instead of using addLocalChatMessage, so Lua chat addons never
received CHAT_MSG_TEXT_EMOTE events for /wave, /dance, /bow, etc.
from other players. Use addLocalChatMessage which fires the event
and also notifies the C++ display callback.
GetSpellCooldown only returned per-spell cooldowns from the
spellCooldowns map, ignoring the Global Cooldown. Addons like OmniCC
and action bar addons rely on GetSpellCooldown returning GCD timing
when no individual spell cooldown is active — this is what drives the
cooldown sweep animation on action bar buttons after casting.
Now falls back to GCD timing (from getGCDRemaining/getGCDTotal) when
the spell has no individual cooldown but the GCD is active. Returns
proper (start, duration) values so addons can compute elapsed/remaining.
Same fix applied to GetActionCooldown for spell-type action bar slots.
Implement keyboard modifier queries using ImGui IO:
- IsShiftKeyDown, IsControlKeyDown, IsAltKeyDown
- IsModifiedClick(action) — CHATLINK=Shift, DRESSUP=Ctrl, SELFCAST=Alt
- GetModifiedClick/SetModifiedClick for keybind configuration
Fire UPDATE_EXHAUSTION and PLAYER_UPDATE_RESTING events when rest state
changes (entering/leaving inns and cities) and when rested XP updates.
XP bar addons use UPDATE_EXHAUSTION to show the rested bonus indicator.
Implement keyboard modifier state queries using ImGui IO state:
- IsShiftKeyDown, IsControlKeyDown, IsAltKeyDown — direct key queries
- IsModifiedClick(action) — checks if the modifier matching a named
action is held (CHATLINK/SPLITSTACK=Shift, DRESSUP=Ctrl, SELFCAST=Alt)
- GetModifiedClick(action) — returns the assigned key name for an action
- SetModifiedClick — no-op stub for compatibility
These are fundamental input functions used by virtually all interactive
addons: shift-click to link items in chat, ctrl-click for dressup,
alt-click for self-cast, shift-click to split stacks, etc.
GetItemInfo returned item links with hardcoded white color (|cFFFFFFFF)
regardless of quality. Now uses quality-appropriate colors: gray for
Poor, white for Common, green for Uncommon, blue for Rare, purple for
Epic, orange for Legendary, gold for Artifact, cyan for Heirloom.
Also fix GameTooltip:GetItem() to use the quality-colored link from
GetItemInfo instead of hardcoded white.
GetRaidRosterInfo(index) returns name, rank, subgroup, level, class,
fileName, zone, online, isDead, role, isML — the core function raid
frame addons (Grid, Healbot, VuhDo) use to populate unit frame data.
Resolves class from UNIT_FIELD_BYTES_0 when entity is available.
GetThreatStatusColor(status) returns RGB for threat indicator coloring
(gray/yellow/orange/red). Used by unit frames and threat meters.
GetReadyCheckStatus(unit) stub returns nil (no check in progress).
RegisterUnitWatch/UnregisterUnitWatch stubs for secure frame compat.
GetItemQualityColor(quality) returns r, g, b, hexString for item
quality coloring (Poor=gray through Heirloom=cyan). Used by bag
addons, tooltips, and item frames to color item names/borders.
GetItemCount(itemId) counts total stacks across backpack + bags.
Used by addons to check material availability, quest item counts,
and consumable tracking.
UseContainerItem(bag, slot) uses/equips an item from a container
slot, delegating to useItemById for the actual equip/use action.
Fire UI_ERROR_MESSAGE from addUIError() so Lua addons can react to
error messages like "Not enough mana" or "Target is too far away".
Previously only the C++ overlay callback was notified. Also add
addUIInfoMessage() helper for informational system messages.
Fire QUEST_REMOVED and QUEST_LOG_UPDATE when quests are removed from
the quest log — both via server-driven removal (SMSG_QUEST_UPDATE_FAILED
etc.) and player-initiated abandon (CMSG_QUESTLOG_REMOVE_QUEST). Quest
tracking addons like Questie register for these events to update their
map markers and objective displays.
Classic and Turtle WoW don't use SMSG_AURA_UPDATE packets — they
pack aura data into UNIT_FIELD_AURAS update fields. The code correctly
rebuilds playerAuras from these fields in both CREATE_OBJECT and VALUES
update paths, but never fired the UNIT_AURA("player") addon event.
Buff frame addons (Buffalo, ElkBuffBars, etc.) register for UNIT_AURA
to refresh their display. Without this event, buff frames on Classic
and Turtle never update when buffs are gained or lost.
addLocalChatMessage only pushed to chatHistory and called the C++
display callback — it never fired Lua addon events. This meant the
player's own sent messages (local echoes from sendChatMessage) and
system messages (loot, XP gains, errors) were invisible to Lua chat
frame addons.
Now fires CHAT_MSG_{type} with the full 12-arg WoW signature from
addLocalChatMessage, matching the incoming message path. Uses the
active character name as sender for player-originated messages.
The SMSG_MESSAGECHAT handler stored messages in chatHistory and
triggered chat bubbles, but never fired Lua addon events. Chat frame
addons (Prat, Chatter, WIM) and the default ChatFrame all register for
CHAT_MSG_SAY, CHAT_MSG_WHISPER, CHAT_MSG_PARTY, CHAT_MSG_GUILD, etc.
to display incoming messages.
Now fires CHAT_MSG_{type} for every incoming message with the full WoW
event signature: message, senderName, language, channelName, displayName,
specialFlags, zoneChannelID, channelIndex, channelBaseName, unused,
lineID, and senderGUID. Covers all chat types: SAY, YELL, WHISPER,
PARTY, RAID, GUILD, OFFICER, CHANNEL, EMOTE, SYSTEM, MONSTER_SAY, etc.
Add the complete cursor state machine needed for drag-and-drop:
- PickupAction(slot) — pick up or swap action bar slots
- PlaceAction(slot) — place cursor content into action bar
- PickupSpell / PickupSpellBookItem — drag spells from spellbook
- PickupContainerItem(bag, slot) — drag items from bags
- PickupInventoryItem(slot) — drag equipped items
- ClearCursor / DeleteCursorItem — clear cursor state
- GetCursorInfo — returns cursor content type/id
- CursorHasItem / CursorHasSpell — query cursor state
- AutoEquipCursorItem — equip item from cursor
Cursor state tracks type (SPELL/ITEM/ACTION), id, and source slot.
PickupAction on empty slots with a spell cursor auto-assigns spells
to the action bar. Enables spellbook-to-action-bar drag-drop and
inventory management through the WoW UI.
Lava M2 models used independent static-local start times in pass 1
and pass 2 for UV scroll animation. Since static locals initialize
on first call, the two timers started at slightly different times
(microseconds to frames apart), causing a permanent UV offset mismatch
between passes — visible as texture flicker/jumping on lava surfaces.
Replace both function-scoped statics with a single file-scoped
kLavaAnimStart constant, ensuring both passes compute identical UV
offsets from the same epoch.
Implement the core map functions needed by WorldMapFrame.lua:
- SetMapToCurrentZone — sets map view from player's current mapId/zone
- GetCurrentMapContinent — returns continent (1=Kalimdor, 2=EK, etc.)
- GetCurrentMapZone — returns current zone ID
- SetMapZoom(continent, zone) — navigate map view
- GetMapContinents — returns continent name list
- GetMapZones(continent) — returns zone names per continent
- GetNumMapLandmarks — stub (returns 0)
Maps game mapId (0=EK, 1=Kalimdor, 530=Outland, 571=Northrend) to
WoW's continent numbering. Internal state tracks which continent/zone
the map UI is currently viewing.
Replace empty stub GameTooltip methods with working implementations:
- SetInventoryItem(unit, slot): populates tooltip with equipped item
name and quality-colored text via GetInventoryItemLink + GetItemInfo
- SetBagItem(bag, slot): populates from GetContainerItemInfo + GetItemInfo
- SetSpellByID(spellId): populates with spell name/rank from GetSpellInfo
- SetAction(slot): delegates to SetSpellByID or item lookup via GetActionInfo
- SetUnitBuff/SetUnitDebuff: populates from UnitBuff/UnitDebuff data
- SetHyperlink: parses item: and spell: links to populate name
- GetItem/GetSpell: now return real item/spell data when tooltip is populated
Also fix GetCVar/SetCVar conflict — remove Lua-side overrides that
were shadowing the C-side implementations (which return real screen
dimensions and sensible defaults for common CVars).
In the CREATE update block, the ufNpcFlags check at the end of the
else-if chain was unreachable dead code — it was already handled
earlier in the same chain. Remove the duplicate.
In the VALUES update block, mount display changes via field updates
fired mountCallback_ but not the UNIT_MODEL_CHANGED addon event,
unlike the CREATE path which fired both. Add the missing event so
Lua addons are notified when the player mounts/dismounts via VALUES
updates (the common case for aura-based mounting).
Also fire UNIT_MODEL_CHANGED for target/focus/pet display ID changes
in the VALUES displayIdChanged path, matching the CREATE path behavior.
Implement GetNumQuestLeaderBoards and GetQuestLogLeaderBoard — the core
functions WatchFrame.lua and QuestLogFrame.lua use to display objective
progress like "Kobold Vermin slain: 3/8" or "Linen Cloth: 2/6".
GetNumQuestLeaderBoards counts kill + item objectives from the parsed
SMSG_QUEST_QUERY_RESPONSE data. GetQuestLogLeaderBoard returns the
formatted progress text, type ("monster"/"item"/"object"), and
completion status for each objective.
Also adds ExpandQuestHeader/CollapseQuestHeader (no-ops for flat quest
list) and GetQuestLogSpecialItemInfo stub.
GetPlayerFacing() returns player orientation in radians, needed by
minimap addons for arrow rotation and facing-dependent mechanics.
GetCVar(name) returns sensible defaults for commonly queried CVars
(uiScale, screen dimensions, nameplate visibility, sound toggles,
autoLoot). SetCVar is a no-op stub for addon compatibility.
PLAYER_ENTERING_WORLD is the single most important WoW addon event —
virtually every addon registers for it to initialize UI, state, and
data structures. It was never fired, causing widespread addon init
failures on login and after teleports.
Now fired from:
- handleLoginVerifyWorld (initial login + same-map teleports)
- handleNewWorld (cross-map teleports, instance transitions)
Also fires:
- PLAYER_LOGIN on initial world entry only
- ZONE_CHANGED_NEW_AREA on all world entries
- UPDATE_WORLD_STATES on initial entry
- SPELLS_CHANGED + LEARNED_SPELL_IN_TAB after SMSG_INITIAL_SPELLS
(so spell book addons can initialize on login)
Implement GetNumSpellTabs, GetSpellTabInfo, GetSpellBookItemInfo, and
GetSpellBookItemName — the core functions SpellBookFrame.lua needs to
organize known spells into class skill line tabs.
Tabs are built lazily from knownSpells grouped by SkillLineAbility.dbc
mappings (category 7 = class). A "General" tab collects spells not in
any class skill line. Tabs auto-rebuild when the spell count changes.
Also adds SpellBookTab struct and getSpellBookTabs() to GameHandler.
Implement the quest tracking functions needed by WatchFrame.lua:
- SelectQuestLogEntry/GetQuestLogSelection — quest log selection state
- GetNumQuestWatches — count of tracked quests
- GetQuestIndexForWatch(watchIdx) — map Nth watched quest to log index
- AddQuestWatch/RemoveQuestWatch — toggle quest tracking by log index
- IsQuestWatched — check if a quest log entry is tracked
- GetQuestLink — generate colored quest link string
Backed by existing trackedQuestIds_ set and questLog_ vector.
Adds selectedQuestLogIndex_ state to GameHandler for quest selection.
Maps WoW equipment slot names (e.g. "HeadSlot", "MainHandSlot") to
inventory slot IDs, empty-slot textures, and relic check flags.
Supports case-insensitive matching with optional "Slot" suffix stripping.
Unblocks PaperDollFrame.lua and BankFrame.lua which call this function
to resolve slot button IDs during UI initialization.
PR #19 (572bb4ef) swapped CharSections.dbc field indices, placing
Texture1-3 at fields 4-6 and VariationIndex/ColorIndex at 8-9. Binary
analysis of the actual DBC files (Classic, TBC, Turtle — all identical
layout, no WotLK-specific override) confirms the correct order is:
Field 4 = VariationIndex
Field 5 = ColorIndex
Field 6 = Texture1 (string)
Field 7 = Texture2 (string)
Field 8 = Texture3 (string)
Field 9 = Flags
With the wrong indices, VariationIndex/ColorIndex reads returned string
offsets (garbage values that never matched), so all CharSections lookups
failed silently — producing white untextured character models at the
login screen and in-world.
Fixes all 4 expansion JSON layouts, hardcoded fallbacks in
character_preview.cpp, application.cpp, and character_create_screen.cpp.
Also handles the single-layer edge case (body skin only, no face/underwear)
by loading the texture directly instead of skipping compositing.
IsActionInRange(slot) checks if the spell on an action bar slot is within
range of the current target, using DBC spell range data and entity positions.
Returns 1/0/nil matching the WoW API contract.
GetActionInfo(slot) returns action type ("spell"/"item"/"macro"), id, and
subType for action bar interrogation by bar addons.
GetActionCount(slot) returns item stack count across backpack and bags for
consumable tracking on action bars.
Expose cast/channel state to Lua addons via UnitCastingInfo(unit) and
UnitChannelInfo(unit), matching the WoW API signature (name, text, texture,
startTime, endTime, isTradeSkill, castID, notInterruptible). Works for
player, target, focus, and pet units using existing UnitCastState tracking.
Also fix handleCastFailed (SMSG_CAST_FAILED, Classic/TBC path) to fire
UNIT_SPELLCAST_FAILED and UNIT_SPELLCAST_STOP events — previously only
the WotLK SMSG_CAST_RESULT path fired these, leaving Classic/TBC addons
unaware of cast failures.
Adds isChannel field to UnitCastState and getCastTimeTotal() accessor.
When logging in while already dead (reconnect/crash recovery), send
MSG_CORPSE_QUERY to get the server-authoritative corpse location.
Without this, the minimap corpse marker would be missing or point to
the wrong position after reconnecting as a ghost.
Parse MSG_CORPSE_QUERY server response to get the exact corpse location
and map ID. Also send the query after releasing spirit so the minimap
corpse marker points to the correct position even when the player died
in an instance and releases to an outdoor graveyard.
Previously the corpse position was only set from the entity death
location, which could be wrong for cross-map ghost runs.
Change the bare shin (no boots) default from geoset 502 to 503 across
all four code paths (character creation, character preview, equipment
update, NPC rendering).
Geoset 503 has Y width ~0.44 which better matches the thigh mesh
width (~0.42) than 502's width (~0.39), reducing the visible gap at
the knee joint where lower and upper leg meshes meet.
Returns squared distance between the player and a unit, plus a boolean
indicating whether the calculation was possible. Squared distance
avoids sqrt for efficient range comparisons.
Used by DBM, BigWigs, and proximity warning addons for raid encounter
range checks (e.g., "spread 10 yards" mechanics).
Returns true if the player is within interaction distance of a unit:
- Index 1: Inspect range (28 yards)
- Index 2: Trade range (11 yards)
- Index 3: Duel range (10 yards)
- Index 4: Follow range (28 yards)
Used by trade addons, inspect addons, and proximity detection addons.
Returns 1 if the spell can reach the target, 0 if out of range, nil
if range can't be determined. Compares player-to-target distance against
the spell's maxRange from Spell.dbc via SpellDataResolver.
Used by healing addons (Healbot, VuhDo, Clique) to check if heals can
reach party members, and by action bar addons for range coloring.
Returns true when the unit's entity exists in the entity manager
(within UPDATE_OBJECT range). Unlike UnitExists which falls back to
party member data, UnitIsVisible only returns true for entities that
can actually be rendered on screen.
Used by nameplate addons and proximity addons to check if a unit is
within visual range.
Session 14 commit #100: 95 new API functions, 113 new events.
Fire AUTOFOLLOW_BEGIN when the player starts following another unit
via /follow. Fire AUTOFOLLOW_END when following is cancelled. Used by
movement addons and AFK detection addons.
Fire PLAYER_LOGOUT from SMSG_LOGOUT_RESPONSE when the server confirms
logout. Addons use this to save state and perform cleanup before the
player leaves the world.
Fire QUEST_TURNED_IN with questId from SMSG_QUESTGIVER_QUEST_COMPLETE
when a quest is successfully completed and removed from the quest log.
Used by quest tracking addons (Questie, QuestHelper) and achievement
tracking addons.
Fire CHAT_MSG_COMBAT_XP_GAIN from SMSG_EXPLORATION_EXPERIENCE when the
player discovers a new area and gains exploration XP. Used by XP
tracking addons to count all XP sources including discovery.
Returns "TANK", "HEALER", "DAMAGER", or "NONE" based on the WotLK LFG
roles bitmask from SMSG_GROUP_LIST. Used by raid frame addons (Grid,
VuhDo, Healbot) to display role icons next to player names.
Fire CHAT_MSG_COMBAT_FACTION_CHANGE with the reputation change message
alongside UPDATE_FACTION when faction standings change. Used by
reputation tracking addons (FactionFriend, RepHelper) that parse
reputation gain messages.
Fire TRAINER_UPDATE from SMSG_TRAINER_BUY_SUCCEEDED so trainer UI
addons refresh the spell list (marking learned spells as unavailable).
Also fire SPELLS_CHANGED so spellbook and action bar addons detect
the newly learned spell.
UnitCanAttack(unit, otherUnit): returns true if otherUnit is hostile
(attackable). UnitCanCooperate(unit, otherUnit): returns true if
otherUnit is friendly (can receive beneficial spells).
Used by nameplate addons for coloring and by targeting addons for
filtering hostile/friendly units.
Returns the creature family name (Wolf, Cat, Bear, etc.) for NPC units.
Data from CreatureInfo cache (creature_template family field). Used by
hunter pet management addons and tooltips that show pet family info.
Enhance RAID_CLASS_COLORS entries with colorStr hex string field and
GenerateHexColor()/GenerateHexColorMarkup() methods. Many addons
(Prat, Details, oUF) use colorStr to build colored chat text and
GenerateHexColor for inline color markup.
Fire UNIT_MODEL_CHANGED for the player when mount display ID changes
(mounting or dismounting). Mount addons and portrait addons now get
notified when the player's visual model switches between ground and
mounted form.
Returns a table of power cost entries: {{ type=powerType, cost=amount,
name=powerName }}. Data from SpellDataResolver (Spell.dbc ManaCost and
PowerType fields). Used by spell tooltip addons and action bar addons
that display mana/rage/energy costs.
Returns true when the player is on a taxi/flight path. Used by action
bar addons to disable abilities during flight and by map addons to
track taxi state.
Previously UnitIsDead returned false for out-of-range party members
(entity not in entity manager). Now checks curHealth==0 from
SMSG_PARTY_MEMBER_STATS data, so raid frame addons correctly show
dead members in other zones as dead.
Previously UnitPowerType returned 0 (MANA) for party members who are
out of entity range. Now falls back to SMSG_PARTY_MEMBER_STATS power
type data, so raid frame addons correctly color rage/energy/runic
power bars for distant party members.
Fire UPDATE_WORLD_STATES from SMSG_UPDATE_WORLD_STATE when BG scores,
zone capture progress, or other world state variables change. Used by
BG score addons and world PvP objective tracking addons.
Fire UNIT_THREAT_LIST_UPDATE from SMSG_THREAT_UPDATE,
SMSG_HIGHEST_THREAT_UPDATE, and SMSG_THREAT_CLEAR. Threat data is
already parsed and stored in threatLists_ — this event notifies
addon systems when the data changes.
Used by Omen, ThreatPlates, and other threat meter addons to refresh
their displays when threat values update.
Implement UnitDetailedThreatSituation(unit, mobUnit) returning:
- isTanking (boolean)
- status (0-3, same as UnitThreatSituation)
- threatPct (100 if tanking, 0 otherwise)
- rawThreatPct (same)
- threatValue (0 — no server threat data available)
Used by Omen and other threat meter addons that query detailed threat
info per mob-target pair.
Implement UnitThreatSituation(unit, mobUnit) returning 0-3 threat level:
- 0: not on threat table
- 1: in combat but not tanking (mob targeting someone else)
- 3: securely tanking (mob is targeting this unit)
Approximated from mob's UNIT_FIELD_TARGET to determine who the mob is
attacking. Used by threat meter addons (Omen, ThreatPlates) and tank
UI addons to display threat state.
Add three tapped-state query functions for addons:
- UnitIsTapped(unit): true if any player has tagged the mob
- UnitIsTappedByPlayer(unit): true if local player can loot (tapped+lootable)
- UnitIsTappedByAllThreatList(unit): true if shared-tag mob
Used by nameplate addons (Plater, TidyPlates) and unit frame addons
to determine and display tap ownership state.
Extend tapped-by-other detection to the focus frame, matching the
target frame and nameplate treatment. All three UI elements (nameplate,
target frame, focus frame) now consistently show grey for tapped mobs.
Extend the tapped-by-other-player check to the target frame. Mobs
tagged by another player now show a grey name color on the target
frame, matching the grey nameplate treatment and WoW's behavior.
Players can now see at a glance on both nameplates AND target frame
whether a mob is tagged.
Check UNIT_DYNFLAG_TAPPED_BY_PLAYER (0x0004) on hostile NPC nameplates.
Mobs tagged by another player now show grey health bars instead of red,
matching WoW's visual indication that the mob won't yield loot/XP.
Mobs with TAPPED_BY_ALL_THREAT_LIST (0x0008) still show red since
those are shared-tag mobs that give loot to everyone.
Fire UNIT_FLAGS for player/target/focus when UNIT_FIELD_FLAGS updates.
Covers PvP flag, combat state, silenced, disarmed, and other flag
changes. Used by nameplate addons for PvP indicators and by unit frame
addons tracking CC/silence state.
Fire UNIT_FACTION for player/target/focus when UNIT_FIELD_FACTIONTEMPLATE
updates. Covers PvP flag toggling, mind control faction swaps, and any
server-side faction changes. Used by nameplate addons to update hostility
coloring and by PvP addons tracking faction state.
Fire UNIT_MODEL_CHANGED for player/target/focus/pet when their
UNIT_FIELD_DISPLAYID update field changes. This covers polymorph,
mount display changes, shapeshifting, and model swaps.
Used by unit frame addons that display 3D portraits and by nameplate
addons that track model state changes.
Fire MIRROR_TIMER_PAUSE from SMSG_PAUSE_MIRROR_TIMER with paused state
(1=paused, 0=resumed). Completes the mirror timer event trio alongside
MIRROR_TIMER_START and MIRROR_TIMER_STOP.
Fire BARBER_SHOP_OPEN when the barber shop UI is enabled
(SMSG_ENABLE_BARBER_SHOP). Fire BARBER_SHOP_CLOSE when the barber
shop completes or is dismissed. Used by UI customization addons.
Fire MIRROR_TIMER_START with type, value, maxValue, scale, and paused
args when breath/fatigue/fire timers begin. Fire MIRROR_TIMER_STOP with
type when they end. Timer types: 0=fatigue, 1=breath, 2=fire.
Used by timer bar addons to display breath/fatigue countdown overlays.
Fire BAG_UPDATE and UNIT_INVENTORY_CHANGED from SMSG_ITEM_PUSH_RESULT
when any item is received (loot, quest reward, trade, mail). Bag addons
(Bagnon, AdiBags) immediately show new items, and equipment tracking
addons detect inventory changes.
Fire PET_BAR_UPDATE when:
- Pet is summoned (SMSG_PET_SPELLS with new spell list)
- Pet learns a new spell (SMSG_PET_LEARNED_SPELL)
Used by pet action bar addons to refresh their display when the pet's
available abilities change.
Fire GUILD_INVITE_REQUEST when another player invites the local player
to a guild. Includes inviterName and guildName as arguments. Used by
auto-accept guild addons and invitation notification addons.
Fire PARTY_INVITE_REQUEST when another player invites the local player
to a group. Used by auto-accept group addons and invite notification
addons. Includes the inviter's name as the first argument.
Fire CONFIRM_TALENT_WIPE with the gold cost when the trainer offers
to reset talents (MSG_TALENT_WIPE_CONFIRM). Used by talent management
addons to show the respec cost dialog.
SMSG_GROUP_DESTROYED clears all party state but wasn't firing addon
events. Raid frame addons (Grid, VuhDo, Healbot) now properly hide
when the group disbands. Also fires PARTY_MEMBERS_CHANGED for compat.
SMSG_SELL_ITEM success now fires BAG_UPDATE so bag addons refresh
their display, and PLAYER_MONEY so gold tracking addons see the
sell price income immediately.
Fire RESURRECT_REQUEST with the caster's name from SMSG_RESURRECT_REQUEST
when a player/NPC offers to resurrect the dead player. Used by auto-accept
resurrection addons and death tracking addons.
Fire CONFIRM_SUMMON from SMSG_SUMMON_REQUEST when another player
summons via warlock portal or meeting stone. Used by auto-accept
summon addons and summon notification addons.
Track horizontal movement flag transitions (forward/backward/strafe)
and fire events when the player starts or stops moving. Used by
Healbot, cast-while-moving addons, and nameplate addons that track
player movement state for positioning optimization.
The loot money handler directly updates playerMoneyCopper_ but wasn't
firing PLAYER_MONEY. The update object path fires it when the coinage
field changes, but there can be a delay. Now gold-tracking addons
(Accountant, GoldTracker) immediately see looted money.
Fire UNIT_PET with "player" as arg from SMSG_PET_SPELLS when:
- Pet is cleared (dismissed/dies) — both size-based and guid=0 paths
- Pet is summoned (new pet GUID received with spell list)
Used by pet frame addons and unit frame addons to show/hide pet
frames and update pet action bars when pet state changes.
SMSG_HEALTH_UPDATE and SMSG_POWER_UPDATE were not checking for the pet
GUID when dispatching addon events. Pet health/power bar addons now
properly receive UNIT_HEALTH and UNIT_POWER with unitId="pet".
The UPDATE_OBJECT and UNIT_AURA paths already had the pet check.
Add pet GUID check to all spellcast event dispatchers:
- UNIT_SPELLCAST_START
- UNIT_SPELLCAST_SUCCEEDED
- UNIT_SPELLCAST_CHANNEL_START
- UNIT_SPELLCAST_CHANNEL_STOP
- UNIT_SPELLCAST_INTERRUPTED + STOP
Previously these events only fired for player/target/focus, meaning
pet cast bars and pet spell tracking addons wouldn't work. Now pet
spellcasts properly fire with unitId="pet".
Extend the Lua error UI display to cover all error paths:
- OnUpdate frame callbacks (previously only logged)
- executeFile loading errors (now also shown as UI error)
- executeString /run errors (now also shown as UI error)
This ensures addon developers see ALL Lua errors in-game, not just
event handler errors from the previous commit.
Fire UPDATE_BATTLEFIELD_STATUS with the status code when battlefield
status changes (queued, ready to join, in progress, waiting to leave).
Used by BG queue addons and PvP addons to track battleground state.
Previously Lua addon errors only logged to the log file. Now they
display as red UI error text to the player (same as spell errors and
game warnings), helping addon developers debug issues in real-time.
Add LuaErrorCallback to LuaEngine, fire it from event handler and
frame OnEvent pcall error paths. Wire the callback to GameHandler's
addUIError in application.cpp.
Fire CHAT_MSG_COMBAT_HONOR_GAIN from SMSG_PVP_CREDIT with the honor
message text. Used by PvP addons (HonorSpy, HonorTracker) to track
honor gains and kill counts.
Fire PLAYER_CONTROL_LOST when SMSG_CLIENT_CONTROL_UPDATE revokes player
movement (stun, fear, mind control, etc.) and PLAYER_CONTROL_GAINED when
movement is restored.
Used by loss-of-control addons and action bar addons to show stun/CC
indicators and disable ability buttons during crowd control.
Fire MAIL_INBOX_UPDATE when the mail list is received/refreshed
(SMSG_MAIL_LIST_RESULT), so mail addons can update their display.
Fire UPDATE_PENDING_MAIL when new mail arrives (SMSG_RECEIVED_MAIL),
enabling minimap mail icon addons and notification addons to react.
Fire QUEST_WATCH_UPDATE (with quest ID for kills) and QUEST_LOG_UPDATE
when quest objectives progress:
- Kill objectives: when SMSG_QUESTUPDATE_ADD_KILL updates a kill count
- Item objectives: when SMSG_QUESTUPDATE_ADD_ITEM updates an item count
Used by quest tracker addons (Questie, QuestHelper) and the built-in
quest tracker to refresh objective display when progress changes.
Fire MERCHANT_UPDATE after a successful SMSG_BUY_ITEM so vendor addons
refresh their stock display. Also fire BAG_UPDATE so bag addons show
the newly purchased item immediately.
SMSG_SPELL_FAILED_OTHER was clearing the unit cast state but not firing
addon events. Cast bar addons (Quartz, ClassicCastbars) showing target/
focus cast bars need UNIT_SPELLCAST_FAILED and UNIT_SPELLCAST_STOP to
clear the bar when another unit's cast fails.
Now fires both events for target and focus units, matching the behavior
already implemented for the player's own cast failures.
Fire PLAYER_GUILD_UPDATE when the player's guild membership changes:
- When guild name is first resolved (player joins guild/logs in)
- When guild is disbanded
Used by guild frame addons and guild info display to update when
guild status changes.
Implement GetAddOnMetadata(addonNameOrIndex, key) which reads arbitrary
TOC file directives. All directives are now stored in the addon info
registry table under a "metadata" sub-table.
This enables addons to read their own version, author, X-* custom
fields, and other TOC metadata at runtime. Used by addon managers,
version checkers, and self-updating addons.
Fire PARTY_LEADER_CHANGED (with GROUP_ROSTER_UPDATE) from both:
- SMSG_GROUP_SET_LEADER: when a new leader is named by string
- SMSG_REAL_GROUP_UPDATE: when leader GUID changes via group update
Used by raid frame addons to update leader crown icons and by
group management addons to track leadership changes.
Fire INSPECT_READY with the inspected player's GUID when inspection
results are received. Fires from both:
- WotLK SMSG_TALENTS_INFO type=1 (talent + gear inspect)
- Classic SMSG_INSPECT (gear-only inspect)
Used by GearScore, TacoTip, and other inspection addons that need
to know when inspect data is available for a specific player.
Fire RAID_TARGET_UPDATE event when raid markers (skull, cross, etc.)
are set or cleared on targets. Add two Lua API functions:
- GetRaidTargetIndex(unit) returns marker index 1-8 (or nil)
- SetRaidTarget(unit, index) sets marker 1-8 (or 0 to clear)
Enables raid marking addons and nameplate addons that display raid
icons to react to marker changes in real-time.
Fire addon events for the raid ready check system:
- READY_CHECK fires when a ready check is initiated, with initiator name
- READY_CHECK_CONFIRM fires for each player's response, with GUID and
ready state (1=ready, 0=not ready)
- READY_CHECK_FINISHED fires when the ready check period ends
These events are used by raid frame addons (Grid, VuhDo, Healbot) to
show ready check status on unit frames, and by raid management addons
to track responsiveness.
Fire ACHIEVEMENT_EARNED event when a player earns an achievement,
enabling achievement tracking addons.
Add 15 previously unmapped chat type → addon event mappings:
- CHAT_MSG_ACHIEVEMENT, CHAT_MSG_GUILD_ACHIEVEMENT
- CHAT_MSG_WHISPER_INFORM (echo of sent whispers)
- CHAT_MSG_RAID_LEADER, CHAT_MSG_BATTLEGROUND_LEADER
- CHAT_MSG_MONSTER_SAY/YELL/EMOTE/WHISPER
- CHAT_MSG_RAID_BOSS_EMOTE/WHISPER
- CHAT_MSG_BG_SYSTEM_NEUTRAL/ALLIANCE/HORDE
These events are needed by boss mod addons (DBM, BigWigs) to detect
boss emotes, by achievement trackers, and by chat filter addons that
process all message types.
IsUsableAction previously always returned notEnoughMana=false. Now it
checks the spell's mana cost from SpellDataResolver against the player's
current power, matching the same fix applied to IsUsableSpell.
This fixes action bar addons (Bartender, Dominos) incorrectly showing
abilities as usable when the player lacks mana/rage/energy.
Extend SpellDataInfo with manaCost and powerType fields, extracted from
Spell.dbc ManaCost and PowerType columns. This enables IsUsableSpell()
to properly check if the player has enough mana/rage/energy to cast.
Previously IsUsableSpell always returned notEnoughMana=false since cost
data wasn't available. Now it compares the spell's DBC mana cost against
the player's current power, returning accurate usability and mana state.
This fixes action bar addons showing abilities as usable when the player
lacks sufficient power, and enables OmniCC-style cooldown text to
properly dim insufficient-power abilities.
Add SpellDataResolver that lazily loads Spell.dbc, SpellCastTimes.dbc,
and SpellRange.dbc to provide cast time and range data. GetSpellInfo()
now returns real castTime (ms), minRange, and maxRange instead of
hardcoded 0 values.
This enables spell tooltip addons, cast bar addons (Quartz), and range
check addons to display accurate spell information. The DBC chain is:
Spell.dbc[CastingTimeIndex] → SpellCastTimes.dbc[Base ms]
Spell.dbc[RangeIndex] → SpellRange.dbc[MinRange, MaxRange]
Follows the same lazy-loading pattern as SpellIconPathResolver and
ItemIconPathResolver.
Add playerClassRaceCache_ that stores classId and raceId from
SMSG_NAME_QUERY_RESPONSE. This enables UnitClass and UnitRace to return
correct data for players who were previously seen but are now out of
UPDATE_OBJECT range.
Fallback chain for UnitClass/UnitRace is now:
1. Entity update fields (UNIT_FIELD_BYTES_0) — for nearby entities
2. Name query cache — for previously queried players
3. getPlayerClass/Race() — for the local player
This improves class-colored names in chat, unit frames, and nameplates
for players who move out of view range.
Previously UnitHealth, UnitHealthMax, UnitPower, UnitPowerMax, UnitLevel,
UnitName, and UnitExists returned 0/"Unknown"/false for party members in
other zones because the entity doesn't exist in the entity manager.
Now these functions fall back to SMSG_PARTY_MEMBER_STATS data stored in
GroupMember structs, which provides health, power, level, and name for
all party members regardless of distance. UnitName also falls back to
the player name cache.
This fixes raid frame addons (Grid, Healbot, VuhDo) showing blank/zero
data for party members who are out of UPDATE_OBJECT range.
Add 6 commonly needed unit state functions:
- UnitIsGhost(unit) checks ghost flag from UNIT_FIELD_FLAGS
- UnitIsDeadOrGhost(unit) combines dead + ghost checks
- UnitIsAFK(unit) / UnitIsDND(unit) check player flags
- UnitPlayerControlled(unit) true for players and player pets
- UnitSex(unit) reads gender from UNIT_FIELD_BYTES_0 byte 2
Fix UnitAffectingCombat to check UNIT_FLAG_IN_COMBAT (0x00080000)
from entity update fields for any unit, not just "player". Previously
returned false for all non-player units.
These functions are needed by unit frame addons (SUF, Pitbull, oUF)
to properly display ghost state, AFK/DND status, and combat state.
Previously UnitClass() only returned the correct class for "player" and
returned "Unknown" for all other units (target, focus, party1-4, etc.).
UnitRace() had the same bug.
Now both functions read UNIT_FIELD_BYTES_0 from the entity's update
fields to resolve class (byte 1) and race (byte 0) for any unit. This
fixes unit frame addons, class-colored names, and race-based logic for
all unit IDs.
Also fix UnitRace to return 3 values (localized, English, raceId) to
match WoW's API signature — previously it only returned 1.
When typing commands like /w, /whisper, /invite, /trade, /duel, /follow,
/inspect, etc., pressing Tab now cycles through matching player names.
Name sources (in priority order):
1. Last whisper sender (most likely target for /r follow-ups)
2. Party/raid members
3. Friends list
4. Nearby visible players
Tab cycles through all matches; single match auto-appends a space.
Complements the existing slash-command tab-completion.
Fix bug where NPCs receiving moveType=4 (FacingAngle) or moveType=3
(FacingTarget) monster move packets with zero waypoints would not
rotate in place. The handler only processed orientation when hasDest
was true, but facing-only updates have no destination waypoints.
Now NPCs properly rotate when:
- moveType=4: server specifies an exact facing angle (e.g., NPC turns
to face the player during dialogue or scripted events)
- moveType=3: NPC should face a specific target entity
This fixes NPCs appearing frozen/unresponsive during scripted events,
quest interactions, and patrol waypoint facing changes.
Implement 5 guild-related WoW Lua API functions:
- IsInGuild() returns whether the player is in a guild
- GetGuildInfo("player") returns guildName, rankName, rankIndex
- GetNumGuildMembers() returns totalMembers, onlineMembers
- GetGuildRosterInfo(index) returns full 11-value tuple: name, rank,
rankIndex, level, class, zone, note, officerNote, online, status, classId
- GetGuildRosterMOTD() returns the guild message of the day
Data sourced from SMSG_GUILD_ROSTER and SMSG_GUILD_QUERY_RESPONSE.
Enables guild management addons (GreenWall, officer tools, roster UIs).
Implement the addon messaging API used by virtually every multiplayer
addon (DBM, BigWigs, EPGP, RC Loot Council, WeakAuras, etc.):
- SendAddonMessage(prefix, text, chatType, target) sends an addon
message encoded as "prefix\ttext" via the appropriate chat channel
- RegisterAddonMessagePrefix(prefix) registers a prefix for filtering
incoming addon messages
- IsAddonMessagePrefixRegistered(prefix) checks registration status
- C_ChatInfo table with aliases for the above functions (newer API compat)
Without these functions, all inter-addon communication between players
fails, breaking boss mods, loot distribution, and group coordination.
Fire PLAYER_UPDATE_RESTING when the player enters or leaves a resting
area (inn/capital city). Fires from both the SET_REST_START packet and
the QUEST_FORCE_REMOVE rest-state update path. Used by XP bar addons
and rest state indicator addons.
The Escape key now properly closes these windows before showing the
escape menu:
- Mail window (closeMailbox)
- Auction house (closeAuctionHouse)
- Quest details dialog (declineQuest)
- Quest offer reward dialog (closeQuestOfferReward)
- Quest request items dialog (closeQuestRequestItems)
- Trade window (cancelTrade)
Previously these windows required clicking their close button since
Escape would skip directly to the escape menu.
Apply at-rest values from M2 color alpha and transparency animation
tracks to batch rendering opacity. This fixes models that should render
as semi-transparent (ghosts, ethereal effects, fading doodads) but were
previously rendering at full opacity.
The fix multiplies colorAlphas[batch.colorIndex] and
textureWeights[batch.transparencyIndex] into batchOpacity during model
setup. Zero values are skipped to avoid the edge case where animated
tracks start at 0 (invisible) and animate up — baking that first
keyframe would make the entire batch permanently invisible.
Implement friend and ignore list query functions for social addons:
- GetNumFriends() returns friend count from contacts list
- GetFriendInfo(index) returns 7-value tuple: name, level, class, area,
connected, status (AFK/DND), note
- GetNumIgnores() returns ignore count
- GetIgnoreName(index) returns ignored player's name
Data sourced from the contacts list populated by SMSG_FRIEND_LIST and
SMSG_CONTACT_LIST. Area names resolved from AreaTable.dbc.
Fire GUILD_ROSTER_UPDATE from SMSG_GUILD_ROSTER and from guild events
(member join/leave/kick, promotions, leader changes, online/offline,
disbanded). Fire GUILD_MOTD with the MOTD text when received.
These events are needed by guild management addons (GuildGreet,
GuildRoster replacements, officer tools) to refresh their UI.
Fire FRIENDLIST_UPDATE from all three friend list packet handlers:
- SMSG_FRIEND_LIST (Classic format)
- SMSG_CONTACT_LIST (WotLK format)
- SMSG_FRIEND_STATUS (add/remove/online/offline updates)
Fire IGNORELIST_UPDATE when SMSG_CONTACT_LIST includes ignore entries.
These events are used by social addons to refresh their UI when the
friend/ignore list changes.
Fire UNIT_NAME_UPDATE for target/focus/player when SMSG_NAME_QUERY_RESPONSE
resolves a player's name. Nameplate and unit frame addons use this event
to update displayed names when they become available asynchronously.
Add missing addon events for three gameplay systems:
Loot rolls:
- START_LOOT_ROLL fires on SMSG_LOOT_START_ROLL with slot and countdown
- LOOT_SLOT_CLEARED fires when a loot item is removed (SMSG_LOOT_REMOVED)
Trade:
- TRADE_REQUEST when another player initiates a trade
- TRADE_SHOW when the trade window opens
- TRADE_CLOSED when trade is cancelled, declined, or completed
- TRADE_ACCEPT_UPDATE when the trade partner accepts
Duels:
- DUEL_REQUESTED with challenger name on incoming duel challenge
- DUEL_FINISHED when a duel completes or is cancelled
Add ItemIconPathResolver that lazily loads ItemDisplayInfo.dbc to map
displayInfoId → icon texture path. This fixes three Lua API functions
that previously returned nil for item icons:
- GetItemInfo() field 10 (texture) now returns the icon path
- GetActionTexture() for item-type action bar slots now returns icons
- GetLootSlotInfo() field 1 (texture) now returns proper item icons
instead of incorrectly using the spell icon resolver
Follows the same lazy-loading pattern as SpellIconPathResolver. The DBC
is loaded once on first query and cached for all subsequent lookups.
Implement the SavedVariablesPerCharacter TOC directive that many addons
use to store different settings per character (Bartender, Dominos,
MoveAnything, WeakAuras, etc.). Without this, all characters share the
same addon data file.
Per-character files are stored as <AddonName>.<CharacterName>.lua.saved
alongside the existing account-wide <AddonName>.lua.saved files. The
character name is resolved from the player GUID at world entry time.
Changes:
- TocFile::getSavedVariablesPerCharacter() parses the TOC directive
- AddonManager loads/saves per-character vars alongside account-wide vars
- Character name set from game handler before addon loading
Add commonly called frame methods as no-ops or with basic state tracking
on the frame metatable, so any CreateFrame result supports them:
Layout: SetFrameLevel/Get, SetFrameStrata/Get, SetScale/Get/GetEffective,
ClearAllPoints, SetID/GetID, GetLeft/Right/Top/Bottom, GetNumPoints,
GetPoint, SetHitRectInsets
Behavior: EnableMouse, EnableMouseWheel, SetMovable, SetResizable,
RegisterForDrag, SetClampedToScreen, SetToplevel, Raise, Lower,
StartMoving, StopMovingOrSizing, RegisterForClicks, IsMouseOver
Visual: SetBackdrop, SetBackdropColor, SetBackdropBorderColor
Scripting: HookScript (chains with existing SetScript handlers),
SetAttribute/GetAttribute, GetObjectType
Sizing: SetMinResize, SetMaxResize
These prevent the most common addon errors when addons call standard
WoW frame methods on CreateFrame results.
Add commonly used WoW global utility functions that many addons depend on:
Table: tContains, tInvert, CopyTable, tDeleteItem
String: strupper, strlower, strfind, strsub, strlen, strrep, strbyte,
strchar, strrev, gsub, gmatch, strjoin
Math: Clamp, Round
Bit ops: bit.band, bit.bor, bit.bxor, bit.bnot, bit.lshift, bit.rshift
(pure Lua implementation for Lua 5.1 which lacks native bit ops)
These prevent nil-reference errors and missing-function crashes in
addons that use standard WoW utility globals.
Add the UIDropDownMenu compatibility framework used by virtually every
addon with settings or selection menus: UIDropDownMenu_Initialize,
CreateInfo, AddButton, SetWidth, SetText, GetText, SetSelectedID, etc.
Add global font object stubs (GameFontNormal, GameFontHighlight, etc.)
referenced by CreateFontString template arguments.
Add UISpecialFrames table, InterfaceOptionsFrame for addon panels,
InterfaceOptions_AddCategory, and common font color constants
(GRAY_FONT_COLOR, NORMAL_FONT_COLOR, etc.).
These globals prevent nil-reference errors in most popular addons.
Fire PLAYER_ENTER_COMBAT when the player's auto-attack starts
(SMSG_ATTACKSTART) and PLAYER_LEAVE_COMBAT when auto-attack stops.
These events are distinct from PLAYER_REGEN_DISABLED/ENABLED — they
specifically track physical melee combat state and are used by
combat-aware addons for weapon swing timers and attack state tracking.
Fix bug where learning a talent caused an early return before firing
LEARNED_SPELL_IN_TAB and SPELLS_CHANGED events, leaving talent addons
unaware of changes. Now talent learning fires CHARACTER_POINTS_CHANGED,
PLAYER_TALENT_UPDATE, LEARNED_SPELL_IN_TAB, and SPELLS_CHANGED.
Also fire CHARACTER_POINTS_CHANGED, ACTIVE_TALENT_GROUP_CHANGED, and
PLAYER_TALENT_UPDATE from handleTalentsInfo (SMSG_TALENTS_INFO), so
talent addons update when the full talent state is received from the
server (login, spec switch, respec).
Also fire UNIT_HEALTH/UNIT_POWER events from SMSG_HEALTH_UPDATE and
SMSG_POWER_UPDATE packets for real-time unit frame updates.
SMSG_HEALTH_UPDATE and SMSG_POWER_UPDATE are high-frequency WotLK
packets that update entity health/power values but weren't firing
addon events. Unit frame addons (Pitbull, oUF, SUF) depend on these
events to update health/mana bars in real-time.
Now fire UNIT_HEALTH for player/target/focus on SMSG_HEALTH_UPDATE
and UNIT_POWER on SMSG_POWER_UPDATE, matching the events already
fired from the UPDATE_OBJECT path.
Implement 5 talent-related WoW Lua API functions:
- GetNumTalentTabs() returns class-specific talent tree count (usually 3)
- GetTalentTabInfo(tab) returns name, icon, pointsSpent, background
- GetNumTalents(tab) returns talent count in a specific tree
- GetTalentInfo(tab, index) returns full 8-value tuple with name, tier,
column, current rank, max rank, and availability
- GetActiveTalentGroup() returns active spec (1 or 2)
Data sourced from Talent.dbc, TalentTab.dbc, and the server-sent talent
info packet. Enables talent addons and spec display addons.
Implement skill line API functions that profession and tradeskill addons
need to display player skills:
- GetNumSkillLines() returns count of player skills
- GetSkillLineInfo(index) returns full 12-value tuple: name, isHeader,
isExpanded, rank, tempPoints, modifier, maxRank, isAbandonable, etc.
Data comes from SMSG_SKILLS_INFO update fields and SkillLine.dbc names.
Implement LibStub — the universal library version management system that
virtually every WoW addon framework depends on (Ace3, LibDataBroker,
LibSharedMedia, etc.). Without LibStub, most popular addons fail to load.
Also implement CallbackHandler-1.0 — the standard event callback library
used by Ace3-based addons for inter-module communication. Supports
RegisterCallback, UnregisterCallback, UnregisterAllCallbacks, and Fire.
These two libraries unlock the entire Ace3 addon ecosystem.
Fire UNIT_SPELLCAST_SENT when the player initiates a spell cast (before
server confirms), enabling cast bar addons like Quartz to show latency.
Includes target name and spell ID as arguments.
Fire UNIT_SPELLCAST_STOP whenever a cast bar should disappear:
- On successful cast completion (SMSG_SPELL_GO)
- On cast failure (SMSG_CAST_RESULT with error)
- On spell interrupt (SMSG_SPELL_FAILURE/SMSG_SPELL_FAILED_OTHER)
- On manual cast cancel
These events are essential for cast bar replacement addons to properly
track when casts begin and end.
Implement 3 critical gameplay Lua API functions:
- UseAction(slot) activates an action bar slot (spell/item), enabling
action bar addons like Bartender/Dominos to fire abilities
- CancelUnitBuff("player", index) cancels a buff by index, enabling
auto-cancel and buff management addons
- CastSpellByID(id) casts a spell by numeric ID, enabling macro addons
and spell queuing systems
Fire UNIT_HEALTH, UNIT_POWER, and UNIT_AURA events from
SMSG_PARTY_MEMBER_STATS with proper unit IDs (party1..4, raid1..40).
Previously, health/power changes for party members via the stats packet
were silent — raid frame addons never got notified.
Also fix closeLoot() not firing LOOT_CLOSED event when the loot window
is closed by the player (only handleLootReleaseResponse fired it).
Fix GetScreenWidth/GetScreenHeight/GetNumLootItems/GetFramerate being
overridden by hardcoded Lua stubs that ran after the C functions were
registered. Now the real C implementations correctly take effect.
Add GameTooltip global frame with 20+ methods (SetOwner, ClearLines,
AddLine, AddDoubleLine, SetText, NumLines, GetText, SetHyperlink, etc.)
and ShoppingTooltip1/2 — critical for virtually all WoW addons.
Add frame:CreateTexture() and frame:CreateFontString() methods returning
stub objects with common API methods, enabling UI creation addons.
Add real GetFramerate() returning actual FPS from ImGui.
Support compound unit IDs that resolve an entity's current target via
UNIT_FIELD_TARGET_LO/HI update fields. This enables addons to query
target-of-target info (e.g., UnitName("targettarget"), UnitHealth("focustarget"))
which is essential for threat meters and unit frame addons.
Fire the following events for addon compatibility:
- LOOT_OPENED, LOOT_CLOSED on loot window open/close
- GOSSIP_SHOW, GOSSIP_CLOSED on gossip/quest-list window open/close
- QUEST_DETAIL when quest details are shown to the player
- QUEST_COMPLETE when quest offer reward dialog opens
- TRAINER_SHOW, TRAINER_CLOSED on trainer window open/close
Implement 10 new WoW Lua API functions for addon compatibility:
- GetComboPoints, UnitReaction, UnitIsConnected for unit frames/raid addons
- HasAction, GetActionTexture, IsCurrentAction, IsUsableAction, GetActionCooldown
for action bar addons (Bartender, Dominos, etc.)
- UnitMana/UnitManaMax as Classic-era aliases for UnitPower/UnitPowerMax
Add "mouseover" as a valid unit ID in resolveUnitGuid so Lua API functions
like UnitName("mouseover"), UnitHealth("mouseover") etc. work for addons.
Fire UPDATE_MOUSEOVER_UNIT event when the mouseover target changes, and
PLAYER_FOCUS_CHANGED event when focus is set or cleared.
Fire MAIL_SHOW when mailbox opens (SMSG_SHOW_MAILBOX) and MAIL_CLOSED
when it closes. Fire AUCTION_HOUSE_SHOW when AH opens and AUCTION_HOUSE_CLOSED
when it closes. Used by mail addons (Postal) and AH addons (Auctionator).
Fire BANKFRAME_OPENED when bank window opens and BANKFRAME_CLOSED when
it closes. Used by bank management addons (Bagnon, BankItems) to detect
when the player is interacting with their bank.
Fire MERCHANT_SHOW when vendor window opens (SMSG_LIST_INVENTORY) and
MERCHANT_CLOSED when vendor is closed. Used by vendor price addons and
auto-sell addons that need to detect vendor interaction state.
Fire when SMSG_ACTION_BUTTONS populates the action bar on login and when
SMSG_SUPERCEDED_SPELL upgrades spell ranks on the bar. Used by action bar
addons (Bartender, Dominos) to refresh their displays.
Detect changes in player skill values after extractSkillFields() and fire
SKILL_LINES_CHANGED when any skill value changes. Used by profession
tracking addons and skill bar displays.
Fire CHAT_MSG_MONEY when gold is looted (used by gold tracking addons
like MoneyFu, Titan Panel). Fire CHAT_MSG_COMBAT_XP_GAIN when XP is
earned (used by XP tracking addons and leveling speed calculators).
Fire CHAT_MSG_LOOT addon event from SMSG_ITEM_PUSH_RESULT with the loot
message text, item ID, and count. Used by loot tracking addons (AutoLootPlus,
Loot Appraiser) and damage meters that track loot distribution.
Fires when AFK/DND status, PvP flag, ghost state, or other player flags
change via PLAYER_FLAGS update field. Enables addons that track player
status changes (FlagRSP, TRP3, etc.).
Parse randomPropertyId from SMSG_AUCTION_OWNER_NOTIFICATION to display
full item names in sold/bid/expired messages like "Your auction of
Gloves of the Monkey has sold!" Completes suffix resolution across
all 9 item display contexts.
Apply getRandomPropertyName() to SMSG_AUCTION_BIDDER_NOTIFICATION and
SMSG_AUCTION_REMOVED_NOTIFICATION so outbid/expired messages show full
item names like "You have been outbid on Leggings of the Eagle" instead
of just "Leggings". Completes suffix name resolution across all AH contexts.
Formats copper amounts into "Xg Ys Zc" strings for addon display.
GetCoinText is aliased to GetCoinTextureString. Used by money display
addons (Titan Panel, MoneyFu) and auction/vendor price formatting.
Returns (class, englishClass, race, englishRace, sex, name, realm) for a
GUID string. Resolves player name from entity cache. Returns class/race
info for the local player. Used by Details!, Recount, and Skada to
identify players in COMBAT_LOG_EVENT_UNFILTERED data.
Returns WoW-format quality-colored item link for any item ID from the
item info cache. Used by loot addons, tooltip addons, and chat formatting
to create clickable item references.
Returns WoW-format spell link string "|cff71d5ff|Hspell:ID|h[Name]|h|r"
for a spell ID or name. Used by damage meters, chat addons, and WeakAuras
to create clickable spell references in chat messages.
Extend random property name resolution to the Bids tab and Your Auctions
(seller) tab. All three auction house tabs now display items with their
full suffix names (e.g., "Gloves of the Monkey" instead of "Gloves").
Append suffix name from getRandomPropertyName() to auction browse results
so items display as "Leggings of the Eagle" instead of just "Leggings"
in the auction house search table. Uses the randomPropertyId field from
the SMSG_AUCTION_LIST_RESULT packet data.
Apply getRandomPropertyName() to SMSG_LOOT_START_ROLL and SMSG_LOOT_ROLL_WON
handlers so items with random suffixes display correctly in group loot
contexts (e.g., "Leggings of the Eagle" in the Need/Greed popup and
"Player wins Leggings of the Eagle (Need 85)" in chat).
Load ItemRandomProperties.dbc and ItemRandomSuffix.dbc lazily to resolve
suffix names like "of the Eagle", "of the Monkey" etc. Add
getRandomPropertyName(id) callback on GameHandler wired through Application.
Append suffix to item names in SMSG_ITEM_PUSH_RESULT loot notifications
so items display as "Leggings of the Eagle" instead of just "Leggings".
Previously returned (0, remaining) which broke addons computing remaining
time as start + duration - GetTime(). Now returns (GetTime(), remaining, 1)
when on cooldown and (0, 0, 1) when off cooldown, plus the third 'enabled'
value that WoW always returns. Fixes cooldown display in OmniCC and similar.
GetXPExhaustion() returns rested XP pool remaining (nil if none).
GetRestState() returns 1 (normal) or 2 (rested) based on inn/city state.
Used by XP bar addons like Titan Panel and XP tracking WeakAuras.
Cap gossipPois_ at 200 entries (both gossip POI and quest POI paths) to
prevent unbounded memory growth from rapid gossip/quest queries. Add soft
240 FPS frame rate limiter when vsync is off to prevent 100% CPU usage —
sleeps for remaining frame budget when frame completes in under 4ms.
Returns (usable, noMana) tuple. Checks if the spell is known and not on
cooldown. Accepts spell ID or name. Used by action bar addons and
WeakAuras for conditional spell display (greyed out when unusable).
IsInInstance() returns whether player is in an instance and the type.
GetInstanceInfo() returns map name, instance type, difficulty index/name,
and max players. GetInstanceDifficulty() returns 1-based difficulty index.
Critical for raid/dungeon addons like DBM for instance detection.
Returns WoW-standard classification strings: "normal", "elite", "rareelite",
"worldboss", or "rare" based on creature rank from CreatureCache. Used by
nameplate addons (Plater, TidyPlates) and boss mods (DBM) to detect elite/
boss/rare mobs for special handling.
Add playMinimapPing() to UiSoundManager with MapPing.wav (falls back to
target select sound). Play the ping sound in MSG_MINIMAP_PING handler
when the sender is not the local player. Provides audio feedback for
party member map pings, matching WoW behavior.
GetGameTime() returns server game hours and minutes from the day/night
cycle. GetServerTime() returns Unix timestamp. UnitXP("player") and
UnitXPMax("player") return current and next-level XP values. Used by
XP tracking addons and time-based conditionals.
Add setZoneName() to LoadingScreen and display the map name from Map.dbc
as large gold text with drop shadow above the progress bar. Shown in both
render() and renderOverlay() paths. Zone name is resolved from gameHandler's
getMapName(mapId) during world load. Improves feedback during zone transitions.
Mirror the zone-transition cleanup in disconnect(): fire despawn callbacks
for all entities before clearing the entity manager. Prevents M2 instances
and character models from leaking when the player disconnects and reconnects
quickly (e.g., server kick, network recovery).
handleNewWorld() previously called entityManager.clear() directly without
notifying the renderer, leaving stale M2 instances and character models
allocated. Now iterates all entities and fires creatureDespawnCallback,
playerDespawnCallback, and gameObjectDespawnCallback before clearing.
Also clears player caches (visible items, cast states, aura cache,
combat text) to prevent state leaking between zones.
The expirationTime field (7th return value of UnitBuff/UnitDebuff/UnitAura)
was hardcoded to 0. Now returns GetTime() + remaining seconds, matching
WoW's convention where addons compute remaining = expirationTime - GetTime().
Enables buff/debuff timer addons like OmniCC and WeakAuras.
The caster field (8th return value) was always nil. Now returns the
caster's unit ID ("player", "target", "focus", "pet") or hex GUID
string for other units. Enables addons to identify who applied a
buff/debuff for filtering and tracking purposes.
The debuffType field (5th return value) was always nil. Now resolves
dispel type from Spell.dbc via getSpellDispelType(): returns "Magic",
"Curse", "Disease", or "Poison" for debuffs. Enables dispel-focused
addons like Decursive and Grid to detect debuff categories.
The 0x4000 (16384) limit was too conservative and could disconnect the
client when the server sends large packets such as SMSG_GUILD_ROSTER
with 500+ members (~30KB) or SMSG_AUCTION_LIST with many results.
Increase to 0x8000 (32768) which covers all normal gameplay while still
protecting against framing desync from encryption errors.
Add GetNumQuestLogEntries(), GetQuestLogTitle(index), GetQuestLogQuestText(index),
and IsQuestComplete(questID). GetQuestLogTitle returns WoW-compatible 8 values
including title, isComplete flag, and questID. Enables quest tracking addons
like Questie and QuestHelper to access the player's quest log.
Show "Loot method changed to Master Looter/Round Robin/etc." in chat when
group loot method changes via SMSG_GROUP_LIST. Fire CRITERIA_UPDATE addon
event with criteria ID and progress when achievement criteria progress
changes, enabling achievement tracking addons.
Fire UPDATE_FACTION when reputation standings change (SMSG_SET_FACTION_STANDING).
Fire QUEST_ACCEPTED with quest ID when a new quest is added to the log.
Fire QUEST_LOG_UPDATE on both quest acceptance and quest completion.
Enables reputation tracking and quest log addons.
Add cancelQueuedSpell() method that clears queuedSpellId_ and
queuedSpellTarget_. Wire /cancelqueuedspell and /stopspellqueue
slash commands. Useful for combat macros that need to prevent
queued spells from firing after a current cast.
When SMSG_DESTROY_OBJECT removes an entity, now also purge combat text
entries targeting that GUID (prevents floating damage numbers on despawned
mobs), erase unit cast state (prevents stale cast bars), and clear cached
auras (prevents stale buff/debuff data for destroyed units).
/dump <expression> evaluates a Lua expression and prints the result to
chat. For tables, iterates key-value pairs and displays them. Aliases:
/print. Useful for addon development and debugging game state queries
like "/dump GetSpellInfo(133)" or "/dump UnitHealth('player')".
Cloud renderer's density was hardcoded at 0.35 and never updated from the
DBC-driven cloudDensity parameter. Now setDensity() is called each frame
with the lighting manager's cloud coverage value. Active weather (rain/
snow/storm) additionally boosts cloud density by up to 0.4 so clouds
visibly thicken during storms.
Add UnitAura() that accepts WoW-compatible filter strings: "HELPFUL" for
buffs, "HARMFUL" for debuffs. Delegates to existing UnitBuff/UnitDebuff
logic. Many addons (WeakAuras, Grid, etc.) use UnitAura with filter
strings rather than separate UnitBuff/UnitDebuff calls.
Fire BAG_UPDATE and UNIT_INVENTORY_CHANGED when item stack/durability
fields change in UPDATE_OBJECT VALUES path. Fire PLAYER_EQUIPMENT_CHANGED
when equipment slot fields change. Enables bag addons (Bagnon, OneBag) and
gear tracking addons to react to inventory changes.
Reduce flare intensity when sun is near the horizon via smoothstep on
sunDir.z (0→0.25 range). Apply amber/orange color shift to flare elements
at sunrise/sunset for a warm golden glow. Prevents overly bright flares
at low sun angles while enhancing atmospheric mood.
SMSG_ENCHANTMENTLOG now resolves spell name and shows "You enchant with
[name]" or "[Caster] enchants your item with [name]" instead of silent
debug log. SMSG_LOG_XPGAIN now shows creature name: "Wolf dies, you gain
45 experience" instead of generic "You gain 45 experience" for kill XP.
Add AddonManager::reload() which saves all SavedVariables, shuts down the
Lua VM, re-initializes it, rescans .toc files, and reloads all addons.
Wire /reload, /reloadui, /rl slash commands that call reload() and fire
VARIABLES_LOADED + PLAYER_LOGIN + PLAYER_ENTERING_WORLD lifecycle events.
Essential for addon development and troubleshooting.
When the dungeon finder initiates a role check (SMSG_LFG_ROLE_CHECK_UPDATE
state=2), show a centered popup with Tank/Healer/DPS checkboxes and
Accept/Leave Queue buttons. Accept sends CMSG_LFG_SET_ROLES with the
selected role mask. Previously only showed passive "Role check in progress"
text with no way to respond.
Add Weather::Type::STORM enum value and wire it from SMSG_WEATHER type 3.
Storm particles are faster (70 units/s vs rain's 50), wind-angled at 15+
units lateral velocity with gusty turbulence, darker blue-grey tint, and
shorter lifetime. Previously storms rendered identically to rain.
Remove unused getPlayerUnit() helper in lua_engine.cpp (-Wunused-function).
Increase countStr buffer from 8 to 16 bytes in action bar item count
display to eliminate -Wformat-truncation warning for %d with int32_t.
Build is now warning-free.
Add zone name query functions using worldStateZoneId + getAreaName lookup.
GetRealZoneText is aliased to GetZoneText. These are heavily used by boss
mod addons (DBM) for zone detection and by quest tracking addons.
Add UNIT_SPELLCAST_CHANNEL_START (MSG_CHANNEL_START), UNIT_SPELLCAST_CHANNEL_STOP
(MSG_CHANNEL_UPDATE with 0ms remaining), UNIT_SPELLCAST_FAILED (SMSG_CAST_RESULT
with error), and UNIT_SPELLCAST_INTERRUPTED (SMSG_SPELL_FAILURE) events. These
enable addons to track channeled spells and cast interruptions for all units.
Show [Winning] (green) or [Outbid] (red) labels on the Bids tab based on
bidderGuid vs player GUID comparison. Show [Bid] (gold) indicator on the
seller's Auctions tab when someone has placed a bid on their listing.
Improves auction house usability by making bid status visible at a glance.
Add UnitAffectingCombat, GetNumRaidMembers, GetNumPartyMembers, UnitInParty,
UnitInRaid, UnitIsUnit, UnitIsFriend, UnitIsEnemy, and UnitCreatureType.
These are commonly used by raid/group addons for party composition checks,
combat state queries, and mob type identification. Total API count now 55.
NPC character models used wrong geoset groups: gloves were group 3 (300s)
instead of group 4 (400s), boots were group 4 (400s) instead of group 5
(500s), matching the character preview code. Also remove spurious "torso"
geoset from group 5 (conflicted with boots) — chest armor controls only
group 8 (sleeves), not a separate torso visibility group. Fixes NPC
equipment rendering with incorrect body part meshes.
Fire ZONE_CHANGED_NEW_AREA and ZONE_CHANGED when worldStateZoneId changes
in SMSG_INIT_WORLD_STATES. Add VARIABLES_LOADED and PLAYER_LOGIN events in
the addon loading sequence (before PLAYER_ENTERING_WORLD), and fire
PLAYER_ENTERING_WORLD on subsequent world entries (teleport, instance).
Enables zone-aware addons like DBM and quest trackers.
Add QuestPoi struct and setQuestPois() to WorldMap, render quest objective
markers as cyan circles with golden outlines and quest title labels. Wire
gossipPois_ (from SMSG_QUEST_POI_QUERY_RESPONSE) through GameScreen to the
world map so quest objectives are visible alongside party dots and taxi nodes.
IsMounted, IsFlying, IsSwimming, IsResting, IsFalling, and IsStealthed
now query actual GameHandler state (mount display ID, movement flags,
resting flag, aura list) instead of returning false. Add GetUnitSpeed
for player run speed. Fixes addon conditionals that depend on player
movement/mount/combat state.
handleGroupUninvite and leaveGroup cleared partyData but did not fire
GROUP_ROSTER_UPDATE/PARTY_MEMBERS_CHANGED events, so addon group tracking
would not update when kicked or leaving. Now both paths fire both events.
Fire COMBAT_LOG_EVENT_UNFILTERED from addCombatText with WoW-compatible
subevent names (SWING_DAMAGE, SPELL_DAMAGE, SPELL_HEAL, etc.), source/dest
GUIDs, names, spell info, and amount. Also fire SPELL_UPDATE_COOLDOWN and
ACTIONBAR_UPDATE_COOLDOWN when cooldowns start (handleSpellCooldown), not
just when they end. Enables damage meter and boss mod addons.
Add PLAYER_REGEN_DISABLED/ENABLED (combat enter/leave) via per-frame
edge detection, LEARNED_SPELL_IN_TAB/SPELLS_CHANGED on spell learn/remove,
SPELL_UPDATE_COOLDOWN/ACTIONBAR_UPDATE_COOLDOWN on cooldown finish, and
PLAYER_XP_UPDATE on XP field changes. Total addon events now at 34.
Extend resolveUnit() to support party1-4, raid1-40, and use resolveUnitGuid
for UnitGUID/UnitIsPlayer/UnitBuff/UnitDebuff (including unitAurasCache for
party member auras). Fire UNIT_HEALTH, UNIT_POWER, UNIT_AURA, UNIT_SPELLCAST_START,
UNIT_SPELLCAST_SUCCEEDED, GROUP_ROSTER_UPDATE, and PARTY_MEMBERS_CHANGED events
to Lua addons from the corresponding packet handlers.
- hooksecurefunc(tblOrName, name, hook) — hook any function to run
additional code after it executes without replacing the original.
Supports both global and table method forms.
- UIParent, WorldFrame — standard parent frames that many addons
reference as parents for their own frames.
- Noop stubs: SetDesaturation, SetPortraitTexture, PlaySound,
PlaySoundFile — prevent errors from addons that call these
visual/audio functions which don't have implementations yet.
Many WoW addons use DEFAULT_CHAT_FRAME:AddMessage(text, r, g, b) to
output colored text to chat. Implemented as a Lua table with AddMessage
that converts RGB floats to WoW color codes and calls print().
Also aliased as ChatFrame1 for compatibility.
Example: DEFAULT_CHAT_FRAME:AddMessage("Hello!", 1, 0.5, 0)
- GetNumAddOns() — returns count of loaded addons
- GetAddOnInfo(indexOrName) — returns name, title, notes, loadable
Addon info is stored in the Lua registry from the .toc directives
and populated before addon files execute. Useful for addon managers
and compatibility checks between addons.
Total WoW API: 33 functions.
Fire ADDON_LOADED(addonName) after all of an addon's files have been
executed. This is the standard WoW pattern for addon initialization —
addons register for this event to set up defaults after SavedVariables
are loaded:
local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", function(self, event, name)
if name == "MyAddon" then
MyAddonDB = MyAddonDB or {defaults}
end
end)
Total addon events: 20.
Implement the most commonly used buff/debuff query functions:
- UnitBuff(unitId, index) — query the Nth buff on a unit
- UnitDebuff(unitId, index) — query the Nth debuff on a unit
Returns WoW-compatible 11-value tuple: name, rank, icon, count,
debuffType, duration, expirationTime, caster, isStealable,
shouldConsolidate, spellId.
Supports "player" and "target" unit IDs. Essential for buff tracking
addons (WeakAuras-style), healer addons, and combat analysis tools.
Total WoW API: 31 functions.
The Semgrep security scan was failing because vendored Lua 5.1.5 source
uses strcpy/strncpy which are flagged as insecure C functions. These are
false positives in frozen third-party code that we don't modify.
Added .semgrepignore to exclude all vendored extern/ directories
(lua-5.1.5, imgui, stb, vk-bootstrap, FidelityFX SDKs).
Addons can now persist data across sessions using the standard WoW
SavedVariables pattern:
1. Declare in .toc: ## SavedVariables: MyAddonDB
2. Use the global in Lua: MyAddonDB = MyAddonDB or {default = true}
3. Data is automatically saved on logout and restored on next login
Implementation:
- TocFile::getSavedVariables() parses comma-separated variable names
- LuaEngine::loadSavedVariables() executes saved .lua file to restore globals
- LuaEngine::saveSavedVariables() serializes Lua tables/values to valid Lua
- Serializer handles tables (nested), strings, numbers, booleans, nil
- Save triggered on PLAYER_LEAVING_WORLD and AddonManager::shutdown()
- Files stored as <AddonDir>/<AddonName>.lua.saved
Updated HelloWorld addon to track login count across sessions.
- UnitRace(unitId) — returns race name ("Human", "Orc", etc.)
- UnitPowerType(unitId) — returns power type ID and name ("MANA", "RAGE", etc.)
- GetNumGroupMembers() — party/raid member count
- UnitGUID(unitId) — returns hex GUID string (0x format)
- UnitIsPlayer(unitId) — true if target is a player (not NPC)
- InCombatLockdown() — true if player is in combat
Total WoW API surface: 29 functions.
Implement WoW's C_Timer API used by most modern addons:
- C_Timer.After(seconds, callback) — fire callback after delay
- C_Timer.NewTicker(seconds, callback, iterations) — repeating timer
with optional iteration limit and :Cancel() method
Implemented in pure Lua using a hidden OnUpdate frame that
auto-hides when no timers are pending (zero overhead when idle).
Example:
C_Timer.After(3, function() print("3 sec later!") end)
local ticker = C_Timer.NewTicker(1, function() print("tick") end, 5)
Frames can now set an OnUpdate script that fires every frame with
the elapsed time as an argument. This enables addon timers, polling,
and animations.
local f = CreateFrame("Frame")
f:SetScript("OnUpdate", function(self, elapsed)
-- called every frame with deltaTime
end)
OnUpdate only fires for visible frames (frame:Hide() pauses it).
Tracked in __WoweeOnUpdateFrames table, dispatched via
LuaEngine::dispatchOnUpdate() called from the Application main loop.
Fire more gameplay events to Lua addons:
- PLAYER_MONEY — when gold/silver/copper changes (both CREATE and VALUES paths)
- PLAYER_DEAD — on forced death (SMSG_FORCED_DEATH_UPDATE)
- PLAYER_ALIVE — when ghost flag clears (player resurrected)
Total addon events: 19 (2 world + 12 chat + 5 gameplay).
Add a generic AddonEventCallback to GameHandler for firing named events
with string arguments directly from game logic. Wire it to the addon
system in Application.
New events fired:
- PLAYER_TARGET_CHANGED — when target is set or cleared
- PLAYER_LEVEL_UP(newLevel) — on level up
The generic callback pattern makes it easy to add more events from
game_handler.cpp without touching Application/AddonManager code.
Total addon events: 16 (2 world + 12 chat + 2 gameplay).
Implement the core WoW frame system that nearly all addons use:
- CreateFrame(type, name, parent, template) — creates a frame table
with metatable methods, optionally registered as a global by name
- frame:RegisterEvent(event) — register frame for event dispatch
- frame:UnregisterEvent(event) — unregister
- frame:SetScript(type, handler) — set OnEvent/OnUpdate/etc handlers
- frame:GetScript(type) — retrieve handlers
- frame:Show()/Hide()/IsShown()/IsVisible() — visibility state
- frame:GetName() — return frame name
Event dispatch now fires both global RegisterEvent handlers AND
frame OnEvent scripts, matching WoW's dual dispatch model.
Updated HelloWorld to use standard WoW addon pattern:
local f = CreateFrame("Frame", "MyFrame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self, event, ...) end)
Add 5 more essential WoW API functions for addon development:
- SendChatMessage(msg, type, lang, target) — send chat messages
(SAY, YELL, WHISPER, PARTY, GUILD, OFFICER, RAID, BG)
- CastSpellByName(name) — cast highest rank of named spell
- IsSpellKnown(spellId) — check if player knows a spell
- GetSpellCooldown(nameOrId) — get remaining cooldown
- HasTarget() — check if player has a target
Total WoW API surface: 18 functions across Unit, Game, and Action
categories. Addons can now query state, react to events, send
messages, and cast spells.
Wire chat messages to the addon event system via AddonChatCallback.
Every chat message now fires the corresponding WoW event:
- CHAT_MSG_SAY, CHAT_MSG_YELL, CHAT_MSG_WHISPER
- CHAT_MSG_PARTY, CHAT_MSG_GUILD, CHAT_MSG_OFFICER
- CHAT_MSG_RAID, CHAT_MSG_RAID_WARNING, CHAT_MSG_BATTLEGROUND
- CHAT_MSG_SYSTEM, CHAT_MSG_CHANNEL, CHAT_MSG_EMOTE
Event handlers receive (eventName, message, senderName) arguments.
Addons can now filter, react to, or log chat messages in real-time.
Implement the WoW-compatible event system that lets addons react to
gameplay events in real-time:
- RegisterEvent(eventName, handler) — register a Lua function for an event
- UnregisterEvent(eventName, handler) — remove a handler
- fireEvent() dispatches events to all registered handlers with args
Currently fired events:
- PLAYER_ENTERING_WORLD — after addons load and world entry completes
- PLAYER_LEAVING_WORLD — before logout/disconnect
Events are stored in a __WoweeEvents Lua table, dispatched via
LuaEngine::fireEvent() which is called from AddonManager::fireEvent().
Error handling logs Lua errors without crashing.
Updated HelloWorld addon to use RegisterEvent for world entry/exit.
Foundation for WoW-compatible addon support:
- Vendor Lua 5.1.5 source as a static library (extern/lua-5.1.5)
- TocParser: parses .toc files (## directives + file lists)
- LuaEngine: Lua 5.1 VM with sandboxed stdlib (no io/os/debug),
WoW-compatible print() that outputs to chat, GetTime() stub
- AddonManager: scans Data/interface/AddOns/ for .toc files,
loads .lua files on world entry, skips LoadOnDemand addons
- /run <code> slash command for inline Lua execution
- HelloWorld test addon that prints to chat on load
Integration: AddonManager initialized after asset manager, addons
loaded once on first world entry, reset on logout. XML frame
parsing is deferred to a future step.
When hovering a /use macro whose item's on-use spell isn't in the DBC
(no rich spell tooltip available), the tooltip fell back to showing raw
macro text. Now searches the item info cache and shows the full item
tooltip (stats, quality, binding, description) as a more useful
fallback for /use macros.
Macros with /use ItemName tried to find the item as a spell name for
icon resolution, which fails for items without a matching spell (e.g.
engineering trinkets, quest items). Now falls back to searching the
item info cache by name and showing the item's display icon when no
spell name matches.
setQuestTracked() modified trackedQuestIds_ but didn't call
saveCharacterConfig(), so tracked quests were only persisted if
another action (like editing a macro or rearranging the action bar)
happened to trigger a save before logout. Now saves immediately
when quests are tracked or untracked.
Quest tracking choices (right-click → Track on the quest objective
tracker) were lost on logout because trackedQuestIds_ was not saved
in the character config. Now saves tracked quest IDs as a comma-
separated list and restores them on login, so the quest tracker
shows the same quests the player chose to track in their previous
session.
Add [target=mouseover] and the @ shorthand syntax (@focus, @pet,
@mouseover, @player, @target) to the /macrohelp output. These are
commonly used for mouseover healing macros and were already supported
but not documented in the in-game help.
The /help text was missing several commonly-used commands: /castsequence,
/use, /threat, /combatlog, /mark, /raidinfo, /assist, /inspect,
/chathelp. Reorganized categories for clarity and added all missing
entries to match the expanded auto-complete list.
The spell visual failed-model cache was never cleared across world
changes, so models that failed to load during initial asset loading
(before MPQ/CASC data was fully indexed) would never retry. Now clears
spellVisualFailedModels_ in resetCombatVisualState() alongside the
active spell visual cleanup, giving failed models a fresh attempt on
each world entry.
Macros with /use ItemName (e.g. /use Healthstone, /use Engineering
trinket) had no icon, cooldown, or tooltip on the action bar because
only /cast and /castsequence were recognized. Now the spell resolution
also handles /use by looking up the item name in the item info cache
and finding its on-use spell ID. Added getItemInfoCache() accessor.
The macro primary spell cache stored 0 (no spell found) when a macro
referenced a spell the player hadn't learned yet. After learning the
spell from a trainer or leveling up, the cache was never refreshed,
so the macro button stayed broken. Now tracks the known spell count
and clears the cache when it changes, ensuring newly learned spells
are resolved on the next frame.
Macros with /castsequence were treated as having no primary spell, so
they showed no icon, cooldown, range, power, or tooltip on the action
bar. Now both resolveMacroPrimarySpellId() and the icon derivation
code recognize /castsequence commands, strip the reset= spec, and
use comma-separation to find the first spell in the sequence. This
gives /castsequence macros the same visual indicators as /cast macros.
The error-flash overlay (red fade on spell cast failure) only applied to
SPELL-type slots. Macro buttons never flashed red when their primary
spell failed to cast. Now resolves the macro's primary spell and checks
the actionFlashEndTimes_ map for a matching flash, completing macro
action bar parity with spell buttons across all 6 visual indicators.
The insufficient-power indicator only applied to SPELL-type slots.
Macro buttons like /cast Fireball never showed the power tint when the
player was out of mana. Now resolves the macro's primary spell and
checks its power cost against the player's current power, giving the
same visual feedback as regular spell buttons.
The out-of-range indicator (red tint) only applied to SPELL-type action
bar slots. Macro buttons like /cast Frostbolt never turned red even when
the target was out of range. Now resolves the macro's primary spell via
the cached lookup and checks its max range against the target distance,
giving the same visual feedback as regular spell buttons.
Hovering a macro button on the action bar previously showed "Macro #N"
with raw macro text. Now resolves the macro's primary spell via the
cached lookup and shows its full rich tooltip (name, school, cost, cast
time, range, description) — same as hovering a regular spell button.
Falls back to the raw text display if no primary spell is found.
Also shows the cooldown remaining in red when the spell is on cooldown.
The macro primary spell cache was keyed by action bar slot index, so
switching characters or rearranging macros could return stale spell IDs
from the previous character's macro in that slot. Now keyed by macro ID,
which is stable per-macro regardless of which slot it occupies.
The macro cooldown display from the previous commit iterated all known
spells (400+) every frame for each macro on the action bar, doing
lowercase string comparisons. Moved the spell name resolution into a
cached lookup (macroPrimarySpellCache_) that only runs once per macro
and is invalidated when macro text is edited. The per-frame path now
just does a single hash map lookup + spellCooldowns check.
Macro buttons on the action bar never showed cooldowns — a /cast
Fireball macro would display no cooldown sweep or timer even when
Fireball was on cooldown. Now resolves the macro's primary spell (from
the first /cast command, stripping conditionals and alternatives) and
checks its cooldown via spellCooldowns. The cooldown sweep overlay and
countdown text display using the resolved spell's remaining time.
The cooldown sync after SMSG_ACTION_BUTTONS and SMSG_INITIAL_SPELLS
only handled SPELL-type action bar slots. ITEM-type slots (potions,
trinkets, engineering items) were skipped, so items on the action bar
showed no cooldown overlay after login even if their on-use spell was
on cooldown. Now looks up each item's on-use spell IDs from the item
info cache and syncs any matching spellCooldowns entries.
FSR2/FSR3 upscaling mode was forcibly reverted to FSR1 on every startup
unless the WOWEE_ALLOW_STARTUP_FSR2 environment variable was set. This
meant users had to re-select FSR 3.x and re-enable frame generation on
every launch. Removed the env var requirement since the deferred
activation (wait until IN_WORLD state) already provides sufficient
startup safety by preventing FSR init during login/character screens.
Some server implementations include cooldown entries for all spells
(even with zero remaining time) to communicate category cooldown data.
The previous 256 cap could truncate these entries, causing missing
cooldown tracking for spells near the end of the list. Raised to match
the spell count cap for consistency.
WotLK characters with all ability ranks, mounts, companion pets,
professions, and racial skills can know 400-600 spells. The previous
256 cap truncated the spell list, causing missing spells in the
spellbook, broken /cast commands for truncated spells, and missing
cooldown tracking for spells beyond the cap.
Active spell visual M2 instances were never cleaned up when the player
teleported to a different map or re-entered the world. Orphaned effects
could linger visually from the previous combat session. Now properly
removes all active spell visual instances in resetCombatVisualState(),
which is called on every world entry.
UNIT_FIELD_AURAFLAGS was defined in the UF enum and used in Classic and
Turtle JSON files (index 98) but missing from the kUFNames lookup table.
The JSON loader silently skipped it, so Classic/Turtle aura flag data
from UPDATE_OBJECT was never mapped. This could cause aura display
issues on Classic 1.12 and Turtle WoW servers.
PLAYER_FIELD_HONOR_CURRENCY and PLAYER_FIELD_ARENA_CURRENCY were added
to the UF enum and JSON files in cycle 1, but the kUFNames lookup table
in update_field_table.cpp was not updated. This meant the JSON loader
could not map these field names to their enum values, so honor and
arena point values from UPDATE_OBJECT were silently ignored.
SMSG_INITIAL_SPELLS cooldown entries have both cooldownMs (individual)
and categoryCooldownMs (shared, e.g. potions). The handler only checked
cooldownMs, so spells with category-only cooldowns (cooldownMs=0,
categoryCooldownMs=120000) were not tracked. Now uses the maximum of
both values, ensuring potion and similar shared cooldowns show on the
action bar after login.
SMSG_SPELL_COOLDOWN arrives before SMSG_ACTION_BUTTONS during login,
so cooldown times were stored in spellCooldowns but never applied to
the newly populated action bar slots. Players would see all abilities
as ready immediately after login even if spells were on cooldown.
Now applies pending cooldowns from the spellCooldowns map to each
matching slot when the action bar is first populated.
Chat tabs with unread messages now pulse yellow to attract attention.
The existing unread count "(N)" suffix was text-only and easy to miss,
especially for whisper and guild tabs. The pulsing color clears when
the tab is clicked, matching standard WoW chat tab behavior.
Add commonly-used WoW macro conditionals:
- [raid]/[noraid] — checks if the player is in a raid group (groupType
== 1) vs a regular party. Used for conditional healing/targeting in
raid content.
- [spec:1]/[spec:2] — checks the active talent spec (1-based index).
Used for dual-spec macros that swap gear sets or use different
rotations per spec.
Updated /macrohelp to list the new conditionals.
The /macrohelp listed [target=pet] as supported but the conditional
evaluator didn't handle the "pet" specifier for target= or @ syntax.
Now resolves to the player's active pet GUID (or skips the alternative
if no pet is active). Essential for hunter/warlock macros like:
/cast [target=pet] Mend Pet
/cast [@pet,dead] Revive Pet
Add indoor/outdoor state macro conditionals using the renderer's WMO
interior detection. Essential for mount macros that need to select
ground mounts indoors vs flying mounts outdoors. The Renderer now
caches the insideWmo state in playerIndoors_ and exposes it via
isPlayerIndoors(). Updated /macrohelp to list the new conditionals.
Add frequently-used macro conditionals for pet and group state:
- [pet]/[nopet] — checks if the player has an active pet (hunters,
warlocks, DKs). Essential for pet management macros.
- [group]/[nogroup]/[party] — checks if the player is in a party or
raid. Used for conditional targeting and ability usage.
Updated /macrohelp output to list the new conditionals.
Players can now type /macrohelp to see all supported macro conditionals
grouped by category (state, target, form, keys, aura). Also added to
the /help output and chat auto-complete list. This helps users discover
the macro system without external documentation.
Add commonly-used WoW macro conditionals that were missing:
- [mounted]/[nomounted] — checks isMounted() state
- [swimming]/[noswimming] — checks SWIMMING movement flag
- [flying]/[noflying] — checks CAN_FLY + FLYING movement flags
- [stealthed]/[nostealthed] — checks UNIT_FLAG_SNEAKING (0x02000000)
- [channeling]/[nochanneling] — checks if currently channeling a spell
These are essential for common macros like mount/dismount toggles,
rogue opener macros, and conditional cast sequences.
The proficiency check added in the previous commit only applied to the
ItemDef tooltip variant (inventory items). Vendor, loot, and AH
tooltips use the ItemQueryResponseData variant which was missing the
check. Now both tooltip paths show "You can't use this type of item."
in red when the player lacks weapon or armor proficiency.
Item tooltips now display a red "You can't use this type of item."
warning when the player lacks proficiency for the weapon or armor
subclass (e.g. a mage hovering over a plate item or a two-handed
sword). Uses the existing canUseWeaponSubclass/canUseArmorSubclass
checks against SMSG_SET_PROFICIENCY bitmasks.
Capital cities and large raids can produce UPDATE_OBJECT packets that
decompress to more than 1MB. The real WoW client handles up to ~10MB.
Bump the limit from 1MB to 5MB to avoid silently dropping entity
updates in densely populated areas like Dalaran or 40-man raids.
Spell visual M2 models that fail to load (missing file, empty model,
or GPU upload failure) were re-attempted on every subsequent spell cast,
causing repeated file I/O during combat. Now caches failed model IDs in
spellVisualFailedModels_ so they are skipped on subsequent attempts.
Spell visual effects previously used a fixed 3.5s duration for all
effects, causing some to linger too long and overlap during combat.
Now queries the M2 model's default animation duration via the new
getInstanceAnimDuration() method and clamps it to 0.5-5s. Effects
without animations fall back to a 2s default. This makes spell impacts
feel more responsive and reduces visual clutter.
Add a "Usable" checkbox to the AH search UI that filters results to
items the player can actually equip/use (server-side filtering via the
usableOnly parameter in CMSG_AUCTION_LIST_ITEMS). Also ensure token
item names for extended costs are queried from the server via
ensureItemInfo() so they display properly instead of "Item#12345".
Load ItemExtendedCost.dbc and show specific costs (e.g. "2000 Honor",
"200 Arena", "30x Badge of Justice") instead of generic "[Tokens]" for
vendor items with extended costs. Items with both gold and token costs
now show both. Token item names are resolved from item info cache.
SMSG_EQUIPMENT_SET_SAVED now updates the local set's GUID from the
server response, preventing duplicate set creation when clicking
"Update" on a newly-saved set. New sets are also added to the local
list immediately so the UI reflects them without a relog.
Additionally, CMSG_PLAYED_TIME is now auto-sent on initial world entry
(with sendToChat=false) so the character Stats tab shows total and
level time immediately without requiring /played.
Classic and TBC lack equipment set opcodes, so sending save/use/delete
packets would transmit wire opcode 0xFFFF and potentially disconnect the
client. Now all three methods check wireOpcode != 0xFFFF before sending,
and the Outfits tab is only shown when the expansion supports equipment
sets (via supportsEquipmentSets() check).
The packet previously sent only a uint32 setId, which does not match
the WotLK protocol. AzerothCore/TrinityCore expect 19 iterations of
(PackedGuid itemGuid + uint8 srcBag + uint8 srcSlot). Now looks up the
equipment set's target item GUIDs and searches equipment, backpack, and
extra bags to provide correct source locations for each item.
Send CMSG_NEXT_CINEMATIC_CAMERA in response to SMSG_TRIGGER_CINEMATIC
and CMSG_COMPLETE_MOVIE in response to SMSG_TRIGGER_MOVIE. Some WotLK
servers block further packets or disconnect clients that don't respond
to these triggers, especially during the intro cinematic on first login.
setWatchedFactionId() previously only stored the faction locally.
Now it also sends CMSG_SET_WATCHED_FACTION with the correct repListId
to the server, so the tracked faction persists across sessions.
Add saveEquipmentSet() and deleteEquipmentSet() methods that send
CMSG_EQUIPMENT_SET_SAVE and CMSG_DELETEEQUIPMENT_SET packets. The save
packet captures all 19 equipment slot GUIDs via packed GUID encoding.
The Outfits tab now always shows (not just when sets exist), with an
input field to create new sets and Update/Delete buttons per set.
Add PLAYER_FIELD_HONOR_CURRENCY and PLAYER_FIELD_ARENA_CURRENCY to the
update field system for WotLK (indices 1422/1423) and TBC (1505/1506).
Parse values from both CREATE_OBJECT and VALUES update paths, and show
them in the character Stats tab under a PvP Currency section.
SMSG_PET_CAST_FAILED: Classic/TBC omit the castCount byte (matching
SMSG_CAST_FAILED pattern). Without this fix, TBC parsing reads garbage.
SMSG_LFG_ROLE_CHOSEN: surface role selection messages in chat during
dungeon finder role checks.
Parse SMSG_PETITION_QUERY_RESPONSE, SMSG_PETITION_SHOW_SIGNATURES,
and SMSG_PETITION_SIGN_RESULTS. Add UI to view signatures, sign
petitions, and turn in completed charters. Send CMSG_PETITION_SIGN
and CMSG_TURN_IN_PETITION packets.
Store team name and type (2v2/3v3/5v5) from SMSG_ARENA_TEAM_QUERY_RESPONSE.
Display proper team labels instead of raw IDs. Add Load/Refresh roster
buttons and CMSG_ARENA_TEAM_ROSTER request support.
PLAYER_BYTES and PLAYER_BYTES_2 changes in SMSG_UPDATE_OBJECT now
update the Character struct's appearanceBytes and facialFeatures,
and fire an appearance-changed callback that resets the inventory
screen preview so it reloads with the new hair/face values.
Arena and BG type IDs now match actual 3.3.5a BattlemasterList.dbc:
Nagrand Arena=4, Blade's Edge=5, Ruins of Lordaeron=8, Dalaran
Sewers=10, Ring of Valor=11, Isle of Conquest=30, Random BG=32.
Eye of the Storm uses bgTypeId 7 (from BattlemasterList.dbc), not 6.
BG invite popup now uses the stored bgName from the queue slot instead
of re-deriving the name with a duplicate switch statement.
Adds a functional barber shop window triggered by SMSG_ENABLE_BARBER_SHOP.
Players can adjust hair style, hair color, and facial features using
sliders bounded by race/gender max values. Sends CMSG_ALTER_APPEARANCE
on confirm; server result closes the window on success. Escape key
also closes the barber shop.
Right-clicking the target or focus frame name now opens a context
menu with Set Focus/Target, Clear Focus, Whisper, Invite, Trade,
Duel, Inspect, Add Friend, and Ignore options (player-specific
options only shown for player targets).
Escape now closes UI windows in priority order (vendor, bank, trainer,
who, combat log, social, talents, spellbook, quest log, character,
inventory, world map) before falling through to the escape menu, matching
standard WoW behavior.
Nameplate debuff indicators now show: clock-sweep overlay for elapsed
duration, countdown text below each dot (color-coded red < 5s, yellow
< 15s), stack count badge, and duration in hover tooltip.
Shows a confirmation dialog before buying items costing 1 gold or more,
preventing accidental purchases. Displays item name, quantity, and
total cost in gold/silver/copper.
Implements CMSG_SPLIT_ITEM (0x10E) with a slider popup for choosing
split count. Auto-finds empty destination slot across backpack and bags.
Shift+right-click on stackable items (count > 1) opens split dialog;
non-stackable items still get the destroy confirmation.
Hand cursor now shows when hovering over any interactive entity in the
3D world (NPCs, players, game objects), not just game objects. Helps
identify clickable targets at a glance.
Play UI error sound on SMSG_CAST_FAILED for consistent audio feedback,
matching other error handlers (vendor, inventory, trainer).
Auto-reply to incoming whispers with AFK/DND message when player has
set /afk or /dnd status.
Captures the Vulkan swapchain image to PNG via stb_image_write.
Screenshots saved to ~/.wowee/screenshots/ with timestamped filenames.
Cross-platform: BGRA→RGBA swizzle, localtime_r/localtime_s.
Type /loc, /coords, or /whereami in chat to display current position
(X, Y, Z) and zone name as a system message. Useful for sharing
locations or debugging position issues.
Show creature type (Beast, Humanoid, etc.) on the focus frame next to
the rank badge, matching the target frame. Also display player guild
names on focus frame for player targets.
Show creature classification (Beast, Humanoid, Demon, etc.) next to the
level on the target frame. Useful for knowing which CC abilities apply
(Polymorph → Humanoid/Beast, Banish → Demon/Elemental, etc.).
Re-enable the login screen music system that was previously disabled.
Randomly selects from available tracks in assets/Original Music/ and
plays them at 80% volume during authentication.
Add rank-specific outer borders on NPC nameplates: gold for Elite and
Rare Elite, red for Boss, silver for Rare. Provides immediate visual
identification of dangerous mobs without needing to target them.
Display creature subtitles (e.g. <Reagent Vendor>, <Innkeeper>) below
NPC names on nameplates, mirroring the guild tag display for players.
The subtitle is fetched from the creature info cache populated by
SMSG_CREATURE_QUERY_RESPONSE.
Store the voteMask from SMSG_LOOT_START_ROLL and use it to conditionally
show Need/Greed/Disenchant/Pass buttons. Previously all four buttons were
always shown regardless of the server's allowed roll types.
SMSG_SPELL_MISS_LOG REFLECT entries include a reflectSpellId field that
was parsed but discarded. Now store it in SpellMissLogEntry and pass it
to addCombatText, so floating combat text shows the actual reflected
spell name instead of the original cast spell.
Combat text (damage, heals, misses, crits, etc.) now floats above the
target entity in 3D space instead of appearing at fixed screen positions.
Text rises upward from the entity's head, with random horizontal stagger
to prevent stacking. HUD-only types (XP, Honor, Procs) and entries
without a valid entity anchor fall back to the original screen overlay.
Read PLAYER_GUILDID from entity update fields (UNIT_END + 3) and query
guild names via CMSG_GUILD_QUERY. Cache results in guildNameCache_ so
each guild ID is queried only once. Display <Guild Name> in grey below
the player name on nameplates. Fix handleGuildQueryResponse to not
overwrite the local player's guild data when querying other guilds.
The WotLK periodic damage format includes an isCrit byte after resisted
(21 bytes total, not 20). Missing this byte caused parse misalignment
for multi-effect periodicauralog packets. Also use the already-read
isCrit on periodic heals to display critical HoT ticks distinctly.
Add 5-dot combo point indicator between target power bar and cast bar.
Lit dots are yellow (1-4 CP) or red (5 CP) with glow effect; unlit
dots show as dark outlines. Only visible when the player's combo target
matches the current target.
loadOnlineWorldTerrain re-registers the death/respawn/swing callbacks,
overriding the ones from setupUICallbacks. The world-load versions only
checked creatureInstances_, so the player lookup fix from the previous
commit was silently reverted whenever the world loaded. Now both
registration sites check playerInstances_ as a fallback.
Player class declared its own 'name' member and getName()/setName()
that shadowed the inherited Unit::name. Since getName() is non-virtual,
code using Unit* pointers (nameplates, target frame, entity list) read
Unit::name (always empty) while Player::setName() wrote to the shadowed
Player::name. Removed the redundant declaration so Player inherits
name storage from Unit.
Death, respawn, and melee swing callbacks only checked
creatureInstances_, so online players never played death animation when
killed, never returned to idle on resurrect, and never showed attack
swings. Extended all three callbacks to also check playerInstances_.
Also extended the game_handler death/respawn callback triggers to fire
for PLAYER entities (not just UNIT), and added spawn-time death
detection for players that are already dead when first seen.
Both CREATE_OBJECT and MOVEMENT update paths called
setPlayerOnTransport(guid, vec3(0)) then immediately overwrote
playerTransportOffset_ on the next line. This left a one-frame window
where the composed world position used (0,0,0) as the local offset,
causing the player to visually snap to the transport origin. Compute the
canonical offset first and pass it directly.
Online players had no animation state machine — once Run started from a
movement packet, it never transitioned back to Stand/Idle. This mirrors
the creature sync loop: position, orientation, and locomotion animation
(Run/Walk/Swim/Fly ↔ Stand/SwimIdle/FlyIdle) are now driven per-frame
based on Entity::isActivelyMoving() state transitions.
Also cleans up creatureRenderPosCache_ on player despawn.
Creatures were stuck in Run/Walk animation during the dead-reckoning
overrun window (up to 2x movement duration). The animation check used
isEntityMoving() which stays true through dead reckoning, causing
creatures to "run in place" after reaching their destination.
Add isActivelyMoving() which is true only during the active
interpolation phase (moveElapsed < moveDuration), and use it for
animation state transitions. Dead reckoning still works for position
extrapolation — only the animation now correctly stops at arrival.
Change WotLK MonsterMove pointCount > 1000 from cap-to-1000 to return
false. Capping caused the parser to read only 1000 of N points, leaving
the remaining point data unread and misaligning subsequent reads.
Also correct misleading loot response comment: Classic/TBC DO include
randomSuffix and randomPropertyId (22 bytes/item, same as WotLK). The
only WotLK difference is the quest item list appended after regular
items.
Check remaining packet data before reading update type, GUIDs, object
type, and block count in parseUpdateBlock and parseUpdateFields. Prevents
silent garbage reads when the parser reaches the end of a truncated or
misaligned packet.
Complete the parser hardening across all expansions. Check remaining
bytes before every conditional read in the WotLK base
UpdateObjectParser::parseMovementBlock: LIVING entry (66-byte minimum),
transport, pitch, fall time, jumping, spline elevation, speeds,
POSITION, STATIONARY, and all tail flags (HAS_TARGET, TRANSPORT,
VEHICLE, ROTATION, LOWGUID, HIGHGUID). Prevents silent garbage reads
when Packet::readUInt8/readFloat return 0 past EOF.
Same hardening as the Classic and Turtle parsers: check remaining bytes
before every conditional read in TbcPacketParsers::parseMovementBlock.
Change spline pointCount > 256 to return false instead of capping to
zero (which silently consumed wrong bytes for the endPoint).
Replace processAllReadyTiles() with bounded processReadyTiles() in the
same-map teleport and reconnect paths. processAllReadyTiles finalizes
every pending tile synchronously with a GPU sync wait, which caused
8+ second main-thread stalls when many tiles were queued. The bounded
version processes 1-4 tiles per call with async GPU upload — remaining
tiles finalize incrementally over subsequent frames.
Mirror the Turtle parser hardening: check remaining bytes before every
conditional read in ClassicPacketParsers::parseMovementBlock. Prevents
silent garbage reads (readUInt8 returns 0 past EOF) that corrupt
subsequent update fields and lose NPC data in multi-block packets.
The Turtle parseMovementBlock had no bounds checking on any reads.
Since Packet::readUInt8() returns 0 past the end without failing, the
parser could "succeed" with all-zero garbage data, then subsequent
parseUpdateFields would read from wrong positions, producing
"truncated field value" and "truncated update mask" errors.
Added bounds checks before every conditional read section (transport,
swimming pitch, fall time, jumping, spline elevation, speeds, spline
data, tail flags). Also removed the WotLK movement block fallback from
the Turtle parser chain — WotLK format is fundamentally incompatible
(uint16 flags, 9 speeds) and false-positive parses corrupt NPC data.
Also changed spline pointCount > 256 from cap-to-zero to return false
so the parser correctly fails instead of silently dropping waypoints.
The spline parser assumed WotLK format (durationMod, durationModNext,
conditional PARABOLIC fields) for all expansions. Classic/Turtle has a
simpler layout: timePassed+duration+splineId+pointCount directly.
Reading WotLK-specific fields from Classic data consumed wrong bytes,
causing pointCount to read garbage and the entire update block to fail
— losing dozens of NPC spawns in multi-block packets.
Now tries Classic format first (pointCount at offset 12), then WotLK
(offset 20+), then compact fallback. Also fixes WotLK SMSG_SPELL_GO
hit/miss targets to use full uint64 GUIDs instead of PackedGuid, which
was the root cause of garbage missCount values (46, 64, 241).
The legacy UPDATE_OBJECT spline path was reading verticalAccel (float)
and effectStartTime (uint32) unconditionally, but these 8 bytes are
only present when SPLINEFLAG_PARABOLIC (0x00000800) is set. Without
the flag, the extra reads shifted the stream by 8 bytes, causing
pointCount to read garbage (e.g. 3323328650) and failing the entire
update block parse.
Models that lose all instances are no longer immediately evicted from
GPU memory. Instead they get a 60-second grace period, preventing the
thrash cycle where GO models (barrels, chests, herbs) were evicted
every 5 seconds and re-loaded when the same object type respawned.
When SMSG_SPELL_GO reads a suspiciously high missCount (>20), log
the surrounding packet bytes, castFlags, and position for debugging
the persistent offset error causing garbage miss counts (46, 48, 241).
Servers may reject CMSG_GAMEOBJ_USE or cancel the resulting pickup
spell cast if movement flags are still active. Now sends MSG_MOVE_STOP
to clear directional movement before the interaction packet. Also adds
diagnostic logging for GO interactions to help trace collection issues.
Quest dialogs were showing literal "$C" instead of the player's class
name. Added support for $c/$C (class) and $r/$R (race) placeholders
in both game_screen and quest_log_screen substitution functions.
SMSG_SPELL_GO packets with unreasonably high miss counts (48, 118, 241)
were causing the entire packet to be discarded, losing all combat hit
data. Now salvage the successfully-parsed hit targets (needed for combat
text, health bars, animations) instead of discarding everything. Also
add spellId/hitCount to truncation warnings for easier diagnosis.
M2Renderer::removeInstance() was calling rebuildSpatialIndex() for every
single removal, causing 25-90ms frame hitches during entity despawns.
Now uses O(1) lookup via instanceIndexById, incremental spatial grid
cell removal, and swap-remove from the instance vector. The auxiliary
index vectors are rebuilt cheaply since they're small.
WotLK and TBC parsers were reading uint32+uint8 (5 bytes) for
SPELL_MISS_REFLECT entries, but the server only sends uint8
reflectResult (1 byte). This caused a 4-byte misalignment after every
reflected spell, corrupting subsequent miss entries and SpellCastTargets
parsing. Classic parser was already correct.
Right-clicking a locked container (e.g. Dead-Tooth's Strong Box) was
sending CMSG_USE_ITEM with spellId=0, which the server rejects. Locked
containers (itemClass==1, inventoryType==0) now send CMSG_OPEN_ITEM
instead, letting the server auto-check the keyring for the required key.
The VK_ERROR_DEVICE_LOST crash on AMD/Mali GPUs (barrier srcAccessMask)
was fixed in 2026-03-18. Enable refraction for new sessions so players
get the improved water visuals without needing to touch Settings.
Existing saved configs that explicitly disabled it are preserved.
'Release in X:XX' implied a client-enforced forced release; renamed to
'Auto-release in X:XX' (server-driven) and added 'Or wait for a player
to resurrect you.' hint so players know they can stay dead without
clicking Release Spirit.
Setting releasedSpirit_=true immediately on CMSG_REPOP_REQUEST raced
with PLAYER_FLAGS field updates that arrive from the server before it
processes the repop: the PLAYER_FLAGS handler saw wasGhost=true /
nowGhost=false and fired the 'ghost cleared' path, wiping corpseMapId_
and corpseGuid_ — so the minimap skull marker and the Resurrect from
Corpse dialog never appeared.
Ghost state is now driven entirely by the server-confirmed PLAYER_FLAGS
GHOST bit (and the login-as-ghost path), eliminating the race.
Macro conditions now support checking aura presence:
[buff:Power Word: Fortitude] — player has the named buff
[nobuff:Frost Armor] — player does NOT have the named buff
[debuff:Faerie Fire] — target has the named debuff
[nodebuff:Hunter's Mark] — target does NOT have the named debuff
Name matching is case-insensitive. When a target override (@target etc.)
is active the check uses that unit's aura list instead of the player's.
Adds /mark [icon], /marktarget, and /raidtarget slash commands that
set a raid mark on the current target. Accepts icon names (star,
circle, diamond, triangle, moon, square, cross, skull), numbers 1-8,
or "clear"/"none" to remove the mark. Defaults to skull when no
argument is given.
Right-clicking a castable pet ability (actionId > 6) in the pet action
bar now sends CMSG_PET_SPELL_AUTOCAST to toggle the spell's autocast
state. The local petAutocastSpells_ set is updated optimistically and
the tooltip shows the current state with a right-click hint.
setOnlinePlayerEquipment used wrong geoset ID ranges for boots (402+ instead
of 501+), gloves (301+ instead of 401+), and chest/sleeves (501+ instead of
801+), and was missing bare-shin (502), bare-wrist (801), and bare-leg (1301)
defaults. This caused other players to render with missing shin/wrist geometry
and wrong geosets when wearing equipment (the "shin mesh" gap in status.md).
Now mirrors the CharacterPreview::applyEquipment logic exactly:
- Group 4 (4xx) forearms/gloves: default 401, equipment 401+gg
- Group 5 (5xx) shins/boots: default 502, equipment 501+gg
- Group 8 (8xx) wrists/sleeves: default 801, equipment 801+gg
- Group 13 (13xx) legs/pants: default 1301, equipment 1301+gg
When a spell is queued in the 400ms window before the current cast ends,
render its icon dimmed (0.8 alpha) to the right of the cast bar progress,
with a "Queued: <name>" tooltip. The progress bar shrinks to accommodate
the icon when one is present.
Also exposes getQueuedSpellId() as a public const accessor on GameHandler
so the UI can observe the spell queue state without friend access.
Add SpellCastFailedCallback to GameHandler, fired from SMSG_CAST_RESULT
when result != 0. GameScreen registers the callback and records each failed
spellId in actionFlashEndTimes_ (keyed by spell ID, value = expiry time).
During action bar rendering, if a slot's spell has an active flash entry,
an AddRectFilled overlay is drawn over the button with alpha proportional
to remaining time (1.0→0.0 over 0.5 s), giving the same error-red flash
visual feedback as the original WoW client.
When SMSG_ITEM_PUSH_RESULT arrives for an item not yet in the cache, store
a PendingItemPushNotif and fire the 'Received: [item]' chat message only
after SMSG_ITEM_QUERY_SINGLE_RESPONSE resolves the name and quality, so the
notification always shows a proper item link instead of 'item #12345'.
Notifications that are already cached emit immediately as before; multiple
pending notifs for the same item are all flushed on the single response.
- /stopmacro [conditions] halts remaining macro commands; supports all existing
macro conditionals ([combat], [nocombat], [mod:shift], etc.) via the sentinel
action trick on evaluateMacroConditionals
- macroStopped_ flag in GameScreen; executeMacroText resets and checks it after
each command so /stopmacro mid-macro skips all subsequent lines
- Emit a "X is about to break!" UI error + system chat when an equipped item's
durability drops below 20% via SMSG_UPDATE_OBJECT field delta; warning fires
once per threshold crossing (prevDur >= maxDur/5, newDur < maxDur/5)
Tracks ITEM_ENCHANTMENT_SLOT 0 (permanent) and 1 (temporary) from item update
fields in OnlineItemInfo, then looks up names from SpellItemEnchantment.dbc and
renders them in both ItemDef and ItemQueryResponseData tooltip variants.
Supports: /castsequence [conds] [reset=N/target/combat] Spell1, Spell2, ...
Cycles through the spell list on successive button presses. State is keyed
by spell list so the same sequence shared across macros stays in sync.
- getMacroShowtooltipArg() parses the #showtooltip [SpellName] directive
- Action bar macro buttons now display the named spell's icon when
#showtooltip SpellName is present at the top of the macro body
- For bare #showtooltip (no argument), derives the icon from the first
/cast line in the macro (stripping conditionals and rank suffixes)
- Falls back to "Macro" text label only when no spell can be resolved
- Adds mouseoverGuid_ to GameHandler (set/cleared each frame by UI)
- renderNameplates() sets mouseoverGuid when the cursor is inside a
nameplate's hit region; resets to 0 at frame start
- Raid frame cells set mouseoverGuid while hovered (IsItemHovered)
- evaluateMacroConditionals() resolves @mouseover / target=mouseover to
the hover GUID; returns false (skip alternative) when no unit is hovered
This enables common healer macros like:
/cast [target=mouseover,help,nodead] Renew; Renew
Adds evaluateMacroConditionals() which parses the [cond1,cond2] Spell;
[cond3] Spell2; Default syntax and returns the first matching
alternative. Supported conditions:
- mod:shift/ctrl/alt, nomod — keyboard modifier state
- target=player/focus/target, @player/@focus/@target — target override
- help / harm (noharm / nohelp) — target faction check
- dead / nodead — target health check
- exists / noexists — target presence check
- combat / nocombat — player combat state
- noform / nostance / form:0 — shapeshift/stance state
- Unknown conditions are permissive (true) to avoid false negatives.
/cast now resolves conditionals before spell lookup and routes
castSpell() to the [target=X] override GUID when specified.
isHostileFaction() exposed as isHostileFactionPublic() for UI use.
- Macro text is now escaped (\\n, \\\\) on save and unescaped on load,
fixing multiline macros silently truncating after the first line in
the character config file.
- executeMacroText() runs every non-comment line of a macro body in
sequence (WoW behaviour), replacing the firstMacroCommand() approach
that only fired the first actionable line. The server still enforces
one spell-cast per click; non-cast commands (target, equip, pet, etc.)
now all execute correctly in the same macro activation.
Adds the standard WoW pet control slash commands used in macros:
- /petattack — attack current target
- /petfollow — follow player
- /petstay / /pethalt — stop and hold position
- /petpassive — set passive react mode
- /petdefensive — set defensive react mode
- /petaggressive — set aggressive react mode
- /petdismiss — dismiss the pet
All commands also appear in Tab-autocomplete.
These are standard WoW macro commands:
- /cancelform / /cancelshapeshift: exits current shapeshift form by
cancelling the first permanent aura (flag 0x20) on the player
- /cancelaura <name|#id>: cancels a specific player buff by spell name
or numeric ID (e.g. /cancelaura Stealth, /cancelaura #1784)
Also expand the Tab-autocomplete command list to include /cancelaura,
/cancelform, /cancelshapeshift, /dismount, /sit, /stand, /startattack,
/stopcasting, /target, and other commands that were previously missing.
Macros often start with a #showtooltip or #show directive line; these
should not be executed as chat commands. The firstMacroCommand() helper
now scans forward through the macro text, skipping blank lines and any
line starting with '#', and executes the first actual command line.
Applies to all three execution paths: left-click, keyboard shortcut,
and right-click Execute menu item.
Adds WoW macro-standard /use argument forms alongside the existing
item-name search:
- /use 0 <slot> — backpack slot N (1-based, bag 0)
- /use 1-4 <slot> — equipped bag slot N (1-based bag index)
- /use <N> — equip slot N (1-based, e.g. /use 16 = main hand)
These are the standard forms used in macros like:
#showtooltip
/use 13 (trinket 1)
/cast Arcane Blast
The captureSceneHistory barrier was using srcAccessMask=0 with
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT when transitioning the swapchain
image from PRESENT_SRC_KHR to TRANSFER_SRC_OPTIMAL. This does not
flush the GPU's color attachment write caches, causing VK_ERROR_DEVICE_LOST
on strict drivers (AMD, Mali) that require explicit cache invalidation
before transfer reads.
Fix: use VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT + COLOR_ATTACHMENT_OUTPUT
as the source mask so color writes are properly made visible to the
transfer unit before the image copy begins.
Also remove the now-unnecessary "requires FSR" restriction in the
settings UI — water refraction can be enabled independently of FSR.
Two companion improvements for the macro system:
- Keyboard shortcut handler now executes MACRO slots (1-0 keys) by running
the first line of their text as a command, same as left-click
- /cast now accepts a numeric spell ID or #ID prefix (e.g. /cast 133,
/cast #133) in addition to spell names — enables standard WoW macro
syntax and direct spell ID testing
Macros in WoW are client-side — the server sends only a macro index via
SMSG_ACTION_BUTTONS, never the text. This commit adds local storage and
a UI so macro slots are actually usable.
- GameHandler: getMacroText/setMacroText accessors backed by macros_ map;
text is persisted to the character .cfg file as macro_N_text= entries
- Action bar left-click: MACRO slot executes first line of macro text as
a chat/slash command (same path as /cast, /use, etc.)
- Context menu: "Execute" and "Edit" items for MACRO slots; "Edit" opens
a multiline modal editor (320×80 px, up to 255 chars) with Save/Cancel
- Tooltip: shows macro text body below the index; hints "right-click to
Edit" when no text is set yet
Both handlers silently cleared state with no visible message, leaving the
player unsure why their attack failed. Split the shared case block:
- NOTSTANDING: show "You need to stand up to fight." (rate-limited to 1.25s
via the existing autoAttackRangeWarnCooldown_ guard), keep auto-attack
active so it fires once the player stands.
- CANT_ATTACK: call stopAutoAttack() to end the attack loop (target is a
critter, civilian, or already dead — no point retrying), then show "You
can't attack that." with the same rate limiter.
Macro slots stored from SMSG_ACTION_BUTTONS had no tooltip and no context
menu header — hovering or right-clicking gave a blank result. Add an
"else if MACRO" branch to both the tooltip and the popup-context-item so
that "Macro #N" is displayed in both places. Clearing via right-click
still works via the existing "Clear Slot" item which was already outside
the type branches.
Macro slots (type 0x40 / 64) were silently dropped by the default branch
of the SMSG_ACTION_BUTTONS type switch, leaving the bar empty for any slot
a player had set to a macro. ActionBarSlot::MACRO already existed and the
UI already rendered it; only the parser was missing the case. Add
case 0x40 to map to ActionBarSlot::MACRO for Classic (type=64), TBC, and
WotLK formats, which all share the same 0x40 encoding for macros.
Turtle WoW is Classic 1.12-based and uses the Classic packet format for
SMSG_INIT_WORLD_STATES (no areaId uint32 field before count), not WotLK
format. Including it in the WotLK branch caused the parser to consume 4
bytes of the count+first-key as a phantom areaId, misaligning all world
state key/value pairs (BG scores, zone events, flag states).
Also remove the dead `turtleMode ? 150 : 150` branch in
performGameObjectInteractionNow — both arms were identical so the ternary
had no effect; replace with a constexpr constant.
craftQueueSpellId_ and craftQueueRemaining_ were already cleared in
cancelCast(), stopCasting(), and SMSG_CAST_RESULT failure, but were
missing from five other cast-abort paths:
- SMSG_SPELL_FAILURE (mid-cast interrupt): queue persisted after
combat interruption, risking a ghost re-cast on the next SMSG_SPELL_GO
- handleCastFailed() (SMSG_CAST_FAILED): queue persisted if the server
rejected a craft before it started
- Player login state reset: leftover queue from prior session survived
into the new world session
- Same-map resurrection (SMSG_NEW_WORLD): queue persisted through
spirit-healer resurrection teleport
- Regular world transfer (SMSG_NEW_WORLD): queue persisted across zone
changes and dungeon portals
- stopCasting() (invoked by /stopcasting) now clears queuedSpellId_/
queuedSpellTarget_ and craftQueueSpellId_/craftQueueRemaining_ so a
queued spell cannot fire silently after the player explicitly cancels.
- SMSG_SPELL_DELAYED now extends castTimeTotal alongside castTimeRemaining
for the local player, matching the existing other-unit handling and
keeping the cast bar progress percentage accurate after server-imposed
cast delays.
- Same-map resurrection path (SMSG_NEW_WORLD same-map) now resets casting,
castIsChannel, currentCastSpellId, castTimeRemaining, and the spell queue
as a defensive measure (player is dead and cannot be casting, but this
ensures state is clean on respawn).
Previously used arbitrary map-iteration order (last match), meaning
'/target Kobold' might target a far-away enemy instead of the closest.
Now computes squared distance for every prefix-matching entity and
keeps the nearest one, matching WoW's own /target behaviour.
When the player starts moving (forward/backward/strafe/jump) while a
timed non-channeled cast is in progress, call cancelCast() before
sending the movement packet. Previously the cast bar kept counting
down until the server sent SMSG_SPELL_FAILED, causing a visible lag.
Channeled spells are excluded (server ends those via MSG_CHANNEL_UPDATE).
Turning opcodes are excluded (turning while casting is allowed in WoW).
When castSpell() is called while a timed cast is in progress and
castTimeRemaining <= 0.4s, store the spell in queuedSpellId_ instead
of silently dropping it. handleSpellGo() fires the queued spell
immediately after clearing the cast state, matching the ~400ms spell
queue window in Blizzlike WoW clients.
Queue is cleared on all cancel/interrupt paths: cancelCast(),
handleCastFailed(), SMSG_CAST_RESULT failure, SMSG_SPELL_FAILED,
world-teardown, and worldport ACK. Channeled casts never queue
(cancelling a channel should remain explicit).
SMSG_PRE_RESURRECT was silently discarded; Shamans with Reincarnation
and Warlocks with Twisting Nether could never see or use the self-res
ability. Now:
- SMSG_PRE_RESURRECT sets selfResAvailable_ flag when addressed to the
local player
- Death dialog gains a "Use Self-Resurrection" button (blue, shown above
Release Spirit) when the flag is set
- Clicking it sends CMSG_SELF_RES (empty body) and clears the flag
- selfResAvailable_ is cleared on all resurrection and session-reset
paths so it never bleeds across deaths or logins
Reset corpseReclaimAvailableMs_ to 0 in both world-teardown/re-login
and ghost-flag-cleared paths so the PvP delay countdown never bleeds
into subsequent deaths or sessions.
SMSG_CORPSE_RECLAIM_DELAY is now stored as an absolute expiry timestamp
(steady_clock ms) instead of being discarded after a chat message.
GameHandler::getCorpseReclaimDelaySec() returns remaining seconds (0 when
reclaim is available). The "Resurrect from Corpse" button now:
- Disables and shows the remaining seconds when a PvP delay is active
- Shows the usual "Corpse: N yards" helper text when available
Also resets corpseReclaimAvailableMs_ on world/session teardown.
Two bugs prevented "Resurrect from Corpse" from working:
1. SMSG_DEATH_RELEASE_LOC was overwriting corpseX_/Y_/Z_/MapId_ with the
graveyard spawn point (where the ghost appears after releasing spirit),
not the actual corpse location. canReclaimCorpse() was therefore comparing
the ghost's distance to the graveyard instead of the real corpse, so the
button never appeared when the ghost returned to the death position.
Fix: read and log the packet but leave corpseX_/Y_/Z_ untouched.
2. reclaimCorpse() fell back to playerGuid when corpseGuid_ == 0.
CMSG_RECLAIM_CORPSE requires the corpse object's own GUID; the server
looks it up by GUID and silently rejects an unknown one.
Fix: gate reclaimCorpse() on corpseGuid_ being known (set when the
corpse object arrives in SMSG_UPDATE_OBJECT), and add canReclaimCorpse()
guard for the same.
Corpse position is now sourced only from:
- Health-drop detection (primary, fires immediately on death)
- SMSG_UPDATE_OBJECT CORPSE type (updates when object enters view range)
Adds Inventory::sortBags() which collects all items from the backpack
and equip bags, sorts them client-side by quality descending → item ID
ascending → stack count descending, then writes them back. A "Sort Bags"
SmallButton is rendered in the backpack footer with a tooltip explaining
the sort order.
The sort is purely local (no server packets) since the WoW protocol has
no sort-bags opcode; it provides an instant, session-persistent visual
reorder.
WotLK inserts a uint32 tradeId between isSelf and slotCount, and
appends uint32 createPlayedTime at the end of each slot (52-byte
trail vs 48 for Classic/TBC). Without the expansion check, Classic
and TBC parsers consumed tradeId as part of slotCount, resulting in
a bogus slot count and corrupted trade window item display.
Now gates the tradeId read and adjusts SLOT_TRAIL size based on
isActiveExpansion("wotlk").
Added skipClassicSpellCastTargets() and skipTbcSpellCastTargets() calls
in parseSpellGo() for both expansions, matching the same fix applied to
WotLK SpellGoParser and both SpellStartParsers. Prevents packet stream
misalignment for ground-targeted and AoE spells (Blizzard, Rain of
Fire, Flamestrike, etc.) where the server appends DEST_LOCATION or
other target fields after the hit/miss lists.
Applied the same SpellCastTargets fix from SpellStartParser (dd64724)
to SpellGoParser: after parsing hit/miss target lists, now reads the
full target section (UNIT/UNIT_MINIPET/CORPSE/GAMEOBJECT packed GUID,
ITEM/TRADE_ITEM packed GUID, SOURCE/DEST PackedGuid+3floats, null-
terminated STRING). Also adds targetGuid field to SpellGoData so
callers can read the primary target. Prevents stream misalignment on
ground-targeted AoE spells (e.g. Blizzard, Rain of Fire).
Replaced partial UNIT/OBJECT-only flag handling with full WotLK
SpellCastTargets layout: UNIT/UNIT_MINIPET/CORPSE/GAMEOBJECT share
one PackedGuid, ITEM/TRADE_ITEM share one PackedGuid, SOURCE_LOCATION
and DEST_LOCATION are each PackedGuid+3floats (transport-relative),
STRING is null-terminated. Prevents byte-stream corruption on
ground-targeted AoE and similar multi-field target packets.
handleMonsterMoveTransport() read a server-supplied pointCount without
any bounds check before iterating. A malformed packet with
pointCount=0xFFFFFFFF would loop billions of times. All other parsers
(MonsterMoveParser::parse, TBC parseMonsterMove) cap at 1000 or 16384.
Added kMaxTransportSplinePoints=1000 cap with a LOG_WARNING, matching
the limit used by MonsterMoveParser::parse() in world_packets.cpp.
The minimap had a comment "skip self (already drawn as arrow)" but no
code that actually drew the arrow. Players had no visual indication of
which direction they were facing on the minimap.
Draws a chevron-shaped white/gold arrow at the minimap center:
- On fixed-north minimap: arrow rotates to match camera compass bearing
(computed from camera forward vector: atan2(-fwd.x, fwd.y))
- On rotating minimap: arrow points straight up because the minimap
already rotates to put camera-forward at the top
- Style: two filled triangles (tip+left half, tip+right half) with dark
outline for readability against all map backgrounds
- Rendered last so it sits on top of all other minimap markers
The funcList_ dispatchers were populated by initializeModule() but the
public tick(), generateRC4Keys(), and unload() methods had their actual
call sites commented out as TODOs.
- tick(): now calls funcList_.tick(deltaMs) so the emulated module can
run its internal periodic scheduler.
- generateRC4Keys(): now calls funcList_.generateRC4Keys(packet) so
the Warden crypto stream is re-keyed as the module expects.
- unload(): now calls funcList_.unload(nullptr) before freeing module
memory, allowing the module to clean up its own state.
All three paths already guard on !loaded_ || !funcList_.<fn> so they
are no-ops when the module is not loaded or Unicorn is unavailable.
- CameraController::resetAngles(): new method that only resets yaw/pitch
without teleporting the player. R key now calls resetAngles() instead
of reset() so pressing R no longer moves the character to spawn.
The full reset() (position + angles) is still used on world-entry and
respawn via application.cpp.
- packet_parsers_classic: parseSpellStart now calls
skipClassicSpellCastTargets() to consume all target payload bytes
(UNIT, ITEM, SOURCE_LOCATION, DEST_LOCATION, etc.) instead of only
handling UNIT/OBJECT. Prevents packet-read corruption for ground-
targeted AoE spells.
- packet_parsers_tbc: added skipTbcSpellCastTargets() static helper
(uint32 targetFlags, full payload coverage including TRADE_ITEM and
STRING targets). parseSpellStart now uses it.
Items with startQuestId != 0 were calling useItemBySlot()/useItemInBag()
which sends CMSG_USE_ITEM — but quest-starting items have no on-use spell,
so the server silently ignored the packet and no quest dialog appeared.
Fix:
- offerQuestFromItem(itemGuid, questId): sends CMSG_QUESTGIVER_QUERY_QUEST
with the item's own GUID as the questgiver GUID. The server responds with
SMSG_QUESTGIVER_QUEST_DETAILS which handleQuestDetails() already picks up
and opens the Accept/Decline dialog with full rewards/description.
- getBagItemGuid(bagIndex, slotIndex): resolves the per-slot item GUID from
the bag's containerContents_ map (mirrors the logic inside useItemInBag).
- inventory_screen.cpp right-click handler: checks item.startQuestId != 0
before the equip/use branch; if set, resolves item GUID and calls
offerQuestFromItem. Works for both backpack slots and bag slots.
Previously initializeModule() read the 4 WardenFuncList function addresses
from emulated memory, logged them, then discarded them — funcList_ was never
populated, so tick(), generateRC4Keys(), and processCheckRequest() were
permanently no-ops even when the Unicorn emulator successfully ran the module.
Changes:
- initializeModule() now wraps each non-null emulated function address in a
std::function lambda that marshals args to/from emulated memory via
emulator_->writeData/callFunction/freeMemory
- generateRC4Keys: copies 4-byte seed to emulated space, calls function
- unload: calls function with NULL (module saves own RC4 state)
- tick: direct uint32_t(deltaMs) dispatch, returns emulated EAX
- packetHandler: 2-arg variant for generic callers
- Stores emulatedPacketHandlerAddr_ for full 4-arg call in processCheckRequest
- processCheckRequest() now calls the emulated PacketHandler with the proper
4-argument stdcall convention: (data, size, responseOut, responseSizeOut),
reads back the response size and bytes, returns them in responseOut
- unload() resets emulatedPacketHandlerAddr_ to 0 for clean re-initialization
- Remove dead no-op renderObjectiveTracker() (no call sites, superseded)
Previously hookAPI() allocated a stub address and registered a C++ handler
but never stored the handler or wrote any executable code to the stub
region, meaning any Warden module call to a Windows API would execute zeros
and crash or silently return garbage.
Changes:
- Store ApiHookEntry {argCount, handler} per stub address in apiHandlers_
- Write RET (0xC3) to stub memory as a safe fallback
- Register UC_HOOK_CODE over the API stub address range during initialize()
- hookCode() now detects stub addresses, reads args from the emulated stack,
dispatches to the C++ handler, then simulates stdcall epilogue by setting
EAX/ESP/EIP so Unicorn returns cleanly to the caller
- Convert static-local nextStubAddr to instance member nextApiStubAddr_
so re-initialization resets the allocator correctly
- Known arg counts for all 7 registered Windows APIs (VirtualAlloc,
VirtualFree, GetTickCount, Sleep, GetCurrentThreadId,
GetCurrentProcessId, ReadProcessMemory)
Items with startQuestId != 0 now show:
- Gold outer glow border (2px) around the item icon
- Gold "!" badge in the top-right corner of the icon
- "Begins a Quest" label in gold on the second text line
Matches WoW's visual convention for quest-pickup items in loot rolls.
Parses the pet talent wipe confirm packet (petGuid + cost), shows a
confirmation dialog matching the player talent reset UX, and sends
CMSG_PET_UNLEARN_TALENTS on confirmation. Completes the pet talent
respec flow for Hunters/Warlocks on WotLK servers.
When the server sends SMSG_PET_RENAMEABLE (after taming a pet for the first
time), the pet rename modal now automatically opens so the player can name
their new pet without needing to right-click the pet frame.
Replaces the silent consume with full packet parsing: reads two lists of
(guid, x, y) positions (typically ally and horde flag carriers) and stores
them in bgPlayerPositions_. Renders each as a colored diamond on the minimap
(blue=group0, red=group1) with a "Flag carrier" tooltip showing the player's
name when available.
Implements renderSkillsWindow() showing all player skills grouped by
DBC category (Professions, Secondary Skills, Class Skills, Weapon Skills,
Armor, Languages) with value/max progress bars and a bonus breakdown tooltip.
Hooked up to the TOGGLE_SKILLS keybinding (K by default).
When 'Auto Repair' is enabled in Settings > Gameplay, all damaged
equipment is automatically repaired when opening any armorer vendor
(canRepair=true). The repair is skipped when no items are actually
damaged to avoid a pointless server round-trip. A system chat message
confirms the repair. Setting persists to ~/.wowee/settings.cfg as
auto_repair.
When 'Auto Sell Greys' is enabled in Settings > Gameplay, all grey
(ItemQuality::POOR) items in the backpack and extra bags are sold
automatically when opening a vendor window. Items with no sell price
are skipped. A system chat message reports the number of items sold
and total gold received. The setting persists to ~/.wowee/settings.cfg
under the key auto_sell_grey.
Shows current local time in HH:MM format in a small dimmed label just
below the coordinate display near the minimap. Uses localtime_r (POSIX)
with a _WIN32 fallback. The clock complements the existing coordinate
and zone name overlays, matching the WoW default UI minimap area.
The ToT (target-of-target) cast bar was still using a fixed orange-yellow
color regardless of spell interruptibility. Now uses the same green/red
scheme as the target frame and nameplate cast bars: green = interruptible
(can Kick/Counterspell), red = not interruptible, both pulse at >80%.
The player's own cast bar now uses spell-school-based colors for quick
identification: Fire=orange-red, Frost=icy blue, Shadow=purple,
Arcane=violet, Nature=green, Holy=golden, Physical=gold. Channels
remain blue regardless of school. Adds getSpellSchoolMask() using the
already-loaded Spell.dbc cache (schoolMask field, covering all
expansions including Classic SchoolEnum→bitmask conversion).
When the player dies and releases spirit, the world map now renders a
bone-white X cross at the corpse's location (matching the existing
minimap skull marker). The marker appears only when the player is a
ghost with an unclaimed corpse on the same map, and shows a "Your
corpse" tooltip on hover. Implemented via setCorpsePos() on WorldMap,
called from renderWorldMap() using getCorpseCanonicalPos().
When a hostile unit has UNIT_FIELD_TARGET pointing to the local player,
highlight its nameplate with an orange border so players can immediately
see which enemies are attacking them vs. attacking group members.
Priority: gold=selected, orange=targeting you, dark=default.
Boss encounter frames were still using the old fixed orange/red cast bar
color. Update them to match the target frame: green = interruptible,
red = SPELL_ATTR_EX_NOT_INTERRUPTIBLE, both pulse at >80% completion.
Load AttributesEx from Spell.dbc for all expansions (Classic/TBC/WotLK/
Turtle). Check SPELL_ATTR_EX_NOT_INTERRUPTIBLE (bit 4 = 0x10) to classify
each cast as interruptible or not when SMSG_SPELL_START arrives.
Target frame and nameplate cast bars now use:
- Green: spell can be interrupted by Kick/Counterspell/Pummel etc.
- Red: spell is immune to interrupt (boss abilities, instant-cast effects)
Both colors pulse faster at >80% completion to signal the closing window.
Adds GameHandler::isSpellInterruptible() and UnitCastState::interruptible.
The zone label above the minimap now preferentially uses the zone/area
name from getWorldStateZoneId() (populated via SMSG_INIT_WORLD_STATES)
rather than the renderer's map-level zone name. This means the label
correctly shows "Ironforge", "Wailing Caverns", etc. instead of always
showing the parent continent zone name.
Previously the "Entering: [Zone]" overlay only triggered when the terrain
renderer loaded a new map. Now it also fires whenever worldStateZoneId_
changes (sent by the server via SMSG_INIT_WORLD_STATES on each zone
crossing), giving correct "Entering: Ironforge", "Entering: Wailing
Caverns" etc. display for sub-zones and dungeon entries without requiring
a full map reload.
- Added lastKnownWorldStateZoneId_ to track server-reported zone changes
- renderZoneText() now takes GameHandler& to access getWorldStateZoneId()
and getWhoAreaName() for name lookup via WorldMapArea.dbc cache
- Renderer zone name still checked as a fallback for map-level transitions
- Both sources de-duplicate to avoid triggering the same text twice
Aura icons on the player buff bar and the target frame now display a
WoW-style dark fan overlay that sweeps clockwise as the buff/debuff
elapses, providing instant visual feedback on remaining duration.
The sweep uses AuraSlot::maxDurationMs / getRemainingMs() — the same
data that already drives the numeric countdown — so no new state is
required. Only temporary auras (maxDurationMs > 0) show a sweep;
permanent buffs remain unaffected.
Add gold diamond markers for every flight master the player has already
discovered (knownTaxiMask_), read from TaxiNodes.dbc and filtered to the
current continent/map being displayed:
- WorldMapTaxiNode struct carries canonical WoW coords + known flag
- WorldMap::setTaxiNodes() accepts the per-frame list from game_screen
- renderImGuiOverlay() projects each known node to UV, draws a gold
diamond (AddQuadFilled) with a dark outline, and shows the node name
as a tooltip on hover
- GameHandler::isKnownTaxiNode(id) checks knownTaxiMask_[] efficiently
- Markers update live — newly discovered nodes appear without reopening
the map
Add GLANCING (hitInfo 0x800) and CRUSHING (hitInfo 0x1000) as distinct
combat text types so players see mechanics feedback they expect from
Classic/TBC content:
- Glancing: shown as "~{amount}" in muted yellow/red; "glances for N" in
the combat log
- Crushing: shown as "{amount}!" in bright orange/red; "crushes for N!"
in the combat log
Both types are counted toward DPS meter accumulation. AttackerStateUpdateData
gains isGlancing()/isCrushing() helpers alongside the existing isCrit()/isMiss().
loadSpellVisualDbc() now builds two distinct maps:
spellVisualCastPath_ — visualId → M2 via SpellVisual.CastKit chain
spellVisualImpactPath_ — visualId → M2 via SpellVisual.ImpactKit chain
playSpellVisual() accepts useImpactKit=false (default, cast) / true (impact).
SMSG_PLAY_SPELL_IMPACT passes useImpactKit=true so impact effects (explosions,
debuff indicators) use the ImpactKit model instead of the CastKit model.
Added ImpactKit field to all four dbc_layouts.json files.
SMSG_PLAY_SPELL_IMPACT has a different wire format from SMSG_PLAY_OBJECT_SOUND:
it carries uint64 targetGuid + uint32 visualId (same as SMSG_PLAY_SPELL_VISUAL),
not uint32 soundId + uint64 sourceGuid.
Previously both were handled together, causing the target GUID low-bytes to be
misread as a sound ID and the visualId to be missed entirely.
Now each handler parses its own format correctly. SMSG_PLAY_SPELL_IMPACT resolves
the target entity position and calls playSpellVisual() to spawn the M2 impact effect
at that location.
Parse SMSG_PLAY_SPELL_VISUAL (casterGuid + visualId) and spawn a
transient M2 spell effect at the caster's world position.
DBC chain: SpellVisual.dbc → SpellVisualKit.dbc → SpellVisualEffectName.dbc
Lookup priority: CastKit.SpecialEffect0, fallback to MissileModel.
Models are lazy-loaded and cached by path; instances auto-expire after 3.5s.
DBC layouts added to all four expansion layout files (Classic/TBC/WotLK/Turtle).
Add addUIError() for remaining error-only chat-message cases:
- SMSG_BARBER_SHOP_RESULT non-zero result (not enough money, wrong
location, must stand up)
- SMSG_NPC_WONT_TALK ("That creature can't talk to you right now")
- SMSG_LFG_AUTOJOIN_FAILED and SMSG_LFG_AUTOJOIN_FAILED_NO_PLAYER
Completes the UIError improvement pass: all server-reported failure
events now surface as the red on-screen overlay, not chat-only.
Add addUIError() alongside addSystemChatMessage() for:
- SMSG_CHAT_WRONG_FACTION / SMSG_CHAT_NOT_IN_PARTY / SMSG_CHAT_RESTRICTED
- SMSG_LFG_JOIN_RESULT failure, LFG proposal failure (state=0), LFG
role check missing-role failure
- SMSG_AUCTION_COMMAND_RESULT error cases (bid/post/cancel/buyout)
- SMSG_PLAYERBINDERROR (hearthstone not bound / bind failed)
- SMSG_READ_ITEM_FAILED
- SMSG_PET_NAME_INVALID
Consistent with the rest of the error-overlay pass: players now see
these failures as the red on-screen overlay text, not just in chat.
- SMSG_TRANSFER_ABORTED: all zone/instance portal rejection reasons shown as UIError
(expansion required, instance full, too many instances, zone in combat, etc.)
- handleGroupUninvite: "You have been removed from the group." now shown as UIError
- handlePartyCommandResult: all party errors (group full, not leader, wrong faction,
ignoring you, etc.) now also shown as UIError overlay
- Fishing bobber splash: "A fish is on your line!" shown as UIError (time-critical)
- SMSG_BATTLEFIELD_PORT_DENIED: shown as UIError
- SMSG_INSTANCE_RESET_FAILED: failure reason shown as UIError
- SMSG_GROUP_DESTROYED: "Party disbanded" shown as UIError
- SMSG_CORPSE_NOT_IN_INSTANCE: shown as UIError
- SMSG_ZONE_UNDER_ATTACK: "[Zone] is under attack!" shown as UIError
- SMSG_AREA_TRIGGER_MESSAGE: zone/area entry messages shown as UIError
- SMSG_TRAINER_BUY_FAILED: "Cannot learn [spell]" now appears as red overlay
- SMSG_RESURRECT_FAILED: all resurrection failure reasons shown as UIError
- SMSG_BINDZONEREPLY error: "Too far from innkeeper" shown as UIError
- SMSG_CHANGEPLAYER_DIFFICULTY_RESULT error: reason shown as UIError
- SMSG_INVENTORY_CHANGE_FAILURE case 1: level-gated equip error now calls
addUIError before the early break, matching all other inventory error paths
SMSG_BUY_FAILED ("Not enough money", "Sold out", etc.) and
SMSG_SELL_ITEM non-zero results now call addUIError() so the error
appears on screen alongside the chat message.
Several server-reported action failures were posting to chat only
without firing the red on-screen UIError overlay:
- SMSG_INVENTORY_CHANGE_FAILURE: bag full, wrong slot, can't equip, etc.
- SMSG_MOUNTRESULT / SMSG_DISMOUNTRESULT: mount denied errors
- SMSG_QUESTLOG_FULL: quest log at capacity
- SMSG_SOCKET_GEMS_RESULT: gem socketing failure
All now call addUIError() in addition to addSystemChatMessage() so
players see the error immediately on screen without looking at chat.
handleCastFailed was only posting to chat; now also calls addUIError
so mid-cast server rejections (e.g. "Interrupted") show the same red
on-screen overlay as SMSG_CAST_RESULT failures already did.
Hovering over Armor, primary stats (Strength/Agility/Stamina/
Intellect/Spirit), and secondary rating stats now shows a brief
description of the stat's in-game effect — matching WoW's native
character screen behavior.
Uses ImGui::BeginGroup/EndGroup to make multi-widget rows (stat +
green bonus) respond to a single IsItemHovered check.
Chat item link tooltips now show "Heroic" (green) for items with
ITEM_FLAG_HEROIC_TOOLTIP (0x8) and "Unique-Equipped" for items with
ITEM_FLAG_UNIQUE_EQUIPPABLE (0x1000000), matching InventoryScreen.
"Unique" text is now gold-colored to match as well.
Chat item link tooltips now show per-school elemental resistances
(Holy/Fire/Nature/Frost/Shadow/Arcane) when non-zero, matching the
inventory tooltip. Spell effect text (Use/Equip/Chance on Hit) now
shows the full spell description instead of just the spell name,
consistent with InventoryScreen::renderItemTooltip.
Chat item link tooltips now match InventoryScreen for required-skill
(SkillLine.dbc), required-reputation (Faction.dbc), class restriction,
and race restriction. Red text when the player does not meet the
requirement, grey otherwise.
Chat link tooltips (hover over item links in chat) were missing item set
information already shown in the inventory tooltip. Now shows:
- Set name with equipped/total piece count (e.g. "Tier 9 (2/5)")
- Each set bonus with its piece-threshold, colored green when active
and grey when inactive
- Falls back to "Set (id N)" when ItemSet.dbc is unavailable
Lazy-loads ItemSet.dbc on first hover; consistent with
InventoryScreen::renderItemTooltip formatting.
Item tooltips shown when hovering chat links already displayed all stats,
spells, and flavor text, but gem sockets were missing. Add the same
socket rendering used in the inventory tooltip:
- Iterate socketColor[0..2]; for each non-zero slot show a colored label
(Meta / Red / Yellow / Blue Socket) in the socket's faction color
- Lazy-load SpellItemEnchantment.dbc to resolve the socketBonus enchant
name; fall back to "(id N)" when the record is not found
- Consistent with InventoryScreen::renderItemTooltip formatting
Weather type and intensity are already tracked from SMSG_WEATHER, but
only an icon was shown next to the zone name. This adds a fullscreen
ImDrawList overlay that renders:
- Rain (type 1): diagonal rain streaks proportional to intensity
- Snow (type 2): gently swaying snowflakes with two-tone highlight
- Storm (type 3): heavy rain + dark fog-vignette on screen edges
Particles wrap at screen boundaries and are re-seeded on type or
resolution change. Delta time is capped at 50 ms to prevent teleporting
after focus loss. No heap allocations at runtime (static local arrays).
Pet spell buttons now dim and display remaining cooldown time when a
spell is on cooldown, matching the feedback available on the player
action bar. Clicking a pet spell while it is on cooldown is also
suppressed to prevent spam-sending CMSG_PET_ACTION to the server.
Cooldown time appears as a text overlay (seconds or "Nm" for minutes)
and is also shown in the hover tooltip.
When nextLevelXp==0 and playerLevel>0, render a gold fully-filled bar
with centered "Max Level" text instead of hiding the XP bar entirely.
Fixes missing closing brace that caused all subsequent methods to fail
compilation.
Ctrl+1, Ctrl+2, Ctrl+3 (up to Ctrl+8 for Druids with many forms) now
cast the Nth available stance spell for classes that use a stance bar.
Ordering matches the stance bar UI so visual and keyboard positions align.
Normal action bar keys 1–= are skipped when Ctrl is held to prevent
accidental spell casts instead of stance switches.
Renders a stance bar to the left of the main action bar showing the
player's known stance spells filtered to only those they have learned:
- Warrior: Battle Stance, Defensive Stance, Berserker Stance
- Death Knight: Blood Presence, Frost Presence, Unholy Presence
- Druid: Bear/Dire Bear, Cat, Travel, Aquatic, Moonkin, Tree, Flight forms
- Rogue: Stealth
- Priest: Shadowform
Active form detected from permanent player auras (maxDurationMs == -1).
Clicking an inactive stance casts the corresponding spell. Active stance
shown with green border/tint; inactive stances are slightly dimmed.
Spell name tooltips shown on hover using existing SpellbookScreen lookup.
When shift-hovering an item link in chat to compare with equipped gear,
also display extra stats (hit/crit/haste/AP/SP/expertise) for the
currently-equipped item, matching what is shown for the hovered item.
Display item description (flavor text) from SMSG_ITEM_QUERY_SINGLE_RESPONSE
at the bottom of item tooltips in gold color, matching WoW's standard
tooltip layout where lore text appears below stats and effects.
Display Use, Equip, Chance on Hit, and Teaches spell effects from
SMSG_ITEM_QUERY_SINGLE_RESPONSE in item tooltips. Looks up spell
name from Spell.dbc via SpellbookScreen for readable descriptions.
Display all ExtraStat entries from SMSG_ITEM_QUERY_SINGLE_RESPONSE in
the item tooltip (hit rating, crit rating, haste, spell power, attack
power, expertise, resilience, etc.). These were previously silently
discarded, making WotLK/TBC gear tooltips incomplete.
Add standard WoW tooltip fields that were previously missing:
- Bind type (Binds when picked up/equipped/used, Quest Item)
- Unique indicator
- Item Level XX
- Weapon damage range (e.g. '22 - 41 Damage Speed 2.20') replacing bare DPS
- Damage per second sub-line in dimmed text
- Requires Level XX
Show '+X Honor' floating text in gold when SMSG_PVP_CREDIT is received,
matching WoW's native behavior. Also add HONOR_GAIN to the combat log
panel for a complete record. Previously only a chat message was added.
REFLECT entries already stored the reflected spell ID but the floating
text display showed only "Reflected"/"You Reflect" without the name.
Now shows "Reflected: Fireball" or "Reflect: Frost Nova", matching the
pattern already used by INTERRUPT, DISPEL, and STEAL entries.
Consistent with the target frame fix: focus targets with level 0
(unknown/?? mobs) now show skull-red instead of grey, and display
"Lv ??" instead of "Lv 0".
Level 0 in the update fields means the server hasn't sent or
the mob is undetectable (e.g. high-level raid bosses). Previously
these were colored grey (no-XP path) and displayed "Lv 0". Now
they correctly show skull-red and display "Lv ??" to match WoW.
Replace the static filled circle with a red triangle arrow that
rotates to match the character's current facing direction.
Uses the same render-space yaw convention as the 3D scene so
the arrow matches in-world orientation.
Extend the existing out-of-range check to cover ITEM slots whose
inventory type is Ranged (bow/gun/crossbow, 40 yd), RangedRight
(wand, 40 yd), or Thrown (30 yd). The check runs after barItemDef
is resolved so the inventory type is available.
The bump-pointer allocator never reused freed blocks, causing the 16 MB
emulated heap to exhaust in long sessions even when blocks were freed.
- First-fit reuse from a free-list before advancing the bump pointer
- Coalesce adjacent free blocks to limit fragmentation
- Roll back the bump pointer when the top free block reaches it
- Reset allocator state on initialize() so re-runs start clean
- Add MT/MA/Asst badges to party frames (matching raid frame treatment)
- Clear learnedTalents_ on SMSG_TALENTS_INVOLUNTARILY_RESET so the
talent screen stays accurate after a server-side talent wipe
Render "MT" (orange), "MA" (blue), and "A" (light blue) in the
bottom-left of each raid cell using member flags from SMSG_GROUP_LIST
and SMSG_REAL_GROUP_UPDATE (bits 0x02/0x04/0x01). Complements the
existing LFG role badges at bottom-right.
Parse group type, member flags, and leader GUID instead of silently
discarding the packet. Updates partyData so group frames reflect
role changes and leadership transitions in real time.
Prevents unbounded GPU memory growth in long play sessions where the player
visits many zones. Tiles are inserted into a FIFO deque; when the count of
successfully-loaded tiles exceeds MAX_TILE_CACHE (128), the oldest entry is
destroyed and removed from both the cache map and the deque.
At 256×256×4 bytes per tile this caps minimap GPU usage at ~32 MB.
Add buildItemLink() helper that formats |cff...|Hitem:...|h[Name]|h|r links so
the chat renderer draws item names in their quality colour (grey/white/green/
blue/purple/orange) with a small icon and tooltip on hover.
Applied to: loot received (SMSG_ITEM_PUSH_RESULT), looted from corpse
(handleLootRemoved), quest item count updates, and all three auction
house notifications (sold, outbid, expired).
Wire up remaining UISoundManager calls for milestone and notification events:
- playLevelUp() on SMSG_LEVELUP_INFO
- playAchievementAlert() on SMSG_ACHIEVEMENT_EARNED (self only)
- playTargetSelect() on duel request and group invite
- playError() on inventory change failure
Wire up UISoundManager calls that were loaded but never invoked:
- playQuestActivate() on quest accept
- playQuestComplete() on server-confirmed quest completion
- playError() on trainer buy failure, vendor buy failure, and sell failure
The XP bar tooltip now displays current progress as a percentage
(e.g., "Current: 45000 / 100000 XP (45.0%)"), making it easier to
gauge leveling progress at a glance.
The ItemQueryResponseData tooltip overload had this hint but the
primary ItemDef overload did not. Players hovering gear in their
inventory now see the comparison prompt when an equipped equivalent
exists.
Area discovery XP was only shown in the system chat log. Now it also
appears as a floating "+XP" number like kill XP, giving immediate
visual feedback when discovering new zones.
WotLK and Classic parsers return true on success regardless of mail
count, but TBC returned !inbox.empty() which falsely signals parse
failure on an empty mailbox, potentially causing callers to skip
valid empty-mailbox state.
The shift-hover gear comparison was missing secondary stat types
(Defense, Dodge, Parry, Block Rating, Hit/Crit/Haste variants,
Healing, Spell Damage, Spell Pen) — only 10 of 22 stat types had
labels. Also adds full extra stats and DPS comparison to the
ItemQueryResponseData tooltip overload (loot window) which had none.
TBC 2.4.3 SMSG_MESSAGECHAT has no senderGuid(u64) or unknown(u32)
prefix before type-specific data. The WotLK base parser reads these
12 bytes unconditionally, causing complete misalignment of all chat
message fields — every chat message on a TBC server would parse
garbage for sender, channel, and message content.
TBC 2.4.3 SMSG_GAMEOBJECT_QUERY_RESPONSE has 2 extra strings after
name[4] (iconName + castBarCaption). WotLK has 3 (adds unk1). Without
this override, the WotLK parser's third readString() consumed bytes
from the data[24] fields, corrupting game object type-specific data
and breaking interactions with doors, chests, mailboxes, and
transports on TBC servers.
TBC 2.4.3 sends quest giver status as uint32 (like Classic), not uint8
(WotLK). Without this override, reading uint8 consumed only 1 of 4
bytes, misaligning all subsequent packet data and breaking quest
markers on NPCs.
TBC channel join/leave packets use Classic format (name+password only).
The WotLK base prepends channelId/hasVoice/joinedByZone, causing
servers to reject the malformed packets and breaking channel features.
TBC 2.4.3 SMSG_GUILD_ROSTER has the same rank structure as WotLK
(variable rankCount + goldLimit + bank tab permissions), but does NOT
include a gender byte per member (WotLK added it). Without this
override, TBC fell through to the WotLK parser which read a spurious
gender byte, causing every subsequent field in each member entry to
misalign.
Combat log now shows specific power type names (Mana, Rage, Energy,
Focus, Happiness, Runic Power) instead of generic "power" for ENERGIZE
and POWER_DRAIN events. Uses the powerType field added to CombatLogEntry
in the previous commit.
Apply a cold blue-grey fullscreen overlay when the player is in ghost
form, creating a desaturated, muted appearance that clearly signals the
death state. Uses the existing overlay pipeline infrastructure. Applied
in both parallel and non-parallel rendering paths, after underwater tint
but before brightness adjustment so UI elements remain unaffected.
Fall, lava, drowning, fatigue, slime, and fire damage now display their
specific type instead of generic "Environmental damage" in both floating
combat text and the combat log window. The envType byte from
SMSG_ENVIRONMENTAL_DAMAGE_LOG is propagated via the powerType field to
the display layer. Added powerType to CombatLogEntry for consistent
access in the persistent combat log.
Other players on transports (boats, zeppelins, trams) were not properly
tracked because handleOtherPlayerMovement() did not read transport data
from MSG_MOVE_* packets. This caused entities to slide off transports
between movement updates since no transport attachment was established.
Now reads the transport GUID and local offset from the packet using
expansion-aware wire flags (0x200 for WotLK/TBC, 0x02000000 for
Classic/Turtle), registers a transport attachment so the entity follows
the transport smoothly via updateAttachedTransportChildren(), and clears
the attachment when the player disembarks.
- Load SkillLine.dbc and SkillLineAbility.dbc during SMSG_INITIAL_SPELLS
so isProfessionSpell() works immediately without visiting a trainer
- Implement SMSG_LOOT_SLOT_CHANGED handler to remove items taken by
other players in group loot, keeping the loot window in sync
SMSG_LOOT_RESPONSE items include randomSuffix and randomPropertyId
fields across all expansions, not just WotLK. Using 14-byte size for
Classic/TBC caused item data to be read at wrong offsets.
Remove stray X-flip in minimap display shader that mirrored the map
horizontally (West on right instead of East). Fix arrow rotation
fallback path (missing negation) and add character-facing-relative
arrow in rotateWithCamera mode.
Compact key ring: 24px slots in 8-column grid, only show rows with
items, hide when empty. Add Show Key Ring toggle in Settings with
persistence.
When Classic is active, loadDBC("Spell.dbc") finds the WotLK base DBC
(234 fields) since no binary Classic DBC exists. The Classic layout says
IconID is at field 117, but in the WotLK DBC that field contains
unrelated data (mostly zeros). This caused all spell icon lookups to
fail silently.
Now detects the DBC/layout field count mismatch and falls back to the
WotLK field index 133, which is correct for the base DBC. Classic spell
IDs are a subset of WotLK, so the icon mapping works correctly.
The sync path's MODULE handler was returning 0x01 (module found) for
unwanted cheat DLLs (WPESPY, TAMIA, PRXDRVPE, etc.) instead of 0x00
(not found). Since VMaNGOS compares the result as a boolean, returning
any non-zero value for a cheat module tells the server "this cheat DLL
is loaded," triggering Warden penalties that accumulate into a kick
after ~3-5 minutes.
Also adds ±4KB hint window search to searchCodePattern for faster
PAGE_A resolution without full brute-force, and restores the turtle
PAGE_A fallback (confirmed patterns are runtime-patched offsets not
present in the on-disk PE).
Multiple fields were at wrong offsets causing MEM_CHECK comparison
failures against expected Windows 7 SP1 values. Key fixes:
- LargePageMinimum: 0x248→0x244
- NtProductType at 0x264 was 0, now 1 (VER_NT_WORKSTATION)
- ProductTypeIsValid at 0x268 was missing
- ProcessorFeatures at 0x274 was clobbered by misplaced NtProductType
- NumberOfPhysicalPages: 0x300→0x2E8
- ActiveConsoleId at 0x2D8 was 4, now 1
- Added SuiteMask, NXSupportPolicy, and other missing fields
Terrain pool 16384→65536, WMO pool 8192→32768. The previous sizes
were too small for the load/unload radii, causing pool exhaustion
and a hard crash when streaming terrain on large maps.
Sending a wrong hash to AzerothCore/WotLK servers triggers an
account ban. When no pre-computed challenge-response entry matches
the server seed, skip the response entirely so the server times out
with a kick (recoverable) instead of verifying a bad hash and
banning (unrecoverable). Turtle/Classic servers remain unchanged
as they only log Warden failures.
Also adds RX silence detection and fixes Turtle isTurtle flag
propagation in MEM_CHECK path.
Move 5-second brute-force HMAC-SHA1 code pattern searches to a
background thread via std::async. The main loop now detects PAGE_A/B
checks, launches the response builder async, and drains the result
in update() — encrypting and sending on the main thread to keep
wardenCrypto_ RC4 state thread-safe.
Also adds Turtle WoW PE binary support (isTurtle flag, dedicated exe
search, runtime patches), searchCodePattern with result caching,
writeLE32 public API, and Warden scan entry verification.
SMSG_ENVIRONMENTALDAMAGELOG and SMSG_ENVIRONMENTAL_DAMAGE_LOG now pass
dstGuid=victimGuid with srcGuid=0 (no caster for env damage), ensuring
the combat log shows an empty source name rather than the player's
current target.
SMSG_SPELLENERGIZERLOG now passes casterGuid/victimGuid so the log
correctly attributes mana/energy restoration to the actual caster
rather than the player's current target.
These handlers already had casterGuid/victimGuid available but were
discarding the packet spellId and not passing GUIDs to addCombatText.
Now the combat log entries show the correct attacker/victim names and
the spell that caused the reflect/immune event.
SMSG_SPELLLOGMISS and SMSG_PROCRESIST already parsed casterGuid /
victimGuid from the packet but discarded them when calling addCombatText.
Now pass those GUIDs so combat log entries record the actual
attacker/victim names rather than falling back to current target.
addCombatText now accepts optional srcGuid/dstGuid parameters. When
provided, the persistent combat log resolves names from the actual
packet GUIDs rather than always falling back to playerGuid/targetGuid.
Updated handleAttackerStateUpdate, handleSpellDamageLog,
handleSpellHealLog, and SMSG_PERIODICAURALOG to pass data.attackerGuid
/ data.targetGuid (or casterGuid/victimGuid), so the combat log
correctly records the attacker name when being hit by enemies the
player has not selected as their current target.
All 48 existing call sites use the 0/0 default and are unaffected.
- Store holyRes/fireRes/natureRes/frostRes/shadowRes/arcaneRes in ItemQueryResponseData
- Parse resistance fields in WotLK, TBC, and Classic parsers (previously discarded)
- Display non-zero resistances (e.g. "+40 Fire Resistance") in both tooltip paths
- Add getPlayerRace() accessor to GameHandler
- Show race restriction line (e.g. "Races: Blood Elf, Draenei") in both tooltip paths,
highlighted red when player's race is not allowed
- Useful for fire/nature/frost resist gear (Onyxia, AQ40, Naxx encounters)
Display the allowableClass bitmask parsed from SMSG_ITEM_QUERY as a
human-readable "Classes: X, Y" line. Text is highlighted red when the
player's own class is not in the allowed set. Hidden when all classes
can use the item (no restriction).
Extend the inventory item (ItemDef) tooltip to also display
skill and reputation requirements by consulting the item query
cache (ItemQueryResponseData) when available, matching the
behavior already added to the ItemQueryResponseData tooltip path.
- Store requiredSkill, requiredSkillRank, allowableClass, allowableRace,
requiredReputationFaction, and requiredReputationRank from
SMSG_ITEM_QUERY_SINGLE_RESPONSE in ItemQueryResponseData (was discarded)
- Show "Requires <Skill> (<rank>)" in item tooltip, highlighted red when
the player doesn't have sufficient skill level
- Show "Requires <Rank> with <Faction>" for reputation-gated items
- Skill names resolved from SkillLine.dbc; faction names from Faction.dbc
- Also fix loot window tooltip suppressing items with names starting with 'I'
Read the third update field (bonusTemp/bonusPerm) for each skill slot so the
skills tab displays the actual buffed value rather than just the base value.
Skills buffed by food/potions/items now show "value / max (+N)" with a cyan
name, and maxed-out skills show a gold bar and name for quick identification.
The inventory screen item tooltip showed only the continent name
(Eastern Kingdoms, Kalimdor, etc.) for the hearthstone home location.
Apply the same zone-name lookup already used by the action bar tooltip:
prefer the zone name from homeBindZoneId_ via getWhoAreaName(), falling
back to the continent name if the zone is unavailable.
- Add trigger 4 (soulstone), 5 (no-delay use), 6 (learn/recipe) as "Use:"
— all show as "Use:" in WoW, matching client behavior
- Fix trigger 6 which was incorrectly labeled "Soulstone" (trigger 4 is
soulstone; trigger 6 is LEARN_SPELL_ID used by recipe/pattern items)
- Both ItemDef tooltip and ItemSlot inline tooltip are now consistent
SMSG_PLAYERBOUND fires when the player sets a new hearthstone location.
Previously homeBindMapId_ and homeBindZoneId_ were only set by
SMSG_BINDPOINTUPDATE (login), so the tooltip would show the old zone
until next login. Now both are updated on SMSG_PLAYERBOUND as well.
- Add light gold (e6cc80) color for quality 6 (Artifact) and 7 (Heirloom)
in the loot roll window and loot toast notification displays
- Add "Repair (Guild)" button next to "Repair All" in vendor window when
player is in a guild, using guild bank funds for the repair cost
Extends ItemQuality enum with ARTIFACT (6) and HEIRLOOM (7) to match
WotLK 3.3.5a quality values, with light gold color (e6cc80) and
display name support in inventory UI and tooltips.
Storm weather (wType==3 from SMSG_WEATHER) previously rendered no
visual particles and no audio. Map it to RAIN in the weather system so
thunderstorms produce rain particles at the server-sent intensity level,
and the ambient sound manager picks up rain_heavy/medium/light audio
from the same intensity logic already used for plain rain.
This pairs with the lightning commit — storms now have both rain
particles and lightning flashes for a complete thunderstorm experience.
The lightning system (lightning.hpp/cpp) was fully implemented but never
wired into the renderer. Connect it now:
- Enable lightning during server storm weather (wType==3, intensity>0.1)
and heavy rain (wType==1, intensity>0.7) as a bonus visual
- Scale lightning intensity proportionally to weather intensity
- Render in both parallel (SEC_POST) and fallback rendering paths
- Update and shutdown alongside the weather system
- Show active lightning info in the performance HUD weather section
Stores the destination node name when activateTaxi() is called and
displays a "✈ → <Destination>" indicator in the minimap indicator
stack while isOnTaxiFlight() is true. Falls back to "✈ In Flight"
when the destination name is unavailable.
- /clear slash command empties the chat history (was listed in
autocomplete but never handled)
- Stats panel shows run/flight/swim speed as percentage of base only
when non-default (e.g. mounted or speed-buffed), under a new
Movement section
Add a pulsing purple "Calendar: N Invite(s)" notification below the
minimap indicator stack when the server reports unacknowledged calendar
invites (SMSG_CALENDAR_SEND_NUM_PENDING / EVENT_INVITE_ALERT).
Only rendered when the WotLK expansion is active since the calendar
system is WotLK-exclusive. Consistent with the existing New Mail, talent
point, BG queue, and LFG queue indicator stack.
Convert raw combat rating values to meaningful percentages using
level-scaled divisors based on known WotLK level-80 constants
(from gtCombatRatings.dbc):
Hit Rating : 26.23 per 1% at level 80
Expertise : 8.19 per expertise pt (0.25% dodge/parry each)
Haste : 32.79 per 1% at level 80
Armor Pen : 13.99 per 1% at level 80
Resilience : 94.27 per 1% at level 80
Each stat now displays as "Hit Rating: 120 (4.58%)" instead of
just "Hit Rating: 120". The divisor scales by pow(level/80, 0.93)
for characters below level 80.
Replace raw zone/map IDs with human-readable names via the existing
getWhoAreaName() and getMapName() DBC caches. The Zone column is also
widened from fixed 55px to a stretch column so names fit properly.
Falls back to numeric IDs gracefully when DBC data is unavailable.
Tracks PLAYER_FIELD_MOD_DAMAGE_DONE_POS (7 schools at field 1171) and
PLAYER_FIELD_MOD_HEALING_DONE_POS (field 1192) from server update fields.
getSpellPower() returns the max damage bonus across magic schools 1-6.
getHealingPower() returns the raw healing bonus.
Both values displayed in the character screen Combat section alongside
the previously added attack power, dodge, parry, crit, and rating fields.
Adds update field tracking for WotLK secondary combat statistics:
- UNIT_FIELD_ATTACK_POWER / RANGED_ATTACK_POWER (fields 123, 126)
- PLAYER_DODGE/PARRY/BLOCK/CRIT_PERCENTAGE (fields 1025-1029)
- PLAYER_RANGED_CRIT_PERCENTAGE, PLAYER_SPELL_CRIT_PERCENTAGE1 (1030, 1032)
- PLAYER_FIELD_COMBAT_RATING_1 (25 slots at 1231, hit/expertise/haste/etc.)
Both CREATE_OBJECT and VALUES update paths now populate these fields.
The Character screen Stats tab shows them when received from the server,
with graceful fallback when not available (Classic/TBC expansions).
Field indices verified against AzerothCore 3.3.5a UpdateFields.h.
Replaces the static local Map.dbc cache in renderInstanceLockoutsWindow()
with the existing GameHandler::getMapName() accessor, eliminating duplicate
DBC loading. Moves getMapName declaration to public interface.
Extend the LFGDungeons.dbc name lookup to the Dungeon Finder window UI:
- Queued state: "In queue for Culling of Stratholme (1:23)"
- Proposal state: "Group found for Halls of Lightning!"
- InDungeon state: "In dungeon (Utgarde Pinnacle)"
- FinishedDungeon state: "Culling of Stratholme complete"
- Proposal accept banner: "A group has been found for <dungeon>!"
All states fall back gracefully when DBC name is unavailable.
Add LFGDungeons.dbc cache (loadLfgDungeonDbc / getLfgDungeonName) and
use it to enrich three LFG chat messages in WotLK:
- handleLfgJoinResult: "Joined the queue for Culling of Stratholme."
- handleLfgProposalUpdate case 1: "Group found for Halls of Lightning!"
- handleLfgProposalUpdate case 2: "A group has been found for ... Accept or decline."
Falls back to generic text when DBC is unavailable or dungeon ID unknown.
Replace generic "Taxi: requesting flight..." and "Flight started." with
"Requesting flight to [node name]..." and "Flight to [node name] started."
using the already-loaded taxiNodes_ map. Falls back to generic text when
the node name is unavailable.
Replace raw bfZoneId integer with getAreaName() lookup in
SMSG_BATTLEFIELD_MGR_ENTRY_INVITE so players see "Wintergrasp"
instead of "zone 4197" in the invitation prompt.
SMSG_EQUIPMENT_SET_SAVED: parse the set index and GUID, look up the
matching set name from equipmentSets_, and show
"Equipment set \"<name>\" saved." instead of the generic message.
Falls back to "Equipment set saved." when the set is not yet in the
local cache (e.g. first save before SMSG_EQUIPMENT_SET_LIST arrives).
SMSG_AUCTION_OWNER_NOTIFICATION action field was ignored — all events
showed "has sold!" regardless. Now:
- action 0 (won/sold): "Your auction of <item> has sold!"
- action 1 (expired): "Your auction of <item> has expired."
- action 2 (bid placed): "A bid has been placed on your auction of <item>."
- SMSG_BUY_ITEM: show "Purchased: <item name> x<count>" confirmation
using pendingBuyItemId_ set at buy time (fallback to "item #N")
- handleFriendStatus: look up name from contacts_ (populated by
SMSG_FRIEND_LIST) before playerNameCache, reducing "Unknown" fallbacks
for online/offline/removed notifications
- Channel member list: also check playerNameCache when entity manager
has no name, reducing "(unknown)" placeholders
- setFocus: use Unit::getName() (covers NPCs too) + playerNameCache
fallback instead of Player-only cast
- SMSG_INSTANCE_LOCK_WARNING_QUERY: show dungeon name + difficulty +
remaining time when auto-accepting a saved instance re-entry
- SMSG_CALENDAR_RAID_LOCKOUT_ADDED: show dungeon name (from Map.dbc)
and difficulty label (Normal/Heroic/25-Man/25-Man Heroic)
- SMSG_CALENDAR_RAID_LOCKOUT_REMOVED: show dungeon name instead of raw
map ID; now emits a chat message (was silent/LOG_DEBUG only)
- SMSG_CHAR_RENAME: map result codes 1-7 to human-readable strings
("Name already in use.", "Name too short.", etc.) instead of
"Character rename failed (error N)."
- Add getMapName() helper backed by Map.dbc (parallel to getAreaName)
- SMSG_RAID_INSTANCE_MESSAGE: show dungeon name instead of raw map ID
- SMSG_INSTANCE_RESET: show dungeon name instead of raw map ID
- SMSG_INSTANCE_RESET_FAILED: show dungeon name instead of raw map ID
- SMSG_EQUIPMENT_SET_SAVED: show "Equipment set saved." confirmation
- SMSG_PROPOSE_LEVEL_GRANT: show mentor name offering a level grant (RAF)
- SMSG_REFER_A_FRIEND_EXPIRED: show link-expired message
- SMSG_REFER_A_FRIEND_FAILURE: show reason-mapped error message
- SMSG_REPORT_PVP_AFK_RESULT: show success/failure feedback
- SMSG_QUEST_CONFIRM_ACCEPT: add playerNameCache fallback for sharer name
- SMSG_LEVELUP_INFO: show \"You have reached level N!\" chat message on level-up
(was only calling the UI ding callback without any chat notification)
- SMSG_PET_LEARNED_SPELL: show \"Your pet has learned X.\" with spell name lookup
(was LOG_DEBUG only)
- SMSG_PET_NAME_INVALID: show \"That pet name is invalid.\"
(was silently consumed)
Replace raw \"Battleground #2\" with proper names (Warsong Gulch, Arathi Basin,
Eye of the Storm, Strand of the Ancients, Isle of Conquest, arena names, etc.)
for all three expansions' BG type ID space.
- handleLfgBootProposalUpdate: was using myAnswer (player's own vote) to determine
if the boot passed — should use bootVotes >= votesNeeded instead. Player who voted
yes would see "passed" even if the vote failed, and vice versa.
- handleLfgPlayerReward: look up item name from item cache instead of showing
raw "item #12345" for dungeon reward items
Previously, PLAYER_NOT_FOUND, ANNOUNCEMENTS_ON/OFF, MODERATION_ON/OFF,
PLAYER_BANNED, PLAYER_UNBANNED, PLAYER_NOT_BANNED, INVITE, WRONG_FACTION,
INVITE_WRONG_FACTION, NOT_MODERATED, PLAYER_INVITED, and PLAYER_INVITE_BANNED
all fell silently to the default log-only path. Now each shows an appropriate
system message in chat.
- SMSG_BINDPOINTUPDATE: show zone name in \"Your home has been set to X.\" (was just \"Your home has been set.\")
- SMSG_PLAYERBOUND: replace \"map N, zone N\" raw IDs with zone name lookup
- SMSG_BINDER_CONFIRM: suppress redundant \"This innkeeper is now your home location.\" since SMSG_PLAYERBOUND fires immediately after with zone context
- SMSG_DUEL_REQUESTED: add playerNameCache fallback before hex GUID for challenger name
- Map GuildCommandError codes to human-readable strings instead of showing raw
error numbers (e.g. \"error 4\" → \"No player named X is online.\")
- Handle errorCode==0 for QUIT command: show \"You have left the guild.\" and
clear guild state (name, ranks, roster) — previously silent
- Handle errorCode==0 for CREATE and INVITE commands with appropriate messages
- Substitute %s-style error messages with the player name from data.name
- Suppress repeated \"Guild: <Name>\" chat message on every SMSG_GUILD_QUERY_RESPONSE;
only announce once when the guild name is first learned at login
Previously "You are now in a group with N members." was shown on every
GROUP_LIST packet, which fires for each party stat update. Now only show
a message on actual state transitions: joining, leaving the group.
- SMSG_SUMMON_REQUEST: fall back to playerNameCache when entity not in
range; include zone name from getAreaName() in the summon message
(e.g. "Bob is summoning you to Stormwind.")
- SMSG_TRADE_STATUS BEGIN_TRADE: fall back to playerNameCache when the
trade initiator's entity is not visible
- SMSG_QUESTGIVER_QUEST_FAILED: look up quest title from questLog_ and
include it in the failure message (same pattern as QUESTUPDATE_FAILED
fix from previous session)
- SMSG_ACHIEVEMENT_EARNED: fall back to playerNameCache for non-visible
players before showing a raw hex GUID in the achievement message
- SMSG_DURABILITY_DAMAGE_DEATH: use the actual pct field from the packet
instead of hardcoding "10%"
- SMSG_PET_CAST_FAILED: read reason as uint8 (not uint32), look up spell
name and show human-readable failure reason to player
- Trade status 9 (REJECTED): show "Trade declined." instead of "Trade
cancelled." to distinguish explicit decline from cancellation
SMSG_QUESTUPDATE_FAILED and SMSG_QUESTUPDATE_FAILEDTIMER were emitting
generic "Quest 12345 failed!" messages. Now looks up the title from
questLog_ and shows e.g. "\"Report to Gryan Stoutmantle\" failed!" for
a much more readable notification. Falls back to the generic form if
the title is not cached.
SMSG_SPELLLOGEXECUTE POWER_DRAIN reads drainPower but was not passing it
to addCombatText, so drained-resource returns showed as blue (mana) even
for rage or energy. Now correctly colored following the energize palette
added in the earlier commit.
The dispel-failed handler was showing the failure notification for every
dispel attempt in the party/raid, regardless of who cast it. Now checks
casterGuid == playerGuid before showing "X failed to dispel." so only
the player's own failed dispels surface in chat.
SMSG_CHANNEL_NOTIFY carries many event types that were silently dropped
in the default case: wrong password, muted, banned, throttled, kicked,
not owner, not moderator, password changed, owner changed, invalid name,
not in area, not in LFG. These are now surfaced as system chat messages
matching WoW-standard phrasing.
SMSG_DUEL_WINNER type=1 means the loser fled the duel zone rather than
being defeated; was previously treated the same as a normal win. Now
shows "X has fled from the duel. Y wins!" for the flee case vs the
standard "X has defeated Y in a duel!" for a normal outcome.
Previously a spell failure like "Not in range" gave no context about
which spell failed. Now the message reads e.g. "Fireball: Not in range"
using the spell name from the DBC cache. Falls back to the bare reason
string if the spell name is not yet cached.
SMSG_REMOVED_SPELL and SMSG_SEND_UNLEARN_SPELLS both erased spells from
knownSpells but left stale references on the action bar. After a respec
or forced spell removal, action bar buttons would show removed talents
and spells as still present. Now both handlers clear matching slots and
persist the updated bar layout.
SMSG_SPELLLOGMISS contains miss events for both directions: spells the
player cast that missed, and enemy spells that missed the player. The
victim side (dodge/parry/block/immune/absorb/resist) was silently
discarded. Now both caster==player and victim==player generate the
appropriate combat text floater.
Mana (0)=blue, Rage (1)=red, Focus (2)=orange, Energy (3)=yellow,
Runic Power (6)=teal. Previously all energize events showed as blue
regardless of resource type, making it impossible to distinguish
e.g. a Warrior's Rage generation from a Mage's Mana return.
Power type is now captured from SMSG_SPELLENERGIZELOG (uint8) and
SMSG_PERIODICAURALOG OBS_MOD_POWER/PERIODIC_ENERGIZE (uint32 cast
to uint8) and stored in CombatTextEntry::powerType.
When buying a higher spell rank from a trainer, SMSG_TRAINER_BUY_SUCCEEDED
already announces "You have learned X", and SMSG_SUPERCEDED_SPELLS would
then also print "Upgraded to X" — two messages for one action.
Fix: check if the new spell ID was already in knownSpells before inserting
it in handleSupercededSpell. If so, the trainer handler already announced
it and we skip the redundant "Upgraded to" message. Non-trainer supersedes
(quest rewards, etc.) where the spell wasn't pre-inserted still show it.
SMSG_INITIAL_SPELLS delivers active cooldowns which were stored in
spellCooldowns but never propagated to the action bar slot
cooldownRemaining/cooldownTotal fields. This meant that spells with
remaining cooldowns at login time showed no countdown overlay on the
action bar. Sync the action bar slots from spellCooldowns after
loadCharacterConfig() to restore the correct timers.
Replace the generic "Party command failed (error N)" message with
WoW-standard error strings for each PartyResult code, matching what
the original client displays (e.g. "Your party is full.", "%s is
already in a group.", "%s is ignoring you.", etc.).
SMSG_TRAINER_BUY_SUCCEEDED pre-inserts the spell into knownSpells and
shows "You have learned X." The subsequent SMSG_LEARNED_SPELL packet
would then show a second "You have learned a new spell: X." message.
Fix: check if the spell was already in knownSpells before inserting in
handleLearnedSpell. If it was pre-inserted by the trainer handler, skip
the chat notification to avoid the duplicate.
After automatically upgrading action bar slots to the new spell rank
in handleSupercededSpell, save the character config so the upgraded
slot IDs persist across sessions.
When a spell is superceded (e.g. Fireball Rank 1 -> Rank 2 after
training), update any action bar slots referencing the old spell ID
to point to the new rank. This matches WoW client behaviour where
training a new rank automatically upgrades your action bars so you
don't have to manually re-place the spell.
Same-map teleports (dungeon teleporters, etc.) clear casting state but
were not clearing lastInteractedGoGuid_. If a gather cast was in progress
when the teleport happened, the stale GO guid could theoretically trigger
a spurious CMSG_LOOT on the destination map.
Also clears lastInteractedGoGuid_ in handleNewWorld alongside the rest of
the casting-state teardown for consistency with other reset paths.
SMSG_LOOT_START_ROLL was not calling queryItemInfo(), so the roll popup
would display item IDs instead of names when the item had not been
previously cached (e.g. first time seeing that item in the session).
Also update renderLootRollPopup to prefer the live ItemQueryResponseData
name/quality over the snapshot captured at parse time, so the popup
shows the correct name once SMSG_ITEM_QUERY_SINGLE_RESPONSE arrives.
wasInTimedCast checked casting == true but not whether the completing spell
was actually the gather cast. A triggered/proc spell (SMSG_SPELL_GO with
a different spellId) could arrive while a gather cast is active (casting==true),
satisfying the old guard and firing lootTarget prematurely.
Require data.spellId == currentCastSpellId so only the spell that started
the cast bar triggers the post-gather CMSG_LOOT dispatch.
When the server sends SMSG_GAMEOBJECT_CUSTOM_ANIM with animId=0 for a GO
of type 17 (FISHINGNODE), a fish has been hooked and the player needs to
click the bobber quickly. Add a system chat message and a UI sound to
alert the player — previously there was no visual/audio feedback beyond
the bobber animation itself.
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.
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.
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)
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.
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.
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.
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.
- 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
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
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 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()
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.
- 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)
- 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()
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.
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.
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
- 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
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).
- 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.
- 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
- 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.
- 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
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).
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
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).
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.
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)
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.
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.
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
- 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
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.
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.
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.
- 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.
SDL yrel > 0 means the mouse moved downward. In WoW, moving the mouse
down should decrease pitch (look down), but the previous code did
+= yrel which increased pitch (look up). This made the camera appear
inverted — moving the mouse down tilted the view upward. The invertMouse
option accidentally produced the correct WoW-default behaviour.
Fix: negate the default invert factor so mouse-down = look down without
InvertMouse, and mouse-down = look up when InvertMouse is enabled.
Add ArenaTeamMember / ArenaTeamRoster structs, parse the WotLK 3.3.5a
roster packet (guid, online flag, name, per-player week/season W/L,
personal rating), store per-teamId, and render a 4-column table
(Name / Rating / Week / Season) inside the existing Arena social tab.
Online members are highlighted green; offline members are greyed out.
Display Defense Rating, Dodge Rating, Parry Rating, Block Rating,
Block Value, Armor Penetration, and Spell Penetration from equipped
items in the Stats tab. Previously these stat types (12-15, 44, 47,
48) were parsed from item data but silently dropped.
- Change maxRow/maxCol from uint8_t to int in renderTalentTree to prevent
infinite loop: uint8_t col <= 255 never exits since col wraps 255→0.
Add sanity cap of 15 rows/cols to guard against corrupt DBC data.
- Fix dangling reference warning in getFormattedTitle (lambda reference)
- Raise MAX_PITCH from 35° to 88° to match WoW standard upward look range
Lists all saved equipment sets from SMSG_EQUIPMENT_SET_LIST with
icon placeholder and an Equip button per set. Clicking either the
icon or the Equip button sends CMSG_EQUIPMENT_SET_USE to swap the
player's gear to that set. Window toggled with the ` (backtick) key.
Track player titles from SMSG_TITLE_EARNED into knownTitleBits_ set,
read active title from PLAYER_CHOSEN_TITLE update field (WotLK index
1349), expose via getFormattedTitle()/sendSetTitle() on GameHandler.
Add SetTitlePacket builder (CMSG_SET_TITLE: int32 titleBit, -1=clear).
Titles window (H key) lists all earned titles from CharTitles.dbc,
highlights the active one in gold, and lets the player click to equip
or unequip a title with a single server round-trip.
Shows a thin progress bar below the player health/power bars whenever
the player is auto-attacking. The bar fills from the last swing timestamp
to the next expected swing based on the main-hand weapon's delay (from
ItemQueryResponseData::delayMs). Falls back to 2.0s for unarmed. Turns
gold and shows "Swing!" when the timer is complete to signal readiness.
Hides when not auto-attacking.
When SMSG_WEATHER sets storm (type 3) with intensity > 0.3,
fire a low-frequency (6Hz) camera shake to simulate thunder.
Magnitude scales with intensity: 0.03–0.07 world units.
- Add triggerShake(magnitude, frequency, duration) to CameraController
- Apply envelope-decaying sinusoidal XYZ offset to camera in update()
- Handle SMSG_CAMERA_SHAKE opcode in GameHandler dispatch
- Translate shakeId to magnitude (minor <50: 0.04, larger: 0.08 world units)
- Wire CameraShakeCallback from GameHandler through to CameraController
- Shake uses 18Hz oscillation with 30% fade-out envelope at end of duration
- Remove fsr2Active guard that prevented FXAA when FSR3 was active
- FXAA checkbox now always enabled; tooltip adapts to explain FSR3+FXAA combo
when FSR3 is active ('recommended ultra-quality combination')
- Performance HUD shows 'FXAA: ON (FSR3+FXAA combined)' when both active
- Ultra graphics preset now enables FXAA (8x MSAA + FXAA for max smoothness)
- Preset detection updated to require FXAA for Ultra match
- Parse MSG_LIST_STABLED_PETS (SMSG): populate StabledPet list with
petNumber, entry, level, name, displayId, and active status
- Detect stable master via gossip option text/keyword matching and
auto-send MSG_LIST_STABLED_PETS request to open the stable UI
- Refresh list automatically after SMSG_STABLE_RESULT to reflect state
- New packet builders: ListStabledPetsPacket, StablePetPacket, UnstablePetPacket
- New public API: requestStabledPetList(), stablePet(slot), unstablePet(petNumber)
- Stable window UI: shows active/stabled pets with store/retrieve buttons,
slot count, refresh, and close; opens when server sends pet list
- Clear stable state on world logout/disconnect
Previously SMSG_TITLE_EARNED only showed the numeric bit index.
Now it lazy-loads CharTitles.dbc and formats the full title string
with the player's name (e.g. "Title earned: Commander Kelsi!").
- Add CharTitles layout to WotLK (TitleBit=36) and TBC (TitleBit=20) layouts
- loadTitleNameCache() maps each titleBit to its English title string
- SMSG_TITLE_EARNED substitutes %s placeholder with local player's name
- Falls back to "Title earned (bit N)!" if DBC is unavailable
cleanupUnusedModels() runs every 5 seconds and freed vertex/index buffers
without waiting for the GPU to finish the previous frame's command buffer.
This caused VK_ERROR_DEVICE_LOST (-4) after extended gameplay when tiles
stream out and their models are freed mid-render.
Add vkDeviceWaitIdle() before the buffer destroy loop in both M2Renderer
and WMORenderer cleanupUnusedModels(). The wait only happens when there are
models to remove, so quiet sessions have no overhead.
- Remove !fsr_.enabled / !fsr2_.enabled guards that blocked FXAA init
- FXAA can now coexist with FSR1 and FSR3 simultaneously
- Priority: FSR3 > FXAA > FSR1
- FSR3 + FXAA: scene renders at FSR3 internal res, temporal AA runs,
then FXAA reads FSR3 history and applies spatial AA to swapchain
(replaces RCAS sharpening for ultra-quality native mode)
- FXAA + FSR1: scene renders at native res, FXAA post-processes;
FSR1 resources exist but are idle (FXAA wins for better quality)
- FSR3 only / FSR1 only: unchanged paths
- Fix missing fxaa.frag.spv: shader was present but uncompiled; the
CMake compile_shaders() function will now pick it up on next build
Parse pet action feedback opcodes and display messages in system chat:
dead, nothing_to_attack, cant_attack_target, target_too_far,
no_path, cant_attack_immune. Replaces consume stub.
handlePageTextQueryResponse() now collects pages into bookPages_ vector
instead of dumping lines to system chat. Multi-page items (nextPageId != 0)
are automatically chained by requesting subsequent pages. The book window
opens automatically when pages arrive, shows formatted text in a parchment-
styled ImGui window with Prev/Next page navigation and a Close button.
SMSG_READ_ITEM_OK clears bookPages_ so each item read starts fresh;
handleGameObjectPageText() does the same before querying the first page.
Closes the long-standing issue where reading scrolls and tattered notes
spammed many separate chat messages instead of showing a readable UI.
Classic 1.12 SMSG_INSPECT (wire 0x115): parse PackedGUID + 19×uint32
itemEntries to populate InspectResult and inspectedPlayerItemEntries_ cache,
enabling gear inspection of other players on Classic servers. Triggers item
queries for all filled slots so the inspect window shows names/ilevels.
SMSG_ITEM_ENCHANT_TIME_UPDATE: parse itemGuid/slot/durationSec/playerGuid and
store per-slot expire timestamps in tempEnchantTimers_. Fires 5min/1min
chat warnings before expiry. getTempEnchantRemainingMs() helper queries live
remaining time. Buff bar renders timed slot buttons (gold/teal/purple per
slot) that pulse red below 60s — useful for Shaman imbues, Rogue poisons,
whetstones and oils across all three expansions.
Parse master loot candidate GUIDs from SMSG_LOOT_MASTER_LIST and display
a "Give to..." popup menu on item click when master loot is active.
Sends CMSG_LOOT_MASTER_GIVE with loot GUID, slot, and target GUID.
Clears candidates when loot window is closed.
Parse hp/mana/str/agi/sta/int/spi deltas from SMSG_LEVELUP_INFO payload
and display them in green below the "You have reached level X!" banner.
Extends DING_DURATION to 4s to give players time to read the gains.
Store glyph IDs from SMSG_TALENTS_INFO (previously discarded) in
learnedGlyphs_[2][6] per talent spec. Load GlyphProperties.dbc to
map glyphId to spellId and major/minor type. Add a Glyphs tab to
the talent screen showing all 6 slots with spell icons and names.
Also clear vehicleId_ on SMSG_ON_CANCEL_EXPECTED_RIDE_VEHICLE_AURA.
Parse SMSG_PLAYER_VEHICLE_DATA (PackedGuid + uint32 vehicleId) and
track in-vehicle state. Add sendRequestVehicleExit() which sends
CMSG_REQUEST_VEHICLE_EXIT. Render a floating red "Leave Vehicle"
button above the action bar whenever vehicleId_ is non-zero.
State cleared on world leave and zone transfer.
Remove the redundant renderObjectiveTracker (simpler, fixed-position) and
apply draggable position tracking to renderQuestObjectiveTracker (the primary
tracker with context menus, item icons, and click-to-open-quest-log).
- questTrackerPos_ / questTrackerPosInit_ now drive the primary tracker
- Default position is top-right (below minimap at y=320)
- Drag saves to settings.cfg immediately (quest_tracker_x/y keys)
- Both trackers were rendering simultaneously — this eliminates the duplicate
SMSG_ITEM_PUSH_RESULT now fires a new ItemLootCallback that
game_screen.cpp uses to push a compact slide-in toast at the
bottom-left of the screen. Each toast:
- Shows a quality-tinted left accent bar (grey/white/green/blue/
purple/orange matching WoW quality colours)
- Displays "Loot: <item name>" with the name in quality colour
- Appends " x<N>" for stacked pickups
- Coalesces repeated pickups of the same item (adds count, resets timer)
- Stacks up to 5 entries, 3 s lifetime with 0.15 s slide-in and 0.7 s
fade-out
SMSG_PVP_CREDIT previously only wrote a system chat message. Now it
also fires a new PvpHonorCallback, which game_screen.cpp uses to push
a compact dark-red toast at the top-right of the screen showing
"⚔ +N Honor" with a 3.5 s lifetime and smooth fade in/out.
When the server sends a level-up update for a non-self player entity,
fire the existing OtherPlayerLevelUpCallback which was previously
unregistered in the UI layer. GameScreen now:
- Registers the callback once and stores {guid, level} entries
- Lazily resolves the player name from the name cache at render time
- Renders gold-bordered toasts bottom-centre with a ★ icon and fade/slide
animation ("Thrall is now level 60!"), coalescing duplicates
- Prunes entries after 4 s with a 1 s fade-out
Quest game-object objectives (negative npcOrGoId entries, e.g. gather 5
crystals) now render as larger bright-cyan triangles distinct from the
standard amber GO markers. Tooltip appends "(quest)" to the name.
Also refactors the minimap quest-entry build to track both NPC and GO
kill-objective entries from the tracked quest log.
Quest kill objective NPCs are now rendered as larger gold dots (3.5px)
with a dark outline on the minimap, distinct from standard hostile (red)
and friendly (white) dots. Only shows mobs for incomplete objectives in
tracked quests (or all active quests if none are tracked). Hovering the
dot shows a tooltip with the unit name and "(quest)" annotation.
Adds a visual progress overlay at bottom-right when quest kill counts
or item collection updates arrive. Each toast shows the quest title,
objective name, a fill-progress bar, and an X/Y count. Toasts coalesce
when the same objective updates multiple times, and auto-dismiss after 4s.
Wires a new QuestProgressCallback through GameHandler to trigger the UI.
Adds a slide-in toast overlay at the bottom-left of the screen whenever
an incoming whisper arrives. Toasts display "Whisper from:", the sender
name in gold, and a truncated message preview. Up to 3 toasts stack with
a 5s lifetime; each fades in over 0.25s and fades out in the final 1s.
A new opt-in panel (Settings > Interface > Show Cooldown Tracker) lists
all spells currently on cooldown, sorted longest-to-shortest, with
spell icons and color-coded remaining time (red>30s, orange>10s,
yellow>5s, green<5s). Adds getSpellCooldowns() accessor to GameHandler.
Setting persists to ~/.wowee/settings.cfg.
Small colored squares appear on the minimap for each online party member
at their server-reported position. Dots use WoW class colors when the
entity is loaded, gold for the party leader, and light blue otherwise;
dead members show as gray. Hovering a dot shows the member's name.
Mirrors the target frame improvements: NPC focus targets now show
quest giver ! / ? indicators, Elite/Rare/Boss/Rare Elite badges,
and the creature subtitle (e.g. '<Grand Marshal of the Alliance>').
Keeps the focus and target frames in consistent feature parity.
For non-hostile NPCs with quest status data, displays a colored symbol
to the right of the nameplate name:
! (gold) — quest available
! (gray) — low-level quest
? (gold) — quest ready to turn in
? (gray) — quest incomplete
Displayed adjacent to the existing quest-kill sword icon, maintaining
the existing icon offset logic so both can coexist.
Reads QuestGiverStatus from the existing npcQuestStatus_ cache and
displays a colored badge next to the target's name:
! (gold) — quest available
! (gray) — low-level quest available
? (gold) — quest ready to turn in
? (gray) — quest incomplete / in progress
Matches the standard WoW quest indicator convention.
Shows the NPC subtitle (e.g. '<Warchief of the Horde>') below the
creature name in the target frame, using the subName field already
parsed from SMSG_CREATURE_QUERY_RESPONSE. Adds getCachedCreatureSubName()
accessor to GameHandler. Matches the official client's presentation.
Reads creature rank (0=Normal, 1=Elite, 2=RareElite, 3=Boss, 4=Rare)
from the existing creatureInfoCache populated by creature query responses.
Shows a colored badge next to the level: gold for Elite, purple for
Rare Elite, red for Boss, cyan for Rare — each with a tooltip. Adds
getCreatureRank() accessor to GameHandler for UI use.
Energy bosses (e.g. Anub'arak, various WotLK encounters) use energy as
their ability cooldown mechanic — tracking it in the boss frame lets
raiders anticipate major ability casts. Mana, rage, focus, and energy
all shown with type-appropriate colors as a slim 6px bar below HP.
When hovering over a player-applied DoT/debuff indicator square on an
enemy nameplate, the spell name is now shown as a tooltip. Uses direct
mouse-position hit test since nameplates render into the background
draw list rather than an ImGui window.
Pet ability buttons now show full spell info (name, description, range,
cost, cooldown) instead of just the spell name. Built-in commands (Follow,
Stay, Attack, etc.) keep their existing simple labels. Autocast-enabled
spells show "Autocast: On" at the bottom of the tooltip.
The focus frame now shows buff/debuff icons matching the target frame:
debuffs first with dispel-type border colors, buffs after with green
borders, duration countdowns on each icon, and rich spell info tooltips
on hover. Uses getUnitAuras() falling back to getTargetAuras() when
focus happens to also be the current target.
- Raid frame now shows dispellable debuff dots (magic/curse/disease/poison)
in the bottom of each cell, matching the existing party frame behavior;
hovering a dot shows the debuff type and spell names for that dispel type
- Minimap quest giver dots (! and ?) now show a tooltip with the NPC name
and whether the NPC has a new quest or a quest ready to turn in
Trainer spell tooltips now show the spell's effect description from
Spell.dbc (e.g. "Sends a shadowy bolt at the enemy...") above the
status/requirement lines, matching the WoW trainer UI style.
Also styles the spell name yellow (like WoW) and moves status to
TextDisabled for better visual hierarchy.
Item "Equip:" and "Use:" spell effects now display the spell's
description text from Spell.dbc (e.g. "Increases your Spell Power by 30.")
rather than the internal spell name (e.g. "Mana Spring Totem").
Falls back to the name when description is unavailable (e.g. older DBCs).
Adds getSpellDescription() to GameHandler, backed by the existing
loadSpellNameCache() pass which now reads the Tooltip field.
socketBonus is a SpellItemEnchantment entry ID, not a spell ID.
Previously getSpellName() was called on it, which produced wrong or
empty results. Now a lazy SpellItemEnchantment.dbc cache in the item
tooltip correctly resolves names like "+6 All Stats".
The Criteria tab now loads AchievementCriteria.dbc to display each
criterion's description text, parent achievement name, and a
current/required progress counter (e.g. "25/100") instead of the
raw numeric IDs. The search filter now also matches by achievement name.
AchievementCriteria DBC layout added to wotlk/dbc_layouts.json.
Hovering an earned achievement now shows its point value (gold badge),
description text from Achievement.dbc field 21, and the earn date.
loadAchievementNameCache() also populates achievementDescCache_ and
achievementPointsCache_ in a single DBC pass; Points field (39) added
to the WotLK Achievement DBC layout.
SMSG_PARTY_MEMBER_STATS includes a 64-bit aura presence mask + per-slot
spellId/flags that was previously read and discarded. Now populate
unitAurasCache_[memberGuid] from this data so party frame debuff dots
show even when no dedicated SMSG_AURA_UPDATE has been received for that
unit. For Classic/TBC (no flags byte), infer debuff status from dispel
type — any spell with a non-zero dispel type is treated as a debuff.
Both actions were defined in KeybindingManager but never wired to the
input handling block — C had no effect and B did nothing. Connect them:
- C toggles inventoryScreen's character panel (equipment slots view)
- B opens all separate bags, or falls back to toggling the unified view
- Parse MSG_PVP_LOG_DATA to populate BgScoreboardData (players, KB, deaths,
HKs, honor, BG-specific stats, winner)
- Add /score command to request the scorecard while in a battleground
- Render sortable per-player table with team color-coding and self-highlight
- Refresh button re-requests live data from server
- Fix TBC SMSG_INIT/SET_EXTRA_AURA_INFO_OBSOLETE to populate unitAurasCache_
for all GUIDs (not just player/target), mirroring WotLK aura update behavior
so party frame debuff dots work on TBC servers
Extend the aura tracking system to cache auras for any unit (not just
player and current target), so healers can see dispellable debuffs on
party members. Colored 8px dots appear below the power bar:
Magic=blue, Curse=purple, Disease=brown, Poison=green
One dot per dispel type; non-dispellable auras are suppressed.
Cache is populated via existing SMSG_AURA_UPDATE/SMSG_AURA_UPDATE_ALL
handling and cleared on world exit.
Track cumulative player damage/healing for the full combat encounter
using the persistent CombatLog, shown alongside the existing 2.5s
rolling window. The encounter row appears after 3s of combat and
persists post-combat until the next engagement, giving a stable
full-fight average rather than the spiky per-window reading.
Show small colored squares below the health bar of the current
hostile target indicating player-applied auras. Colors map to
dispel types from Spell.dbc: blue=Magic, purple=Curse,
yellow=Disease, green=Poison, grey=other/physical.
Dots are positioned below the cast bar if one is active,
otherwise directly below the health bar. They are clipped
to the nameplate width and only rendered for the targeted
hostile unit to keep the display readable.
Each non-General chat tab now shows an unread count in parentheses
(e.g. "Whispers (3)") when messages arrive while that tab is inactive.
The counter clears when the tab is selected. The General tab is excluded
since it shows all messages anyway.
Inserts a dedicated "Guild" tab between Whispers and Trade/LFG that
shows guild, officer, and guild achievement messages. Updates the
Trade/LFG channel-name filter from hardcoded index 3 to 4 to match
the new tab order.
Adds an 11th chat type "CHANNEL" to the dropdown, displaying a secondary
combo box populated from the player's joined channels. Typing /1, /2 etc.
in the input now also auto-switches the dropdown to CHANNEL mode and
selects the corresponding channel. Input text is colored cyan for channel
messages to visually distinguish them from other chat types.
SMSG_AREA_TRIGGER_MESSAGE events (dungeon enter messages, objective
triggers, etc.) were previously only appended to chat. Now they also
appear as animated slide-up toasts in the lower-center of the screen:
blue-bordered dark panel with light-blue text, 4.5s lifetime with
35ms slide-in/out animation. Up to 4 simultaneous toasts stack
vertically. Messages still go to chat as before.
Dead units with UNIT_DYNFLAG_LOOTABLE (0x0001) set are rendered as small
yellow-green diamonds on the minimap, distinct from live NPC dots. A hover
tooltip shows the unit name. Uses the dynamic flags already tracked by the
update-object parser, so no new server data is needed.
Stores up to 500 combat events in a rolling deque alongside the existing
floating combat text. Events are populated via the existing addCombatText()
call site, resolving attacker/target names from the entity manager and
player name cache at event time.
- CombatLogEntry struct in spell_defines.hpp (type, amount, spellId,
isPlayerSource, timestamp, sourceName, targetName)
- getCombatLog() / clearCombatLog() accessors on GameHandler
- renderCombatLog() in GameScreen: scrollable two-column table (Time +
Event), color-coded by event category, with Damage/Healing/Misc filter
checkboxes, auto-scroll toggle, and Clear button
- /combatlog (/cl) chat command toggles the window
Add a search field with "Search" button directly in the who results
window so players can query without using the chat box. Pressing Enter
in the search field also triggers a new /who query.
Store structured WhoEntry data from SMSG_WHO responses and show them
in a dedicated popup window with Name/Guild/Level/Class/Zone columns.
Right-click on any row to Whisper, Invite, Add Friend, or Ignore.
Window auto-opens when /who or /whois is typed; shows online count
in the title bar. Results persist until the next /who query.
- Renders below pet frame, visible only for Shaman (class 7)
- Shows each active totem (Earth/Fire/Water/Air) with its spell name
and a colored countdown progress bar
- Colored element dot (brown/red/blue/light-blue) identifies element
- Only rendered when at least one totem is active
- /use <item name> — searches backpack then bags (case-insensitive),
calls useItemBySlot() / useItemInBag() for the first match
- /equip <item name> — same search, calls autoEquipItemBySlot() /
autoEquipItemInBag() for the first match
- Both commands print an error if the item is not found
- Added both to tab-autocomplete list and /help output
- "Trade" option initiates a trade via existing initiateTrade(guid) API
(only shown for online friends with a known GUID)
- "Set Note" option opens an inline popup with InputText pre-filled with
the current note; Enter or OK saves via setFriendNote(), Esc/Cancel discards
Parse the optional reason and target name strings from
SMSG_LFG_BOOT_PROPOSAL_UPDATE and display them in the Dungeon
Finder vote-kick section. Strings are cleared when the vote ends.
Track each player's ready/not-ready response as MSG_RAID_READY_CHECK_CONFIRM
packets arrive. Display a color-coded table (green=Ready, red=Not Ready) in
the ready check popup so the raid leader can see who has responded in real
time. Results clear when a new check starts or finishes.
Show Psv/Def/Agg stance buttons (color-coded blue/green/red) above the
pet action bar. Active stance is highlighted; clicking sends CMSG_PET_ACTION
with the server-provided slot value for correct packet format, falling back
to the wire-protocol action ID if the slot is not in the action bar.
Also label stance slots 1/4/6 in the action bar as Psv/Def/Agg with
proper full-name tooltips.
SMSG_LOOT_ROLL_WON signals the roll contest is over for this slot;
clear pendingLootRollActive_ unconditionally so the popup does not
linger if a different group member wins while we have not yet voted.
Track each player's roll (need/greed/disenchant/pass + value) as
SMSG_LOOT_ROLL packets arrive while our roll window is open. Display
a color-coded table in the popup: green=need, blue=greed,
purple=disenchant, gray=pass. Roll value hidden for pass.
Player entities shown in the focus frame now display their canonical
WoW class color (from UNIT_FIELD_BYTES_0), consistent with how player
names are now colored in the target frame, party frame, raid frame,
and nameplates.
Replace the duplicated 10-case switch in renderPlayerFrame with a call
to the shared classColorVec4() helper, keeping the single source of truth
for Blizzard class colors.
Add classColorVec4(), classColorU32(), and entityClassId() to the
anonymous namespace so the canonical Blizzard class colors are defined
in exactly one place. Refactor the three existing class color blocks
(party frame, raid frame, nameplates) to use these helpers. Also apply
class colors to player names in the target frame.
Player nameplates previously used a flat cyan for all players. Now they
display the canonical Blizzard class color (Warrior=#C79C6E,
Paladin=#F58CBA, Hunter=#ABD473, etc.) read from UNIT_FIELD_BYTES_0.
This makes it easy to identify player classes at a glance in the world,
especially useful in PvP and group content. NPC nameplates keep the
existing red (hostile) / yellow (friendly) coloring.
Same class color logic as the party frame: read UNIT_FIELD_BYTES_0 byte 1
from the entity manager to determine each member's class, then draw their
name in the canonical Blizzard class color. Dead/offline members keep the
gray color since their status is more important than their class identity.
Uses UNIT_FIELD_BYTES_0 (byte 1) from the entity's update fields to
determine each party member's class when they are loaded in the world,
and applies canonical WoW class colors to their name in the 5-man
party frame. Falls back to gold (leader) or light gray (others) when
the entity is not currently loaded. All 10 classes (Warrior, Paladin,
Hunter, Rogue, Priest, Death Knight, Shaman, Mage, Warlock, Druid)
use the standard Blizzard-matching hex values.
Switch combat float text from ImGui::TextColored to draw list rendering
for drop shadows on all entries (readability over complex backgrounds).
Critical hit/heal events render at 1.35× normal font size for visual
impact, matching the WoW combat feedback convention.
Consistent with the player cast bar, show the spell icon (12×12 for
boss, 10×10 for party) to the left of each cast bar progress widget.
Falls back gracefully to the icon-less layout when no icon is found.
Shows the player's current zone name (from server zone ID via
ZoneManager) as a golden label at the top of the minimap circle.
Gracefully absent when zone ID is 0 (loading screens, undetected zones).
Display the casting spell's icon (20×20) to the left of the progress
bar using the existing getSpellIcon DBC lookup. Falls back gracefully
to the icon-less layout when no icon is available (e.g. before DBC
load or for unknown spells).
- Draw zone name text centered in each zone rect on the continent view;
only rendered when the rect is large enough to fit the label without
crowding (explored zones get gold text, unexplored get dim grey)
- Show WoW coordinates under the cursor when hovering the map image in
continent or zone view, bottom-right corner of the map panel
The persistent red-edge vignette (below 20% HP) now has an on/off
checkbox under Settings > Interface > Screen Effects, alongside the
existing Damage Flash toggle. The preference is persisted to settings.cfg.
Hovering the XP bar now shows a breakdown: current XP, XP remaining
to the next level, rested bonus amount in XP and as a percentage of
a full level, and whether the player is currently resting.
The camera bakes the Vulkan Y-flip into the projection matrix, so no
extra Y-inversion is needed when converting NDC to screen pixels. This
matches the convention used by the nameplate and minimap marker code.
The old formula double-flipped Y, causing chat bubbles to appear at
mirrored positions (e.g. below characters instead of above their heads).
Screen edges pulse red (at ~1.5 Hz) whenever the player is alive but
below 20% HP, with intensity scaling inversely with remaining health.
Complements the existing on-hit damage flash by providing continuous
danger awareness during sustained low-HP situations.
Live NPCs that match active tracked quest kill objectives are now shown
on the minimap as gold circles with an 'x' mark, making it easier to
spot remaining quest targets at a glance without needing to open the map.
Only shows targets for incomplete objectives in tracked quests.
Party members beyond 40 yards show a gray desaturated health bar
with 'OOR' text instead of HP values. Raid frame cells get a dark
overlay and gray health bar when a member is out of range. Range is
computed from the server-reported posX/posY in SMSG_PARTY_MEMBER_STATS
vs the local player entity position.
Read DispelType from Spell.dbc (new field in all expansion DBC layouts)
and use it to color debuff icon borders: magic=blue, curse=purple,
disease=brown, poison=green, other=red. Buffs remain green-bordered.
Adds getSpellDispelType() to GameHandler for lazy cache lookup.
When a chat message contains the player's character name, the message
is rendered with a golden highlight background and bright yellow text.
A whisper notification sound plays (at most once per new-message scan)
to alert the player. Outgoing whispers and system messages are
excluded from mention detection.
Pressing Tab while typing a slash command cycles through all matching
commands (e.g. /em<Tab> → /emote, /emote<Tab> → /emote again).
Unambiguous matches append a trailing space. Repeated Tab presses
cycle forward through all matches. History navigation (Up/Down)
resets the autocomplete session.
Extend chat renderTextWithLinks to handle |Hspell:, |Hquest:, and
|Hachievement: link types in addition to |Hitem:. Spell links show
a small icon and tooltip via renderSpellInfoTooltip; quest links
open the quest log on click; achievement links show a tooltip.
Also wire assetMgr into renderChatWindow for icon lookup.
The "You are dead." dialog now shows a "Release in M:SS" countdown
tracking time elapsed since death. The countdown runs from 6 minutes
(WoW's forced-release window) and disappears once it reaches zero.
Timer resets automatically when the player is no longer dead.
Adds UiSoundManager::playWhisperReceived() which uses the dedicated
Whisper_TellMale/Female.wav files (or falls back to iSelectTarget.wav
if absent). The sound is triggered once per new incoming CHAT_MSG_WHISPER
message by scanning new chat history entries in the raid warning overlay
update loop.
Both the player buff bar and target frame aura display now sort auras
so that shorter-duration (more urgent) buffs/debuffs appear first.
Permanent auras (no duration) sort to the end. In the target frame,
debuffs are sorted before buffs. In the player buff bar, the existing
buffs-first / debuffs-second pass ordering is preserved, with
ascending duration sort within each group.
Handles SMSG_SPELL_CHANCE_PROC_LOG (previously silently ignored) to
display gold "PROC!" floating text when the player triggers a spell
proc. Reads caster/target packed GUIDs and spell ID from the packet
header; skips variable-length effect payload.
Adds CombatTextEntry::PROC_TRIGGER type with gold color rendering,
visible alongside existing damage/heal/energize floating numbers.
Item slots on the action bar now display a dark grey tint when the
item is no longer in the player's backpack, bags, or equipment slots.
This mirrors WoW's visual feedback for consumed or missing items,
matching the priority chain: cooldown > GCD > out-of-range >
insufficient-power > item-missing.
Spell icons now render with a purple desaturated tint when the player
lacks enough mana/rage/energy/runic power to cast them. Power cost and
type are read from Spell.dbc via the spellbook's DBC cache. The spell
tooltip also shows "Not enough power" in purple when applicable.
Priority: cooldown > GCD > out-of-range > insufficient-power so states
don't conflict. Adds SpellbookScreen::getSpellPowerInfo() as a public
DBC accessor.
Ranged spell icons dim to a red tint when the current target is farther
than the spell's max range (read from SpellRange.dbc via spellbook data).
Melee/self spells (max range ≤ 5 yd or unknown) are excluded. The
spell tooltip also shows "Out of range" in red when applicable.
Adds SpellbookScreen::getSpellMaxRange() as a public accessor so
game_screen can query DBC range data without duplicating DBC loading.
- Zone entry toast: centered slide-down banner when entering a new
zone (tracks renderer's zone name, fires on change)
- Talent indicator: pulsing green '! N Talent Points Available' below
minimap alongside existing New Mail / BG queue indicators
- GameHandler tracks GCD in gcdTotal_/gcdStartedAt_ (time-based)
- SMSG_SPELL_COOLDOWN: spellId=0 entries (<=2s) are treated as GCD
- castSpell(): optimistically starts 1.5s GCD client-side on cast
- Action bar: non-cooldown slots show subtle dark sweep + dim tint
during the GCD window, matching WoW standard behavior
Appends a color-coded Unicode weather symbol to the zone name:
- Rain (type 1): blue ⛆ when intensity > 5%
- Snow (type 2): ice-blue ❄ when intensity > 5%
- Storm/Fog (type 3): gray ☁ when intensity > 5%
Symbol is hidden when weather is clear or absent.
- Player frame shows pulsing red [Combat] badge next to level when in combat
- Target frame shows pulsing [Attacking] badge when engaged with target
- Both pulse at 4Hz and include hover tooltips for clarity
Adds a WoW-style popup on right-click within the minimap circle with:
- Zoom In / Zoom Out controls
- Rotate with Camera toggle (with checkmark state)
- Square Shape toggle (with checkmark state)
- Show NPC Dots toggle (with checkmark state)
Extend renderItemTooltip(ItemQueryResponseData) to accept an optional
Inventory* parameter. When Shift is held and an equipped item in the
same slot exists, show: equipped item name, item level diff (▲/▼/=),
and stat diffs for Armor/Str/Agi/Sta/Int/Spi. Pass the player's
inventory from the vendor window hover handler to enable this.
Store active totem state (slot, spellId, duration, placedAt) from
SMSG_TOTEM_CREATED. Render 4 element slots (Earth/Fire/Water/Air) as
color-coded duration bars in the player frame for Shamans (class 7).
Shows countdown seconds, element letter when inactive, and tooltip
with spell name + remaining time on hover.
When a spell's cooldown expires, its action bar slot briefly animates
with a pulsing gold border (4 pulses over 1.5 seconds, fading out) to
draw attention that the ability is ready again. Uses per-slot state
tracking with static maps inside the render lambda.
Parse SMSG_DUEL_COUNTDOWN to get the countdown duration, track the
start time, and render a large centered countdown overlay. Numbers
display in pulsing gold; transitions to pulsing red 'Fight!' for the
last 0.5 seconds. Countdown clears on SMSG_DUEL_COMPLETE.
Show a color-coded reputation progress bar for the most recently gained
faction above the XP bar. The bar is auto-shown when any faction rep
changes (watchedFactionId_ tracks the last changed faction). Colors
follow WoW conventions: red=Hated/Hostile, orange=Unfriendly,
yellow=Neutral, green=Friendly, blue=Honored, purple=Revered,
gold=Exalted. Tooltip shows exact standing values on hover.
Show an orange cast bar in the pet frame when the pet is casting a
spell, matching the party frame cast bar pattern. Displays spell name
and time remaining; falls back to 'Casting...' when spell name is
unavailable from Spell.dbc.
Show a color-coded progress bar (green→yellow→pulsing red) in the loot
roll window indicating time remaining to make a roll decision. The
countdown duration is read from SMSG_LOOT_START_ROLL (or defaults to
60s for the SMSG_LOOT_ROLL path). Remaining seconds are displayed on
the bar itself.
When a quest is turned in (SMSG_QUESTGIVER_QUEST_COMPLETE), a gold-bordered
toast slides in from the right showing "Quest Complete" header with the quest
title, consistent with the rep change and achievement toast systems.
Online guild members now show their class name in the standard WoW
class color in the guild roster table, matching the familiar in-game
appearance. Offline members retain the dimmed gray style.
Completed kill/item objectives now display in green instead of gray,
giving an immediate visual cue about which objectives are done vs.
still in progress on the on-screen quest tracker.
Player name in the unit frame now shows in the official WoW class
color (warrior=tan, paladin=pink, hunter=green, rogue=yellow,
priest=white, DK=red, shaman=blue, mage=cyan, warlock=purple,
druid=orange) matching the familiar in-game appearance.
Buff/debuff countdown timers now change color as expiry approaches:
white (>30s) → orange (<30s) → pulsing red (<10s). This gives players
a clear visual cue to reapply important buffs before they fall off.
Dead party members now show a gray "Dead" progress bar instead of
"0/2000" health values, and offline members show a dimmed "Offline"
bar. The power bar is suppressed for both states to reduce clutter.
Always-visible player coordinates (X, Y in canonical WoW space) rendered
as warm-yellow text on a semi-transparent pill just below the minimap
circle, eliminating the need to hover for position info.
- Mana bar pulses dim blue when below 20% (matches health bar low-hp pulse)
- Target, focus, and boss cast bars pulse orange when cast is > 80% complete,
signalling the closing interrupt window across all frame types
Consumable items (potions, food, etc.) on the action bar now show their
remaining stack count in the bottom-right corner of the icon. Shows red
when count is 1 (last one), white otherwise. Counts across all bag slots.
Player health bar now transitions green→orange→pulsing red as HP drops
(>50%=green, 20-50%=orange, <20%=pulsing red). Pet frame gets the same
3-tier color scheme. Target and party frames already had color coding.
Floating window right of the cast bar showing player's DPS and healing
per second, derived from combat text entries. Uses actual combat duration
as denominator for accurate readings at fight start. Toggle in Settings
> Network. Saves to settings.cfg.
2026-03-12 04:04:27 -07:00
367 changed files with 96740 additions and 26814 deletions
> **Legal Disclaimer**: This is an educational/research project. It does not include any Blizzard Entertainment assets, data files, or proprietary code. World of Warcraft and all related assets are the property of Blizzard Entertainment, Inc. This project is not affiliated with or endorsed by Blizzard Entertainment. Users are responsible for supplying their own legally obtained game data files and for ensuring compliance with all applicable laws in their jurisdiction.
## Status & Direction (2026-03-07)
## Status & Direction (2026-03-30)
- **Compatibility**: **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a** are all supported via expansion profiles and per-expansion packet parsers (`src/game/packet_parsers_classic.cpp`, `src/game/packet_parsers_tbc.cpp`). All three expansions are roughly on par — no single one is significantly more complete than the others.
- **Tested against**: AzerothCore, TrinityCore, Mangos, and Turtle WoW (1.17).
- **Compatibility**: **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a** are all supported via expansion profiles and per-expansion packet parsers. All three expansions are roughly on par.
- **Tested against**: AzerothCore/ChromieCraft, TrinityCore, Mangos, and Turtle WoW (1.17).
- **Warden**: Full module execution via Unicorn Engine CPU emulation. Decrypts (RC4→RSA→zlib), parses and relocates the PE module, executes via x86 emulation with Windows API interception. Module cache at `~/.local/share/wowee/warden_cache/`.
- **CI**: GitHub Actions builds for Linux (x86-64, ARM64), Windows (MSYS2), and macOS (ARM64). Security scans via CodeQL, Semgrep, and sanitizers.
- **CI**: GitHub Actions builds for Linux (x86-64, ARM64), Windows (MSYS2 x86-64 + ARM64), and macOS (ARM64). Security scans via CodeQL, Semgrep, and sanitizers.
- **Container builds**: Multi-platform Docker build system for Linux, macOS (arm64/x86_64 via osxcross), and Windows (LLVM-MinGW) cross-compilation.
- **Map Exploration** -- Subzone-level fog-of-war reveal, world map with continent/zone views, quest POI markers, taxi node markers, party member dots
- **NPC Voices** -- Race/gender-specific NPC greeting, farewell, vendor, pissed, aggro, and flee sounds for all playable races including Blood Elf and Draenei
- **Warden** -- Warden anti-cheat module execution via Unicorn Engine x86 emulation (cross-platform, no Wine)
- **UI** -- Loading screens with progress bar, settings window with graphics quality presets (LOW/MEDIUM/HIGH/ULTRA), shadow distance slider, minimap with zoom/rotation/square mode, top-right minimap mute speaker, separate bag windows with compact-empty mode (aggregate view)
Windows applications link against `vulkan-1.dll` (the Khronos Vulkan loader). Since the LLVM-MinGW toolchain doesn't ship a Vulkan import library, the build script generates one:
1. Parses `vulkan_core.h` for `VKAPI_CALL vk*` function names
2. Creates a `.def` file mapping symbols to `vulkan-1.dll`
3. Uses `dlltool` to produce `libvulkan-1.a` (PE import library)
This allows the linker to resolve Vulkan symbols at build time, while deferring actual loading to the runtime DLL.
The first build takes longer because Docker builds the toolchain image (installing compilers, vcpkg packages, etc.). Subsequent builds reuse the cached image and only run the compilation step.
To force a full image rebuild:
```bash
./container/run-linux.sh --rebuild-image
```
## Output Locations
| Target | Binary | Size |
|--------|--------|------|
| Linux | `build/linux/bin/wowee` | ~135 MB |
| macOS | `build/macos/bin/wowee` | ~40 MB |
| Windows | `build/windows/bin/wowee.exe` | ~135 MB |
- Warden anti-cheat: full module execution via Unicorn Engine x86 emulation; module caching
- Audio: ambient, movement, combat, spell, and UI sound systems
- Bag UI: separate bag windows, open-bag indicator on bag bar, optional collapse-empty mode in aggregate bag view
- Audio: ambient, movement, combat, spell, and UI sound systems; NPC voice lines for all playable races (greeting/farewell/vendor/pissed/aggro/flee)
- Bag UI: independent bag windows (any bag closable independently), open-bag indicator on bag bar, server-synced bag sort, off-screen position reset, optional collapse-empty mode in aggregate view
- DBC auto-detection: CharSections.dbc field layout auto-detected at runtime (handles stock WotLK vs HD-textured clients)
- Multi-expansion: Classic/Vanilla, TBC, WotLK, and Turtle WoW (1.17) protocol and asset variants
- CI: GitHub Actions for Linux (x86-64, ARM64), Windows (MSYS2), macOS (ARM64); container builds via Podman
- CI: GitHub Actions for Linux (x86-64, ARM64), Windows (MSYS2 x86-64 + ARM64), macOS (ARM64); container builds via Podman
In progress / known gaps:
- Transports: M2 transports (trams) working with position-delta riding; WMO transports (ships, zeppelins) working with path following; some edge cases remain
- Visual edge cases: some M2/WMO rendering gaps (character shin mesh, some particle effects)
- Lava steam particles: sparse in some areas (tuning opportunity)
- Water refraction: implemented but disabled by default (can cause VK_ERROR_DEVICE_LOST on some GPUs); currently requires FSR to be active
- Quest GO interaction: CMSG_GAMEOBJ_USE + CMSG_LOOT sent correctly, but some AzerothCore/ChromieCraft servers don't grant quest credit for chest-type GOs (server-side limitation)