Restore LooksMenu camera state

Capture the pre-LooksMenu third-person camera settings and restore them when character customization closes, so first-time customization returns to the normal over-the-shoulder view and movement controls. Also clear the saved snapshot during reset paths so cleanup still runs if the menu flow exits early.
This commit is contained in:
2026-06-28 18:59:59 +12:00
parent 22394a77ff
commit 2f37feb5b7
3 changed files with 133 additions and 3 deletions
+103 -2
View File
@@ -33,6 +33,24 @@ namespace
constexpr float kPi = 3.14159265358979323846F;
constexpr float kLooksMenuFaceCameraZoomOffset = -90.0F;
struct ThirdPersonCameraSnapshot
{
RE::NiPoint3 targetShoulderOffset;
RE::NiPoint3 currentShoulderOffset;
RE::NiPoint3 sideOffsetCollisionBlend;
RE::NiPoint2 freeRotation;
float targetZoomOffset;
float currentZoomOffset;
float targetYaw;
float currentYaw;
float cameraHeightAdjust;
bool freeRotationEnabled;
bool zoomingInto1st;
bool show3rdPersonModel;
bool preserveRotation;
bool applyOffsets;
};
enum class CharGenPhase
{
None,
@@ -54,6 +72,7 @@ namespace
bool g_sawSpecialMenuOpen = false;
bool g_looksMenuFallbackIssued = false;
bool g_looksMenuCameraContextApplied = false;
std::optional<ThirdPersonCameraSnapshot> g_preLooksCameraSnapshot;
int g_spawnPollFrameCounter = 0;
int g_spawnWaitAttempts = 0;
int g_worldReadyPolls = 0;
@@ -266,6 +285,81 @@ namespace
return a_angle;
}
RE::ThirdPersonState* GetThirdPersonCameraState()
{
auto* camera = RE::PlayerCamera::GetSingleton();
if (!camera) {
return nullptr;
}
auto thirdPersonState = camera->GetState(RE::CameraState::k3rdPerson);
return thirdPersonState ? static_cast<RE::ThirdPersonState*>(thirdPersonState.get()) : nullptr;
}
void CapturePreLooksCameraState()
{
if (g_preLooksCameraSnapshot) {
return;
}
auto* state = GetThirdPersonCameraState();
if (!state) {
LogWarning("Third-person camera state unavailable while capturing pre-Looks camera state.");
return;
}
g_preLooksCameraSnapshot = ThirdPersonCameraSnapshot{
state->targetShoulderOffset,
state->currentShoulderOffset,
state->sideOffsetCollisionBlend,
state->freeRotation,
state->targetZoomOffset,
state->currentZoomOffset,
state->targetYaw,
state->currentYaw,
state->cameraHeightAdjust,
state->freeRotationEnabled,
state->zoomingInto1st,
state->show3rdPersonModel,
state->preserveRotation,
state->applyOffsets
};
}
void RestorePreLooksCameraState()
{
if (!g_preLooksCameraSnapshot) {
return;
}
auto* state = GetThirdPersonCameraState();
if (!state) {
LogWarning("Third-person camera state unavailable while restoring pre-Looks camera state.");
g_preLooksCameraSnapshot.reset();
return;
}
const auto snapshot = *g_preLooksCameraSnapshot;
state->SetFreeRotationMode(snapshot.freeRotationEnabled, false);
state->targetShoulderOffset = snapshot.targetShoulderOffset;
state->currentShoulderOffset = snapshot.currentShoulderOffset;
state->sideOffsetCollisionBlend = snapshot.sideOffsetCollisionBlend;
state->freeRotation = snapshot.freeRotation;
state->targetZoomOffset = snapshot.targetZoomOffset;
state->currentZoomOffset = snapshot.currentZoomOffset;
state->targetYaw = snapshot.targetYaw;
state->currentYaw = snapshot.currentYaw;
state->cameraHeightAdjust = snapshot.cameraHeightAdjust;
state->freeRotationEnabled = snapshot.freeRotationEnabled;
state->zoomingInto1st = snapshot.zoomingInto1st;
state->show3rdPersonModel = snapshot.show3rdPersonModel;
state->preserveRotation = snapshot.preserveRotation;
state->applyOffsets = snapshot.applyOffsets;
g_preLooksCameraSnapshot.reset();
LogInfo("Restored third-person camera state after Looks menu.");
}
bool ForcePlayerThirdPersonCamera()
{
auto* camera = RE::PlayerCamera::GetSingleton();
@@ -307,6 +401,7 @@ namespace
void PrepareCharGenCameraContext()
{
CapturePreLooksCameraState();
ForcePlayerThirdPersonCamera();
RE::Console::ExecuteCommand("ForceThirdPerson");
LogInfo("Prepared camera context before Looks menu.");
@@ -319,6 +414,7 @@ namespace
}
RE::Console::ExecuteCommand("SetInChargen 1");
CapturePreLooksCameraState();
ForcePlayerThirdPersonCamera();
RE::Console::ExecuteCommand("ForceThirdPerson");
g_looksMenuCameraContextApplied = true;
@@ -328,11 +424,14 @@ namespace
void RestoreCharGenCameraContext()
{
if (!g_looksMenuCameraContextApplied) {
if (!g_looksMenuCameraContextApplied && !g_preLooksCameraSnapshot) {
return;
}
RE::Console::ExecuteCommand("SetInChargen 0");
if (g_looksMenuCameraContextApplied) {
RE::Console::ExecuteCommand("SetInChargen 0");
}
RestorePreLooksCameraState();
g_looksMenuCameraContextApplied = false;
g_looksMenuCameraNudgePolls = 0;
LogInfo("Restored chargen camera context after Looks menu.");
@@ -350,6 +449,7 @@ namespace
g_sawSpecialMenuOpen = false;
g_looksMenuFallbackIssued = false;
g_looksMenuCameraContextApplied = false;
g_preLooksCameraSnapshot.reset();
g_spawnPollFrameCounter = 0;
g_spawnWaitAttempts = 0;
g_worldReadyPolls = 0;
@@ -369,6 +469,7 @@ namespace
g_sawSpecialMenuOpen = false;
g_looksMenuFallbackIssued = false;
g_looksMenuCameraContextApplied = false;
g_preLooksCameraSnapshot.reset();
g_spawnPollFrameCounter = 0;
g_spawnWaitAttempts = 0;
g_worldReadyPolls = 0;