Preserve stolen intel evidence and safely clean up spent folders
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -1,9 +1,34 @@
|
||||
// Base mission discovery: recovering physical documents works without companions.
|
||||
// The Intel addon optionally assigns extra documents to enemy inventories.
|
||||
class ERR_IntelDeleteCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
bool Finished;
|
||||
bool Succeeded;
|
||||
protected void Finish(bool success) { if (!Finished) { Finished = true; Succeeded = success; } }
|
||||
protected override void OnComplete() { Finish(true); }
|
||||
protected override void OnFailed() { Finish(false); }
|
||||
protected override void OnAllComplete() { Finish(true); }
|
||||
protected override void OnAllFailed() { Finish(false); }
|
||||
}
|
||||
|
||||
class ERR_SpentIntelCleanup
|
||||
{
|
||||
IEntity Folder;
|
||||
ref ERR_IntelDeleteCallback Operation;
|
||||
int Attempts;
|
||||
float NextAttempt;
|
||||
float Deadline;
|
||||
bool TimeoutReported;
|
||||
}
|
||||
|
||||
class ERR_DiscoveryService
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ref set<IEntity> m_Spent = new set<IEntity>();
|
||||
protected ref array<ref ERR_SpentIntelCleanup> m_Cleanup = {};
|
||||
protected int m_CleanupCursor;
|
||||
protected float m_CleanupNow;
|
||||
protected bool m_Stopped;
|
||||
void ERR_DiscoveryService(ERR_GameMode mode) { m_Mode = mode; }
|
||||
// Shared ledger for recovered and administratively retired documents.
|
||||
bool IsSpent(IEntity folder) { return folder && m_Spent.Contains(folder); }
|
||||
@@ -37,14 +62,74 @@ class ERR_DiscoveryService
|
||||
|
||||
void Tick(float now)
|
||||
{
|
||||
if (!Replication.IsServer()) return;
|
||||
if (!Replication.IsServer() || m_Stopped || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return;
|
||||
array<IEntity> players = {};
|
||||
ERR_Actors.Players(players);
|
||||
m_CleanupNow = now;
|
||||
foreach (IEntity player : players) Recover(player);
|
||||
// Pending callbacks are observed only; even a missing callback never
|
||||
// permits a second operation against the same physical item.
|
||||
int budget = Math.Min(8, m_Cleanup.Count());
|
||||
while (budget-- > 0)
|
||||
{
|
||||
if (m_CleanupCursor >= m_Cleanup.Count()) m_CleanupCursor = 0;
|
||||
if (ProcessCleanup(m_Cleanup[m_CleanupCursor], players, now)) m_Cleanup.Remove(m_CleanupCursor);
|
||||
else m_CleanupCursor++;
|
||||
}
|
||||
}
|
||||
protected void QueueCleanup(IEntity folder, float now)
|
||||
{
|
||||
if (!folder || folder.FindComponent(ERR_DocumentComponent)) return;
|
||||
// One request per newly spent folder; admission shares the spent-ledger
|
||||
// bound. Administrative retirement does not enter this queue.
|
||||
ERR_SpentIntelCleanup request = new ERR_SpentIntelCleanup();
|
||||
request.Folder = folder;
|
||||
request.Deadline = now + 60000;
|
||||
m_Cleanup.Insert(request);
|
||||
}
|
||||
protected bool ProcessCleanup(ERR_SpentIntelCleanup request, array<IEntity> players, float now)
|
||||
{
|
||||
if (!request.Folder) return true;
|
||||
if (request.Operation)
|
||||
{
|
||||
if (!request.Operation.Finished)
|
||||
{
|
||||
if (now >= request.Deadline && !request.TimeoutReported)
|
||||
{
|
||||
request.TimeoutReported = true;
|
||||
Print("Reintroduced [intel]: spent-folder deletion still pending; no duplicate request", LogLevel.WARNING);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (request.Operation.Succeeded) return true;
|
||||
request.Operation = null;
|
||||
request.NextAttempt = now + 5000;
|
||||
}
|
||||
if (now >= request.Deadline || request.Attempts >= 3)
|
||||
{
|
||||
Print("Reintroduced [intel]: spent-folder cleanup expired; item retained without granting more intel", LogLevel.WARNING);
|
||||
return true;
|
||||
}
|
||||
if (request.Folder.FindComponent(ERR_DocumentComponent)) return true;
|
||||
if (now < request.NextAttempt) return false;
|
||||
foreach (IEntity player : players)
|
||||
{
|
||||
if (!m_Mode.Players() || !m_Mode.Players().CurrentActor(player) || !ERR_Actors.Alive(player)) continue;
|
||||
if (!m_Mode.PlayerFaction() || SCR_FactionManager.SGetFaction(player) != m_Mode.PlayerFaction()) continue;
|
||||
if (!ERR_InfantryAmmoTransfer.StoredUnder(request.Folder, player)) continue;
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(player.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory || !inventory.Contains(request.Folder)) continue;
|
||||
request.Attempts++;
|
||||
request.NextAttempt = now + 5000;
|
||||
request.Operation = new ERR_IntelDeleteCallback();
|
||||
if (!inventory.TryDeleteItem(request.Folder, request.Operation)) request.Operation = null;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected void Recover(IEntity player)
|
||||
{
|
||||
if (!Replication.IsServer() || !m_Mode || !m_Mode.Players() || !m_Mode.Players().CurrentActor(player) || !ERR_Actors.Alive(player)) return;
|
||||
if (!Replication.IsServer() || m_Stopped || !m_Mode || !m_Mode.Players() || !m_Mode.Players().CurrentActor(player) || !ERR_Actors.Alive(player)) return;
|
||||
if (!m_Mode.PlayerFaction() || SCR_FactionManager.SGetFaction(player) != m_Mode.PlayerFaction()) return;
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(player.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory) return;
|
||||
@@ -72,7 +157,11 @@ class ERR_DiscoveryService
|
||||
}
|
||||
if (eligible.IsEmpty())
|
||||
{
|
||||
if (!document && !m_Mode.WorldPreparing() && MarkSpent(folder) && !mineRecovered) m_Mode.ERR_IntelExhausted(!sealedTarget.IsEmpty());
|
||||
if (!document && !m_Mode.WorldPreparing() && MarkSpent(folder))
|
||||
{
|
||||
QueueCleanup(folder, m_CleanupNow);
|
||||
if (!mineRecovered) m_Mode.ERR_IntelExhausted(!sealedTarget.IsEmpty());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ERR_LocationComponent chosen = eligible.GetRandomElement();
|
||||
@@ -80,17 +169,11 @@ class ERR_DiscoveryService
|
||||
m_Spent.Insert(folder);
|
||||
PrintFormat("Reintroduced [intel]: player %1 recovered folder; location=%2 knowledge=%3", GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(player), chosen.Id(), chosen.Knowledge());
|
||||
|
||||
// Delete intel folder :)
|
||||
if (!document)
|
||||
{
|
||||
m_Mode.Evidence().Retire(folder);
|
||||
SCR_InventoryStorageManagerComponent scrInventory = SCR_InventoryStorageManagerComponent.Cast(inventory);
|
||||
bool accepted = scrInventory && scrInventory.TryDeleteItem(folder);
|
||||
PrintFormat("Reintroduced [intel]: folder delete accepted=%1", accepted);
|
||||
if (!accepted) SCR_EntityHelper.DeleteEntityAndChildren(folder);
|
||||
}
|
||||
// Preserve issued evidence: a later corpse inspection must still
|
||||
// report this site's missing document after the physical item is gone.
|
||||
if (!document) QueueCleanup(folder, m_CleanupNow);
|
||||
}
|
||||
}
|
||||
|
||||
void Stop() { m_Spent.Clear(); }
|
||||
void Stop() { m_Stopped = true; m_Cleanup.Clear(); m_Spent.Clear(); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user