Add COVault109 solo-cell handling

Implement solo-cell logic to suppress transform packet sends and remote proxy representation while the local player is in COVault109, allowing first-time character creation to remain visually single-player. One entry location update is sent so other clients can hold stale proxies before suppression begins. Added F4TSoloCell.h helper for load-order-independent solo cell recognition by editor ID and cached FormID.
This commit is contained in:
2026-06-29 14:46:11 +12:00
parent aefeb8b51f
commit b505477e08
8 changed files with 153 additions and 16 deletions
+8
View File
@@ -9,6 +9,7 @@
#include "F4RemoteActionComponent.h"
#include "F4TInterpolationSystem.h"
#include "F4TProxyGameAPI.h"
#include "F4TSoloCell.h"
#include "RE/A/ACTOR_STANCE.h"
#include "RE/A/ActorValue.h"
@@ -1700,6 +1701,13 @@ namespace
const RE::PlayerCharacter& a_player,
const RE::TESObjectCELL& a_parentCell)
{
if (F4T::SoloCell::IsSoloCell(a_parentCell)) {
LogThrottledInfo(
"solo_cell_proxy_suppressed",
"COVault109 is a solo cell; remote proxy representation is suppressed while local player is inside.");
return false;
}
const auto remoteCellId = ParseHexFormId(a_remotePlayer.cellId);
if (!remoteCellId) {
LogThrottledWarning(
+37 -1
View File
@@ -6,6 +6,7 @@
#include "F4TProxyActorController.h"
#include "F4TProxyAnimationSync.h"
#include "F4TProxyPuppet.h"
#include "F4TSoloCell.h"
#include "F4TWorldStateSync.h"
#include "RE/A/ACTOR_STANCE.h"
@@ -34,6 +35,7 @@ namespace
std::uint32_t cellId;
bool hasWorldspaceId;
std::uint32_t worldspaceId;
bool isSoloCell;
};
struct PlayerMovementState
@@ -133,13 +135,15 @@ namespace
false,
0,
false,
0
0,
false
};
const auto* parentCell = a_player.GetParentCell();
if (parentCell) {
location.hasCellId = true;
location.cellId = parentCell->GetFormID();
location.isSoloCell = F4T::SoloCell::IsSoloCell(*parentCell);
}
if (a_player.cachedWorldspace) {
@@ -480,6 +484,7 @@ namespace
static auto lastDebugSampleTime = std::chrono::steady_clock::time_point{};
static auto debugMovingHoldUntil = std::chrono::steady_clock::time_point{};
static auto networkMovingHoldUntil = std::chrono::steady_clock::time_point{};
static bool soloCellTransformSuppressed = false;
const auto currentTransform = GetPlayerTransform(*player);
const auto currentLocation = GetPlayerLocation(*player);
@@ -501,6 +506,29 @@ namespace
debugMovementState.weaponDrawn,
debugMovementState.movementSpeed,
currentTime);
if (currentLocation.isSoloCell && soloCellTransformSuppressed) {
lastSentTransform = currentTransform;
lastDebugSampleTransform = currentTransform;
lastKnownLocation = currentLocation;
lastSentMovementState = debugMovementState;
lastSendTime = currentTime;
lastLogTime = currentTime;
lastDebugSampleTime = currentTime;
hasLastTransform = true;
return;
}
if (currentLocation.isSoloCell) {
LogInfoWithLocalPlayerPrefix("COVault109 is a solo cell; sending entry location update before suppressing further transforms.");
}
if (soloCellTransformSuppressed) {
LogInfoWithLocalPlayerPrefix("Local player left solo cell; transform packet sending resumed.");
soloCellTransformSuppressed = false;
}
if (!hasLastTransform) {
lastSentTransform = currentTransform;
lastDebugSampleTransform = currentTransform;
@@ -538,6 +566,9 @@ namespace
std::format(", worldspaceId={:08X}", currentLocation.worldspaceId) :
std::string{}));
}
if (currentLocation.isSoloCell) {
soloCellTransformSuppressed = true;
}
return;
}
@@ -627,6 +658,11 @@ namespace
LogPlayerTransform(currentTransform);
lastLogTime = currentTime;
}
}
if (currentLocation.isSoloCell) {
soloCellTransformSuppressed = true;
}
lastDebugSampleTransform = currentTransform;