174 lines
5.5 KiB
C
174 lines
5.5 KiB
C
// Adapted from the owner's Escapists Intel reroll: snapshot the cohort/chance,
|
|
// wait for accepted native operations, and process a bounded amount each tick.
|
|
class ERR_IntelRerollEntry
|
|
{
|
|
ref ERR_IntelInsertCallback Operation;
|
|
IEntity Actor;
|
|
IEntity Removing;
|
|
int Attempts;
|
|
string WaitingFor = "inventory";
|
|
bool GiveIntel;
|
|
bool Inserting;
|
|
bool Accepted;
|
|
bool Done;
|
|
}
|
|
|
|
class ERR_IntelReroll
|
|
{
|
|
protected ERR_GameMode m_Mode;
|
|
protected ERR_IntelService m_Service;
|
|
protected ref array<ref ERR_IntelRerollEntry> m_Entries = {};
|
|
protected int m_Percent;
|
|
protected int m_Next;
|
|
protected int m_Completed;
|
|
protected int m_Failed;
|
|
protected int m_Skipped;
|
|
protected bool m_Cancelled;
|
|
|
|
void ERR_IntelReroll(ERR_GameMode mode, ERR_IntelService service, int percent)
|
|
{
|
|
m_Mode = mode;
|
|
m_Service = service;
|
|
m_Percent = percent;
|
|
}
|
|
|
|
void Add(IEntity actor)
|
|
{
|
|
ERR_IntelRerollEntry entry = new ERR_IntelRerollEntry();
|
|
entry.Actor = actor;
|
|
entry.GiveIntel = Math.RandomInt(0, 100) < m_Percent;
|
|
m_Entries.Insert(entry);
|
|
}
|
|
|
|
bool Running() { return !m_Cancelled && m_Completed < m_Entries.Count(); }
|
|
|
|
void Tick()
|
|
{
|
|
if (!Replication.IsServer() || !Running()) return;
|
|
// Round-robin visits keep delayed inventory operations from starving others.
|
|
int budget = Math.Min(8, m_Entries.Count());
|
|
while (budget-- > 0)
|
|
{
|
|
if (m_Next >= m_Entries.Count()) m_Next = 0;
|
|
ERR_IntelRerollEntry entry = m_Entries[m_Next++];
|
|
if (!entry.Done) Process(entry);
|
|
}
|
|
}
|
|
|
|
protected void Finish(ERR_IntelRerollEntry entry, bool failed = false, bool skipped = false)
|
|
{
|
|
entry.Done = true;
|
|
entry.Removing = null;
|
|
m_Completed++;
|
|
if (failed) m_Failed++;
|
|
if (skipped) m_Skipped++;
|
|
}
|
|
|
|
protected void Fail(ERR_IntelRerollEntry entry, string reason)
|
|
{
|
|
Print(string.Format("Rereintroduced [intel]: reroll failed reason=%1 actor=%2 attempts=%3", reason, entry.Actor.GetID(), entry.Attempts), LogLevel.WARNING);
|
|
Finish(entry, true);
|
|
}
|
|
|
|
protected void Process(ERR_IntelRerollEntry entry)
|
|
{
|
|
if (!ERR_Actors.Alive(entry.Actor) || ERR_Actors.Player(entry.Actor))
|
|
{
|
|
if (entry.Accepted) m_Service.Unsettled(entry.Actor);
|
|
Finish(entry, false, true);
|
|
return;
|
|
}
|
|
entry.Attempts++;
|
|
if (entry.Attempts > 30)
|
|
{
|
|
// A timed-out accepted insertion can still finish inside the engine.
|
|
// Keep its actor quarantined for the remainder of this round.
|
|
if (entry.Accepted) m_Service.Unsettled(entry.Actor);
|
|
Fail(entry, "timeout-" + entry.WaitingFor);
|
|
return;
|
|
}
|
|
SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(entry.Actor.FindComponent(SCR_InventoryStorageManagerComponent));
|
|
if (!inventory) { entry.WaitingFor = "inventory"; return; }
|
|
array<IEntity> folders = {};
|
|
ERR_DiscoveryService.Folders(inventory, folders);
|
|
if (m_Service.HasPendingInsertion(entry.Actor)) { entry.WaitingFor = "original-insertion"; return; }
|
|
if (!m_Service.Settled(entry.Actor)) { entry.WaitingFor = "quarantined-insertion"; return; }
|
|
|
|
if (!entry.Inserting)
|
|
{
|
|
if (entry.Removing)
|
|
{
|
|
if (folders.Contains(entry.Removing)) { entry.WaitingFor = "native-removal"; return; }
|
|
InventoryItemComponent item = InventoryItemComponent.Cast(entry.Removing.FindComponent(InventoryItemComponent));
|
|
// It may have transferred to another inventory meanwhile. Preserve it
|
|
// and decline the replacement rather than racing its new owner.
|
|
if (!item || item.GetParentSlot() || entry.Removing.GetParent())
|
|
{
|
|
Finish(entry, false, true);
|
|
return;
|
|
}
|
|
entry.Removing = null;
|
|
}
|
|
foreach (IEntity known : folders)
|
|
{
|
|
// Never reroll mission clue documents or recovered/spent folders.
|
|
if (m_Mode.Discovery().IsSpent(known) || known.FindComponent(ERR_DocumentComponent))
|
|
{
|
|
Finish(entry, false, true);
|
|
return;
|
|
}
|
|
}
|
|
if (!folders.IsEmpty())
|
|
{
|
|
IEntity folder = folders[0];
|
|
// Check capacity before mutation; rejected removal leaves usable intel.
|
|
if (!m_Mode.Discovery().CanMarkSpent(folder)) { Fail(entry, "spent-capacity"); return; }
|
|
// Busy native inventory may reject a request transiently. Retry within
|
|
// the existing phase budget without spending or owning the folder.
|
|
if (!inventory.TryRemoveItemFromInventory(folder)) { entry.WaitingFor = "removal-refused"; return; }
|
|
m_Mode.Discovery().MarkSpent(folder);
|
|
m_Mode.Own(folder);
|
|
entry.Removing = folder;
|
|
entry.WaitingFor = "native-removal";
|
|
return;
|
|
}
|
|
entry.Inserting = true;
|
|
entry.Attempts = 0;
|
|
if (!entry.GiveIntel) { Finish(entry); return; }
|
|
}
|
|
if (entry.Accepted)
|
|
{
|
|
entry.WaitingFor = "native-insertion";
|
|
if (entry.Operation && entry.Operation.Finished)
|
|
{
|
|
if (entry.Operation.Succeeded) Finish(entry);
|
|
else Fail(entry, "insertion-callback");
|
|
}
|
|
return;
|
|
}
|
|
if (!folders.IsEmpty()) { Finish(entry); return; }
|
|
entry.Operation = new ERR_IntelInsertCallback();
|
|
entry.Accepted = inventory.TrySpawnPrefabToStorage(ERR_Resources.IntelFolder(), purpose: EStoragePurpose.PURPOSE_ANY, cb: entry.Operation);
|
|
entry.WaitingFor = "insertion-refused";
|
|
if (entry.Accepted) entry.WaitingFor = "native-insertion";
|
|
}
|
|
|
|
string Status()
|
|
{
|
|
string state = "complete";
|
|
if (Running()) state = "running";
|
|
if (m_Cancelled) state = "cancelled";
|
|
return string.Format("Intel reroll %1 at %2 percent: %3/%4 AI processed, %5 failed, %6 skipped.", state, m_Percent, m_Completed, m_Entries.Count(), m_Failed, m_Skipped);
|
|
}
|
|
|
|
void Stop()
|
|
{
|
|
m_Cancelled = true;
|
|
foreach (ERR_IntelRerollEntry entry : m_Entries)
|
|
{
|
|
entry.Actor = null;
|
|
entry.Removing = null;
|
|
}
|
|
}
|
|
}
|