Both passes were rendering the entire loaded scene (17×17 tile radius)
into a shadow map that only covers 360×360 world units — submitting
10-50× more geometry than the shadow frustum can actually use.
- TerrainRenderer::renderShadow: skip chunks whose bounding sphere
doesn't overlap the shadow frustum AABB in XY. Reduces terrain draw
calls from O(all loaded chunks) to O(chunks within ~180 units).
- M2Renderer::renderShadow: skip instances whose world AABB doesn't
overlap the shadow frustum in XY. Reduces M2 draw calls similarly.
- Both functions now take shadowCenter + halfExtent parameters.
- SHADOW_MAP_SIZE 2048→1024: 4x fewer pixels rasterized in depth pass
- Replace 9-tap manual PCF loop with single hardware PCF tap in all 4 receiver
shaders (terrain.frag, wmo_renderer, m2_renderer, character_renderer).
GL_LINEAR + GL_COMPARE_REF_TO_TEXTURE already gives 2×2 bilinear PCF per
tap for free, so quality is maintained while doing 9x fewer texture fetches.
- Throttle shadow depth pass to every 2 frames; OpenGL depth texture persists
between frames so receivers always have a valid shadow map. 1-frame lag at
60 fps is invisible.
- game_handler.cpp: use-after-move on node.id after std::move(node)
(save nodeId before the move)
- tcp_socket.cpp, world_socket.cpp: virtual call in destructor bypasses
dispatch; use qualified TCPSocket::disconnect() / WorldSocket::disconnect()
to make intent explicit
- wmo_renderer.cpp: float loop counters risk precision drift; replace with
integer step counts and reconstruct float from index
- game_screen.cpp: (float + 0.5) cast to int is incorrect rounding;
use std::lround instead
CMake processes escape sequences twice: once reading CMakeLists.txt and
once loading the generated CPackConfig.cmake. Backslashes need four
levels of escaping (\\) to survive both passes and land as a single
backslash in the injected NSIS script.
Copy Wowee.ico into the build tree at configure time so llvm-rc can
resolve the relative assets\\wowee.ico path in wowee.rc. Also remove a
redundant #include <sys/mman.h> that was incorrectly placed inside a
function body.
warden_emulator.cpp: guard unicorn include + entire implementation with
HAVE_UNICORN; provide stub implementations for platforms without Unicorn
(Windows ARM64 which has no unicorn MSYS2 package)
warden_module.cpp: include <windows.h> for VirtualAlloc/HMODULE/etc on
Windows; always include warden_emulator.hpp so unique_ptr destructor compiles
regardless of HAVE_UNICORN
world_packets.hpp + game_handler.cpp: rename CharCreateResult::ERROR to
CharCreateResult::CHAR_ERROR to avoid wingdi.h #define ERROR 0 collision
logger.hpp: extend ERROR macro push/pop region to cover entire header so
LogLevel::ERROR inside template bodies compiles correctly on Windows
CMakeLists.txt: move tool install() rules next to each target definition
so they run after the targets exist (fixes macOS 'target does not exist')
Linux: DEB with /opt/wowee prefix and /usr/local/bin/wowee wrapper script
Windows x86-64: NSIS installer with DLL bundling and Start Menu shortcut
Windows ARM64: ZIP archive with bundled DLLs
macOS ARM64: .app bundle via dylibbundler + DMG via hdiutil
Assets are bundled into all packages (Original Music excluded). StormLib
is installed where available so asset_extract is included in packages.
- net_platform.hpp: guard ssize_t typedef with !__MINGW32__ since MinGW-w64
already defines ssize_t as __int64 in corecrt.h
- logger.hpp: push/pop ERROR macro around LogLevel enum (same wingdi.h clash
as world_packets.hpp)
- logger.cpp: use localtime_s on Windows (reversed arg order vs localtime_r)
- process.hpp: drop constexpr on INVALID_PROCESS (INVALID_HANDLE_VALUE is a
reinterpret_cast, not valid in constexpr context)
- world_packets.hpp: push/pop ERROR macro around CharCreateResult enum to avoid
clash with wingdi.h #define ERROR 0
Add macOS matrix job (arm64/macos-15, x86-64/macos-13) using Homebrew.
Add Windows ARM64 job using windows-11-arm runner with MSYS2 CLANGARM64.
Fix memory_monitor.cpp: add __APPLE__ branch using sysctl hw.memsize/hw.usermem
instead of Linux-only sysinfo/proc.
Guard X11 display crash handler with __linux__, add Windows GlobalMemoryStatusEx
path in memory_monitor, guard warden cache paths with APPDATA on Windows, and
make pkg-config optional in CMakeLists with a find_library fallback. Add Windows
x86-64 CI job using MSYS2 MINGW64 to the build workflow.
SMSG_QUEST_QUERY_RESPONSE skipped only 18 uint32s before reading the
title string, but Classic layout has 40 fields before the title
(16 header + 8 reward items + 12 choice items + 4 POI fields).
Reading from the wrong position landed inside reward item data where
empty slots contain 0-bytes, so readString() returned "", overwriting
the "Quest #N" placeholder with an empty title — making quests
invisible in the UI even though they were in the quest log.
Fixes:
- Expansion-aware skip count: 40 for Classic/Turtle, 55 for WotLK
- Guard: only update title if parsed string is non-empty and printable
- Also scan quest log fields in VALUES update path (not just CREATE_OBJECT2),
so quests are detected even when the server sends partial updates
- Add Entity::setOrientation() to update facing without cancelling movement
- Force attacker and victim to face each other on SMSG_ATTACKSTART
- Fix orientation sign error in MonsterMove: use atan2(-dy, dx) throughout so
NPCs don't glide backward; clamp FacingAngle moves that are >90° off travel vector
- Tab-target: skip dead units and non-hostiles at both build and advance time;
stale entries (killed between presses) are skipped inline rather than cycling to them
- Spirit healer resurrection: detect same-map SMSG_NEW_WORLD with resurrectPending_
and skip the full world reload/entity clear, preventing the fall-forever bug
- add per-frame nearby creature render sync from entity positions/orientation to prevent model-vs-target-circle drift
- treat lootable dynflag as dead state hint for unit spawn/deferred-display paths
- fire NPC death callback when a late display spawn is already dead/lootable
- remove loot-response money fallback announce/SFX to stop duplicate copper messages on re-opened corpses
- use interpolated target position (getX/Y/Z) for selection circle placement
- avoids drawing circle at movement destination before creature arrives
- keeps previous target interpolation update guarantees
- choose run only when movement speed is above threshold
- prefer walk for duration-less synthesized movement deltas
- keep animation fallback behavior when specific sequence is unavailable
- use movement animation (prefer run, fallback walk) for server-driven creature moves
- synthesize short movement duration for duration-less movement deltas to avoid glide/attack-pose sliding
- return to idle after both walk/run movement states
- drive target circle from entity latest position and always interpolate selected/engaged targets
- propagate item damage range and delay into ItemDef during inventory rebuild
- show weapon damage, speed, and DPS in inventory/character slot tooltips
- fix online spawn camera pitch sign so third-person camera starts above ground
- parse and cache item class/subclass, damage range, and attack delay from item query responses
- render weapon damage, speed, and DPS in the shared item-link tooltip
- render weapon damage, speed, and DPS in vendor hover tooltips
- keep armor and primary stat lines intact
- send CMSG_BUY_ITEM as vendorGuid + itemId + count (drop extra slot/bag fields)
- reset vendor list state before parsing SMSG_LIST_INVENTORY to prevent stale items carrying over
- add packet length guards for list-inventory header and per-item rows
- keep optional extended-cost parsing for cross-core compatibility
Batches whose named texture fails to load now render invisible instead of
white (the swampreeds01a.blp case causing a white shell around aquatic plants).
Also implements proper M2 opacity plumbing:
- Parse texture weight tracks (M2Track<fixed16>) and color animation alpha
tracks (M2Color.alpha) to resolve per-batch opacity at load time
- Skip batches with batchOpacity < 0.01 in the render loop
- Apply M2Texture.flags (bit0=WrapS, bit1=WrapT) to GL sampler wrap mode
- Upload both UV sets (texCoords[0] and texCoords[1]) and select via
textureUnit uniform, so batches referencing UV set 1 render correctly
PushID(item.slot) causes conflicting IDs when the server sends items with
duplicate or zero slot values. Use the loop index instead, which is always
unique regardless of what the server puts in the slot field.
Add AssetManager hookup to AudioEngine so the path-based playSound2D/3D
overloads can load files on demand rather than requiring preloading.
- Add setAssetManager() to AudioEngine (called during world load alongside
other audio manager initializations)
- playSound2D(mpqPath) now calls assetManager->readFile() then delegates
to the vector<uint8_t> overload (removes the "not yet implemented" warning)
- playSound3D(mpqPath, position) same — delegates to the fully spatialized
vector overload (was previously silently falling back to 2D)
TrinityCore's HandleBuyItemOpcode reads vendorGuid → item → slot → count → bagIndex.
The previous fix had accidentally reversed item and slot, so the server received
the vendor slot number as the item ID (a small number like 1-5) and the actual
item ID as the slot, causing every purchase to be silently rejected.