Commit Graph

1492 Commits

Author SHA1 Message Date
Kelsi
418e330213 fix taxi early landing before destination 2026-07-13 12:53:27 -07:00
Josh Anderson
ae62001736 fix(taxi): time out a taxi activation reply that never arrives
Live-found while validating the activation-race fix against the TBC
server: a CMSG_ACTIVATETAXI the server can't make sense of (e.g. a
stale start node vs. the player's actual position - reproduced by
requesting from a stale cached nearestNode after moving) can go
completely unanswered, no SMSG_ACTIVATETAXIREPLY at all in either
direction. Before activation was deferred to the reply,
taxiActivatePending_ being permanently stuck true didn't matter -
the speculative flight had already started regardless. Now that
activation correctly waits for the reply, a dropped one left
taxiActivatePending_ true forever, silently no-op'ing every future
activateTaxi() call (the "already pending" guard) - a permanent
soft-lock of the character's taxi system until relog.

Live-confirmed the fix: forced the drop scenario, the warning fired
at exactly 8.011s (kTaxiActivateReplyTimeoutSeconds=8.0f), pending
state cleared, and a subsequent request flew normally.
2026-07-13 06:09:14 -05:00
Josh Anderson
a7e3e2e992 fix(taxi): select landing-clamp floor by proximity, not by source
The third merge-blocking issue Kelsidavis flagged in review of PR #96:
unconditionally preferring any WMO/M2 floor over terrain fixed
underground landings (terrain has no notion of being underground - a
WMO tunnel beneath a mountain, like Ironforge's flight point, reports
the outdoor mountain surface far above the correct tunnel floor), but
could just as easily snap an outdoor landing down onto an unrelated
structure sitting underneath it.

Now captures the character's render-position Z the moment the landing
clamp arms (WorldEntryCallbackHandler::taxiLandingReferenceZ_) -
wherever the taxi flight simulation itself left the player, before
terrain/WMO streaming has a chance to move things around - and picks
whichever candidate (terrain/WMO/M2) is numerically closest to that
reference, rather than preferring non-terrain sources outright. Still
correctly picks the WMO tunnel floor for Ironforge (the flight lands
right at it, ~0 distance, vs. ~267 units to the terrain surface above)
while no longer able to prefer a distant WMO/M2 floor under an outdoor
landing over the much-closer terrain surface.
2026-07-13 05:27:59 -05:00
Josh Anderson
6ccc2656a5 fix(taxi): defer flight activation until server confirms, disambiguate dismount authority
Two of the three merge-blocking issues Kelsidavis flagged in review of
PR #96 (taxi flight-path/landing/dismount fixes):

1. Activation race (the actual merge blocker). activateTaxi() set
   onTaxiFlight_, mounted, and started the client spline immediately
   after sending CMSG_ACTIVATETAXI, before SMSG_ACTIVATETAXIREPLY
   confirmed the server accepted it. Making updateClientTaxi() run
   unconditionally (this session's earlier fix) meant a rejection
   reply now arrived while onTaxiFlight_/taxiClientActive_ were
   already true, so handleActivateTaxiReply() treated it as a stale
   duplicate and ignored it - the client kept flying an unauthorized
   route. startClientTaxiPath() now only builds taxiClientPath_ as
   pending data; the actual activation (mount, onTaxiFlight_,
   taxiClientActive_) moved into a new beginTaxiFlightMotion(), called
   only from handleActivateTaxiReply()'s success branch. The failure
   branch explicitly clears the pending path data.

2. SMSG_DISMOUNT / UNIT_FIELD_MOUNTDISPLAYID=0 guards were too broad.
   Both unconditionally ignored the signal whenever the client's own
   taxi simulation was active, which fixed CMaNGOS's early-completion-
   estimate quirk but could make the client fly past a genuine
   server-side landing on cores (e.g. AzerothCore) that finalize taxi
   flights authoritatively. Both call sites now check the server's own
   UNIT_FLAG_TAXI_FLIGHT: still set means premature (ignore, let the
   client spline finish naturally); already cleared means the server
   considers the flight over. Extracted the finishTaxiFlight lambda
   into MovementHandler::finishClientTaxiFlight(snapToFinalWaypoint) so
   both the natural-completion path and these authoritative-signal
   handlers share one landing/cleanup implementation - the authoritative
   case passes false, since the server's actual stop point may not match
   our path's precomputed final waypoint.
2026-07-13 05:27:59 -05:00
Kelsi
4d9a36dae5 Fix quest marker types on maps 2026-07-12 18:56:34 -07:00
Kelsi
df0efc3063 fix weapon sheathing placement and animation 2026-07-12 17:40:41 -07:00
Kelsi
1edafd6f57 fix fishing bite and bobber interaction 2026-07-12 16:36:28 -07:00
Kelsi
2858c68e89 fix(character): render ghost opacity through blended pipeline
Route faded character instances through a dedicated translucent character
pipeline so dead and ghost characters fade as a whole model instead of only
showing translucent hair.

Opaque body batches previously wrote the shader alpha from instance opacity,
but the opaque pipeline had blending disabled, so the body stayed fully
solid. Hair went through the alpha-test path, where reduced alpha affected
coverage, making only hair appear faded.

Add a translucent pipeline with alpha blending and depth writes enabled. This
keeps normal self-occlusion for faded characters while allowing the whole
model to respect instance opacity. Reuse the existing per-batch alpha-test
flag so cutout materials such as hair keep crisp edges while fading with the
rest of the body.

Also make the whole-model fallback path bind an explicit opaque or
translucent pipeline instead of inheriting the previously bound pipeline.

Create, recreate, validate, and destroy the new pipeline alongside the other
character pipelines.

Build passes.
2026-07-12 16:12:31 -07:00
Kelsi
4ba6b51b2e feat(camera): ray-march the terrain heightfield so hills can't clip through the orbit camera
Terrain was only a floor clamp at the camera's final XY; a hill between the
pivot and the camera sliced straight through the view. March the heightfield
along the pivot ray (coarse steps + bisection) and feed the limit through the
same asymmetric pull-in/recover smoothing as the WMO raycast, snapping 1:1
while actively rotating.
2026-07-12 16:02:47 -07:00
Kelsi
792b359bcb fix(game): make /quit actually quit, and /logout exist at all
Three things were wrong with logging out.

SMSG_LOGOUT_COMPLETE only printed a chat line. The server confirms the character
is out of the world there, and the client is supposed to leave on that signal — so
the countdown ran out and nothing happened. It now fires a callback that Application
acts on.

/quit and /exit were aliases of /logout, so even once the above works they would
only ever drop to character select. They now carry an exit-after-logout flag through
the same handshake and close the game when the server confirms.

/logout was not a command at all: aliases() is the complete name list and it only
listed camp, quit and exit — /help has been advertising a command that silently did
nothing.

Also log the stand state from SMSG_STANDSTATE_UPDATE: the logout pose looks wrong
(slumped rather than seated), and a wrong state from the server is indistinguishable
in-game from a wrong animation.
2026-07-12 14:53:28 -07:00
Kelsi
16e74ef808 fix(ui): count the breath bar down instead of freezing it
SMSG_START_MIRROR_TIMER hands the client a remaining time and a scale, then the
server only speaks again when something changes. Nothing ticked the value, so the
breath, fatigue and feign bars sat at whatever the server last sent and never
moved. Count them down locally at the scale the server gave (-1 while drowning,
faster while the bar refills at the surface), carrying the sub-millisecond
remainder so the timer doesn't run slow at high frame rates.

Also fix the tail of the packet: it is paused(1) + spellId(4), not the other way
round. Harmless while the spell id is zero, but a non-zero one would land its high
byte in paused and freeze the bar outright.
2026-07-12 14:46:08 -07:00
Kelsi Rae Davis
33a3bf2833 Merge pull request #95 from paleophyte/feat/faction-hostility-map-shared
refactor(faction): extract buildFactionHostilityMap into a shared free function
2026-07-12 14:37:40 -07:00
Kelsi
10ebc18471 fix(render): stop the character alpha heuristics erasing the backdrop's buildings
Stormwind's walls, trim and roofs are DXT5 with an unused alpha channel — mean
alpha 17/255, every texel below the cutoff. The renderer infers an alpha cutout
for an opaque batch whenever its texture "has alpha", a rescue for character
textures, so all of Stormwind was being discarded and the sky showed through the
buildings with speckle where a stray texel cleared the threshold.

Mark backdrops as scene models and take them at their word: only an alpha-key
material (blendMode 1) cuts out. The hair-geoset and skin-composite guesses are
skipped for them too — a scene's submesh ids are all 0, which the hair guess reads
as a hair geoset for every batch.
2026-07-12 13:55:32 -07:00
Kelsi
36d2705c2f fix(ui): stop preview weapons and enchants leaking between characters
Two bugs, one symptom each.

attachWeapons() ran at the end of applyEquipment(), past an early return taken by
any character whose body skin could not be composited. Those characters showed no
weapon at all, and kept the previously selected character's weapon and enchant,
because detaching happens in attachWeapons() too. Weapon attachment depends on
nothing in the geoset/skin work, so do it first and unconditionally.

Weapon and effect models were also loaded under fixed ids. CharacterRenderer's
model cache is keyed by id and skips loading when the id is present, so every
character after the first was handed the first one's weapon model. Key the id by
the asset path instead.

Also drop the renderUnlit plumbing added for the backdrop: preview instances take
the simple-texture shader path, which never reaches the lighting branch it
changed, so it was a no-op.
2026-07-12 13:47:18 -07:00
Kelsi
b17db99790 fix(render): draw the character-select backdrop with its authored colours
The glue scenes carry their lighting baked into their textures, but 21 of the 29
materials in the human scene are flagged lit, so all of Stormwind was being shaded
by the portrait light rig that exists to light a character.

Render backdrops unlit. The unlit path multiplies the texture by (1 + emissiveBoost),
which is what an enchant glow wants but would wash a backdrop out, so backdrops
cancel the boost and show the texture exactly as painted.
2026-07-12 13:37:22 -07:00
Kelsi
ee297a177a fix(ui): place the racial backdrop using the scene's own camera
The glue scenes are authored in their own space — the human one's geometry sits
around x=-230, hundreds of units from its origin — so an instance at the origin
put the whole scene off-camera, and the 130-unit cull radius would have dropped it
anyway. Nothing was visible.

Parse the camera M2s already carry (M2Loader ignored them) and use it: the scene
stays in its authored coordinates, the character stands on its attachment 0 mark
facing the camera, and the preview camera keeps its framing distance but swings
round to the artist's viewing angle. Backdrops are exempt from culling, since
their origin says nothing about where their geometry is.

Also show the weapon's enchant glint in the preview: SMSG_CHAR_ENUM reports an
equipped item's enchant as its ItemVisual id, so it needs no SpellItemEnchantment
lookup — split resolveItemVisualModels() out to enter the chain at that point.
2026-07-12 13:31:12 -07:00
Kelsi Rae Davis
42a5405587 Merge pull request #94 from paleophyte/fix/game-handler-known-taxi-node
fix(taxi): GameHandler::isKnownTaxiNode() always returned false
2026-07-12 11:50:50 -07:00
Kelsi Rae Davis
ef661f69dd Merge pull request #93 from paleophyte/fix/movement-real-follow
feat(movement): make /follow actually walk toward the target
2026-07-12 11:49:51 -07:00
Kelsi
7d36826214 feat(ui): bigger character-select preview with weapons and racial backdrop
The preview was a 400x500 portrait clamped to 320px in a 360px panel, showing an
unarmed character against nothing. Widen the panel, let the image take the height
the details can spare, and raise the render target to 640x800 so the larger
display is not upscaled mush.

Attach the character's weapons to the preview's hand attachment points — the
equipment was already parsed from SMSG_CHAR_ENUM, but only ever applied as
geosets and textures.

Load the race's glue scene (Interface\Glues\Models\UI_<Race>) behind the
character, so humans stand in Stormwind and orcs in Durotar as they do in the
original client.
2026-07-12 09:46:14 -07:00
Kelsi
10148db807 fix(ui): show temporary weapon enchants as timed icons in the right slot
SMSG_ITEM_ENCHANT_TIME_UPDATE carries the item's *enchantment* slot
(TEMP_ENCHANTMENT_SLOT = 1), not the equipment slot. Reading it as one labelled
every temporary enchant "Off Hand", even on a two-hander. Resolve the weapon slot
from the item GUID the packet already carries instead.

Render each enchant as the weapon's item icon with its remaining time across the
bottom, like the retail client, rather than a full-width labelled bar.
2026-07-12 09:41:09 -07:00
Kelsi
7e77d90f91 feat(ui): show build version and date on the login screen and in settings
Generate core/version.hpp from `git describe --tags --abbrev=0` so the client
always reports the last tagged release, refreshed on every build rather than only
when cmake happens to reconfigure. The date carries no clock time, so the header
changes at most once a day instead of forcing a recompile every build.

Also un-ignore cmake/*.cmake: the repo's *.cmake rule is aimed at build output,
and would have silently dropped the new module from the tree.
2026-07-12 09:33:00 -07:00
Kelsi
63f0866ad1 feat(render): show enchant visuals on enchanted weapons
A sharpened blade should glint. Resolve the enchant's visual through
SpellItemEnchantment -> ItemVisuals -> ItemVisualEffects and attach the effect M2
to the weapon model's item-visual attachment points, riding the weapon transform.

Such a model is nothing but the additive FX batches that attached weapons
otherwise drop, so effect instances are exempted from that cull, keep their
animation advanced, and are forced additive and unlit — their materials declare
Mod/alpha blending, which would otherwise composite the glow card's black
background as an opaque quad.

Applying an enchant leaves the displayInfoId untouched, so the equipment-dirty
check now also tracks per-slot enchant ids; the visual appears without re-equipping.
2026-07-12 09:24:15 -07:00
Kelsi
bcd032fde7 fix(dbc): read enchant names from the right SpellItemEnchantment column
The name column moved across expansions (Vanilla 10, TBC 13, WotLK 14), but the
layouts and every caller used field 8. That column holds an integer, which
getString() then treats as a string-block offset, so enchant names came back
garbled mid-string ("Sharpened (+2 Damage)" surfaced as "ockbiter 3"). Resolve
the column from the record width via detectEnchantmentNameField() and use it at
all four call sites.
2026-07-12 09:10:02 -07:00
Kelsi
e16b165512 fix(items): send TARGET_FLAG_ITEM so sharpening stones and oils apply
Item-enhancement consumables cast a spell onto another item, but CMSG_USE_ITEM
only ever wrote a unit or self target, so the server dropped the cast. Using one
now reads the on-use spell's Spell.dbc Targets mask and, when TARGET_FLAG_ITEM is
set, arms a targeting cursor whose next item click sends the use against that
item's GUID.
2026-07-12 09:09:49 -07:00
Kelsi
0db44eeb57 fix(vendor): keep buyback list across vendor window sessions
Buyback slots live on the player server-side (wire slots 74-85) and
persist across vendor interactions, but the client cleared its local
buyback mirror in both openVendor() and closeVendor() — so closing a
vendor and reopening it made the buyback section vanish even though
the server still held the items. Keep the mirror alive for the whole
world session and clear it on initial world entry instead, so it
cannot leak into another character's session.

Also handle successful buybacks in SMSG_BUY_ITEM: the entry is now
removed from the local list (with a chat confirmation), and the
pending buyback slot is reset — previously it lingered, and a later
unrelated SMSG_BUY_FAILED could misread it as a buyback retry and
erase the wrong list entry.
2026-07-12 08:38:51 -07:00
Kelsi
ac02a87ad4 perf(render): skip redundant mega-bone uploads and cache selection circle floor
prepareRender() re-uploaded every animated M2 instance's bone matrices
into the mega bone SSBO every frame — including the majority whose
bones were never recomputed because they are distance, frustum, or
frame-skip culled and keep their previous matrices. The bonesDirty
flag computeBoneMatrices() sets was never consumed. Track the slot
each instance last uploaded to per frame index and memcpy only when
the bones changed or the slot moved, cutting megabytes of redundant
host-visible writes per frame in doodad-dense scenes.

The selection circle also re-ran terrain/WMO/M2 floor raycasts every
frame; reuse the result while the target's position is unchanged,
refreshing every 30 frames so streamed-in geometry still snaps.
2026-07-12 08:30:15 -07:00
Kelsi
9f1a9185dd perf(ui): cache formatted chat lines instead of re-parsing every frame
The chat panel rebuilt every visible line each frame: gender placeholder
replacement, timestamp formatting, sender/type prefix assembly, an
O(len*name) mention scan, and markup parsing into freshly allocated
segment vectors — up to 100 messages per frame, thousands of small
string allocations per second.

Give each history entry a monotonic uid (assigned by ChatHandler at the
two append sites) and cache the formatted text, parsed segments, and
mention flag in ChatPanel keyed by that uid. Entries rebuild when the
sender name resolves from a late name query or the timestamp toggle
flips, the whole cache clears on self-name change, and stale uids are
pruned once the cache outgrows the history window.

Also reuse the minimap's three per-frame entity partition vectors
across frames instead of reallocating them.
2026-07-12 08:19:29 -07:00
Kelsi
45f7baac5c perf: replace per-frame std::async with a persistent thread pool
Every frame the render loop spawned and destroyed OS threads via
std::async: the M2 doodad animation update, three secondary command
buffer recording tasks, up to N bone-matrix chunks in the M2 and
character renderers, and two collision floor queries while moving —
hundreds of thread create/destroy cycles per second. Route all of them
through a shared persistent core::ThreadPool sized to the machine.

Bone-matrix chunk dispatch now runs its last chunk on the calling
thread so the nested case (M2 update running on a pool worker while
submitting sub-work) always makes progress and cannot deadlock.
2026-07-12 08:06:27 -07:00
Kelsi
46a7e97f95 feat(ui): add combined bags toggle 2026-07-12 07:52:45 -07:00
Kelsi
03b2f6aa66 fix(ui): restore item readiness and full quest tracking 2026-07-12 07:44:58 -07:00
Kelsi
7bedd04fbc perf: reuse nearby entity queries without breaking previews 2026-07-12 07:38:35 -07:00
Kelsi
da39be57d6 perf: cache action item and quest marker lookups 2026-07-12 07:33:16 -07:00
Kelsi
1eb25b8da4 perf: reduce per-frame entity and render overhead 2026-07-12 07:26:56 -07:00
Josh Anderson
624287983d refactor(faction): extract buildFactionHostilityMap into a shared free function
Application::buildFactionHostilityMap() (called from WorldLoader as
part of world-entry setup) had its entire DBC-driven implementation
inline, tightly coupled to Application's own assetManager/gameHandler
members. Extracted the logic (unchanged) into
game::buildFactionHostilityMap(), taking AssetManager&/GameHandler&
directly instead. Application::buildFactionHostilityMap() is now a
thin wrapper.

No behavior change for the existing call site - this is pure
de-duplication/reusability, making the DBC-driven hostility
computation (FactionTemplate.dbc/Faction.dbc reaction-group bitmasks,
matching what CMaNGOS computes server-side) callable from contexts
that don't have an Application instance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 08:42:24 -05:00
Josh Anderson
ee93d9fbb8 fix(taxi): GameHandler::isKnownTaxiNode() always returned false
It read a GameHandler-local knownTaxiMask_ that nothing ever wrote to
- handleShowTaxiNodes() (SMSG_SHOWTAXINODES) only updates
MovementHandler's own separate copy of the same name. This silently
broke the world map's discovered-taxi-node display and the Lua
IsTaxiNodeKnown-equivalent addon API for as long as they've existed:
every flight point always showed as "not discovered" regardless of
what the player had actually visited.

Fixed by delegating to movementHandler_->isKnownTaxiNode() (the real,
correctly-maintained mask), matching how the other taxi accessors on
GameHandler already delegate (getTaxiCurrentNode, getTaxiNodes,
isTaxiWindowOpen), and removed the dead duplicate members.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 08:34:00 -05:00
Josh Anderson
22df85a0fd feat(movement): make /follow actually walk toward the target
/follow already existed but was camera-only: it set followTargetGuid_/
followRenderPos_ and told the camera controller to track the target's
render position each frame, but the character itself never moved.

Adds MovementHandler::updateFollowMovement(), called from the same
per-frame block that already refreshes followRenderPos_ in
GameHandler::update(), so no new per-frame wiring is needed. It's the
same straight-line step the headless client's single-shot
/movement/goto uses (updateMovementTask() in
tools/headless_client/main.cpp), but the target position is read
fresh from the live entity every call instead of a stored waypoint (so
it re-chases a moving target, and never needs that command's
long-distance Z-interpolation guard), and it stops at a fixed distance
(1 yard, matching retail's tight follow) instead of snapping onto the
target.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 08:18:49 -05:00
Kelsi
e9b6973468 fix(gameplay): correct ability state, item casts, and quest tracking
Fix several gameplay paths where client-side state diverged from server
semantics.

Ability availability now checks the resource pool declared by Spell.dbc
instead of comparing only against the player's currently displayed power
type. This makes action-bar greying work for mana, rage, focus, energy,
runes, and runic power without class-name assumptions, and supports reactive
aura-state abilities such as Overpower.

Improve bandage handling:
  - translate the hidden Recently Bandaged aura failure into a clear message
  - report bandage interruptions with actionable text
  - apply spell-aware failure text across all cast-failure packet paths
  - synchronize a stationary movement state before CMSG_USE_ITEM for bandages
    so stale server-side movement does not falsely interrupt the channel

Fix quest objective tracking by decoding quest-query objectives with the
correct expansion-specific packet layouts. Classic and Turtle, TBC, and
Wrath now use the proper objective offsets and structures, game-object IDs
are decoded correctly, implausible objective data is rejected, and stale
corrupted tracker entries are removed when valid quest data arrives.

Fix thrown-weapon use:
  - keep Throw out of the eight-yard melee range gate
  - route Throw through the ranged-weapon animation path
  - avoid duplicate melee/special attack animations
  - correct thrown and ranged-right inventory type constants used by
    action-bar range checks

Build passes. Packet, entity, animation capability, and combat FSM tests
pass.
2026-07-11 19:03:02 -07:00
Kelsi
d55b2809fe feat(ui): show reagents, item details, and correct icons in crafting panel
Read Reagent[0-7], ReagentCount[0-7], and EffectItemType[0-2] from
Spell.dbc during spell name cache loading. The crafting panel now shows
the produced item with its proper icon, quality color, and stats (armor,
damage, attributes, flavor text), plus a reagent list with icons and
counts. Trainer list icons use the crafted item icon instead of the
generic spell icon for profession recipes.
2026-07-11 17:05:00 -07:00
Kelsi
20ade2ae1d fix(network): restore WotLK Deeprun Tram, auction parsing, and quest reward alignment
Gate Deeprun Tram CMaNGOS workarounds (forced client animation, despawn
blocking, unconditional DESTROY_OBJECT preservation) behind isPreWotlk()
so AzerothCore's server-driven transport updates work correctly on WotLK.

Fix auction list response parser reading 3 enchant slots per entry when
WotLK/TBC send 6 (MAX_INSPECTED_ENCHANTMENT_SLOT), causing 36 bytes of
misalignment per entry that corrupted all subsequent item IDs and prices.

Fix quest offer-reward heuristic scorer not boosting prefix=4 (the stock
WotLK OFFER_REWARD format) and strengthen item plausibility checks to
break ties away from misaligned parses.
2026-07-11 16:16:45 -07:00
Kelsi
1bff4f7039 fix(trainer): correct weapon trainer button and add NPC gossip text
Three fixes for weapon trainers like Woo Ping:

1. Known weapon skills showed a "Create" button (calling castSpell on a
   passive proficiency — no effect). Now only shows "Create" for actual
   craft recipes where profDialog is non-zero; non-recipe known spells
   fall through to the disabled "Train" button.

2. Added NPC gossip body text display. The gossip window now queries
   CMSG_NPC_TEXT_QUERY and handles SMSG_NPC_TEXT_UPDATE to render the
   NPC's text body (e.g. "Other weapon masters can teach...") above
   the gossip options.

3. Fixed misleading TrainerSpell::state comment (was 0=unavailable,
   1=available; actual WotLK convention is the reverse).
2026-07-11 12:21:44 -07:00
Kelsi
a36cc3553c fix(rendering): hide sky birds until close enough for animation
Bird and bat doodads appeared frozen mid-flap in the sky because bone
computation stops beyond 150 units (LOD3 distance) but the model was
still rendered in its bind pose. Now classified as sky birds and their
render distance is capped to the bone-update range, fading in smoothly
as the player approaches.
2026-07-11 11:40:49 -07:00
Kelsi
70e46bfe1a fix(map): use Rotating-MinimapCorpseArrow.blp for corpse marker
Replace the procedural X with the actual WoW corpse icon texture loaded
from game data. Falls back to the X if the BLP fails to load.
2026-07-11 11:16:41 -07:00
Kelsi
4263412ff0 fix(lighting): apply saved brightness before opening settings
GameScreen loads settings before renderer services are injected. Shadow and brightness values were parsed into pending state but never applied during normal startup, leaving the renderer at shadows enabled and neutral brightness. Opening the settings window then applied the saved shadow toggle for the first time, causing caverns and dungeons to brighten while the menu was visible.

Add a dedicated one-time lighting initialization path that applies saved shadow enablement, shadow distance, and post-process brightness as soon as the renderer becomes available. This keeps scene lighting independent of settings-window visibility and restores the user's configured interior brightness from the first rendered gameplay frame.

Verification: full wowee build succeeded and all 31 CTest tests passed.
2026-07-11 10:29:40 -07:00
Kelsi
8f1e22b258 fix(animation): keep nearby monster skeletons at full frame rate
CharacterRenderer began skipping bone updates beyond only 10 yards, reduced monsters to one update every four frames beyond 20 yards, and reached one update every eight frames beyond 40 yards. Animation time continued advancing between those updates, so poses jumped forward in large steps and locomotion looked like a low-frame-rate slideshow even when movement interpolation was smooth.

Keep character and creature bones at the render frame rate through 45 yards, use every-second-frame updates from 45 to 90 yards, and reserve every-fourth-frame updates for models beyond 90 yards. This brings the creature path in line with the generic M2 renderer while preserving meaningful distant-animation cost reduction.

Verification: full wowee build succeeded and all 31 CTest tests passed.
2026-07-11 10:24:53 -07:00
Kelsi Rae Davis
86927ced2b Merge pull request #89 from paleophyte/fix/tbc-deeprun-tram-boarding
Fix Deeprun Tram: boarding, positioning, orientation, and polish
2026-07-10 17:31:26 -07:00
Kelsi
11d45e32b3 fix(geosets): correct boot/shin geoset selection and DBC loading
The DBC loader skipped binary DBCs in Data/db/ on Linux because
filenames are lowercase but code requested mixed-case names.  This
caused the corrupted CSV fallback to be used, where nearly all
integer fields (including GeosetGroup) were misclassified as strings
and exported as string-block lookups instead of numeric values.

Add case-insensitive filename matching for the Data/db/ directory
scan and prioritize expansion overlay DBCs.  Fix kGeosetBareShins
from 503 to 501, add per-race lowestInGroup fallback (Gnome, Tauren),
and use DBC layout JSON for field indices in game_screen_hud.  Exclude
field 0 (record ID) from string detection in both dbc_to_csv and the
asset extractor to prevent future CSV corruption.
2026-07-10 17:17:24 -07:00
Josh Anderson
d4cc43473f fix(transport): make TransportManager phase-sync helpers inline
isDeeprunTramTransport/nowEpochMs/deeprunTramSeedDurationMs were
defined out-of-line in transport_manager.cpp, which also pulls in
WMORenderer/M2Renderer. The test_transport_components unit test links
transport_clock_sync.cpp and transport_animator.cpp (which call these
helpers) without the rest of TransportManager, since pulling in the
full renderer subsystem for a unit test isn't viable - so the test
binary failed to link with undefined references.

The three helpers only touch ActiveTransport fields and <chrono>, so
move them inline into the header instead of adding transport_manager.cpp
(and its renderer dependency) to the test target.
2026-07-10 17:57:27 -05:00
Kelsi
89dcdc4fe4 fix(death): show corpse markers on Eastern Kingdoms maps
Corpse state used corpseMapId == 0 as a missing-position sentinel, but map ID 0 is the valid Eastern Kingdoms continent. Death positions were cached correctly and then rejected by world-map, minimap, distance, and reclaim checks, leaving ghosts without a corpse marker.

Add an explicit corpsePositionValid flag independent of map ID. Set it for normal deaths, forced deaths, corpse objects, and MSG_CORPSE_QUERY responses; clear it on character reset and resurrection. Update corpse position getters and reclaim eligibility to use the validity flag so map ID 0 remains fully supported.

Verification: rebuilt the wowee target and ran all 31 CTest tests successfully.
2026-07-10 15:16:35 -07:00
Josh Anderson
8afd7260f8 Merge master (synced with upstream) into tram fix branch
Pulls in upstream's StormLib Windows CI fix (missing libtomcrypt/
libtommath packages), chat/UI polish, and other fixes. Clean
auto-merge, no conflicts. NOT locally build-verified this round per
request to pause local builds - worth a build check before opening
the PR.
2026-07-10 15:50:45 -05:00
Kelsi
3148391d23 fix: polish gameplay settings and item tooltips 2026-07-10 13:44:19 -07:00