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.
This commit is contained in:
foxtacles 2026-03-07 08:32:21 -08:00 committed by GitHub
parent 237dca8f51
commit dcf3b66173
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 35 additions and 0 deletions

View File

@ -3,6 +3,7 @@
#include "3dmanager/lego3dmanager.h" #include "3dmanager/lego3dmanager.h"
#include "decomp.h" #include "decomp.h"
#include "define.h" #include "define.h"
#include "extensions/multiplayer.h"
#include "islepathactor.h" #include "islepathactor.h"
#include "legoanimationmanager.h" #include "legoanimationmanager.h"
#include "legoanimpresenter.h" #include "legoanimpresenter.h"
@ -20,6 +21,8 @@
#include "mxtimer.h" #include "mxtimer.h"
#include "mxutilities.h" #include "mxutilities.h"
using namespace Extensions;
DECOMP_SIZE_ASSERT(LegoAnimMMPresenter, 0x74) DECOMP_SIZE_ASSERT(LegoAnimMMPresenter, 0x74)
// FUNCTION: LEGO1 0x1004a8d0 // FUNCTION: LEGO1 0x1004a8d0
@ -480,6 +483,10 @@ MxBool LegoAnimMMPresenter::FUN_1004b6d0(MxLong p_time)
} }
actor->SetActorState(LegoPathActor::c_initial); actor->SetActorState(LegoPathActor::c_initial);
if (m_tranInfo->m_unk0x29) {
Extension<MultiplayerExt>::Call(HandleCamAnimEnd, actor);
}
} }
return TRUE; return TRUE;

View File

@ -38,6 +38,7 @@ class MultiplayerExt {
static void HandleActorEnter(IslePathActor* p_actor); static void HandleActorEnter(IslePathActor* p_actor);
static void HandleActorExit(IslePathActor* p_actor); static void HandleActorExit(IslePathActor* p_actor);
static void HandleCamAnimEnd(LegoPathActor* p_actor);
static MxBool ShouldInvertMovement(LegoPathActor* p_actor); static MxBool ShouldInvertMovement(LegoPathActor* p_actor);
// Returns true if the multiplayer connection was rejected (e.g. room full). // Returns true if the multiplayer connection was rejected (e.g. room full).
@ -59,6 +60,7 @@ constexpr auto HandleWorldEnable = &MultiplayerExt::HandleWorldEnable;
constexpr auto HandleEntityNotify = &MultiplayerExt::HandleEntityNotify; constexpr auto HandleEntityNotify = &MultiplayerExt::HandleEntityNotify;
constexpr auto HandleActorEnter = &MultiplayerExt::HandleActorEnter; constexpr auto HandleActorEnter = &MultiplayerExt::HandleActorEnter;
constexpr auto HandleActorExit = &MultiplayerExt::HandleActorExit; constexpr auto HandleActorExit = &MultiplayerExt::HandleActorExit;
constexpr auto HandleCamAnimEnd = &MultiplayerExt::HandleCamAnimEnd;
constexpr auto ShouldInvertMovement = &MultiplayerExt::ShouldInvertMovement; constexpr auto ShouldInvertMovement = &MultiplayerExt::ShouldInvertMovement;
constexpr auto CheckRejected = &MultiplayerExt::CheckRejected; constexpr auto CheckRejected = &MultiplayerExt::CheckRejected;
#else #else
@ -66,6 +68,7 @@ constexpr decltype(&MultiplayerExt::HandleWorldEnable) HandleWorldEnable = nullp
constexpr decltype(&MultiplayerExt::HandleEntityNotify) HandleEntityNotify = nullptr; constexpr decltype(&MultiplayerExt::HandleEntityNotify) HandleEntityNotify = nullptr;
constexpr decltype(&MultiplayerExt::HandleActorEnter) HandleActorEnter = nullptr; constexpr decltype(&MultiplayerExt::HandleActorEnter) HandleActorEnter = nullptr;
constexpr decltype(&MultiplayerExt::HandleActorExit) HandleActorExit = nullptr; constexpr decltype(&MultiplayerExt::HandleActorExit) HandleActorExit = nullptr;
constexpr decltype(&MultiplayerExt::HandleCamAnimEnd) HandleCamAnimEnd = nullptr;
constexpr decltype(&MultiplayerExt::ShouldInvertMovement) ShouldInvertMovement = nullptr; constexpr decltype(&MultiplayerExt::ShouldInvertMovement) ShouldInvertMovement = nullptr;
constexpr decltype(&MultiplayerExt::CheckRejected) CheckRejected = nullptr; constexpr decltype(&MultiplayerExt::CheckRejected) CheckRejected = nullptr;
#endif #endif

View File

@ -30,6 +30,7 @@ class ThirdPersonCamera {
// Core hooks // Core hooks
void OnActorEnter(IslePathActor* p_actor); void OnActorEnter(IslePathActor* p_actor);
void OnActorExit(IslePathActor* p_actor); void OnActorExit(IslePathActor* p_actor);
void OnCamAnimEnd(LegoPathActor* p_actor);
// Called every frame from NetworkManager::Tickle() // Called every frame from NetworkManager::Tickle()
void Tick(float p_deltaTime); void Tick(float p_deltaTime);

View File

@ -124,6 +124,13 @@ void MultiplayerExt::HandleActorExit(IslePathActor* p_actor)
} }
} }
void MultiplayerExt::HandleCamAnimEnd(LegoPathActor* p_actor)
{
if (s_networkManager) {
s_networkManager->GetThirdPersonCamera().OnCamAnimEnd(p_actor);
}
}
MxBool MultiplayerExt::ShouldInvertMovement(LegoPathActor* p_actor) MxBool MultiplayerExt::ShouldInvertMovement(LegoPathActor* p_actor)
{ {
if (s_networkManager && UserActor() == p_actor) { if (s_networkManager && UserActor() == p_actor) {

View File

@ -207,6 +207,23 @@ void ThirdPersonCamera::OnActorExit(IslePathActor* p_actor)
} }
} }
void ThirdPersonCamera::OnCamAnimEnd(LegoPathActor* p_actor)
{
if (!m_active) {
return;
}
// 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.
LegoROI* roi = (m_currentVehicleType == VEHICLE_NONE) ? m_playerROI : p_actor->GetROI();
if (roi) {
FlipROIDirection(roi);
}
SetupCamera(p_actor);
}
void ThirdPersonCamera::Tick(float p_deltaTime) void ThirdPersonCamera::Tick(float p_deltaTime)
{ {
if (!m_active) { if (!m_active) {