diff --git a/extensions/include/extensions/multiplayer/charactercustomizer.h b/extensions/include/extensions/multiplayer/charactercustomizer.h index ca5e7d57..ac4e0eb4 100644 --- a/extensions/include/extensions/multiplayer/charactercustomizer.h +++ b/extensions/include/extensions/multiplayer/charactercustomizer.h @@ -13,7 +13,7 @@ struct CustomizeState; class CharacterCustomizer { public: - static uint8_t ResolveActorInfoIndex(uint8_t p_displayActorIndex, uint8_t p_actorId); + static uint8_t ResolveActorInfoIndex(uint8_t p_displayActorIndex); static bool SwitchColor(LegoROI* p_rootROI, uint8_t p_actorInfoIndex, CustomizeState& p_state, int p_partIndex); diff --git a/extensions/include/extensions/multiplayer/networkmanager.h b/extensions/include/extensions/multiplayer/networkmanager.h index 2d68a575..ee08a90f 100644 --- a/extensions/include/extensions/multiplayer/networkmanager.h +++ b/extensions/include/extensions/multiplayer/networkmanager.h @@ -97,6 +97,7 @@ class NetworkManager : public MxCore { void HandleEmote(const EmoteMsg& p_msg); void HandleCustomize(const CustomizeMsg& p_msg); + void DeriveDisplayActorIndex(uint8_t p_actorId); void ProcessPendingRequests(); void RemoveRemotePlayer(uint32_t p_peerId); void RemoveAllRemotePlayers(); @@ -122,6 +123,7 @@ class NetworkManager : public MxCore { uint8_t m_localWalkAnimId; uint8_t m_localIdleAnimId; uint8_t m_localDisplayActorIndex; + bool m_displayActorFrozen; bool m_localAllowRemoteCustomize; bool m_inIsleWorld; bool m_registered; diff --git a/extensions/src/multiplayer.cpp b/extensions/src/multiplayer.cpp index 7d0022c0..d7fdc5e7 100644 --- a/extensions/src/multiplayer.cpp +++ b/extensions/src/multiplayer.cpp @@ -108,8 +108,9 @@ MxBool MultiplayerExt::HandleROIClick(LegoROI* p_rootROI, LegoEventNotificationP Multiplayer::RemotePlayer* remote = mgr->FindPlayerByROI(p_rootROI); // Check if it's our own 3rd-person display actor override - bool isSelf = (mgr->GetThirdPersonCamera().GetDisplayROI() != nullptr && - mgr->GetThirdPersonCamera().GetDisplayROI() == p_rootROI); + bool isSelf = + (mgr->GetThirdPersonCamera().GetDisplayROI() != nullptr && + mgr->GetThirdPersonCamera().GetDisplayROI() == p_rootROI); if (!remote && !isSelf) { return FALSE; @@ -160,11 +161,7 @@ MxBool MultiplayerExt::HandleROIClick(LegoROI* p_rootROI, LegoEventNotificationP // For remote targets this avoids flip-flop from stale state messages; for self targets // it keeps the code path uniform. uint32_t targetPeerId = remote ? remote->GetPeerId() : mgr->GetLocalPeerId(); - mgr->SendCustomize( - targetPeerId, - changeType, - static_cast(partIndex >= 0 ? partIndex : 0xFF) - ); + mgr->SendCustomize(targetPeerId, changeType, static_cast(partIndex >= 0 ? partIndex : 0xFF)); return TRUE; } diff --git a/extensions/src/multiplayer/charactercustomizer.cpp b/extensions/src/multiplayer/charactercustomizer.cpp index 94b3e435..8183acac 100644 --- a/extensions/src/multiplayer/charactercustomizer.cpp +++ b/extensions/src/multiplayer/charactercustomizer.cpp @@ -50,17 +50,9 @@ LegoROI* CharacterCustomizer::FindChildROI(LegoROI* p_rootROI, const char* p_nam // MARK: Public API -uint8_t CharacterCustomizer::ResolveActorInfoIndex(uint8_t p_displayActorIndex, uint8_t p_actorId) +uint8_t CharacterCustomizer::ResolveActorInfoIndex(uint8_t p_displayActorIndex) { - if (IsValidDisplayActorIndex(p_displayActorIndex)) { - return p_displayActorIndex; - } - - if (p_actorId >= 1 && p_actorId <= 5) { - return p_actorId - 1; - } - - return 0; + return p_displayActorIndex; } bool CharacterCustomizer::SwitchColor( diff --git a/extensions/src/multiplayer/networkmanager.cpp b/extensions/src/multiplayer/networkmanager.cpp index 049333e0..b8ad1729 100644 --- a/extensions/src/multiplayer/networkmanager.cpp +++ b/extensions/src/multiplayer/networkmanager.cpp @@ -35,10 +35,10 @@ void NetworkManager::SendMessage(const T& p_msg) NetworkManager::NetworkManager() : m_transport(nullptr), m_callbacks(nullptr), m_localPeerId(0), m_hostPeerId(0), m_sequence(0), m_lastBroadcastTime(0), m_lastValidActorId(0), m_localWalkAnimId(0), m_localIdleAnimId(0), - m_localDisplayActorIndex(DISPLAY_ACTOR_NONE), m_localAllowRemoteCustomize(true), m_inIsleWorld(false), - m_registered(false), m_pendingToggleThirdPerson(false), m_pendingToggleNameBubbles(false), - m_pendingWalkAnim(-1), m_pendingIdleAnim(-1), m_pendingEmote(-1), - m_pendingToggleAllowCustomize(false), m_showNameBubbles(true) + m_localDisplayActorIndex(DISPLAY_ACTOR_NONE), m_displayActorFrozen(false), m_localAllowRemoteCustomize(true), + m_inIsleWorld(false), m_registered(false), m_pendingToggleThirdPerson(false), m_pendingToggleNameBubbles(false), + m_pendingWalkAnim(-1), m_pendingIdleAnim(-1), m_pendingEmote(-1), m_pendingToggleAllowCustomize(false), + m_showNameBubbles(true) { } @@ -49,6 +49,15 @@ NetworkManager::~NetworkManager() MxResult NetworkManager::Tickle() { + // Derive display actor early so it is valid before ProcessPendingRequests + // may toggle the 3rd-person camera (which needs a valid display actor index). + { + LegoPathActor* userActor = UserActor(); + if (userActor) { + DeriveDisplayActorIndex(static_cast(userActor)->GetActorId()); + } + } + ProcessPendingRequests(); m_thirdPersonCamera.Tick(0.016f); @@ -345,11 +354,7 @@ void NetworkManager::BroadcastLocalState() } } - uint8_t displayIndex = m_localDisplayActorIndex; - if (displayIndex == DISPLAY_ACTOR_NONE) { - displayIndex = actorId - 1; // actorId already validated above - } - msg.displayActorIndex = displayIndex; + msg.displayActorIndex = m_localDisplayActorIndex; m_thirdPersonCamera.GetCustomizeState().Pack(msg.customizeData); msg.customizeFlags = m_localAllowRemoteCustomize ? 0x01 : 0x00; @@ -597,9 +602,22 @@ void NetworkManager::SendEmote(uint8_t p_emoteId) void NetworkManager::SetDisplayActorIndex(uint8_t p_displayActorIndex) { m_localDisplayActorIndex = p_displayActorIndex; + m_displayActorFrozen = true; m_thirdPersonCamera.SetDisplayActorIndex(p_displayActorIndex); } +void NetworkManager::DeriveDisplayActorIndex(uint8_t p_actorId) +{ + if (m_displayActorFrozen || !IsValidActorId(p_actorId)) { + return; + } + uint8_t derived = p_actorId - 1; + if (derived != m_localDisplayActorIndex) { + m_localDisplayActorIndex = derived; + m_thirdPersonCamera.SetDisplayActorIndex(derived); + } +} + void NetworkManager::HandleEmote(const EmoteMsg& p_msg) { uint32_t peerId = p_msg.header.peerId; @@ -747,17 +765,19 @@ void NetworkManager::HandleCustomize(const CustomizeMsg& p_msg) if (effectROI) { CharacterCustomizer::PlayClickSound( - effectROI, m_thirdPersonCamera.GetCustomizeState(), p_msg.changeType == CHANGE_MOOD + effectROI, + m_thirdPersonCamera.GetCustomizeState(), + p_msg.changeType == CHANGE_MOOD ); // Only play click animation in 3rd person (not visible in 1st person) if (m_thirdPersonCamera.GetDisplayROI() && !m_thirdPersonCamera.IsInVehicle()) { MxU32 clickAnimId = CharacterCustomizer::PlayClickAnimation( - m_thirdPersonCamera.GetDisplayROI(), m_thirdPersonCamera.GetCustomizeState() + m_thirdPersonCamera.GetDisplayROI(), + m_thirdPersonCamera.GetCustomizeState() ); m_thirdPersonCamera.SetClickAnimObjectId(clickAnimId); } } } } - diff --git a/extensions/src/multiplayer/remoteplayer.cpp b/extensions/src/multiplayer/remoteplayer.cpp index a0e092ac..0a7f515a 100644 --- a/extensions/src/multiplayer/remoteplayer.cpp +++ b/extensions/src/multiplayer/remoteplayer.cpp @@ -6,7 +6,6 @@ #include "3dmanager/lego3dmanager.h" #include "anim/legoanim.h" #include "extensions/multiplayer/charactercloner.h" -#include "legoactor.h" #include "legoanimpresenter.h" #include "legocharactermanager.h" #include "legovideomanager.h" @@ -86,7 +85,7 @@ void RemotePlayer::Spawn(LegoWorld* p_isleWorld) m_visible = false; // Initialize customize state from the display actor's info - uint8_t actorInfoIndex = CharacterCustomizer::ResolveActorInfoIndex(m_displayActorIndex, m_actorId); + uint8_t actorInfoIndex = CharacterCustomizer::ResolveActorInfoIndex(m_displayActorIndex); m_customizeState.InitFromActorInfo(actorInfoIndex); // Build initial walk and idle animation caches @@ -128,10 +127,7 @@ void RemotePlayer::Despawn() const char* RemotePlayer::GetDisplayActorName() const { - if (IsValidDisplayActorIndex(m_displayActorIndex)) { - return CharacterManager()->GetActorName(m_displayActorIndex); - } - return LegoActor::GetActorName(m_actorId); + return CharacterManager()->GetActorName(m_displayActorIndex); } void RemotePlayer::UpdateFromNetwork(const PlayerStateMsg& p_msg) @@ -180,7 +176,7 @@ void RemotePlayer::UpdateFromNetwork(const PlayerStateMsg& p_msg) newState.Unpack(p_msg.customizeData); if (newState != m_customizeState) { - uint8_t actorInfoIndex = CharacterCustomizer::ResolveActorInfoIndex(m_displayActorIndex, m_actorId); + uint8_t actorInfoIndex = CharacterCustomizer::ResolveActorInfoIndex(m_displayActorIndex); m_customizeState = newState; if (m_spawned && m_roi) { CharacterCustomizer::ApplyFullState(m_roi, actorInfoIndex, m_customizeState); diff --git a/extensions/src/multiplayer/thirdpersoncamera.cpp b/extensions/src/multiplayer/thirdpersoncamera.cpp index 4a08f066..d797042a 100644 --- a/extensions/src/multiplayer/thirdpersoncamera.cpp +++ b/extensions/src/multiplayer/thirdpersoncamera.cpp @@ -68,13 +68,9 @@ void ThirdPersonCamera::Disable() // direction. This keeps the 1st-person camera facing the same way // as the 3rd-person camera, and ensures the network direction stays // consistent (no 180-degree flip for others). - // For walking characters the target is m_playerROI; for vehicles it - // is the vehicle actor's ROI (UserActor() returns the vehicle). - // When a display actor override is active, flip the native ROI (not the - // display clone) since TransformPointOfView uses it for the 1st-person camera. - LegoROI* turnAroundROI = (m_currentVehicleType == VEHICLE_NONE && !HasDisplayOverride()) - ? m_playerROI - : (userActor ? userActor->GetROI() : nullptr); + // Flip the native ROI (not the display clone) since TransformPointOfView + // uses it for the 1st-person camera. + LegoROI* turnAroundROI = userActor ? userActor->GetROI() : nullptr; if (turnAroundROI) { FlipROIDirection(turnAroundROI); @@ -160,14 +156,9 @@ void ThirdPersonCamera::OnActorEnter(IslePathActor* p_actor) } // Non-vehicle (walking character) entry — Enter() already called TurnAround. - if (IsValidDisplayActorIndex(m_displayActorIndex)) { - newROI->SetVisibility(FALSE); - if (!EnsureDisplayROI()) { - return; - } - } - else { - m_playerROI = newROI; + newROI->SetVisibility(FALSE); + if (!EnsureDisplayROI()) { + return; } m_roiUnflipped = false; m_active = true; @@ -244,9 +235,9 @@ void ThirdPersonCamera::OnCamAnimEnd(LegoPathActor* p_actor) // FUN_1004b6d0's PlaceActor set the ROI with standard direction // (z = visual forward). The 3rd person camera needs backward-z. // Flip the ROI direction, then re-setup the camera. - // When a display actor override is active, flip the native ROI (not the - // display clone) since Tick() syncs the clone's transform from it. - LegoROI* roi = (m_currentVehicleType == VEHICLE_NONE && !HasDisplayOverride()) ? m_playerROI : p_actor->GetROI(); + // Flip the native ROI (not the display clone) since Tick() syncs the + // clone's transform from it. + LegoROI* roi = p_actor->GetROI(); if (roi) { FlipROIDirection(roi); } @@ -497,10 +488,7 @@ void ThirdPersonCamera::TriggerEmote(uint8_t p_emoteId) void ThirdPersonCamera::ApplyCustomizeChange(uint8_t p_changeType, uint8_t p_partIndex) { - uint8_t actorInfoIndex = CharacterCustomizer::ResolveActorInfoIndex( - m_displayActorIndex, - GameState() ? GameState()->GetActorId() : 0 - ); + uint8_t actorInfoIndex = CharacterCustomizer::ResolveActorInfoIndex(m_displayActorIndex); CharacterCustomizer::ApplyChange(m_displayROI, actorInfoIndex, m_customizeState, p_changeType, p_partIndex); } @@ -936,7 +924,7 @@ void ThirdPersonCamera::ReinitForCharacter() m_currentVehicleType = vehicleType; if (vehicleType != VEHICLE_NONE) { - if (IsValidDisplayActorIndex(m_displayActorIndex) && !EnsureDisplayROI()) { + if (!EnsureDisplayROI()) { m_active = false; return; } @@ -969,23 +957,18 @@ void ThirdPersonCamera::ReinitForCharacter() } // Reinitializing for walking character - if (IsValidDisplayActorIndex(m_displayActorIndex)) { - roi->SetVisibility(FALSE); - if (!EnsureDisplayROI()) { - m_active = false; - return; - } - } - else { - m_playerROI = roi; + roi->SetVisibility(FALSE); + if (!EnsureDisplayROI()) { + m_active = false; + return; } // Re-apply TurnAround if we undid it in Disable(). // Only set the local matrix here; the subsequent Add() will propagate world data. - // When a display actor override is active, flip the native ROI (not the - // display clone) since Tick() syncs the clone's transform from it. + // Flip the native ROI (not the display clone) since Tick() syncs the + // clone's transform from it. if (m_roiUnflipped) { - FlipROIDirection(HasDisplayOverride() ? roi : m_playerROI); + FlipROIDirection(roi); m_roiUnflipped = false; }