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.
|
||||
- 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/).
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
### Summary
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user