321 lines
16 KiB
C
321 lines
16 KiB
C
#ifdef WORKBENCH
|
|
// Controlled scene setup only: reuse two already admitted formations, relocate
|
|
// and kill one original non-radio member. Production LOS, snapshots and radio
|
|
// delivery must establish knowledge; this is not organic patrol-discovery proof.
|
|
class ERR_NativeHQBodySetup
|
|
{
|
|
ref ERR_HQUnit VictimUnit;
|
|
ref ERR_HQUnit ObserverUnit;
|
|
IEntity Actor;
|
|
string SourceId;
|
|
int InitialSlot;
|
|
int InitialStrength;
|
|
float SavedNextStatus;
|
|
int StatusMessageId;
|
|
float StatusObservedAt = -1;
|
|
vector StatusPosition;
|
|
}
|
|
|
|
modded class ERR_HQ
|
|
{
|
|
protected bool ERR_BodyFixtureHealthy(ERR_HQUnit unit)
|
|
{
|
|
if (!unit || !unit.Eligible(m_Mode) || !unit.StatusSampler.Bound() || !unit.Radio || unit.Uplink
|
|
|| unit.PendingReport || unit.PendingStatus || unit.PendingSupplyStatus || unit.PendingSweep) return false;
|
|
ERR_HQRoute route = m_Network.Route(unit.Radio);
|
|
if (!route || route.Nodes.IsEmpty()) return false;
|
|
for (int i = 0; i < route.Nodes.Count(); i++)
|
|
if (!route.Nodes[i] || !route.Nodes[i].Valid(i == route.Nodes.Count() - 1)) return false;
|
|
return true;
|
|
}
|
|
ERR_NativeHQBodySetup ERR_BodyFixturePrepare(float now)
|
|
{
|
|
if (!ERR_NativeHQBodyProbe.Enabled()) return null;
|
|
foreach (ERR_HQUnit observer : m_Units)
|
|
{
|
|
if (!observer.HomeGuard || !ERR_BodyFixtureHealthy(observer)) continue;
|
|
bool originalObserver;
|
|
for (int observerSlot = 0; observerSlot < observer.StatusSampler.InitialStrength(); observerSlot++)
|
|
if (observer.StatusSampler.InitialMember(observerSlot) == observer.Radio
|
|
&& observer.StatusSampler.CanAttribute(observerSlot, observer.Radio)) originalObserver = true;
|
|
if (!originalObserver) continue;
|
|
foreach (ERR_HQUnit victim : m_Units)
|
|
{
|
|
if (victim == observer || victim.StatusSampler.InitialStrength() < 2 || !ERR_BodyFixtureHealthy(victim))
|
|
continue;
|
|
string sourceId = victim.StatusSampler.InitialSourceId();
|
|
ERR_HQWatchEntry status = m_Watch.Find(sourceId);
|
|
if (status && status.ConfirmedLosses != 0) continue;
|
|
for (int slot = 0; slot < victim.StatusSampler.InitialStrength(); slot++)
|
|
{
|
|
IEntity actor = victim.StatusSampler.InitialMember(slot);
|
|
if (!actor || actor == victim.Radio || !ERR_Actors.CanFight(actor)
|
|
|| !victim.StatusSampler.CanAttribute(slot, actor) || CompartmentAccessComponent.GetVehicleIn(actor)) continue;
|
|
ERR_NativeHQBodySetup fixture = new ERR_NativeHQBodySetup();
|
|
fixture.VictimUnit = victim; fixture.ObserverUnit = observer; fixture.Actor = actor;
|
|
fixture.SourceId = sourceId; fixture.InitialSlot = slot;
|
|
fixture.InitialStrength = victim.StatusSampler.InitialStrength();
|
|
fixture.SavedNextStatus = victim.NextStatus;
|
|
// Isolate the BODY uplink from the deceased member's own group check-in.
|
|
// No report/route is discarded: selection required that group to be idle.
|
|
victim.NextStatus = now + 180000;
|
|
if (status)
|
|
{
|
|
fixture.StatusMessageId = status.MessageId; fixture.StatusObservedAt = status.ObservedAt;
|
|
fixture.StatusPosition = status.Position;
|
|
}
|
|
return fixture;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
ERR_HQWatchEntry ERR_BodyFixtureWatch(string sourceId)
|
|
{
|
|
if (!ERR_NativeHQBodyProbe.Enabled()) return null;
|
|
return m_Watch.Find(sourceId);
|
|
}
|
|
bool ERR_BodyFixtureContact(IEntity actor)
|
|
{
|
|
foreach (ERR_HQReport report : m_Contacts) if (report.Target == actor) return true;
|
|
return false;
|
|
}
|
|
// Invoke the real production observer/queue method. It must do its own native LOS
|
|
// and physical route lookup. The fixture does not construct or enqueue a report.
|
|
ERR_HQUnit ERR_BodyFixturePending(ERR_NativeHQBodySetup fixture, float now)
|
|
{
|
|
if (!ERR_NativeHQBodyProbe.Enabled() || !fixture) return null;
|
|
ObserveBody(fixture.ObserverUnit, now);
|
|
foreach (ERR_HQUnit unit : m_Units)
|
|
if (unit.PendingReport && unit.Uplink && unit.PendingReport.Target == fixture.Actor
|
|
&& unit.PendingReport.TravelMode == "body") return unit;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class ERR_NativeHQBodyProbe
|
|
{
|
|
protected ERR_GameMode m_Mode;
|
|
protected ref ERR_NativeHQBodySetup m_Setup;
|
|
protected ref ERR_HQReport m_Report;
|
|
protected ref ERR_HQRoute m_Route;
|
|
protected ref ERR_HQStatusReport m_Status;
|
|
protected ref ERR_HQAmmoSnapshot m_AmmoAtQueue;
|
|
protected ref ERR_HQRoute m_StatusRoute;
|
|
protected float m_Deadline;
|
|
protected float m_StableUntil;
|
|
protected int m_Step;
|
|
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-body";
|
|
}
|
|
void ERR_NativeHQBodyProbe(ERR_GameMode mode) { m_Mode = mode; }
|
|
protected bool Check(bool condition, string detail)
|
|
{
|
|
if (condition) { Print("ERR SMOKE PASS: HQ body " + detail); return true; }
|
|
m_Failed = true; Print("ERR SMOKE FAIL: HQ body " + detail, LogLevel.ERROR); return false;
|
|
}
|
|
// Get(target) intentionally exposes SIGHTING only. Inspect the bounded detached
|
|
// evaluations to locate this exact BODY report without changing memory semantics.
|
|
protected ERR_HQBelief BodyBelief(ERR_HQ hq, IEntity actor, int messageId)
|
|
{
|
|
for (int i = 0; i < hq.Memory().Count(); i++)
|
|
{
|
|
ERR_HQBelief belief = hq.Memory().At(i);
|
|
if (belief && belief.Kind == ERR_HQMemoryKind.BODY && belief.Target == actor
|
|
&& belief.MessageId == messageId) return belief;
|
|
}
|
|
return null;
|
|
}
|
|
protected bool SameAmmo(ERR_HQAmmoSnapshot a, ERR_HQAmmoSnapshot b)
|
|
{
|
|
if (!a || !b || a.Complete != b.Complete || a.LauncherComplete != b.LauncherComplete) return false;
|
|
if (a.Members != b.Members || a.SmallArmsMuzzles != b.SmallArmsMuzzles) return false;
|
|
if (a.SmallArmsRounds != b.SmallArmsRounds || a.SmallArmsMagazines != b.SmallArmsMagazines) return false;
|
|
if (a.SmallArmsChambers != b.SmallArmsChambers || a.LauncherRounds != b.LauncherRounds) return false;
|
|
return a.LauncherMagazines == b.LauncherMagazines && a.LauncherChambers == b.LauncherChambers;
|
|
}
|
|
// Read-only independent observation of original living members. Nothing here is
|
|
// assigned to PendingStatus or delivered to Watch; the real production sender did that.
|
|
protected bool NativeAmmo(ERR_HQUnit unit, ERR_HQAmmoSnapshot queued)
|
|
{
|
|
if (!unit || !unit.Group || !queued) return false;
|
|
ERR_HQAmmoSnapshot inspected = new ERR_HQAmmoSnapshot();
|
|
inspected.Complete = true; inspected.LauncherComplete = true;
|
|
array<IEntity> loadedIdentities = {};
|
|
int nativeLoadedRounds;
|
|
int nativeLoadedMagazines;
|
|
for (int slot = 0; slot < unit.StatusSampler.InitialStrength(); slot++)
|
|
{
|
|
IEntity actor = unit.StatusSampler.InitialMember(slot);
|
|
if (!ERR_Actors.Alive(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != unit.Group
|
|
|| SCR_FactionManager.SGetFaction(actor) != m_Mode.EnemyFaction()
|
|
|| !unit.StatusSampler.CanAttribute(slot, actor)) continue;
|
|
ERR_HQAmmoSampler.Inspect(actor, unit.Group, inspected);
|
|
Print(string.Format("ERR HQ BODY AMMO slot=%1 complete=%2 launcherComplete=%3 members=%4 rounds=%5",
|
|
slot, inspected.Complete, inspected.LauncherComplete, inspected.Members, inspected.SmallArmsRounds));
|
|
BaseWeaponManagerComponent manager;
|
|
manager = BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent));
|
|
if (!manager) return false;
|
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
|
array<IEntity> inventoryItems = {};
|
|
if (inventory) inventory.GetItems(inventoryItems);
|
|
foreach (IEntity inventoryItem : inventoryItems)
|
|
{
|
|
if (!inventoryItem) { Print("ERR HQ BODY AMMO null inventory entry"); continue; }
|
|
BaseMagazineComponent magazineItem = BaseMagazineComponent.Cast(inventoryItem.FindComponent(BaseMagazineComponent));
|
|
if (magazineItem && !magazineItem.GetMagazineWell()) Print(string.Format("ERR HQ BODY AMMO no-well item=%1", inventoryItem.GetPrefabData().GetPrefabName()));
|
|
}
|
|
array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);
|
|
if (weapons.Count() > 8) return false;
|
|
foreach (IEntity entity : weapons)
|
|
{
|
|
if (!entity) continue;
|
|
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(entity.FindComponent(BaseWeaponComponent));
|
|
if (!weapon) return false;
|
|
EWeaponType kind = weapon.GetWeaponType();
|
|
Print(string.Format("ERR HQ BODY AMMO weapon kind=%1 prefab=%2", kind, entity.GetPrefabData().GetPrefabName()));
|
|
bool smallArms = kind == EWeaponType.WT_RIFLE || kind == EWeaponType.WT_SNIPERRIFLE
|
|
|| kind == EWeaponType.WT_MACHINEGUN || kind == EWeaponType.WT_HANDGUN;
|
|
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
|
Print(string.Format("ERR HQ BODY AMMO muzzle count=%1", muzzles.Count()));
|
|
if (muzzles.Count() > 4) return false;
|
|
foreach (BaseMuzzleComponent muzzle : muzzles)
|
|
{
|
|
if (muzzle) Print(string.Format("ERR HQ BODY AMMO muzzle kind=%1 barrels=%2 magazineWell=%3", muzzle.GetMuzzleType(), muzzle.GetBarrelsCount(), muzzle.GetMagazineWell() != null));
|
|
if (!smallArms || !muzzle || muzzle.GetMuzzleType() != EMuzzleType.MT_BaseMuzzle) continue;
|
|
BaseMagazineComponent loaded = muzzle.GetMagazine();
|
|
if (!loaded || !loaded.GetOwner() || loadedIdentities.Contains(loaded.GetOwner())) continue;
|
|
loadedIdentities.Insert(loaded.GetOwner());
|
|
nativeLoadedRounds += loaded.GetAmmoCount(); nativeLoadedMagazines++;
|
|
}
|
|
}
|
|
}
|
|
Print(string.Format("ERR HQ BODY AMMO native members=%1 loaded-rounds=%2 loaded-mags=%3; queued rounds=%4 mags=%5 chambers=%6",
|
|
inspected.Members, nativeLoadedRounds, nativeLoadedMagazines, queued.SmallArmsRounds,
|
|
queued.SmallArmsMagazines, queued.SmallArmsChambers));
|
|
return inspected.Valid(queued.Members) && SameAmmo(queued, inspected) && nativeLoadedRounds > 0
|
|
&& nativeLoadedMagazines > 0 && queued.SmallArmsRounds >= nativeLoadedRounds
|
|
&& queued.SmallArmsMagazines >= nativeLoadedMagazines && !queued.KnownSmallArmsEmpty();
|
|
}
|
|
protected bool NoLoss(ERR_HQ hq)
|
|
{
|
|
ERR_HQWatchEntry status = hq.ERR_BodyFixtureWatch(m_Setup.SourceId);
|
|
return !status || status.ConfirmedLosses == 0;
|
|
}
|
|
int Tick(ERR_HQ hq, float now)
|
|
{
|
|
if (!Enabled() || !hq || !m_Mode || m_Failed) return -1;
|
|
if (m_Deadline == 0) m_Deadline = now + 150000;
|
|
if (now > m_Deadline) { Check(false, "bounded native LOS/radio fixture deadline"); return -1; }
|
|
if (m_Step == 0)
|
|
{
|
|
if (!hq.ERR_ProbeSquadsReady()) return 0;
|
|
m_Setup = hq.ERR_BodyFixturePrepare(now);
|
|
if (!m_Setup) return 0;
|
|
if (!Check(m_Setup.VictimUnit.Group != m_Setup.ObserverUnit.Group
|
|
&& ERR_Actors.Group(m_Setup.Actor) == m_Setup.VictimUnit.Group,
|
|
"two distinct existing physical formations and immutable victim slot selected")) return -1;
|
|
// Explicit fixture arrangement. One native corpse, no new actor/group/item.
|
|
vector position = ERR_Actors.Ground(m_Setup.ObserverUnit.Radio.GetOrigin() + "0 0 5");
|
|
m_Setup.Actor.SetOrigin(position);
|
|
SCR_CharacterDamageManagerComponent damage;
|
|
damage = SCR_CharacterDamageManagerComponent.Cast(m_Setup.Actor.FindComponent(SCR_CharacterDamageManagerComponent));
|
|
if (!Check(damage != null, "original fixture actor has native character damage")) return -1;
|
|
damage.EnableDamageHandling(true);
|
|
damage.Kill(Instigator.CreateInstigator(m_Setup.Actor));
|
|
if (!Check(NoLoss(hq), "local fixture death has not informed HQ")) return -1;
|
|
m_Step = 1; return 0;
|
|
}
|
|
if (m_Step == 1)
|
|
{
|
|
if (!NoLoss(hq)) { Check(false, "uncaptured delivery preceded pending-payload inspection"); return -1; }
|
|
ERR_HQUnit reporter = hq.ERR_BodyFixturePending(m_Setup, now);
|
|
if (!reporter) return 0;
|
|
m_Report = reporter.PendingReport.Snapshot(); m_Route = reporter.Uplink;
|
|
if (!Check(m_Report.MessageId > 0 && m_Report.Source == reporter.Group
|
|
&& m_Report.Target == m_Setup.Actor && m_Report.CasualtySourceId == m_Setup.SourceId
|
|
&& m_Report.CasualtySlot == m_Setup.InitialSlot && m_Report.CasualtyStrength == m_Setup.InitialStrength,
|
|
"actual native LOS produced body snapshot with original formation/slot attribution")) return -1;
|
|
if (!Check(m_Route.MessageId == m_Report.MessageId && !m_Route.Nodes.IsEmpty()
|
|
&& m_Route.Nodes[0].Carrier == reporter.Radio && ERR_Actors.CanFight(reporter.Radio)
|
|
&& !m_Report.HasVelocity && !m_Report.Distress && m_Report.TravelMode == "body",
|
|
"living observer formation queued its actual radio route without target-motion privilege")) return -1;
|
|
if (!Check(NoLoss(hq), "pending radio payload still gives HQ no casualty knowledge")) return -1;
|
|
m_Step = 2; return 0;
|
|
}
|
|
if (m_Step == 2)
|
|
{
|
|
if (m_Route.Failed) { Check(false, "actual body uplink failed before delivery"); return -1; }
|
|
if (m_Route.Cursor < m_Route.Nodes.Count() || m_Route.Sending)
|
|
{
|
|
if (!NoLoss(hq)) { Check(false, "in-transit body report created early knowledge"); return -1; }
|
|
return 0;
|
|
}
|
|
ERR_HQWatchEntry status = hq.ERR_BodyFixtureWatch(m_Setup.SourceId);
|
|
ERR_HQBelief belief = BodyBelief(hq, m_Setup.Actor, m_Report.MessageId);
|
|
if (!Check(status && status.ConfirmedLosses == 1 && !status.Closed,
|
|
"completed native radio uplink records exactly one original casualty")) return -1;
|
|
if (!Check(status.MessageId == m_Setup.StatusMessageId && status.ObservedAt == m_Setup.StatusObservedAt
|
|
&& status.Position == m_Setup.StatusPosition,
|
|
"body delivery preserves the formation's independent status chronology and position")) return -1;
|
|
if (!Check(belief && belief.Kind == ERR_HQMemoryKind.BODY && belief.MessageId == m_Report.MessageId
|
|
&& !belief.HasVelocity && !hq.ERR_BodyFixtureContact(m_Setup.Actor),
|
|
"delivered body is uncertain investigation memory without a firing contact")) return -1;
|
|
// Resume a real surviving caller's own cumulative status for overlap proof.
|
|
m_Setup.VictimUnit.NextStatus = now;
|
|
m_Step = 3; return 0;
|
|
}
|
|
if (m_Step == 3)
|
|
{
|
|
ERR_HQUnit victim = m_Setup.VictimUnit;
|
|
if (!victim.PendingStatus || !victim.Uplink) return 0;
|
|
m_Status = victim.PendingStatus; m_StatusRoute = victim.Uplink;
|
|
if (!Check(m_Status.SourceId == m_Setup.SourceId && m_Status.ConfirmedLosses == 1
|
|
&& m_Status.DeathSlots.Count() == 1 && m_Status.DeathSlots[0] == m_Setup.InitialSlot,
|
|
"real surviving caller independently reports the same original casualty slot")) return -1;
|
|
bool nativeAmmo = NativeAmmo(victim, m_Status.AmmoState);
|
|
if (!Check(m_Status.AmmoState && m_Status.AmmoState.Complete && m_Status.AmmoState.Valid(m_Status.Count)
|
|
&& m_Status.AmmoState.Members == m_Setup.InitialStrength - 1 && nativeAmmo,
|
|
"real pending status matches original living native inventories and positive loaded small-arms ammunition"))
|
|
return -1;
|
|
m_AmmoAtQueue = m_Status.AmmoState.Snapshot();
|
|
if (!Check(m_AmmoAtQueue != m_Status.AmmoState && SameAmmo(m_AmmoAtQueue, m_Status.AmmoState),
|
|
"captured pending ammunition evidence is a detached unchanged scalar snapshot")) return -1;
|
|
m_Step = 4; return 0;
|
|
}
|
|
if (m_Step == 4)
|
|
{
|
|
if (m_StatusRoute.Failed) { Check(false, "actual overlapping status uplink failed"); return -1; }
|
|
if (m_StatusRoute.Cursor < m_StatusRoute.Nodes.Count() || m_StatusRoute.Sending) return 0;
|
|
ERR_HQWatchEntry status = hq.ERR_BodyFixtureWatch(m_Setup.SourceId);
|
|
if (!Check(status && status.MessageId == m_Status.MessageId && status.ConfirmedLosses == 1
|
|
&& status.Count == m_Setup.InitialStrength - 1,
|
|
"delivered cumulative status overlaps body evidence without double-counting")) return -1;
|
|
if (!Check(status.AmmoState && status.AmmoState != m_Status.AmmoState
|
|
&& status.AmmoState != m_AmmoAtQueue && SameAmmo(status.AmmoState, m_AmmoAtQueue)
|
|
&& SameAmmo(m_Status.AmmoState, m_AmmoAtQueue) && !status.AmmoState.KnownSmallArmsEmpty(),
|
|
"actual radio delivery stores an independent Watch ammunition snapshot equal to the pending evidence")) return -1;
|
|
m_StableUntil = now + 20000; m_Step = 5; return 0;
|
|
}
|
|
ERR_HQWatchEntry stable = hq.ERR_BodyFixtureWatch(m_Setup.SourceId);
|
|
if (!stable || stable.ConfirmedLosses != 1 || hq.ERR_BodyFixtureContact(m_Setup.Actor))
|
|
{ Check(false, "confirmed corpse lost idempotence or became a firing contact"); return -1; }
|
|
if (now < m_StableUntil) return 0;
|
|
if (!Check(SameAmmo(stable.AmmoState, m_AmmoAtQueue) && SameAmmo(m_Status.AmmoState, m_AmmoAtQueue),
|
|
"delivered and pending ammunition snapshots remain unchanged after twenty further seconds")) return -1;
|
|
Check(true, "confirmed corpse remains one loss with no firing contact after twenty further seconds");
|
|
return 1;
|
|
}
|
|
void Stop()
|
|
{
|
|
if (m_Setup && m_Setup.VictimUnit && m_Step < 3)
|
|
m_Setup.VictimUnit.NextStatus = m_Setup.SavedNextStatus;
|
|
}
|
|
void ~ERR_NativeHQBodyProbe() { Stop(); }
|
|
}
|
|
#endif
|