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:
@@ -40,6 +40,8 @@ For testing notes, milestone summaries, known issues, and next steps, see [`docs
|
|||||||
- World time no longer re-applies on every `worldState` heartbeat; weather heartbeats continue at ~1 Hz while time stays in sync during normal gameplay.
|
- World time no longer re-applies on every `worldState` heartbeat; weather heartbeats continue at ~1 Hz while time stays in sync during normal gameplay.
|
||||||
- Updated Weather/Time UI and protocol wording to use world-state host terminology instead of hardcoded player-1 wording.
|
- Updated Weather/Time UI and protocol wording to use world-state host terminology instead of hardcoded player-1 wording.
|
||||||
- Reformatted `changelog.md` to follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
- Reformatted `changelog.md` to follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
|
- First-time character creation launch now uses `coc COVault109` and waits for the `COVault109` start cell instead of `SanctuaryExt`.
|
||||||
|
- First-time `COVault109` launch now starts `CO_Vault109StartQuest` once per first-time join flow so the quest can move the player to its preset spawn marker.
|
||||||
|
|
||||||
|
|
||||||
## [0.0.1] - 2026-06-23
|
## [0.0.1] - 2026-06-23
|
||||||
|
|||||||
Binary file not shown.
@@ -9,6 +9,39 @@ failed experiments, successful tests, and next steps.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-06-26 - First-Time Join Starts in `COVault109`
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Updated first-time session launch flow to start in the custom `COVault109` cell from `CommonwealthOnline_Vault109.esp` instead of `SanctuaryExt`.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
- `plugin/src/F4TSessionLaunch.cpp`
|
||||||
|
- `changelog.md`
|
||||||
|
- `docs/dev-log.md`
|
||||||
|
|
||||||
|
### Details
|
||||||
|
- Replaced first-time launch command from `coc SanctuaryExt` to `coc COVault109`.
|
||||||
|
- Updated first-time spawn readiness checks to validate the loaded parent cell by editor ID `COVault109`.
|
||||||
|
- Added first-time-flow quest trigger to run `startquest CO_Vault109StartQuest` once after entering `COVault109` so the quest script can move the player to its preset location.
|
||||||
|
- Updated first-time flow logging and timeout messages to reference the new start cell name.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Not run in-game in this session.
|
||||||
|
- Recommended manual checks:
|
||||||
|
- Start the server and connect one Fallout 4 instance from main menu for first-time join; confirm launch uses `COVault109`.
|
||||||
|
- Complete Looks/SPECIAL flow and confirm profile save binding still completes.
|
||||||
|
- Reconnect to the same server and confirm associated save load behavior remains unchanged.
|
||||||
|
- Connect two Fallout 4 instances and verify transform packets still send/relay, remote state parsing still works, and proxy actor movement remains visible in `F4TTestCell01`.
|
||||||
|
- Check `CommonwealthOnline.log` for `coc COVault109` and first-time flow progress logs.
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- If `CommonwealthOnline_Vault109.esp` is missing or the `COVault109` editor ID changes, first-time launch will timeout waiting for the start cell.
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- If needed, add a fallback path or explicit user-facing error when `COVault109` cannot be loaded.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-06-26 - Deleted Save Recreates First-Time Flow
|
## 2026-06-26 - Deleted Save Recreates First-Time Flow
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
|
|||||||
@@ -21,8 +21,7 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr std::uint32_t kCommonwealthWorldspaceFormId = 0x0000003C;
|
constexpr std::string_view kFirstJoinStartCellEditorId = "COVault109";
|
||||||
constexpr std::uint32_t kSanctuaryExtCellFormId = 0x0000DD60;
|
|
||||||
constexpr int kSpawnPollIntervalFrames = 15;
|
constexpr int kSpawnPollIntervalFrames = 15;
|
||||||
constexpr int kMaxSpawnWaitAttempts = 600;
|
constexpr int kMaxSpawnWaitAttempts = 600;
|
||||||
constexpr int kWorldReadyDelayPolls = 10;
|
constexpr int kWorldReadyDelayPolls = 10;
|
||||||
@@ -43,6 +42,7 @@ namespace
|
|||||||
CharGenPhase g_charGenPhase = CharGenPhase::None;
|
CharGenPhase g_charGenPhase = CharGenPhase::None;
|
||||||
bool g_pendingProfileBind = false;
|
bool g_pendingProfileBind = false;
|
||||||
bool g_cocIssued = false;
|
bool g_cocIssued = false;
|
||||||
|
bool g_vault109StartQuestIssued = false;
|
||||||
bool g_sawLooksMenuOpen = false;
|
bool g_sawLooksMenuOpen = false;
|
||||||
bool g_sawSpecialMenuOpen = false;
|
bool g_sawSpecialMenuOpen = false;
|
||||||
int g_spawnPollFrameCounter = 0;
|
int g_spawnPollFrameCounter = 0;
|
||||||
@@ -117,7 +117,7 @@ namespace
|
|||||||
return a_saveName.size() >= 8 && _strnicmp(a_saveName.data(), "Exitsave", 8) == 0;
|
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)) {
|
if (IsPlaceholderSpawnPosition(a_player)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -128,16 +128,8 @@ namespace
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentCell->GetFormID() == kSanctuaryExtCellFormId) {
|
const auto* editorId = parentCell->GetFormEditorID();
|
||||||
return true;
|
return editorId && _stricmp(editorId, kFirstJoinStartCellEditorId.data()) == 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (!parentCell->IsExterior()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto* worldspace = parentCell->worldSpace ? parentCell->worldSpace : a_player.cachedWorldspace;
|
|
||||||
return worldspace && worldspace->GetFormID() == kCommonwealthWorldspaceFormId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogPlayerLocation(std::string_view a_label, const RE::PlayerCharacter& a_player)
|
void LogPlayerLocation(std::string_view a_label, const RE::PlayerCharacter& a_player)
|
||||||
@@ -258,6 +250,7 @@ namespace
|
|||||||
g_pendingProfileBind = false;
|
g_pendingProfileBind = false;
|
||||||
g_pendingProfileBindServerKey.clear();
|
g_pendingProfileBindServerKey.clear();
|
||||||
g_cocIssued = false;
|
g_cocIssued = false;
|
||||||
|
g_vault109StartQuestIssued = false;
|
||||||
g_sawLooksMenuOpen = false;
|
g_sawLooksMenuOpen = false;
|
||||||
g_sawSpecialMenuOpen = false;
|
g_sawSpecialMenuOpen = false;
|
||||||
g_spawnPollFrameCounter = 0;
|
g_spawnPollFrameCounter = 0;
|
||||||
@@ -271,6 +264,7 @@ namespace
|
|||||||
g_pendingProfileBind = true;
|
g_pendingProfileBind = true;
|
||||||
g_pendingProfileBindServerKey = a_serverKey;
|
g_pendingProfileBindServerKey = a_serverKey;
|
||||||
g_cocIssued = false;
|
g_cocIssued = false;
|
||||||
|
g_vault109StartQuestIssued = false;
|
||||||
g_sawLooksMenuOpen = false;
|
g_sawLooksMenuOpen = false;
|
||||||
g_sawSpecialMenuOpen = false;
|
g_sawSpecialMenuOpen = false;
|
||||||
g_spawnPollFrameCounter = 0;
|
g_spawnPollFrameCounter = 0;
|
||||||
@@ -279,15 +273,26 @@ namespace
|
|||||||
LogInfo(std::format("Queued first-time coc join for server {}.", a_serverKey));
|
LogInfo(std::format("Queued first-time coc join for server {}.", a_serverKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
void IssueCocSanctuaryExt()
|
void IssueCocFirstJoinStartCell()
|
||||||
{
|
{
|
||||||
if (g_cocIssued) {
|
if (g_cocIssued) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_cocIssued = true;
|
g_cocIssued = true;
|
||||||
RE::Console::ExecuteCommand("coc SanctuaryExt");
|
RE::Console::ExecuteCommand("coc COVault109");
|
||||||
LogInfo("Issued coc SanctuaryExt from main menu.");
|
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()
|
void OpenLooksMenu()
|
||||||
@@ -406,13 +411,13 @@ namespace
|
|||||||
|
|
||||||
if (g_charGenPhase == CharGenPhase::CocLoading) {
|
if (g_charGenPhase == CharGenPhase::CocLoading) {
|
||||||
if (!g_cocIssued) {
|
if (!g_cocIssued) {
|
||||||
IssueCocSanctuaryExt();
|
IssueCocFirstJoinStartCell();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto* player = RE::PlayerCharacter::GetSingleton();
|
const auto* player = RE::PlayerCharacter::GetSingleton();
|
||||||
if (!player || !IsPlayerInSanctuaryExt(*player)) {
|
if (!player || !IsPlayerInFirstJoinStartCell(*player)) {
|
||||||
if (++g_spawnWaitAttempts >= kMaxSpawnWaitAttempts) {
|
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();
|
ResetFirstJoinState();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -421,16 +426,18 @@ namespace
|
|||||||
g_charGenPhase = CharGenPhase::WaitingForWorldReady;
|
g_charGenPhase = CharGenPhase::WaitingForWorldReady;
|
||||||
g_spawnWaitAttempts = 0;
|
g_spawnWaitAttempts = 0;
|
||||||
g_worldReadyPolls = 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_charGenPhase == CharGenPhase::WaitingForWorldReady) {
|
if (g_charGenPhase == CharGenPhase::WaitingForWorldReady) {
|
||||||
const auto* player = RE::PlayerCharacter::GetSingleton();
|
const auto* player = RE::PlayerCharacter::GetSingleton();
|
||||||
if (!player || !IsPlayerInSanctuaryExt(*player)) {
|
if (!player || !IsPlayerInFirstJoinStartCell(*player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IssueVault109StartQuest();
|
||||||
|
|
||||||
if (!IsWorldReadyForCharGen(*player)) {
|
if (!IsWorldReadyForCharGen(*player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -534,7 +541,7 @@ namespace F4T::SessionLaunch
|
|||||||
|
|
||||||
LogWarning(std::format("Server profile '{}' has no bound save file; running first-time flow.", profileKey));
|
LogWarning(std::format("Server profile '{}' has no bound save file; running first-time flow.", profileKey));
|
||||||
QueueFirstTimeCocJoin(profileKey);
|
QueueFirstTimeCocJoin(profileKey);
|
||||||
IssueCocSanctuaryExt();
|
IssueCocFirstJoinStartCell();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,7 +585,7 @@ namespace F4T::SessionLaunch
|
|||||||
*saveFile,
|
*saveFile,
|
||||||
profileKey));
|
profileKey));
|
||||||
QueueFirstTimeCocJoin(profileKey);
|
QueueFirstTimeCocJoin(profileKey);
|
||||||
IssueCocSanctuaryExt();
|
IssueCocFirstJoinStartCell();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -607,7 +614,7 @@ namespace F4T::SessionLaunch
|
|||||||
}
|
}
|
||||||
|
|
||||||
QueueFirstTimeCocJoin(serverKey);
|
QueueFirstTimeCocJoin(serverKey);
|
||||||
IssueCocSanctuaryExt();
|
IssueCocFirstJoinStartCell();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user