Improve Vault109 chargen flow and LooksMenu handling
Move first-time character menu orchestration out of Papyrus and into the C++ session-launch flow. CO_Vault109StartQuestScript.psc now only teleports the player and sets stage 10. F4TSessionLaunch.cpp adds camera preparation/restoration, direct PlayerCamera/ThirdPersonState face-camera tuning, primary console ShowLooksMenu 14 path, Actor.ShowLooksMenu fallback, retry/timeouts and guarded state resets. Added logging and counters for Looks/SPECIAL menu polling. Updated changelog.md and docs/dev-log.md to describe the new flow and testing notes.
This commit is contained in:
@@ -10,7 +10,9 @@
|
||||
#include "RE/G/GameScript.h"
|
||||
#include "RE/L/LoadingMenu.h"
|
||||
#include "RE/M/MainMenu.h"
|
||||
#include "RE/P/PlayerCamera.h"
|
||||
#include "RE/P/PlayerCharacter.h"
|
||||
#include "RE/T/ThirdPersonState.h"
|
||||
#include "RE/T/TESWorldSpace.h"
|
||||
#include "RE/U/UI.h"
|
||||
|
||||
@@ -25,6 +27,11 @@ namespace
|
||||
constexpr int kSpawnPollIntervalFrames = 15;
|
||||
constexpr int kMaxSpawnWaitAttempts = 600;
|
||||
constexpr int kWorldReadyDelayPolls = 10;
|
||||
constexpr int kLooksMenuFallbackDelayPolls = 40;
|
||||
constexpr int kLooksMenuOpenMaxPolls = 300;
|
||||
constexpr int kLooksMenuCameraNudgeMaxPolls = 20;
|
||||
constexpr float kPi = 3.14159265358979323846F;
|
||||
constexpr float kLooksMenuFaceCameraZoomOffset = -90.0F;
|
||||
|
||||
enum class CharGenPhase
|
||||
{
|
||||
@@ -45,9 +52,13 @@ namespace
|
||||
bool g_vault109StartQuestIssued = false;
|
||||
bool g_sawLooksMenuOpen = false;
|
||||
bool g_sawSpecialMenuOpen = false;
|
||||
bool g_looksMenuFallbackIssued = false;
|
||||
bool g_looksMenuCameraContextApplied = false;
|
||||
int g_spawnPollFrameCounter = 0;
|
||||
int g_spawnWaitAttempts = 0;
|
||||
int g_worldReadyPolls = 0;
|
||||
int g_looksMenuOpenPolls = 0;
|
||||
int g_looksMenuCameraNudgePolls = 0;
|
||||
|
||||
std::string GetLogPrefix()
|
||||
{
|
||||
@@ -244,8 +255,92 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
float NormalizeRadians(float a_angle)
|
||||
{
|
||||
while (a_angle > kPi) {
|
||||
a_angle -= 2.0F * kPi;
|
||||
}
|
||||
while (a_angle < -kPi) {
|
||||
a_angle += 2.0F * kPi;
|
||||
}
|
||||
return a_angle;
|
||||
}
|
||||
|
||||
bool ForcePlayerThirdPersonCamera()
|
||||
{
|
||||
auto* camera = RE::PlayerCamera::GetSingleton();
|
||||
if (!camera) {
|
||||
LogWarning("PlayerCamera unavailable while forcing third-person camera.");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto thirdPersonState = camera->GetState(RE::CameraState::k3rdPerson);
|
||||
if (!thirdPersonState) {
|
||||
LogWarning("Third-person camera state unavailable while forcing Looks menu camera.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto* state = static_cast<RE::ThirdPersonState*>(thirdPersonState.get())) {
|
||||
const auto* player = RE::PlayerCharacter::GetSingleton();
|
||||
const float faceCameraYaw = NormalizeRadians((player ? player->GetHeading() : camera->heading) + kPi);
|
||||
|
||||
state->SetFreeRotationMode(true, true);
|
||||
state->freeRotation.x = faceCameraYaw;
|
||||
state->freeRotation.y = 0.0F;
|
||||
state->targetYaw = faceCameraYaw;
|
||||
state->currentYaw = faceCameraYaw;
|
||||
state->targetShoulderOffset = RE::NiPoint3{ 0.0F, 0.0F, 0.0F };
|
||||
state->currentShoulderOffset = RE::NiPoint3{ 0.0F, 0.0F, 0.0F };
|
||||
state->sideOffsetCollisionBlend = RE::NiPoint3{ 0.0F, 0.0F, 0.0F };
|
||||
state->targetZoomOffset = kLooksMenuFaceCameraZoomOffset;
|
||||
state->currentZoomOffset = kLooksMenuFaceCameraZoomOffset;
|
||||
state->cameraHeightAdjust = 0.0F;
|
||||
state->show3rdPersonModel = true;
|
||||
state->zoomingInto1st = false;
|
||||
state->preserveRotation = true;
|
||||
state->applyOffsets = true;
|
||||
}
|
||||
|
||||
camera->SetState(thirdPersonState.get());
|
||||
return camera->QCameraEquals(RE::CameraState::k3rdPerson);
|
||||
}
|
||||
|
||||
void PrepareCharGenCameraContext()
|
||||
{
|
||||
ForcePlayerThirdPersonCamera();
|
||||
RE::Console::ExecuteCommand("ForceThirdPerson");
|
||||
LogInfo("Prepared camera context before Looks menu.");
|
||||
}
|
||||
|
||||
void ApplyLooksMenuCameraContext()
|
||||
{
|
||||
if (g_looksMenuCameraContextApplied) {
|
||||
return;
|
||||
}
|
||||
|
||||
RE::Console::ExecuteCommand("SetInChargen 1");
|
||||
ForcePlayerThirdPersonCamera();
|
||||
RE::Console::ExecuteCommand("ForceThirdPerson");
|
||||
g_looksMenuCameraContextApplied = true;
|
||||
g_looksMenuCameraNudgePolls = 0;
|
||||
LogInfo("Applied chargen camera context after Looks menu open.");
|
||||
}
|
||||
|
||||
void RestoreCharGenCameraContext()
|
||||
{
|
||||
if (!g_looksMenuCameraContextApplied) {
|
||||
return;
|
||||
}
|
||||
|
||||
RE::Console::ExecuteCommand("SetInChargen 0");
|
||||
g_looksMenuCameraContextApplied = false;
|
||||
g_looksMenuCameraNudgePolls = 0;
|
||||
LogInfo("Restored chargen camera context after Looks menu.");
|
||||
}
|
||||
|
||||
void ResetFirstJoinState()
|
||||
{
|
||||
RestoreCharGenCameraContext();
|
||||
g_charGenPhase = CharGenPhase::None;
|
||||
g_pendingProfileBind = false;
|
||||
g_pendingProfileBindServerKey.clear();
|
||||
@@ -253,13 +348,18 @@ namespace
|
||||
g_vault109StartQuestIssued = false;
|
||||
g_sawLooksMenuOpen = false;
|
||||
g_sawSpecialMenuOpen = false;
|
||||
g_looksMenuFallbackIssued = false;
|
||||
g_looksMenuCameraContextApplied = false;
|
||||
g_spawnPollFrameCounter = 0;
|
||||
g_spawnWaitAttempts = 0;
|
||||
g_worldReadyPolls = 0;
|
||||
g_looksMenuOpenPolls = 0;
|
||||
g_looksMenuCameraNudgePolls = 0;
|
||||
}
|
||||
|
||||
void QueueFirstTimeCocJoin(const std::string& a_serverKey)
|
||||
{
|
||||
RestoreCharGenCameraContext();
|
||||
g_charGenPhase = CharGenPhase::CocLoading;
|
||||
g_pendingProfileBind = true;
|
||||
g_pendingProfileBindServerKey = a_serverKey;
|
||||
@@ -267,9 +367,13 @@ namespace
|
||||
g_vault109StartQuestIssued = false;
|
||||
g_sawLooksMenuOpen = false;
|
||||
g_sawSpecialMenuOpen = false;
|
||||
g_looksMenuFallbackIssued = false;
|
||||
g_looksMenuCameraContextApplied = false;
|
||||
g_spawnPollFrameCounter = 0;
|
||||
g_spawnWaitAttempts = 0;
|
||||
g_worldReadyPolls = 0;
|
||||
g_looksMenuOpenPolls = 0;
|
||||
g_looksMenuCameraNudgePolls = 0;
|
||||
LogInfo(std::format("Queued first-time coc join for server {}.", a_serverKey));
|
||||
}
|
||||
|
||||
@@ -295,7 +399,14 @@ namespace
|
||||
LogInfo("Issued startquest CO_Vault109StartQuest for first-time COVault109 flow.");
|
||||
}
|
||||
|
||||
void OpenLooksMenu()
|
||||
void OpenLooksMenuPrimary()
|
||||
{
|
||||
PrepareCharGenCameraContext();
|
||||
RE::Console::ExecuteCommand("ShowLooksMenu 14");
|
||||
LogInfo("Issued 'ShowLooksMenu 14' for first-time character creation.");
|
||||
}
|
||||
|
||||
void OpenLooksMenuFallback()
|
||||
{
|
||||
auto* gameVM = RE::GameVM::GetSingleton();
|
||||
auto* player = RE::PlayerCharacter::GetSingleton();
|
||||
@@ -307,13 +418,13 @@ namespace
|
||||
"Actor",
|
||||
"ShowLooksMenu",
|
||||
nullptr);
|
||||
LogInfo("Dispatched Actor.ShowLooksMenu for first-time character creation.");
|
||||
LogInfo("Fallback: dispatched Actor.ShowLooksMenu for first-time character creation.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RE::Console::ExecuteCommand("CallFunction Actor ShowLooksMenu player");
|
||||
LogInfo("Fell back to CallFunction Actor ShowLooksMenu player.");
|
||||
LogInfo("Fallback: used CallFunction Actor ShowLooksMenu player.");
|
||||
}
|
||||
|
||||
void OpenSpecialMenu()
|
||||
@@ -397,6 +508,7 @@ namespace
|
||||
|
||||
void CompleteFirstTimeJoin(RE::PlayerCharacter& a_player)
|
||||
{
|
||||
RestoreCharGenCameraContext();
|
||||
LogPlayerLocation("First-time join complete after character creation", a_player);
|
||||
g_charGenPhase = CharGenPhase::Complete;
|
||||
g_spawnWaitAttempts = 0;
|
||||
@@ -452,21 +564,46 @@ namespace
|
||||
}
|
||||
|
||||
if (g_charGenPhase == CharGenPhase::OpeningLooksMenu) {
|
||||
OpenLooksMenu();
|
||||
g_sawLooksMenuOpen = false;
|
||||
g_looksMenuFallbackIssued = false;
|
||||
g_looksMenuOpenPolls = 0;
|
||||
g_looksMenuCameraNudgePolls = 0;
|
||||
OpenLooksMenuPrimary();
|
||||
g_charGenPhase = CharGenPhase::WaitingForLooksMenuClose;
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_charGenPhase == CharGenPhase::WaitingForLooksMenuClose) {
|
||||
if (IsMenuOpen("LooksMenu")) {
|
||||
g_sawLooksMenuOpen = true;
|
||||
if (!g_sawLooksMenuOpen) {
|
||||
g_sawLooksMenuOpen = true;
|
||||
ApplyLooksMenuCameraContext();
|
||||
}
|
||||
if (g_looksMenuCameraNudgePolls < kLooksMenuCameraNudgeMaxPolls) {
|
||||
++g_looksMenuCameraNudgePolls;
|
||||
ForcePlayerThirdPersonCamera();
|
||||
RE::Console::ExecuteCommand("ForceThirdPerson");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_sawLooksMenuOpen) {
|
||||
++g_looksMenuOpenPolls;
|
||||
|
||||
if (!g_looksMenuFallbackIssued && g_looksMenuOpenPolls >= kLooksMenuFallbackDelayPolls) {
|
||||
g_looksMenuFallbackIssued = true;
|
||||
LogWarning("Looks menu did not open after primary ShowLooksMenu command; issuing Actor.ShowLooksMenu fallback.");
|
||||
OpenLooksMenuFallback();
|
||||
}
|
||||
|
||||
if (g_looksMenuOpenPolls >= kLooksMenuOpenMaxPolls) {
|
||||
LogWarning("Timed out waiting for Looks menu to open.");
|
||||
ResetFirstJoinState();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RestoreCharGenCameraContext();
|
||||
g_charGenPhase = CharGenPhase::OpeningSpecialMenu;
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user