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:
@@ -42,6 +42,7 @@ For testing notes, milestone summaries, known issues, and next steps, see [`docs
|
|||||||
- When `LooksMenu` is confirmed open, C++ now applies chargen camera context (`SetInChargen 1` + `ForceThirdPerson`) and restores it on close, fixing first-person camera persistence during customization.
|
- When `LooksMenu` is confirmed open, C++ now applies chargen camera context (`SetInChargen 1` + `ForceThirdPerson`) and restores it on close, fixing first-person camera persistence during customization.
|
||||||
- Looks menu camera correction now directly sets `PlayerCamera` to third-person state for several polls while `LooksMenu` is open, avoiding reliance on console-only camera commands.
|
- Looks menu camera correction now directly sets `PlayerCamera` to third-person state for several polls while `LooksMenu` is open, avoiding reliance on console-only camera commands.
|
||||||
- Looks menu camera correction now configures the `ThirdPersonState` for face editing by zeroing shoulder offsets, flipping camera yaw toward the player, enabling free rotation, and applying a closer zoom offset.
|
- Looks menu camera correction now configures the `ThirdPersonState` for face editing by zeroing shoulder offsets, flipping camera yaw toward the player, enabling free rotation, and applying a closer zoom offset.
|
||||||
|
- Looks menu camera cleanup now restores the pre-customization third-person offsets, yaw, zoom, and free-rotation flags so movement controls return to normal after first-time customization closes.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Weather presets store the exact `fw` console argument (8-digit hex); edit `server/world_state_presets.py` only if a preset ID fails in your console.
|
- Weather presets store the exact `fw` console argument (8-digit hex); edit `server/world_state_presets.py` only if a preset ID fails in your console.
|
||||||
|
|||||||
+29
-1
@@ -6245,4 +6245,32 @@ character setup menus using compile-safe Fallout 4 Papyrus calls, then the SPECI
|
|||||||
|
|
||||||
### Next Steps
|
### Next Steps
|
||||||
- Run in-game first-time flow and verify C++ LooksMenu then SPECIAL menu sequence.
|
- Run in-game first-time flow and verify C++ LooksMenu then SPECIAL menu sequence.
|
||||||
- If SPECIAL does not reliably include name entry in your setup, evaluate explicit `ShowNameMenu` before or after SPECIAL based on observed FO4 behavior.
|
- If SPECIAL does not reliably include name entry in your setup, evaluate explicit `ShowNameMenu` before or after SPECIAL based on observed FO4 behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-06-28 - Looks menu camera restoration
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Fixed the first-time character customization close path so the third-person camera returns to its pre-LooksMenu state instead of staying in the front-facing face-edit view.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
- `plugin/src/F4TSessionLaunch.cpp`
|
||||||
|
- `changelog.md`
|
||||||
|
- `docs/dev-log.md`
|
||||||
|
|
||||||
|
### Details
|
||||||
|
- Added a pre-LooksMenu third-person camera snapshot before the C++ flow forces the face-edit camera.
|
||||||
|
- Restored shoulder offsets, zoom offsets, yaw, free-rotation state, model visibility, and offset application flags when LooksMenu closes.
|
||||||
|
- Made reset/retry cleanup restore the snapshot even if the menu flow exits before `SetInChargen 1` was applied.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Not run in this session (Fallout 4/F4SE runtime not available in this environment).
|
||||||
|
- Expected manual check: after closing LooksMenu during first-time server join, third-person camera returns behind the player and `W`/`S`/strafe inputs move normally.
|
||||||
|
- Verify in `CommonwealthOnline.log` that the flow logs `Restored third-person camera state after Looks menu.` before opening SPECIAL.
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- Not yet runtime-validated in-game.
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- Run first-time join through Vault 109, close LooksMenu, then confirm camera offset and movement controls before and after SPECIAL closes.
|
||||||
|
|||||||
@@ -33,6 +33,24 @@ namespace
|
|||||||
constexpr float kPi = 3.14159265358979323846F;
|
constexpr float kPi = 3.14159265358979323846F;
|
||||||
constexpr float kLooksMenuFaceCameraZoomOffset = -90.0F;
|
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
|
enum class CharGenPhase
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
@@ -54,6 +72,7 @@ namespace
|
|||||||
bool g_sawSpecialMenuOpen = false;
|
bool g_sawSpecialMenuOpen = false;
|
||||||
bool g_looksMenuFallbackIssued = false;
|
bool g_looksMenuFallbackIssued = false;
|
||||||
bool g_looksMenuCameraContextApplied = false;
|
bool g_looksMenuCameraContextApplied = false;
|
||||||
|
std::optional<ThirdPersonCameraSnapshot> g_preLooksCameraSnapshot;
|
||||||
int g_spawnPollFrameCounter = 0;
|
int g_spawnPollFrameCounter = 0;
|
||||||
int g_spawnWaitAttempts = 0;
|
int g_spawnWaitAttempts = 0;
|
||||||
int g_worldReadyPolls = 0;
|
int g_worldReadyPolls = 0;
|
||||||
@@ -266,6 +285,81 @@ namespace
|
|||||||
return a_angle;
|
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()
|
bool ForcePlayerThirdPersonCamera()
|
||||||
{
|
{
|
||||||
auto* camera = RE::PlayerCamera::GetSingleton();
|
auto* camera = RE::PlayerCamera::GetSingleton();
|
||||||
@@ -307,6 +401,7 @@ namespace
|
|||||||
|
|
||||||
void PrepareCharGenCameraContext()
|
void PrepareCharGenCameraContext()
|
||||||
{
|
{
|
||||||
|
CapturePreLooksCameraState();
|
||||||
ForcePlayerThirdPersonCamera();
|
ForcePlayerThirdPersonCamera();
|
||||||
RE::Console::ExecuteCommand("ForceThirdPerson");
|
RE::Console::ExecuteCommand("ForceThirdPerson");
|
||||||
LogInfo("Prepared camera context before Looks menu.");
|
LogInfo("Prepared camera context before Looks menu.");
|
||||||
@@ -319,6 +414,7 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
RE::Console::ExecuteCommand("SetInChargen 1");
|
RE::Console::ExecuteCommand("SetInChargen 1");
|
||||||
|
CapturePreLooksCameraState();
|
||||||
ForcePlayerThirdPersonCamera();
|
ForcePlayerThirdPersonCamera();
|
||||||
RE::Console::ExecuteCommand("ForceThirdPerson");
|
RE::Console::ExecuteCommand("ForceThirdPerson");
|
||||||
g_looksMenuCameraContextApplied = true;
|
g_looksMenuCameraContextApplied = true;
|
||||||
@@ -328,11 +424,14 @@ namespace
|
|||||||
|
|
||||||
void RestoreCharGenCameraContext()
|
void RestoreCharGenCameraContext()
|
||||||
{
|
{
|
||||||
if (!g_looksMenuCameraContextApplied) {
|
if (!g_looksMenuCameraContextApplied && !g_preLooksCameraSnapshot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RE::Console::ExecuteCommand("SetInChargen 0");
|
if (g_looksMenuCameraContextApplied) {
|
||||||
|
RE::Console::ExecuteCommand("SetInChargen 0");
|
||||||
|
}
|
||||||
|
RestorePreLooksCameraState();
|
||||||
g_looksMenuCameraContextApplied = false;
|
g_looksMenuCameraContextApplied = false;
|
||||||
g_looksMenuCameraNudgePolls = 0;
|
g_looksMenuCameraNudgePolls = 0;
|
||||||
LogInfo("Restored chargen camera context after Looks menu.");
|
LogInfo("Restored chargen camera context after Looks menu.");
|
||||||
@@ -350,6 +449,7 @@ namespace
|
|||||||
g_sawSpecialMenuOpen = false;
|
g_sawSpecialMenuOpen = false;
|
||||||
g_looksMenuFallbackIssued = false;
|
g_looksMenuFallbackIssued = false;
|
||||||
g_looksMenuCameraContextApplied = false;
|
g_looksMenuCameraContextApplied = false;
|
||||||
|
g_preLooksCameraSnapshot.reset();
|
||||||
g_spawnPollFrameCounter = 0;
|
g_spawnPollFrameCounter = 0;
|
||||||
g_spawnWaitAttempts = 0;
|
g_spawnWaitAttempts = 0;
|
||||||
g_worldReadyPolls = 0;
|
g_worldReadyPolls = 0;
|
||||||
@@ -369,6 +469,7 @@ namespace
|
|||||||
g_sawSpecialMenuOpen = false;
|
g_sawSpecialMenuOpen = false;
|
||||||
g_looksMenuFallbackIssued = false;
|
g_looksMenuFallbackIssued = false;
|
||||||
g_looksMenuCameraContextApplied = false;
|
g_looksMenuCameraContextApplied = false;
|
||||||
|
g_preLooksCameraSnapshot.reset();
|
||||||
g_spawnPollFrameCounter = 0;
|
g_spawnPollFrameCounter = 0;
|
||||||
g_spawnWaitAttempts = 0;
|
g_spawnWaitAttempts = 0;
|
||||||
g_worldReadyPolls = 0;
|
g_worldReadyPolls = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user