Recheck storage slots after spawn preload and streaming handoff
This commit is contained in:
@@ -13,6 +13,9 @@ modded class SCR_SpawnRequestComponent
|
||||
protected float m_ERRSceneInventoryPoll;
|
||||
protected bool m_ERRScenePreloadRequired;
|
||||
protected bool m_ERRSceneObserver;
|
||||
protected vector m_ERRScenePosition;
|
||||
protected bool m_ERRSceneReleased;
|
||||
protected bool m_ERRSceneVerified;
|
||||
protected ref array<IEntity> m_ERRSceneRoots = {};
|
||||
protected ref array<IEntity> m_ERRSceneStockOwners = {};
|
||||
protected ref array<IEntity> m_ERRSceneStockItems = {};
|
||||
@@ -23,6 +26,8 @@ modded class SCR_SpawnRequestComponent
|
||||
protected ref array<RplId> m_ERRClientStockOwners = {};
|
||||
protected ref array<RplId> m_ERRClientStockItems = {};
|
||||
protected float m_ERRClientStockReportAt;
|
||||
protected bool m_ERRClientSceneFinal;
|
||||
protected float m_ERRClientSceneStableSince = -1;
|
||||
|
||||
protected void ERR_StreamScene(IEntity entity, bool normalStreaming)
|
||||
{
|
||||
@@ -37,6 +42,7 @@ modded class SCR_SpawnRequestComponent
|
||||
{
|
||||
if (!Replication.IsServer()) return;
|
||||
ERR_EndSpawnScene();
|
||||
m_ERRScenePosition = position;
|
||||
// Native StartSpawnPreload normally installs this observer. Keep streaming
|
||||
// the requested position while we defer asset preload until inventory arrives.
|
||||
ObserversSystem observers = GetObserversSystem();
|
||||
@@ -81,6 +87,8 @@ modded class SCR_SpawnRequestComponent
|
||||
m_ERRSceneEntities.Clear();
|
||||
m_ERRSceneToken++;
|
||||
m_ERRSceneAcknowledged = false;
|
||||
m_ERRSceneReleased = false;
|
||||
m_ERRSceneVerified = false;
|
||||
m_ERRScenePreloadRequired = true;
|
||||
m_ERRSceneInventoryPoll = GetGame().GetWorld().GetWorldTime() + 500;
|
||||
m_ERRSceneStockOwners.Copy(owners);
|
||||
@@ -129,11 +137,33 @@ modded class SCR_SpawnRequestComponent
|
||||
{
|
||||
return m_ERRScenePending && m_ERRSceneAcknowledged && !m_ERRSceneTimedOut;
|
||||
}
|
||||
bool ERR_SpawnSceneVerified()
|
||||
{
|
||||
return ERR_SpawnSceneReady() && m_ERRSceneVerified;
|
||||
}
|
||||
// Called only after native preload completes. Recheck under normal streaming,
|
||||
// with the observer still at the spawn point, before handing over control.
|
||||
bool ERR_VerifySceneRelease()
|
||||
{
|
||||
if (m_ERRSceneTimedOut) return true;
|
||||
if (!ERR_SpawnSceneReady() || m_ERRScenePreloadRequired || IsPreloading()) return false;
|
||||
if (!m_ERRSceneReleased)
|
||||
{
|
||||
ObserversSystem observers = GetObserversSystem();
|
||||
if (observers && GetPlayerController())
|
||||
observers.InsertObserverMP(GetPlayerController().GetRplIdentity(), m_ERRScenePosition[0], m_ERRScenePosition[2], null);
|
||||
foreach (IEntity entity : m_ERRSceneEntities) ERR_StreamScene(entity, true);
|
||||
m_ERRSceneReleased = true;
|
||||
PrintFormat("Reintroduced [spawn scene]: player=%1 token=%2 preload finished; verifying storage after stream release", GetPlayerId(), m_ERRSceneToken);
|
||||
Rpc(ERR_ConfirmClientSpawnScene, m_ERRSceneToken);
|
||||
}
|
||||
return m_ERRSceneVerified;
|
||||
}
|
||||
bool ERR_SpawnSceneMayFinish()
|
||||
{
|
||||
if (!m_ERRScenePending) return true;
|
||||
ERR_RefreshSceneInventory();
|
||||
if (!m_ERRSceneAcknowledged && !m_ERRSceneTimedOut && GetGame().GetWorld().GetWorldTime() >= m_ERRSceneDeadline)
|
||||
if (!m_ERRSceneVerified && !m_ERRSceneTimedOut && GetGame().GetWorld().GetWorldTime() >= m_ERRSceneDeadline)
|
||||
{
|
||||
m_ERRSceneTimedOut = true;
|
||||
PrintFormat("Reintroduced [spawn scene]: player=%1 token=%2 timed out; refusing assignment and retrying", GetPlayerId(), m_ERRSceneToken);
|
||||
@@ -157,6 +187,8 @@ modded class SCR_SpawnRequestComponent
|
||||
m_ERRSceneAcknowledged = false;
|
||||
m_ERRSceneTimedOut = false;
|
||||
m_ERRScenePreloadRequired = false;
|
||||
m_ERRSceneReleased = false;
|
||||
m_ERRSceneVerified = false;
|
||||
}
|
||||
|
||||
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
|
||||
@@ -192,7 +224,7 @@ modded class SCR_SpawnRequestComponent
|
||||
previousOwner = ownerId;
|
||||
}
|
||||
RplComponent item = RplComponent.Cast(Replication.FindItem(m_ERRClientStockItems[i]));
|
||||
if (!item || !item.GetEntity() || !contents.Contains(item.GetEntity()))
|
||||
if (!item || !item.GetEntity() || !contents.Contains(item.GetEntity()) || !ERR_SpawnSceneInventory.InStorage(item.GetEntity()))
|
||||
{
|
||||
ERR_ReportStockWait(ownerId, m_ERRClientStockItems[i], contents.Count());
|
||||
return false;
|
||||
@@ -205,33 +237,58 @@ modded class SCR_SpawnRequestComponent
|
||||
float now = GetGame().GetWorld().GetWorldTime();
|
||||
if (now < m_ERRClientStockReportAt) return;
|
||||
m_ERRClientStockReportAt = now + 5000;
|
||||
PrintFormat("Reintroduced [spawn scene]: client token=%1 waiting for inventory owner=%2 item=%3 visible=%4", m_ERRClientSceneToken, owner, item, visible);
|
||||
PrintFormat("Reintroduced [spawn scene]: client token=%1 final=%2 waiting for storage owner=%3 item=%4 managerItems=%5", m_ERRClientSceneToken, m_ERRClientSceneFinal, owner, item, visible);
|
||||
}
|
||||
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
|
||||
protected void ERR_ConfirmClientSpawnScene(int token)
|
||||
{
|
||||
if (token != m_ERRClientSceneToken || m_ERRClientSceneNodes.IsEmpty()) return;
|
||||
m_ERRClientSceneFinal = true;
|
||||
m_ERRClientScenePending = true;
|
||||
m_ERRClientSceneStableSince = -1;
|
||||
GetGame().GetCallqueue().Remove(ERR_CheckClientSpawnScene);
|
||||
GetGame().GetCallqueue().CallLater(ERR_CheckClientSpawnScene, 100, true);
|
||||
}
|
||||
protected void ERR_CheckClientSpawnScene()
|
||||
{
|
||||
if (!m_ERRClientScenePending || !ERR_GameMode.Get()) { ERR_ClearClientSpawnScene(); return; }
|
||||
if (!ERR_GameMode.Get()) { ERR_ClearClientSpawnScene(); return; }
|
||||
if (!m_ERRClientScenePending) return;
|
||||
foreach (RplId id : m_ERRClientSceneNodes)
|
||||
{
|
||||
RplComponent component = RplComponent.Cast(Replication.FindItem(id));
|
||||
if (!component || !component.GetEntity()) return;
|
||||
if (!component || !component.GetEntity()) { m_ERRClientSceneStableSince = -1; return; }
|
||||
}
|
||||
if (!ERR_ClientStockReady()) { m_ERRClientSceneStableSince = -1; return; }
|
||||
if (m_ERRClientSceneFinal)
|
||||
{
|
||||
float now = GetGame().GetWorld().GetWorldTime();
|
||||
if (m_ERRClientSceneStableSince < 0) m_ERRClientSceneStableSince = now;
|
||||
if (now - m_ERRClientSceneStableSince < 1000) return;
|
||||
}
|
||||
if (!ERR_ClientStockReady()) return;
|
||||
int token = m_ERRClientSceneToken;
|
||||
PrintFormat("Reintroduced [spawn scene]: client token=%1 all %2 objects and %3 inventory items ready", token, m_ERRClientSceneNodes.Count(), m_ERRClientStockItems.Count());
|
||||
ERR_ClearClientSpawnScene();
|
||||
Rpc(ERR_AcknowledgeSpawnScene, token);
|
||||
PrintFormat("Reintroduced [spawn scene]: client token=%1 final=%2 all %3 objects and %4 storage items ready", token, m_ERRClientSceneFinal, m_ERRClientSceneNodes.Count(), m_ERRClientStockItems.Count());
|
||||
GetGame().GetCallqueue().Remove(ERR_CheckClientSpawnScene);
|
||||
m_ERRClientScenePending = false;
|
||||
Rpc(ERR_AcknowledgeSpawnScene, token, m_ERRClientSceneFinal);
|
||||
}
|
||||
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
|
||||
protected void ERR_AcknowledgeSpawnScene(int token)
|
||||
protected void ERR_AcknowledgeSpawnScene(int token, bool finalCheck)
|
||||
{
|
||||
if (!Replication.IsServer() || !m_ERRScenePending || m_ERRSceneTimedOut || token != m_ERRSceneToken) return;
|
||||
m_ERRSceneAcknowledged = true;
|
||||
PrintFormat("Reintroduced [spawn scene]: player=%1 token=%2 inventory acknowledged after %3ms", GetPlayerId(), token, GetGame().GetWorld().GetWorldTime() - m_ERRSceneStartedAt);
|
||||
if (finalCheck)
|
||||
{
|
||||
if (!m_ERRSceneReleased || !m_ERRSceneAcknowledged) return;
|
||||
m_ERRSceneVerified = true;
|
||||
}
|
||||
else m_ERRSceneAcknowledged = true;
|
||||
PrintFormat("Reintroduced [spawn scene]: player=%1 token=%2 final=%3 storage acknowledged after %4ms", GetPlayerId(), token, finalCheck, GetGame().GetWorld().GetWorldTime() - m_ERRSceneStartedAt);
|
||||
}
|
||||
protected void ERR_ClearClientSpawnScene()
|
||||
{
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(ERR_CheckClientSpawnScene);
|
||||
m_ERRClientScenePending = false;
|
||||
m_ERRClientSceneFinal = false;
|
||||
m_ERRClientSceneStableSince = -1;
|
||||
m_ERRClientSceneNodes.Clear();
|
||||
m_ERRClientStockOwners.Clear();
|
||||
m_ERRClientStockItems.Clear();
|
||||
|
||||
@@ -56,6 +56,8 @@ modded class SCR_FreeSpawnHandlerComponent
|
||||
{
|
||||
if (!requestComponent.ERR_SpawnSceneMayFinish()) return false;
|
||||
if (requestComponent.ERR_TakeScenePreload()) super.OnFinalizeBegin_S(requestComponent, data, entity);
|
||||
if (!super.CanFinalize_S(requestComponent, data, entity)) return false;
|
||||
return requestComponent.ERR_VerifySceneRelease();
|
||||
}
|
||||
return super.CanFinalize_S(requestComponent, data, entity);
|
||||
}
|
||||
@@ -95,7 +97,7 @@ modded class SCR_FreeSpawnHandlerComponent
|
||||
if (logic) ticket = logic.TakeSpawnTicket(requestComponent, data, entity);
|
||||
// No ticket means no ownership proof: reject without deleting arbitrary input.
|
||||
if (!ticket) return SCR_ESpawnResult.SPAWN_NOT_ALLOWED;
|
||||
if (!requestComponent.ERR_SpawnSceneReady() || !logic.MayFinalizeSpawn(ticket))
|
||||
if (!requestComponent.ERR_SpawnSceneVerified() || !logic.MayFinalizeSpawn(ticket))
|
||||
{
|
||||
requestComponent.ERR_EndSpawnScene();
|
||||
ERR_RejectOwnedActor(requestComponent, entity, data);
|
||||
|
||||
@@ -2,6 +2,22 @@
|
||||
// prisoner may already have taken loot when a late arrival joins the round.
|
||||
class ERR_SpawnSceneInventory
|
||||
{
|
||||
// Manager membership alone does not establish that the storage UI can read
|
||||
// the item. Check both directions of the replicated slot and the UI's GetAll.
|
||||
static bool InStorage(IEntity entity)
|
||||
{
|
||||
if (!entity) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
||||
if (!item) return false;
|
||||
InventoryStorageSlot slot = item.GetParentSlot();
|
||||
if (!slot) return false;
|
||||
BaseInventoryStorageComponent storage = slot.GetStorage();
|
||||
if (!storage || storage.FindItemSlot(entity) != slot || storage.Get(slot.GetID()) != entity) return false;
|
||||
array<IEntity> visible = {};
|
||||
storage.GetAll(visible);
|
||||
return visible.Contains(entity);
|
||||
}
|
||||
|
||||
static bool Gather(array<IEntity> roots, out array<IEntity> owners, out array<IEntity> items)
|
||||
{
|
||||
owners = {}; items = {};
|
||||
|
||||
Reference in New Issue
Block a user