* WIP: NPC animation local playback
Add NpcAnimCatalog and NpcAnimPlayer for playing NPC interaction
animations directly on player ROIs in multiplayer, bypassing the
singleplayer streaming pipeline.
- NpcAnimCatalog: reads animation entries from LegoAnimationManager
with eligibility filtering per actor
- NpcAnimPlayer: minimal SI file reader (header + offset table only,
then single MxSt read per object), extracts animation/audio/phoneme
data from ISLE.SI composite objects
- Skeletal animation with position rebasing (absolute world coords
converted to player-relative deltas)
- Audio via LegoCacheSound with proper WaveFormat parsing from SI
chunks, wall-clock sync via SDL_GetTicks
- Phoneme lip sync with FLC decode, palette update, and proper
texture restore on cleanup
- Movement lock via Controller::m_npcAnimPlaying flag
- Test trigger: emote 0 plays first eligible NPC animation
Still WIP: debug logging present, network sync not implemented,
needs testing with more animations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* WIP: Fix NPC animation playback, props, crash safety, and movement lock
- Fix SI file reading: use declared offset count, handle RIFF word-alignment
for odd-sized MxCh chunks, parse WaveFormat struct directly (not RIFF WAV)
- Fix animation type matching: use presenter name instead of MxOb::Type enum
(skeletal anim is type Object=0x0B, phoneme is type Video=0x03)
- Fix animation positioning: compute full rigid-body rebase transform
(savedTransform * inverse(animPose0)) so all motion, rotation, and extra
actor positions are preserved relative to the player's current pose
- Add extra character support: use CharacterCloner::Clone for root-level
characters (RHODA, RD, BD, PG), extend AssignROIIndices to match
non-*-prefixed root-level nodes against extra ROIs
- Fix phoneme: update palette via SetEntries after FLC decode, restore
original texture by passing saved pointer (not NULL), initialize
filetype_/volume_ to avoid UBSan errors
- Fix sync: use SDL_GetTicks (wall-clock) instead of Timer()->GetTime()
(game timer stalls during freezes), defer clock start to first Tick
- Fix crash on camera transition: add NPC anim stop callback in
Controller::Deactivate and OnWorldDisabled (fires before ROI destruction)
- Block camera toggle and scroll/zoom disable during NPC animation
- Block player movement and camera-relative input during NPC animation
- Add StopNpcAnimation() public API on NetworkManager
- Add EnsureROIMapVisibility in Tick for prop visibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* WIP: Fix rebase for nested camera nodes, revert test to eligible[0]
Accumulate parent transforms when computing the player's animation-
space world pose at time 0. Fixes position offset for animations with
nested '-' nodes (e.g. -SBA001BU -> -TILT -> BU) where the local
transform alone didn't account for parent TILT offset.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* WIP: Catalog filtering, prop LOD trimming, click anim blocking
- Split NpcAnimCatalog into NPC (location==-1) and cam (location>=0) buckets
- Filter by display actor's character index (not actorId)
- Eligibility: require all 5 main actor bits set (no counterpart for now)
- DisplayActorToCharacterIndex maps display actor -> g_characters index
- Trim trailing digits/underscores from prop LOD names matching original
game's e_managedInvisibleRoiTrimmed logic (LETR12 -> letr)
- Handle *-prefixed non-actor root siblings as props via CreateAutoROI
- Block click animations during NPC animation playback (both local and
remote player paths)
- Remove verbose per-entry catalog logging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* WIP: Actor metadata ROI creation, vehicle reuse, AssignROIIndices fix
- Replace scanForCharacters heuristic with LegoAnimActorEntry metadata
loop (GetNumActors/GetActorType/GetActorName) matching original game
- Player identified by animation name suffix, not tree position
- Handle all actor types: managed actors (2), trimmed props (3),
exact props (4), scene ROIs (5/6), and scene actors (0/1)
- Vehicle ROI reuse: borrow existing ride vehicle ROI for type 0/1
actors when CreateAutoROI fails, restore name on Stop
- Fix AssignROIIndices: check extras before claiming root to handle
tree ordering (BIKESY before SY)
- Use GetRefCount(ROI*) for cleanup instead of name-based Exists()
- Block click animations during NPC anim playback
- Vehicle animation not yet fully working (known issue)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* WIP: Fix vehicle animation, skip Controller tick during NPC anim
The ride animation and SyncTransformFromNative in Controller::Tick
were overwriting ROI transforms set by NpcAnimPlayer every frame.
Skip the entire ride animation path and non-vehicle character
animation path when m_npcAnimPlaying is true, so only NpcAnimPlayer
controls ROI positioning during NPC animations.
Also add comprehensive ROI map and tree assignment debug logging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Move SI parsing from NpcAnimPlayer into libweaver
Replace ~300 lines of custom RIFF/MxOb/MxCh parsing in NpcAnimPlayer
with libweaver's new HeaderOnly read mode and slot-based ReadObject API.
This reuses ReadChunk's existing logic for the full MxSt->MxOb->MxCh
chain, eliminating code duplication.
Extract HD/CD path resolution into a shared ResolveGamePath utility
(extensions/common/pathutils), replacing the duplicated pattern in
NpcAnimPlayer, SiLoader, and TextureLoader.
Update libweaver to 17c7736 (HeaderOnly + ReadObject support).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Refactor animation playback into Multiplayer::Animation namespace
Split the monolithic NpcAnimPlayer and NpcAnimCatalog into five focused
components under extensions/multiplayer/animation/:
- Catalog: AnimInfo index with category enum, stores all animations
- Loader: SI file I/O, parsing, AnimData cache
- Controller: Play/Tick/Stop orchestrator, ROI creation, rebase matrix
- AudioPlayer: LegoCacheSound timed playback
- PhonemePlayer: FLC decoding, texture swap, lip sync
Also removes ~50 SDL_Log debug calls, renames all NpcAnim* references
to match the new structure, simplifies the camera animation callback
API, and documents AnimUtils divergences from the original game.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* DRY: Extract TrimLODSuffix helper and AnimData::ReleaseTracks
- controller.cpp: Extract repeated digit/underscore trimming loop into
static TrimLODSuffix() (was duplicated 3 times in CreateExtraROIs)
- loader.cpp: Extract duplicated track cleanup loops into
AnimData::ReleaseTracks() (was in both destructor and move-assignment)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Updates
* Clarify animation system separation with renames and DRY extractions
Rename Controller → ScenePlayer to distinguish multi-actor scene
animations from simple character poses. Rename AnimData → SceneAnimData
to avoid confusion with the character lookup tables. Rename
animdata.h/cpp → charactertables.h/cpp to reflect their actual content
(walk/idle/emote/vehicle tables).
Extract ApplyTree, TrimLODSuffix, and ResolvePropLODName into AnimUtils
to reduce duplication across CharacterAnimator and ScenePlayer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove dta.py accidentally committed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix CleanupProps corrupting global NPC actor state
ReleaseActor looks up g_actorInfo[] by ROI name, which for renamed
clones (e.g. "ma") matches the real global NPC and deletes its actor
entity. Since all props are independent clones (not obtained via
GetActorROI), use ReleaseAutoROI unconditionally — it performs
identical map/ROI cleanup without touching g_actorInfo[].
Also removes the redundant explicit Remove() call since ReleaseAutoROI
already calls RemoveROI() internally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* WIP: Add animation catalog rework, location proximity, and coordinator
Prerequisite systems for cooperative animation reenactment feature:
- Catalog: expanded CatalogEntry with performerMask, spectatorMask,
location index. Exact character matching (replaces engine's lossy
2-char prefix). New CanTrigger() checks collective player eligibility
(spectator + all performers, mutually exclusive roles).
- LocationProximity: 2D XZ distance to nearest g_locations entry,
integrated into Tickle with OnNearestLocationChanged callback.
Remote player locations derived from ROI positions.
- Coordinator: state machine (idle/interested/countdown/playing/completed),
ComputeEligibility for frontend consumption (pre-filtered to local
player's participable animations). Networking hooks are stubs.
- NetworkManager: location proximity + coordinator integration, atomic
request pattern for anim interest/cancel, state resets on all
transition paths (world disable, disconnect, reconnect, stop).
- Bridge: OnNearestLocationChanged callback (Emscripten + Native),
mp_set_anim_interest / mp_cancel_anim_interest WASM exports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* WIP: Add push-based animation state bridge and slot fill computation
Add OnAnimationsAvailable callback that pushes full animation eligibility
state (location, coordinator state, per-animation slot fill status) to
the frontend whenever relevant state changes. Uses a dirty flag system
with 250ms cooldown (bypassed for interest changes) to batch updates.
- Add CanTriggerDetailed to Catalog (refactor CanTrigger as wrapper)
- Enrich EligibilityInfo with SlotInfo vector and CatalogEntry pointer
- Add Coordinator::OnLocationChanged for auto-clearing stale interest
- Add dirty flag triggers at all 7 state change points in NetworkManager
- Build JSON payload with unified slots (performers + spectator)
- Remove OnNearestLocationChanged (superseded by push-based approach)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Ghastly
* WIP
* Fix animation session sync, camera-cancel, and state management bugs
- Add m_cancelPending to coordinator to prevent stale session re-enrollment
- Allow ClearInterest during countdown/playing for camera-cancel support
- Camera toggle now cancels animation in any active state instead of blocking
- Safety net: cancel animation when camera is disabled by any source
- Push idle JSON when camera unavailable so frontend clears countdown UI
- HandleCancel includes playing sessions and erases them (explicit cancel stops all)
- HandleAnimCancel/HandleAnimUpdate detect playing→idle to stop local scenes
- SendAnimUpdateToPlayer for targeted session sync to newly joined players
- HandleHostAssign: skip ResetAnimationState on initial assignment to avoid race
- IsPeerNearby helper for shared proximity checks
- HandleAnimInterest: evict far-away participants when session is full
- PushAnimationState: suppress session display when no participant is nearby
- World filter in UpdateRemotePlayers and PushAnimationState
- Set m_animStateDirty on camera change and remote player world change
- Use anyInIsle instead of anyNearby for continuous proximity-based push
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Clean up animation code: extract DRY helpers, remove debug logging
- Extract BuildAnimUpdateMsg() and ExtractSlotPeerIds() to deduplicate
message-building logic in BroadcastAnimUpdate/SendAnimUpdateToPlayer
- Replace manual tree iteration in controller.cpp with AnimUtils::ApplyTree
- Remove all [Anim]/[SessionHost] SDL_Log debug calls and unused includes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* WIP: ScenePlayer multi-participant support and cam_anim playback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* WIP: Vehicle ROI support, alias-based ROI mapping, audio fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Refactor animation infrastructure: remove dead code, DRY, tighten data
Remove all debug logging (~20 SDL_Log calls), dead fields (m_savedVehicleName,
m_debugFirstTickLogged, boundingRadius, centerPoint), dead methods
(RestoreVehicleROI, HasActiveSounds), and unused parameters (Tick's deltaTime).
Tighten data structures: replace raw m_propROIs array with vector, derive
isSpectator from charIndex via IsSpectator() method (SessionSlot,
ParticipantROI), group action transform fields into sub-struct, replace
vehicle category magic numbers with VehicleCategory enum.
Extract DRY helpers: addAlias/createProp lambdas in SetupROIs,
StopScenePlayback() in NetworkManager (5 call sites), combine dual slot
iteration into single loop in HandleAnimStartLocally. Simplify Play()
signature by removing redundant p_localROI/p_vehicleROI params.
Fix bug: HandleAnimCancel now unlocks remote player ROIs when stopping
during playback (was skipping unlock, leaving remotes permanently locked).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Async SI asset preloading during animation countdown
Preload animation data from isle.si on a background thread when the
countdown starts (4s window), so ScenePlayer::Play() finds the data
already cached and avoids a 500ms-1s main-thread stall.
Adds Loader::PreloadAsync() with a one-shot MxThread subclass that
opens its own si::File/Interleaf, parses the object, and inserts into
the cache under MxCriticalSection. EnsureCached() joins any in-progress
preload before falling back to synchronous load.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Refactor animation system: fix dangling pointer, DRY extractions, correctness
- Fix dangling .c_str() in ScenePlayer::SetupROIs by using std::deque
for aliasNames (vector reallocation invalidated stored pointers)
- Push idle JSON fallback when userActor is null in PushAnimationState
instead of silently returning with stale frontend state
- Extract GetPerformerIndices() to eliminate 3 duplicate bit-iteration
loops across coordinator.cpp and sessionhost.cpp
- Promote CheckSpectatorMask to public Catalog method, replacing
inlined duplicate in sessionhost.cpp
- Extract Loader::OpenSIHeaderOnly() to consolidate duplicated SI file
open/parse between OpenSI() and PreloadThread::Run()
- Extract IDLE_ANIM_STATE_JSON constant to avoid string duplication
- Clarify proximity radius distinction with comment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Observer mode: uninvolved players see animations play out locally
Non-participant players in the same world now see scene animations
(cam_anim, npc_anim) play out on the performing players' ROIs as an
ambient background scene. The observer's camera, movement, and vehicle
are completely unaffected.
Key changes:
- HandleAnimStartLocally always runs (not just for session participants)
- ScenePlayer gains observer mode: skips camera control, spectator
hiding, and vehicle hiding
- Preload during countdown for all clients, not just participants
- Fix 1st person camera: skip display ROI check for observers (the
display clone is destroyed in 1st person mode)
- Track m_playingAnimIndex for observer early-stop detection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove unused `using namespace Extensions` from isleapp.cpp and replace
per-header forward declaration blocks in 7 LEGO1 headers with a single
shared forward declarations header.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use strcasecmp, strncasecmp, strlwr, strupr and itoa from SDL
* Use SDL_GetTicks instead of timeGetTime
* Use MxDSFile::OPEN_READ instead of OF_READ
* Use SDL_IOStream to read bitmaps
* Use SDL_LogXXX instead of OutputDebugString
* Undo mxvideoparam.h change
* Revert "Undo mxvideoparam.h change"
This reverts commit 4a20cf6c46.
* Fix _MxTrace
* Reapply "Undo mxvideoparam.h change"
This reverts commit b3a09dc520.
* fix _MxTrace
* Use __declspec(dllexport) for exporting symbols from dll
Refactored CMake script such that all objects are passed to the lego1 library.
* clang-format
* fix msvc build
* MSVC fixed for real now?
* Forgot about d3drm_guid
* Fix naming issue
* Use Uint64 in LegoCarBuild::Tickle for dTime
* Introduce LPD3DRM_APPDATA typedef for setting d3drm appdata
* Fix warning about assigning const string literals to variable char pointers
* Don't cast pointers to integers on non-32-bit architectures
* memset 2nd argument is int
* Assume cpuid is available on x86_64, needs testing on i386 and unavailable on anything else
* Store HFILE in its own member variable