Commit Graph

29 Commits

Author SHA1 Message Date
foxtacles
a9747dec11
Character customization (#8)
* WIP: Add character customization to multiplayer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Refine character customization: fix message buffering, DRY up code, request-based model

- Register NetworkManager with TickleManager via HandleCreate hook in
  LegoOmni::Create(), so packets are processed continuously instead of
  buffering between Connect() and OnWorldEnabled()
- Spawn unspawned remote players in OnWorldEnabled() (created before
  world was ready)
- Switch to request-based customization: HandleROIClick sends
  MSG_CUSTOMIZE to server, server echoes to all peers, HandleCustomize
  applies state and plays effects
- DRY up SwitchVariant to delegate LOD cloning to ApplyHatVariant
- Add ApplyChange helper consolidating the switch-on-changeType pattern
- Fix InitFromActorInfo to derive dependent color parts from independent
  parts (matching Unpack rules)
- Remove dead code: m_hasBeenTicked, ApplyCustomizeChange on
  RemotePlayer, m_localCustomizeState on NetworkManager
- Add null ROI checks in HandleCustomize for unspawned players
- Move MSG_CUSTOMIZE constant to shared protocol.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix

* Fix character customization bugs from code review

- BUG-1: Add bounds check in SwitchColor to prevent OOB access from
  unvalidated network input (p_partIndex could exceed array bounds)
- BUG-2: Enforce allowRemoteCustomize on receiver side in HandleCustomize
  (was only checked on sender side, byppassable by malicious client)
- BUG-3: Document stale-state sound asymmetry for remote targets
- OBS-1: Remove unused bodyVariantIndex from CustomizeState (never
  modified, wasted 3 bits per state message)
- NAME-1: Fix p_ prefix convention on ApplyCustomizeChange parameters
2026-03-07 23:20:55 +01:00
foxtacles
c943d2455d
Add player name bubbles above remote players' heads in multiplayer (#7)
Renders a billboard text bubble showing each remote player's display
name. Includes a WASM export to toggle visibility from the frontend.

- Bitmap font renderer generates paletted textures for name labels
- Billboard quad faces the camera each frame via orientation matrix
- Bubble visibility managed globally by NetworkManager toggle
- Fix miniwin D3DRMIMAGE constructor code style (static_cast, const)
2026-03-07 19:38:33 +01:00
foxtacles
ed4e248be4
Implement display actors (#6)
* Implement display actor override for multiplayer extension

Add displayActorIndex to the multiplayer protocol, allowing players to
choose any of the 66 character models from g_actorInfoInit via the
multiplayer:actor INI setting. The visual display is decoupled from the
gameplay actor ID while maintaining backward compatibility.

- Protocol: Add displayActorIndex field to PlayerStateMsg and validation helpers
- RemotePlayer: Use display actor name for cloning instead of actorId
- NetworkManager: Broadcast/handle displayActorIndex, respawn on display change
- ThirdPersonCamera: Create/manage display clone ROI for local player override
- INI: Read multiplayer:actor setting and resolve to g_actorInfoInit index

* Use array syntax for INI option access in display actor setup

Consistent with how relayUrl and room are read from options.

* Fix display actor ROI handling in 3rd person camera

- Fix direction flip targeting display clone instead of native ROI in
  Disable(), ReinitForCharacter(), and OnCamAnimEnd(). The native ROI is
  the source of truth for TransformPointOfView and Tick() sync.
- Fix use-after-free: DestroyDisplayClone() now nulls m_playerROI when
  it points to the destroyed clone, preventing dangling pointer access
  in BuildRideAnimation after a 3rd→1st→3rd person toggle on a vehicle.
- Recreate display clone in ReinitForCharacter() vehicle branch.
- Extract EnsureDisplayROI() helper to deduplicate clone setup pattern.
- Move IsValidDisplayActorIndex() to charactercloner.h, replacing magic
  number 66 with sizeOfArray(g_actorInfoInit).

* Remove display actors plan document
2026-03-07 18:02:53 +01:00
foxtacles
dcf3b66173
Fix 3rd person camera 180-degree flip after cam anim ends (#5)
* Fix 3rd person camera 180-degree flip after cam anim ends

When a cam anim (NPC interaction cutscene) ends, FUN_1004b6d0 places the
player actor at the camera's final position using the camera's direction,
which uses standard convention (z = visual forward). The 3rd person camera
relies on backward-z convention (z = visual backward, from TurnAround).
This mismatch caused the camera to face 180 degrees wrong permanently
after any cam anim.

Add a HandleCamAnimEnd extension hook in FUN_1004b6d0 that, when the 3rd
person camera is active, flips the ROI direction back to backward-z and
re-establishes the correct camera position.
2026-03-07 17:32:21 +01:00
foxtacles
c8c3b7276e
Fix 3rd/1st person camera switch direction bugs (#4)
- Fix broadcast direction: use IsActive() instead of IsROITurnedAround()
  so the negate in BroadcastLocalState only fires when movement inversion
  is active, not based on a default-true flag

- Fix vehicle ROI direction for 3rd-person camera: undo Enter()'s
  TurnAround on small vehicles so the backward-z convention is preserved.
  Vehicles are placed with ROI z opposite to visual forward, and Enter()'s
  TurnAround breaks this for 3rd-person rendering. Applied in both
  OnActorEnter (entering while 3rd-person enabled) and ReinitForCharacter
  (enabling 3rd-person while already on a vehicle)

- Fix vehicle direction on exit: apply extra TurnAround in OnActorExit
  when 3rd-person is active, since Exit()'s TurnAround assumes Enter()'s
  TurnAround is still in effect

- Add WrappedUpdateWorldData() after manual direction flips in Disable()
  and ReinitForCharacter() to keep bounding volumes consistent and prevent
  stale world data from causing momentary camera/direction glitches

- Remove unused IsROITurnedAround() method

- Fix data race between WASM exports and game thread
2026-03-07 05:49:39 +01:00
foxtacles
37b328a595
3rd person camera (#3)
* Add feasibility plan for reusing multiplayer animation system for third-person camera

Evaluates reusing the multiplayer extension's RemotePlayer animation system
(BuildROIMap, AssignROIIndices, ApplyAnimationTransformation) for the local
player to enable a third-person camera mode. Conclusion: feasible with only
3 single-line extension hooks added to core game code.

https://claude.ai/code/session_01NC3zdQZ4nqEcYjyvStqcdD

* WIP: Third-person camera with animation reuse and movement fix

* Fix third-person camera bugs: vehicles, remote facing, emote distortion (#2)

- Fix spawn pose and building re-entry by applying idle frame 0 and
  reinitializing on world enable
- Handle vehicle transitions: ride animations for small vehicles,
  first-person fallback for large vehicles and helicopter
- Keep vehicle dashboards visible for exit controls
- Disable third-person camera for large vehicles, fix ROI cleanup
- Move HandleActorExit hook to end of Exit() for immediate reinit
- Fix remote player facing 180 degrees wrong by negating direction
  in BroadcastLocalState when third-person camera is active
- Fix Hat Tip emote distortion from compounding transform scale by
  saving clean parent transform at emote start and restoring after
  each frame's animation application

* DRY cleanup for third-person camera branch

- Extract shared DetectVehicleType() to protocol.h/cpp (was duplicated
  in ThirdPersonCamera and NetworkManager)
- Remove no-op HandlePostApplyTransform hook chain (called every frame
  for every LegoPathActor but did nothing)
- Add ThirdPersonCamera::ClearAnimCaches() helper (pattern repeated 5x)
- Add AnimUtils::EnsureROIMapVisibility() inline helper (loop repeated
  5x across ThirdPersonCamera and RemotePlayer)
- Remove redundant static_cast in multiplayer.cpp (UserActor() already
  returns LegoPathActor*)
- Delete THIRD_PERSON_CAMERA_ANIMATION_REUSE_PLAN.md development artifact
2026-03-07 01:38:45 +01:00
Christian Semmler
0997610bad
Extract PlatformCallbacks interface and consolidate Emscripten files
Move all Emscripten-specific multiplayer code under platforms/emscripten/
and introduce an abstract PlatformCallbacks interface for outbound
notifications, mirroring the existing NetworkTransport pattern. This
removes all #ifdef __EMSCRIPTEN__ blocks from networkmanager.cpp.
2026-03-02 15:46:37 -08:00
Christian Semmler
e47d2dab67
Refactor WASM exports and add push-based player count events
Move WASM exports (mp_set_walk_animation, mp_set_idle_animation,
mp_trigger_emote) from multiplayer.cpp into a dedicated
wasm_exports.cpp, added to the isle target (guarded by both
EMSCRIPTEN and ISLE_EXTENSIONS) so the linker keeps the symbols.

Replace the polling mp_get_player_count export with push-based
playerCountChanged CustomEvents dispatched from NetworkManager.
The count only reflects players visible in the Isle world: null
when the local player is outside Isle, filtered by remote player
worldId when inside. Remove the now-unused GetPlayerCount() method.
2026-03-02 15:26:27 -08:00
Claude
3e85941cbc
Add animation protocol: walk/idle selection, emote triggers, WASM exports
Implement the animation system from the Phase 1 plan:

Protocol: Add walkAnimId/idleAnimId fields to PlayerStateMsg (2 extra bytes
per 15Hz tick), add MSG_EMOTE (type 9) with EmoteMsg struct, and define
shared animation lookup tables (walk: 6 anims, idle: 3, emote: 2).

NetworkManager: Store local walk/idle animation indices, include them in
every state broadcast, handle incoming MSG_EMOTE by dispatching to the
target remote player's TriggerEmote(). Add SetWalkAnimation(),
SetIdleAnimation(), SendEmote(), GetPlayerCount() public API.

RemotePlayer: Replace per-animation raw pointers with AnimCache struct
and lazy m_animCacheMap (name -> ROI map, built on first use, cleared on
Despawn). UpdateFromNetwork() detects walk/idle ID changes and swaps the
active animation cache. UpdateAnimation() now has three states: moving
(configurable walk anim), emote (one-shot with duration tracking,
interrupted by movement), and idle (configurable idle anim after 2.5s
timeout). Add TriggerEmote() for one-shot emote playback.

WASM exports: mp_set_walk_animation(), mp_set_idle_animation(),
mp_trigger_emote(), mp_get_player_count() with EMSCRIPTEN_KEEPALIVE.
CMakeLists.txt adds EXPORTED_FUNCTIONS and EXPORTED_RUNTIME_METHODS
for Svelte ccall/cwrap access.

https://claude.ai/code/session_01BEYdu8gXr1QmYwzRRgaEA6
2026-03-02 03:06:48 +00:00
foxtacles
29955df947
Export MultiplayerExt symbols accessed from isle executable (#1)
* Export MultiplayerExt symbols accessed from isle executable

When lego1 is built as a shared library (DLL), symbols used by the isle
executable through the Extension<T>::Call() template need to be exported.
Add LEGO1_EXPORT to MultiplayerExt::enabled and MultiplayerExt::CheckRejected()
which are referenced from isleapp.cpp via the template instantiation.

https://claude.ai/code/session_01HHMmowothg25fephi6iidq

* Use inline const instead of constexpr for extension function pointers

constexpr cannot be used with dllimport function addresses since they
are resolved at load time through the IAT, not at compile time. Change
to inline const which preserves the single-definition semantics (via
inline) while allowing runtime initialization of the function pointers.

https://claude.ai/code/session_01HHMmowothg25fephi6iidq

* Fix multiplayer extension link errors across DLL boundary

When lego1 is a shared library, Extension<T>::Call() instantiated from
isle.exe references MultiplayerExt::enabled and CheckRejected() which
are not exported. Instead of exporting internal symbols (which also
breaks constexpr with dllimport), add an exported IsMultiplayerRejected()
wrapper that keeps the Extension<T>::Call() instantiation inside lego1.

https://claude.ai/code/session_01HHMmowothg25fephi6iidq

* Guard IsMultiplayerRejected with EXTENSIONS ifdef

extensions.cpp is only compiled when ISLE_EXTENSIONS is ON, so the
wrapper function and its call site need #ifdef EXTENSIONS guards for
targets like x86 MSVC where extensions are disabled.

https://claude.ai/code/session_01HHMmowothg25fephi6iidq

* Move IsMultiplayerRejected definition to multiplayer.cpp

The function is declared in multiplayer.h and belongs with the rest of
the multiplayer extension code, not in the general extensions.cpp file.

https://claude.ai/code/session_01HHMmowothg25fephi6iidq

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 18:21:15 -08:00
Christian Semmler
5b56db3c33
Add room management, relay capacity, and rejection handling
- Remove hardcoded multiplayer config from emscripten config.cpp
- Add relay HTTP endpoints for room preview (GET) and creation (POST)
  with capacity check, CORS headers, and configurable max players
- Add WebSocket rejection detection (room full/503) via onclose flag
- Add CheckRejected extension call in IsleApp::Tick for clean shutdown
  through SDL_APP_SUCCESS path instead of calling exit()
- Set Module._exitCode in JS for sessionStorage-based toast after reload
2026-03-01 14:37:08 -08:00
Christian Semmler
762a5f3941
Add configurable room name, fix parameter naming in multiplayer extension
Read "multiplayer:room" from INI options (default "default") and use it
in Connect() instead of a hardcoded string. Return early if room is blank,
matching the relay URL check. Rename p_mgr to p_networkManager for consistency.
2026-03-01 10:35:16 -08:00
Christian Semmler
c760db50a9
Extract WorldStateSync from NetworkManager
Move world state synchronization logic (snapshots, events, entity
mutation routing) into a dedicated WorldStateSync class, reducing
NetworkManager from ~790 to ~420 lines.
2026-03-01 10:14:47 -08:00
Christian Semmler
a0629c45a1
Minimize LEGO1 changes: revert globals, move CreateCharacterClone to extension
Revert plant/building manager globals back to private class statics
(matching master) and use friend declarations for extension access.
Move CreateCharacterClone out of LegoCharacterManager into a new
CharacterCloner class in the multiplayer extension.
2026-03-01 10:04:13 -08:00
Christian Semmler
12a63c105c
Implement multiplayer world state sync for plants and buildings
Add serialization framework using C++ templates and table-driven lookup
to sync plant and building state between players. Includes world snapshot
routing to requesting peer, relay server Docker support, and fixes for
building color sync, ride vehicle visibility, and ARM compilation.
2026-03-01 09:48:03 -08:00
Christian Semmler
fb1d596704
Clean up multiplayer extension: use vec.h macros, remove debug logging
- Replace manual vector math in UpdateTransform with CalcLocalTransform and vec.h macros (LERP3, SET3, MV3, DISTSQRD3, ZEROVEC3)
- Remove all SDL_Log debug logging from multiplayer code
- Strip extraneous comments that restate the code
- Extract CreateAndSpawnPlayer helper to consolidate repeated spawn pattern
- Simplify UpdateVehicleState control flow
- Remove unused includes (SDL_log.h, mxgeometry/mxmatrix.h)
2026-02-28 12:11:19 -08:00
Christian Semmler
5c8a2ffd3b
Implement multiplayer MVP: WebSocket networking, remote player rendering, vehicle support
- WebSocket relay server (Cloudflare Worker + Durable Object)
- Remote player character cloning with walk/idle/ride animations
- Vehicle support for remote players
- INI config for relay URL
- Extension hook for world transition ROI management
2026-02-28 12:00:01 -08:00
Christian Semmler
effa6ffabb
(SiLoader) Fix restoration of juke box state for HD music (#706)
* (SiLoader) Fix restoration of juke box state for HD music

* Fix
2025-09-04 22:57:34 +00:00
Christian Semmler
cbc51e8148
(SiLoader) Remove Replace directive recursion (#705) 2025-09-04 16:36:26 +00:00
Christian Semmler
ace6b7fc7e
(SiLoader) Start actions attached to world startup when world is ready (#704) 2025-09-01 18:56:25 +00:00
Christian Semmler
ef2eaf04bf
Add badend.si asset pack (#693)
* WIP

* Add bad ending SI file

* Add SiLoader adjustments
2025-08-21 02:00:55 +02:00
Christian Semmler
e709400dc6
Add new directives to SiLoader, allow specifying directives in .ini (#688)
* WIP Prepend directive

* WIP

* Add new directives
2025-08-20 19:21:28 +02:00
Christian Semmler
d10ac7e77c
Add hdmusic.si asset pack, extend SiLoader with Replace directive (#671)
* Replace SI objects

* Changes

* Fix

* Remove obsolete file

* Add delete handler

* Updates

* Update assets

* Configure git LFS

* Add HD music

* Move files to LFS

* chmod files

* fix gitattributes

* Fixes

* Fixes

* Add other HD music

* Fix file perms

* Add comment

* Minor update

* Refactor

* Fix

* Add LFS flag

* Add check for file replace

* Exclude assets from CPack

* Attempt fix

* Update libweaver

* Fix MxDSBuffer leak

* Add .lfsconfig

* Set LFS URL

* Fix LFS CI

* Fix

* Space
2025-08-16 21:21:39 +02:00
Christian Semmler
6a8aaaabe2
Add widescreen.si to web port (#666)
* Add widescreen.si to web port

* Move logic
2025-08-11 04:41:42 +02:00
Christian Semmler
841db2a577
Add SI Loader extension (#664)
* SiLoader draft

* Fixes

* Fixes

* Fix asset build on Windows

* Remove whitespace

* Package assets in CI

* Disable clang-tidy

* Fix NCC

* Try this

* Disable extensions on Xbox for now

* Update bitmap

* Update 3rdparty/CMakeLists.txt

Co-authored-by: Anonymous Maarten <madebr@users.noreply.github.com>

* Add DEPFILE for asset command

* Use assets.d instead of .d

---------

Co-authored-by: Anonymous Maarten <madebr@users.noreply.github.com>
2025-08-11 00:59:55 +00:00
Christian Semmler
89f2f5cefe
(Web port) Improve loading UX for HD Textures (#648) 2025-07-25 23:08:55 +00:00
Christian Semmler
7473330e47
(Web port) Add HD textures option (#647) 2025-07-24 22:10:15 +00:00
Christian Semmler
232ef07b51
Allow extensions to define options (#590)
* Allow extensions to define their own options

* Fixes

* Remove logging
2025-07-13 02:30:29 +00:00
Christian Semmler
68c967ebdf
Add extensions, TextureLoader (#570)
* Add extensions, `TextureLoader`

* Fix wording

* Add to default ini

* Add folder to flatpak

* Use different enable strategy
2025-07-10 00:24:59 -07:00