Fix bugs (#16)

* Fix camera drifting during multi-part emotes

Block translational movement in HandleCameraRelativeMovement() when
a multi-part emote is active. Previously, movement input was processed
unconditionally, causing the native ROI (and camera) to move forward
while the display ROI stayed pinned at the emote position. Now the
emote check forces hasInput=false and zeros m_smoothedSpeed so neither
position updates nor coasting occur during any phase of a multi-part
emote. Camera orbit controls (rotation/pan/zoom) remain unaffected.

https://claude.ai/code/session_01QMcZSa3ysdyACVea66QA5K

* Fix use-after-free crash in NameBubbleRenderer::Update

Lego3DSound::FUN_10011a60 (used by LegoCacheSound::Play) did not reset
m_isActor and m_enabled when reassigning the ROI reference. When a
cached sound was reused — first for a known actor name (setting
m_isActor=TRUE), then for a multiplayer clone found via FindROI — the
stale m_isActor flag caused Reset() to call ReleaseActor on the clone's
ROI, freeing it while ThirdPersonCamera still held a pointer. The next
Tick then dereferenced the dangling pointer in NameBubbleRenderer::Update.

Reset the ownership flags at the top of the reassignment path so they
match the clean-state semantics of Lego3DSound::Create.

Also guard multi-part emotes behind an active 3rd-person camera check
and remove a leftover debug log in RemotePlayer.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
foxtacles 2026-03-12 20:52:58 -07:00 committed by GitHub
parent 3bcd8cc908
commit 004e3b3bbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 20 additions and 8 deletions

View File

@ -186,6 +186,12 @@ void Lego3DSound::FUN_10011a60(ma_sound* p_sound, const char* p_name)
}
}
else {
// Reset ownership flags before reassigning. Reset() only clears m_roi
// but not these flags, so stale values from a previous actor-backed play
// would cause an incorrect ReleaseActor call for non-actor ROIs
m_isActor = FALSE;
m_enabled = FALSE;
if (CharacterManager()->IsActor(p_name)) {
m_roi = CharacterManager()->GetActorROI(p_name, TRUE);
m_enabled = m_isActor = TRUE;

View File

@ -1,5 +1,6 @@
#include "extensions/multiplayer/networkmanager.h"
#include "extensions/multiplayer/animdata.h"
#include "extensions/multiplayer/charactercloner.h"
#include "extensions/multiplayer/charactercustomizer.h"
#include "legoanimationmanager.h"
@ -552,6 +553,12 @@ void NetworkManager::SendEmote(uint8_t p_emoteId)
return;
}
// Multi-part emotes require 3rd person camera to be active (they need the display clone).
// In 1st person mode, skip them entirely to avoid broadcasting an emote the local player can't play.
if (!m_thirdPersonCamera.IsActive() && IsMultiPartEmote(p_emoteId)) {
return;
}
m_thirdPersonCamera.TriggerEmote(p_emoteId);
EmoteMsg msg{};

View File

@ -11,7 +11,6 @@
#include "realtime/realtime.h"
#include "roi/legoroi.h"
#include <SDL3/SDL_log.h>
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_timer.h>
#include <vec.h>
@ -141,13 +140,6 @@ void RemotePlayer::UpdateFromNetwork(const PlayerStateMsg& p_msg)
SDL_memcpy(newName, p_msg.name, sizeof(newName));
newName[sizeof(newName) - 1] = '\0';
if (SDL_strcmp(m_displayName, newName) != 0) {
SDL_Log(
"RemotePlayer[%u] name changed: '%s' -> '%s' (spawned=%d)",
m_peerId,
m_displayName,
newName,
m_spawned
);
SDL_memcpy(m_displayName, newName, sizeof(m_displayName));
// Recreate bubble with new name (or create for the first time)

View File

@ -664,6 +664,13 @@ MxBool ThirdPersonCamera::HandleCameraRelativeMovement(
// Normalize movement direction
float moveDirLen = SDL_sqrtf(moveDirX * moveDirX + moveDirZ * moveDirZ);
bool hasInput = moveDirLen > 0.001f;
// Block translation during multi-part emotes (rotation/pan/zoom handled separately)
if (m_animator.IsInMultiPartEmote()) {
hasInput = false;
m_smoothedSpeed = 0.0f;
}
if (hasInput) {
moveDirX /= moveDirLen;
moveDirZ /= moveDirLen;