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.