Deliver local sighting handoffs and preserve unsubmitted resupply attempts
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
#ifdef WORKBENCH
|
||||
// Arranged report/physical local handoff fixture, NOT a native perception proof.
|
||||
// Existing source radios and one recipient's radios are deliberately removed;
|
||||
// no equipment or force is spawned to repair that disposable test-world setup.
|
||||
// Read-only readiness: a terminal allocator may still own an accepted native
|
||||
// insertion. Admit only terminal allocators with no submitted operation.
|
||||
modded class ERR_EnemyRadio
|
||||
{
|
||||
bool ERR_LocalFixtureSettled()
|
||||
{
|
||||
return m_Stopped && !m_Submitted;
|
||||
}
|
||||
}
|
||||
modded class ERR_EnemyEquipment
|
||||
{
|
||||
bool ERR_LocalFixtureRadiosSettled(SCR_AIGroup group)
|
||||
{
|
||||
if (!ERR_NativeLocalHandoffProbe.Enabled() || !group) return false;
|
||||
foreach (ERR_EnemyEquipmentGroup state : m_Groups)
|
||||
if (state.Group == group) return !state.Radio || state.Radio.ERR_LocalFixtureSettled();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
modded class ERR_HQ
|
||||
{
|
||||
bool ERR_LocalFixtureRadiosSettled(ERR_HQUnit unit)
|
||||
{
|
||||
return unit && m_Equipment && m_Equipment.ERR_LocalFixtureRadiosSettled(unit.Group);
|
||||
}
|
||||
void ERR_LocalFixtureUnits(out array<ERR_HQUnit> units)
|
||||
{
|
||||
units.Clear();
|
||||
if (!ERR_NativeLocalHandoffProbe.Enabled()) return;
|
||||
foreach (ERR_HQUnit unit : m_Units) units.Insert(unit);
|
||||
}
|
||||
ERR_HQRoute ERR_LocalFixtureQueue(ERR_HQUnit unit, ERR_HQReport arranged, IEntity witness, SCR_AIGroup receiver, out string reason)
|
||||
{
|
||||
reason = "fixture owner unavailable";
|
||||
if (!ERR_NativeLocalHandoffProbe.Enabled() || !unit || !m_Units.Contains(unit) || !arranged) return null;
|
||||
reason = "source has an existing pending uplink";
|
||||
if (unit.Uplink || unit.PendingReport || unit.PendingStatus || unit.PendingSweep || unit.PendingSupplyStatus) return null;
|
||||
// Rebuild the real topology after deliberate physical radio removal.
|
||||
IndexRadios();
|
||||
ERR_HQRoute route = ObservationRoute(unit);
|
||||
reason = "no eligible original local route";
|
||||
if (!route) return null;
|
||||
if (route.Downlink || route.LocalSource != unit.Group || !route.LocalWitness || route.Nodes.IsEmpty())
|
||||
{ reason = string.Format("route is not local; source radio=%1 capability=%2", unit.Radio, ERR_HQRadio.Capability(unit.Radio)); return null; }
|
||||
if (route.LocalWitness != witness || route.Nodes[0].Group != receiver)
|
||||
{ reason = string.Format("different local pair chosen: witness=%1 receiver=%2", route.LocalWitness, route.Nodes[0].UnitId); return null; }
|
||||
arranged.Source = unit.Group;
|
||||
unit.PendingReport = arranged.Snapshot(); unit.Uplink = route;
|
||||
route.MessageId = arranged.MessageId;
|
||||
route.Payload = "FIXTURE arranged immutable sighting; real local handoff and radio required";
|
||||
m_Journal.Write("REPORT", route.Payload, route.MessageId);
|
||||
return route;
|
||||
}
|
||||
}
|
||||
class ERR_NativeLocalHandoffProbe
|
||||
{
|
||||
static const int TOKEN = 220001;
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_HQ m_HQ;
|
||||
protected ERR_HQUnit m_Source;
|
||||
protected ERR_HQUnit m_Recipient;
|
||||
protected IEntity m_Witness;
|
||||
protected IEntity m_Radio;
|
||||
protected vector m_OriginalPosition;
|
||||
protected ref ERR_HQRoute m_Route;
|
||||
protected ref ERR_HQReport m_Payload;
|
||||
protected int m_DeliveredId;
|
||||
protected int m_State;
|
||||
protected float m_Wait;
|
||||
protected float m_Deadline;
|
||||
protected float m_NextQueueDiagnostic;
|
||||
protected bool m_Positioned;
|
||||
protected bool m_Stopped;
|
||||
protected bool m_Failed;
|
||||
static bool Enabled()
|
||||
{
|
||||
string enabled, outcome;
|
||||
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1"
|
||||
&& System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "hq-local-handoff";
|
||||
}
|
||||
void ERR_NativeLocalHandoffProbe(ERR_GameMode mode) { m_Mode = mode; }
|
||||
protected bool Check(bool condition, string detail)
|
||||
{
|
||||
if (condition) { Print("ERR SMOKE PASS: local handoff " + detail); return true; }
|
||||
m_Failed = true; Print("ERR SMOKE FAIL: local handoff " + detail, LogLevel.ERROR); Stop(); return false;
|
||||
}
|
||||
protected bool Original(ERR_HQUnit unit, IEntity actor)
|
||||
{
|
||||
if (!unit || !actor || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) return false;
|
||||
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, unit.Group, actor) || CompartmentAccessComponent.GetVehicleIn(actor)) return false;
|
||||
int count = unit.StatusSampler.InitialStrength();
|
||||
if (count < 1 || count > 32) return false;
|
||||
for (int i = 0; i < count; i++)
|
||||
if (unit.StatusSampler.InitialMember(i) == actor) return unit.StatusSampler.CanAttribute(i, actor);
|
||||
return false;
|
||||
}
|
||||
protected int RemoveRadios(ERR_HQUnit unit, IEntity actor)
|
||||
{
|
||||
if (!Original(unit, actor)) return -1;
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory) return -1;
|
||||
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
|
||||
if (items.Count() > 128) return -1;
|
||||
int removed;
|
||||
foreach (IEntity item : items)
|
||||
{
|
||||
if (!item || !item.FindComponent(BaseRadioComponent)) continue;
|
||||
SCR_EntityHelper.DeleteEntityAndChildren(item); removed++;
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
protected bool Select()
|
||||
{
|
||||
array<ERR_HQUnit> units = {}; m_HQ.ERR_LocalFixtureUnits(units);
|
||||
foreach (ERR_HQUnit recipient : units)
|
||||
{
|
||||
if (!m_HQ.ERR_LocalFixtureRadiosSettled(recipient)) continue;
|
||||
if (!recipient || !recipient.Radio || !Original(recipient, recipient.Radio) || ERR_HQRadio.Capability(recipient.Radio) != 2) continue;
|
||||
if (!m_HQ.ReserveLogistics(recipient.Group, TOKEN)) continue;
|
||||
m_Recipient = recipient; m_Radio = recipient.Radio;
|
||||
foreach (ERR_HQUnit source : units)
|
||||
{
|
||||
if (!m_HQ.ERR_LocalFixtureRadiosSettled(source)) continue;
|
||||
if (!source || source == recipient || !source.StatusSampler || !m_HQ.ReserveLogistics(source.Group, TOKEN)) continue;
|
||||
int count = source.StatusSampler.InitialStrength();
|
||||
if (count >= 1 && count <= 32)
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
IEntity actor = source.StatusSampler.InitialMember(i);
|
||||
if (Original(source, actor)) { m_Source = source; m_Witness = actor; break; }
|
||||
}
|
||||
if (m_Source) return true;
|
||||
m_HQ.ReleaseLogistics(source.Group, TOKEN);
|
||||
}
|
||||
m_HQ.ReleaseLogistics(recipient.Group, TOKEN); m_Recipient = null; m_Radio = null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected bool Arrange()
|
||||
{
|
||||
if (!Original(m_Source, m_Witness) || !Original(m_Recipient, m_Radio)) return false;
|
||||
int removed;
|
||||
int count = m_Source.StatusSampler.InitialStrength();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
IEntity actor = m_Source.StatusSampler.InitialMember(i);
|
||||
int result = RemoveRadios(m_Source, actor);
|
||||
if (result < 0) return false;
|
||||
removed += result;
|
||||
}
|
||||
vector point = ERR_Actors.Ground(m_Radio.GetOrigin() + "4 0 0");
|
||||
TraceBox volume = new TraceBox(); volume.Start = point + "0 1 0"; volume.End = volume.Start + "0 0.01 0";
|
||||
volume.Mins = "-0.3 -0.9 -0.3"; volume.Maxs = "0.3 0.9 0.3"; volume.Flags = TraceFlags.ENTS;
|
||||
array<IEntity> exclude = {m_Witness}; volume.ExcludeArray = exclude;
|
||||
float fraction = GetGame().GetWorld().TraceMove(volume, null);
|
||||
if (!(fraction >= 1 && fraction <= 1)) return false;
|
||||
m_OriginalPosition = m_Witness.GetOrigin(); m_Witness.SetOrigin(point); m_Positioned = true;
|
||||
PrintFormat("ERR LOCAL HANDOFF ARRANGEMENT: source=%1 recipient=%2 original witness=%3 caller=%4 source radios removed=%5; arranged snapshot, no perception assertion", m_Source.Id, m_Recipient.Id, m_Witness, m_Radio, removed);
|
||||
return ERR_HQRadio.Capability(m_Witness) == 0;
|
||||
}
|
||||
protected void QueueWait(string reason, float now)
|
||||
{
|
||||
if (now < m_NextQueueDiagnostic) return;
|
||||
m_NextQueueDiagnostic = now + 10000;
|
||||
PrintFormat("ERR LOCAL HANDOFF WAIT: state=%1 reason=%2 sourceRadio=%3 witnessCapability=%4 distance=%5", m_State, reason, m_Source.Radio, ERR_HQRadio.Capability(m_Witness), vector.Distance(m_Witness.GetOrigin(), m_Radio.GetOrigin()));
|
||||
}
|
||||
protected bool Queue(float now)
|
||||
{
|
||||
if (m_Recipient.Uplink) { QueueWait("recipient has an existing uplink", now); return false; }
|
||||
ERR_HQReport arranged = new ERR_HQReport();
|
||||
arranged.MessageId = m_HQ.Journal().Message(); arranged.Target = m_Mode;
|
||||
arranged.Position = m_Mode.PrisonPosition() + "300 0 300";
|
||||
if (m_DeliveredId > 0) arranged.Position += "10 0 0";
|
||||
arranged.ObservedAt = now; arranged.SeenEscapists = 1; arranged.TravelMode = "on foot";
|
||||
string reason;
|
||||
m_Route = m_HQ.ERR_LocalFixtureQueue(m_Source, arranged, m_Witness, m_Recipient.Group, reason);
|
||||
if (!m_Route) { QueueWait(reason, now); return false; }
|
||||
m_Payload = m_Source.PendingReport.Snapshot();
|
||||
// Mutating the caller's arranged object must not mutate the queued snapshot.
|
||||
arranged.Position += "100 0 0"; arranged.ObservedAt += 1000;
|
||||
return true;
|
||||
}
|
||||
protected bool SameSnapshot(ERR_HQReport report)
|
||||
{
|
||||
return report && m_Payload && report.MessageId == m_Payload.MessageId && report.Source == m_Source.Group
|
||||
&& report.Target == m_Mode && report.Position == m_Payload.Position && report.ObservedAt == m_Payload.ObservedAt;
|
||||
}
|
||||
int Tick(ERR_HQ hq, float now)
|
||||
{
|
||||
if (!Enabled() || m_Stopped || m_Failed || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return -1;
|
||||
m_HQ = hq; if (!hq) return -1;
|
||||
if (m_Deadline == 0) m_Deadline = now + 120000;
|
||||
if (now >= m_Deadline) { Check(false, "bounded fixture deadline"); return -1; }
|
||||
if (now < m_Wait) return 0;
|
||||
if (m_State == 0)
|
||||
{
|
||||
if (!hq.ERR_ProbeSquadsReady()) return 0;
|
||||
if (!Select()) return 0;
|
||||
if (!Check(Arrange(), "two original owned squads retained; source radios removed and witness positioned with clear volume")) return -1;
|
||||
// Native aiming/trace poses may lag SetOrigin until the next world tick.
|
||||
m_Wait = now + 1500; m_State = 1; return 0;
|
||||
}
|
||||
if (!Original(m_Source, m_Witness) || !Original(m_Recipient, m_Radio)
|
||||
|| hq.LogisticsToken(m_Source.Group) != TOKEN || hq.LogisticsToken(m_Recipient.Group) != TOKEN)
|
||||
{ Check(false, "original non-player source/recipient or fixture reservation changed"); return -1; }
|
||||
if (m_State == 1 || m_State == 3)
|
||||
{
|
||||
if (!Queue(now)) return 0;
|
||||
if (!Check(m_Route.LocalSource == m_Source.Group && m_Route.LocalWitness == m_Witness && m_Route.Nodes[0].Group == m_Recipient.Group
|
||||
&& m_Route.Nodes[0].Carrier != m_Witness && SameSnapshot(m_Source.PendingReport), "arranged source snapshot uses distinct original nearby caller without becoming its sighting")) return -1;
|
||||
if (!Check(m_Route.Advance(now, hq.Journal()) == 0 && !m_Route.Originated && !m_Route.Sending,
|
||||
"local handoff is pending and has not started a radio transmission")) return -1;
|
||||
ERR_HQBelief before = hq.Memory().Get(m_Mode);
|
||||
if (!Check((!before && m_DeliveredId == 0) || (before && before.MessageId == m_DeliveredId), "pending local handoff adds no HQ belief")) return -1;
|
||||
if (m_State == 3)
|
||||
{
|
||||
IEntity caller = m_Route.Nodes[0].Carrier;
|
||||
if (!Check(RemoveRadios(m_Recipient, caller) > 0, "original receiving caller radio removed during pending local handoff")) return -1;
|
||||
m_State = 4; return 0;
|
||||
}
|
||||
m_State = 2; return 0;
|
||||
}
|
||||
ERR_HQBelief belief = hq.Memory().Get(m_Mode);
|
||||
if (m_State == 2)
|
||||
{
|
||||
if (!Check(!m_Route.Failed, "positive arranged route retains real local/radio eligibility")) return -1;
|
||||
if (!belief)
|
||||
{
|
||||
if (!Check(SameSnapshot(m_Source.PendingReport), "queued snapshot remains unchanged before actual HQ delivery")) return -1;
|
||||
return 0;
|
||||
}
|
||||
if (!Check(m_Route.Originated && !m_Route.Sending && m_Route.Cursor == m_Route.Nodes.Count()
|
||||
&& SameSnapshot(belief.Report),
|
||||
"actual local plus radio completion delivers the unchanged original source snapshot to HQ")) return -1;
|
||||
m_DeliveredId = m_Payload.MessageId; m_State = 3; return 0;
|
||||
}
|
||||
if (m_State == 4)
|
||||
{
|
||||
if (!Check(belief && belief.MessageId == m_DeliveredId, "interrupted second snapshot never replaces delivered HQ knowledge")) return -1;
|
||||
if (!m_Route.Failed) return 0;
|
||||
if (!Check(!m_Route.Originated && !m_Route.Sending, "loss of actual local recipient radio interrupts before originating handoff completes")) return -1;
|
||||
Stop(); return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
bool restore;
|
||||
if (m_HQ && m_Source && m_HQ.LogisticsToken(m_Source.Group) == TOKEN)
|
||||
{
|
||||
restore = m_Positioned && Original(m_Source, m_Witness);
|
||||
if (m_Source.Uplink == m_Route && m_Source.PendingReport && m_Payload && m_Source.PendingReport.MessageId == m_Payload.MessageId)
|
||||
{ m_Source.Uplink = null; m_Source.PendingReport = null; }
|
||||
m_HQ.ReleaseLogistics(m_Source.Group, TOKEN);
|
||||
}
|
||||
if (m_HQ && m_Recipient) m_HQ.ReleaseLogistics(m_Recipient.Group, TOKEN);
|
||||
if (restore) m_Witness.SetOrigin(m_OriginalPosition);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -17,6 +17,10 @@ modded class ERR_GameMode
|
||||
protected bool m_ERRTestRadioNetwork;
|
||||
protected bool m_ERRTestHQResponse;
|
||||
protected bool m_ERRTestHQMemory;
|
||||
protected bool m_ERRTestLocalHandoff;
|
||||
protected ref ERR_NativeLocalHandoffProbe m_ERRLocalHandoff;
|
||||
protected ERR_PlayerLife m_ERRLocalHandoffLife;
|
||||
protected SCR_PlayerController m_ERRLocalHandoffController;
|
||||
protected bool m_ERRTestHQBody;
|
||||
protected ref ERR_NativeHQBodyProbe m_ERRHQBodyProbe;
|
||||
protected bool m_ERRTestInfantryTransfer;
|
||||
@@ -63,6 +67,7 @@ modded class ERR_GameMode
|
||||
m_ERRTestRadioNetwork = outcome == "radio-network";
|
||||
m_ERRTestHQResponse = outcome == "hq-response";
|
||||
m_ERRTestHQMemory = outcome == "hq-memory";
|
||||
m_ERRTestLocalHandoff = outcome == "hq-local-handoff";
|
||||
m_ERRTestHQBody = outcome == "hq-body";
|
||||
m_ERRTestInfantryTransfer = outcome == "infantry-transfer";
|
||||
m_ERRTestConvoy = outcome == "convoy";
|
||||
@@ -79,7 +84,9 @@ modded class ERR_GameMode
|
||||
m_ERRTestMortarTransfer = outcome == "mortar-transfer";
|
||||
m_ERRTestPatrolHQ = outcome == "patrol-hq";
|
||||
m_ERRTestPlayerDeath = outcome == "player-death" || m_ERRTestRadioNetwork || m_ERRTestHQResponse || m_ERRTestMortar || m_ERRTestHQMemory || m_ERRTestHQBody || m_ERRTestConvoy || m_ERRTestInfantryTransfer;
|
||||
if (m_ERRTestLocalHandoff) m_ERRTestPlayerDeath = true;
|
||||
m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 150000;
|
||||
if (m_ERRTestLocalHandoff) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 240000;
|
||||
if (m_ERRTestConvoy) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 540000;
|
||||
if (ERR_NativeConvoyCoverProbe.Enabled()) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 840000;
|
||||
if (m_ERRTestHQMemory) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 360000;
|
||||
@@ -517,6 +524,18 @@ modded class ERR_GameMode
|
||||
m_ERRSmokeStep = 110;
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestLocalHandoff)
|
||||
{
|
||||
m_ERRLocalHandoffController = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
m_ERRLocalHandoffLife = Players().CurrentActor(m_ERRSmokePlayer);
|
||||
if (!ERR_Check(m_ERRLocalHandoffController && m_ERRLocalHandoffLife && m_ERRLocalHandoffController.GetControlledEntity() == m_ERRSmokePlayer, "local handoff retains original canonical prisoner")) return;
|
||||
DamageManagerComponent protection = DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent));
|
||||
if (!ERR_Check(protection != null, "local handoff original prisoner damage component exists")) return;
|
||||
protection.EnableDamageHandling(false);
|
||||
m_ERRLocalHandoff = new ERR_NativeLocalHandoffProbe(this);
|
||||
OnStop().Insert(m_ERRLocalHandoff.Stop);
|
||||
m_ERRSmokeStep = 109; return;
|
||||
}
|
||||
if (m_ERRTestInfantryTransfer)
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
@@ -718,6 +737,21 @@ modded class ERR_GameMode
|
||||
damage.Kill(Instigator.CreateInstigator(m_ERRSmokePlayer));
|
||||
m_ERRSmokeWait = now + 4000;
|
||||
}
|
||||
else if (m_ERRSmokeStep == 109)
|
||||
{
|
||||
int result = m_ERRLocalHandoff.Tick(ERR_HQ.Cast(Command()), now);
|
||||
if (result == 0) return;
|
||||
m_ERRLocalHandoff.Stop();
|
||||
if (!ERR_Check(result == 1, "arranged immutable report, actual local handoff/radio delivery and interrupted recipient radio")) return;
|
||||
if (!ERR_Check(m_ERRSmokePlayer && m_ERRLocalHandoffLife && m_ERRLocalHandoffController && m_ERRLocalHandoffController == GetGame().GetPlayerController(), "local handoff final original controller and body retained")) return;
|
||||
if (!ERR_Check(Players().CurrentActor(m_ERRSmokePlayer) == m_ERRLocalHandoffLife && m_ERRLocalHandoffController.GetControlledEntity() == m_ERRSmokePlayer, "local handoff final prisoner identity unchanged")) return;
|
||||
m_ERRSmokeStep = 9;
|
||||
SCR_CharacterDamageManagerComponent damage;
|
||||
damage = SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent));
|
||||
if (!ERR_Check(damage != null, "local handoff final original prisoner damage component retained")) return;
|
||||
damage.EnableDamageHandling(true); damage.Kill(Instigator.CreateInstigator(m_ERRSmokePlayer));
|
||||
m_ERRSmokeWait = now + 4000;
|
||||
}
|
||||
else if (m_ERRSmokeStep == 108)
|
||||
{
|
||||
int result = m_ERRInfantryTransfer.Tick(ERR_HQ.Cast(Command()), now);
|
||||
|
||||
Reference in New Issue
Block a user