Start first-time join in COVault109 and trigger quest

Change first-time session launch to use the COVault109 start cell and invoke a start quest so the quest can place the player at its preset spawn. Replaces coc SanctuaryExt with coc COVault109, changes the spawn-detection to match the COVault109 editor ID, adds a g_vault109StartQuestIssued flag and IssueVault109StartQuest() to run `startquest CO_Vault109StartQuest` once, and updates logs and reset logic. Also updates docs (changelog.md, docs/dev-log.md) and includes the updated CommonwealthOnline_Vault109.esp binary.
This commit is contained in:
2026-06-26 17:59:57 +12:00
parent 8772e024f8
commit 904394999a
4 changed files with 66 additions and 24 deletions
+31 -24
View File
@@ -21,8 +21,7 @@
namespace
{
constexpr std::uint32_t kCommonwealthWorldspaceFormId = 0x0000003C;
constexpr std::uint32_t kSanctuaryExtCellFormId = 0x0000DD60;
constexpr std::string_view kFirstJoinStartCellEditorId = "COVault109";
constexpr int kSpawnPollIntervalFrames = 15;
constexpr int kMaxSpawnWaitAttempts = 600;
constexpr int kWorldReadyDelayPolls = 10;
@@ -43,6 +42,7 @@ namespace
CharGenPhase g_charGenPhase = CharGenPhase::None;
bool g_pendingProfileBind = false;
bool g_cocIssued = false;
bool g_vault109StartQuestIssued = false;
bool g_sawLooksMenuOpen = false;
bool g_sawSpecialMenuOpen = false;
int g_spawnPollFrameCounter = 0;
@@ -117,7 +117,7 @@ namespace
return a_saveName.size() >= 8 && _strnicmp(a_saveName.data(), "Exitsave", 8) == 0;
}
bool IsPlayerInSanctuaryExt(const RE::PlayerCharacter& a_player)
bool IsPlayerInFirstJoinStartCell(const RE::PlayerCharacter& a_player)
{
if (IsPlaceholderSpawnPosition(a_player)) {
return false;
@@ -128,16 +128,8 @@ namespace
return false;
}
if (parentCell->GetFormID() == kSanctuaryExtCellFormId) {
return true;
}
if (!parentCell->IsExterior()) {
return false;
}
const auto* worldspace = parentCell->worldSpace ? parentCell->worldSpace : a_player.cachedWorldspace;
return worldspace && worldspace->GetFormID() == kCommonwealthWorldspaceFormId;
const auto* editorId = parentCell->GetFormEditorID();
return editorId && _stricmp(editorId, kFirstJoinStartCellEditorId.data()) == 0;
}
void LogPlayerLocation(std::string_view a_label, const RE::PlayerCharacter& a_player)
@@ -258,6 +250,7 @@ namespace
g_pendingProfileBind = false;
g_pendingProfileBindServerKey.clear();
g_cocIssued = false;
g_vault109StartQuestIssued = false;
g_sawLooksMenuOpen = false;
g_sawSpecialMenuOpen = false;
g_spawnPollFrameCounter = 0;
@@ -271,6 +264,7 @@ namespace
g_pendingProfileBind = true;
g_pendingProfileBindServerKey = a_serverKey;
g_cocIssued = false;
g_vault109StartQuestIssued = false;
g_sawLooksMenuOpen = false;
g_sawSpecialMenuOpen = false;
g_spawnPollFrameCounter = 0;
@@ -279,15 +273,26 @@ namespace
LogInfo(std::format("Queued first-time coc join for server {}.", a_serverKey));
}
void IssueCocSanctuaryExt()
void IssueCocFirstJoinStartCell()
{
if (g_cocIssued) {
return;
}
g_cocIssued = true;
RE::Console::ExecuteCommand("coc SanctuaryExt");
LogInfo("Issued coc SanctuaryExt from main menu.");
RE::Console::ExecuteCommand("coc COVault109");
LogInfo("Issued coc COVault109 from main menu.");
}
void IssueVault109StartQuest()
{
if (g_vault109StartQuestIssued) {
return;
}
g_vault109StartQuestIssued = true;
RE::Console::ExecuteCommand("startquest CO_Vault109StartQuest");
LogInfo("Issued startquest CO_Vault109StartQuest for first-time COVault109 flow.");
}
void OpenLooksMenu()
@@ -406,13 +411,13 @@ namespace
if (g_charGenPhase == CharGenPhase::CocLoading) {
if (!g_cocIssued) {
IssueCocSanctuaryExt();
IssueCocFirstJoinStartCell();
}
const auto* player = RE::PlayerCharacter::GetSingleton();
if (!player || !IsPlayerInSanctuaryExt(*player)) {
if (!player || !IsPlayerInFirstJoinStartCell(*player)) {
if (++g_spawnWaitAttempts >= kMaxSpawnWaitAttempts) {
LogWarning("Timed out waiting for coc SanctuaryExt to finish loading.");
LogWarning("Timed out waiting for coc COVault109 to finish loading.");
ResetFirstJoinState();
}
return;
@@ -421,16 +426,18 @@ namespace
g_charGenPhase = CharGenPhase::WaitingForWorldReady;
g_spawnWaitAttempts = 0;
g_worldReadyPolls = 0;
LogPlayerLocation("SanctuaryExt loaded, waiting for loading screen to finish", *player);
LogPlayerLocation("COVault109 loaded, waiting for loading screen to finish", *player);
return;
}
if (g_charGenPhase == CharGenPhase::WaitingForWorldReady) {
const auto* player = RE::PlayerCharacter::GetSingleton();
if (!player || !IsPlayerInSanctuaryExt(*player)) {
if (!player || !IsPlayerInFirstJoinStartCell(*player)) {
return;
}
IssueVault109StartQuest();
if (!IsWorldReadyForCharGen(*player)) {
return;
}
@@ -534,7 +541,7 @@ namespace F4T::SessionLaunch
LogWarning(std::format("Server profile '{}' has no bound save file; running first-time flow.", profileKey));
QueueFirstTimeCocJoin(profileKey);
IssueCocSanctuaryExt();
IssueCocFirstJoinStartCell();
return true;
}
@@ -578,7 +585,7 @@ namespace F4T::SessionLaunch
*saveFile,
profileKey));
QueueFirstTimeCocJoin(profileKey);
IssueCocSanctuaryExt();
IssueCocFirstJoinStartCell();
return true;
}
} else {
@@ -607,7 +614,7 @@ namespace F4T::SessionLaunch
}
QueueFirstTimeCocJoin(serverKey);
IssueCocSanctuaryExt();
IssueCocFirstJoinStartCell();
return true;
}