Add bounded aircraft control and verify current packaged runtime closure

This commit is contained in:
2026-09-19 13:31:51 +02:00
parent 4125018d04
commit a880e0485c
695 changed files with 79317 additions and 44 deletions
+3 -1
View File
@@ -4,7 +4,9 @@ An independently authored replacement for the abandoned Escapists base mission,
The current base supplies prison spawning, two independent scavenging/clue routes, finite medical and outpost stock, an optional ground vehicle, physical radio documents, named discovery markers, native objectives, a separate radio-defence service, capacity-checked helicopter extraction, identity-based partial results, early helicopter-loss defeat, saved mission time/speed, defeat/restart, identity-based lives and local spectator following. Authored site definitions and an injected vanilla faction/asset catalog own the initial physical population. Core provides administration and startup diagnostics; the optional AI, Intel, Chat and Map addons extend its interfaces. HQ coordinates enemy responses through physical radios and available forces.
See [current development status](docs/current-status.md) for checkpoints. Packed Core and all-five-addon Chat each pass ordinary startup with a real prisoner, no loose successor source and zero captured errors. The [refreshed mortar salvo](docs/evidence/native-2026-09-19-112900-mortar-refresh/report.json), [report expiry](docs/evidence/native-2026-09-19-114751-mortar-expiry/report.json) and [isolated vertical flight](docs/evidence/native-2026-09-19-114504-powered-flight/report.json) pass 55, 48 and 81 assertions respectively, all without captured errors. These establish mechanisms, not operational HQ aircraft support or a completed manual escape. Native packaging still produces [UI shutdown errors also reproduced by an empty-addon control](docs/native-packaging-control.md). Manual gameplay, real multiplayer, measured FPS improvement and release qualification remain unfinished. The [original responsibility inventory](docs/original-feature-coverage.md) records remaining scope.
See [current development status](docs/current-status.md) for source-bound checkpoints. The ordinary game loads all five addons and shows the [real prisoner view and starting objective](docs/evidence/game-2026-09-19-125906-integrated-prison/ordinary-prison-view.png). Native tests cover scripted escape/restart, finite mortar support, physical radio delivery, shared-map ownership, and the reusable aircraft controller's vertical flight, airborne cancellation and short fixed-target movement. The current five-addon packages pass ordinary Chat startup without loose successor source and compile/prepare the empty mission on an isolated Linux server. The latter retains four installed-asset errors reproduced by a vanilla control.
The complete player-controlled escape, real multiplayer, operational HQ aircraft routes and measured FPS improvement remain unfinished. Native packaging still produces [UI shutdown errors also reproduced by an empty-addon control](docs/native-packaging-control.md); successful packed startup does not qualify that build process. All seventeen first-release acceptance cases remain pending. The [original responsibility inventory](docs/original-feature-coverage.md) tracks the unfinished scope.
## Projects and scenarios
@@ -0,0 +1,436 @@
// Prepared control mechanism only. No global hooks, force allocation or HQ selection.
enum ERR_AircraftFlightState
{
IDLE, HOOKS, STARTING, POSITIVE, NEGATIVE, CLIMB, LANDING, SHUTDOWN,
COMPLETE, CANCELLED, FAILED, RELINQUISHED, TRANSLATING
}
class ERR_AircraftFlightRules
{
static bool Terminal(int state) { return state >= ERR_AircraftFlightState.COMPLETE && state <= ERR_AircraftFlightState.RELINQUISHED; }
static bool Deadline(float now, float deadline) { return now >= 0 && deadline > now && deadline - now <= 240000; }
static bool Envelope(float height, float drift, float upright)
{
return height >= -3 && height <= 18 && drift >= 0 && drift < 8 && upright >= 0.85;
}
static bool Hover(float height, float speed, float vertical, float upright)
{
return height >= 7 && height <= 9 && speed >= 0 && speed < 1.5 && Math.AbsFloat(vertical) < 0.5 && upright > 0.98;
}
static bool Landed(bool contact, float height, float speed, float angular, float upright)
{
return contact && height > -1 && height < 1 && speed >= 0 && speed < 0.5 && angular >= 0 && angular < 0.1 && upright > 0.98;
}
static float Vertical(float height, bool landing)
{
float target = 8; if (landing) target = 0;
return Math.Clamp((target - height) * 0.6, -0.8, 1.5);
}
// Ground aborts may stop the engine; airborne cancellation retains the flight lease.
static int CancelAction(bool ownershipValid, bool landed, bool engineOn)
{
if (!ownershipValid) return 0;
if (!landed) return 1;
if (engineOn) return 2;
return 3;
}
}
// Bounded migration of the separately proven native-autohover translation.
class ERR_AircraftTranslationRules
{
static bool Target(vector home, vector target)
{
float distance = vector.DistanceXZ(home, target);
return distance >= 0 && distance <= 5;
}
static bool Reachable(float distance) { return distance >= 0 && distance <= 4.05; }
static vector Demand(vector target, vector position, vector velocity)
{
vector error = target - position; error[1] = 0;
vector desired = error * 0.25;
float speed = desired.Length();
if (speed > 0.5) desired = desired * (0.5 / speed);
velocity[1] = 0;
vector demand = (desired - velocity) * 0.05;
float magnitude = demand.Length();
if (magnitude > 0.03) demand = demand * (0.03 / magnitude);
return demand;
}
static bool Held(bool contact, float height, float speed, float vertical, float upright, float horizontalSpeed, float targetError, float displacement, float distance)
{
if (contact || !ERR_AircraftFlightRules.Hover(height, speed, vertical, upright)) return false;
if (!(horizontalSpeed >= 0 && horizontalSpeed < 0.25 && targetError >= 0 && targetError <= 0.75)) return false;
return Reachable(distance) && displacement >= Math.Max(0, distance - 0.75);
}
}
class ERR_AircraftFlight
{
protected static ref map<Vehicle, ERR_AircraftFlight> s_Owners = new map<Vehicle, ERR_AircraftFlight>();
protected ERR_GameMode m_Mode;
protected Vehicle m_Aircraft;
protected SCR_AIGroup m_Group;
protected IEntity m_Pilot;
protected PilotCompartmentSlot m_Seat;
protected HelicopterControllerComponent m_Controller;
protected VehicleHelicopterSimulation m_Simulation;
protected CharacterControllerComponent m_Character;
protected CharacterInputContext m_Input;
protected AIAgent m_Agent;
protected ActionManager m_Actions;
protected FuelManagerComponent m_Fuel;
protected ref ERR_AircraftFlightEnvelope m_Qualification;
protected ERR_AircraftFlightState m_State;
protected int m_Token;
protected vector m_Home;
protected float m_Heading;
protected float m_Deadline;
protected float m_StageDeadline;
protected float m_Stable = -1;
protected float m_LastTick = -1;
protected float m_LastControl = -1;
protected float m_InitialFuel;
protected float m_Collective = -0.8;
protected int m_ActionUpdates;
protected int m_HelicopterUpdates;
protected int m_GateUpdates;
protected int m_OriginalLOD;
protected CollectiveMode m_OriginalMode;
protected bool m_OriginalAutohover;
protected bool m_SettingsOwned;
protected bool m_Readback = true;
protected bool m_Cancelled;
protected bool m_StartAttempted;
protected string m_Reason;
protected bool m_TranslationRequested;
protected bool m_TranslationStarted;
protected bool m_TranslationProven;
protected vector m_TranslationTarget;
protected vector m_TranslationOrigin;
protected float m_TranslationDistance;
protected int m_TranslationUpdates;
ERR_AircraftFlightState State() { return m_State; }
string Reason() { return m_Reason; }
int OrderToken() { return m_Token; }
bool Finished() { return ERR_AircraftFlightRules.Terminal(m_State); }
Vehicle Aircraft() { return m_Aircraft; }
bool TranslationStarted() { return m_TranslationStarted; }
bool TranslationProven() { return m_TranslationProven; }
vector TranslationTarget() { return m_TranslationTarget; }
vector TranslationOrigin() { return m_TranslationOrigin; }
int TranslationUpdates() { return m_TranslationUpdates; }
// Absolute horizontal target fixed before takeoff. Its altitude is the existing
// eight-metre hover, not a caller-supplied route or a new qualification home.
bool StartTranslation(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftFlightEnvelope qualification, int token, float deadline, vector target)
{
if (m_StartAttempted || m_TranslationRequested || !qualification || !ERR_AircraftTranslationRules.Target(qualification.Home(), target)) return false;
m_TranslationRequested = true; m_TranslationTarget = target;
vector home = qualification.Home(); m_TranslationTarget[1] = home[1] + 8;
return Start(mode, aircraft, group, pilot, seat, qualification, token, deadline);
}
protected float Now()
{
if (!GetGame() || !GetGame().GetWorld()) return -1;
return GetGame().GetWorld().GetWorldTime();
}
protected bool NoPlayers()
{
if (!GetGame() || !GetGame().GetPlayerManager()) return false;
if (!m_Group || m_Group.GetPlayerCount(true) > 0 || ERR_Actors.Player(m_Pilot)) return false;
array<int> players = {}; GetGame().GetPlayerManager().GetPlayers(players);
foreach (int id : players)
{
IEntity actor = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
if (actor && (actor == m_Pilot || CompartmentAccessComponent.GetVehicleIn(actor) == m_Aircraft)) return false;
}
return true;
}
protected bool Owns()
{
if (!GetGame() || !GetGame().GetWorld() || !GetGame().GetPlayerManager()) return false;
if (!Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || !m_Aircraft || !m_Group || !m_Pilot) return false;
if (!m_Controller || m_Controller.GetOwner() != m_Aircraft || !m_Seat || m_Controller.GetPilotCompartmentSlot() != m_Seat || m_Seat.GetOccupant() != m_Pilot) return false;
if (CompartmentAccessComponent.GetVehicleIn(m_Pilot) != m_Aircraft || ERR_Actors.Group(m_Pilot) != m_Group || m_Group.GetFaction() != m_Mode.EnemyFaction()) return false;
if (SCR_FactionManager.SGetFaction(m_Aircraft) != m_Mode.EnemyFaction() || !NoPlayers()) return false;
return m_Agent && m_Agent.GetControlledEntity() == m_Pilot && m_Agent.GetActionManager() == m_Actions && m_Actions && m_Actions != GetGame().GetInputManager();
}
protected bool Healthy()
{
if (!ERR_Actors.Alive(m_Aircraft) || !ERR_Actors.CanFight(m_Pilot) || !m_Simulation || !m_Simulation.IsValid() || !m_Fuel || !m_Fuel.HasFuel()) return false;
SCR_VehicleDamageManagerComponent damage = SCR_VehicleDamageManagerComponent.Cast(m_Aircraft.FindComponent(SCR_VehicleDamageManagerComponent));
if (!damage || !damage.GetEngineFunctional() || damage.GetMovementDamage() >= 1 || damage.IsOnFire() || m_Simulation.RotorCount() != 2) return false;
for (int rotor = 0; rotor < 2; rotor++) if (m_Simulation.RotorGetState(rotor) == RotorState.DESTROYED) return false;
return true;
}
protected bool Grounded()
{
if (!GetGame() || !GetGame().GetWorld() || !m_Aircraft || !m_Simulation) return false;
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
return ERR_AircraftFlightRules.Landed(m_Simulation.HasAnyGroundContact(), transform[3][1] - m_Home[1], m_Simulation.GetVelocity().Length(), m_Simulation.GetAngularVelocity().Length(), transform[1][1]);
}
// Single use. A future HQ owner supplies its immutable unique order token and
// retains this instance, calling Tick and the two scoped callbacks until terminal.
bool Start(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftFlightEnvelope qualification, int token, float deadline)
{
if (m_StartAttempted || m_State != ERR_AircraftFlightState.IDLE || m_Token != 0 || !Replication.IsServer() || !GetGame() || !qualification || !aircraft || !pilot || !group || !seat) return false;
m_StartAttempted = true;
float now = Now();
if (!ERR_AircraftFlightRules.Deadline(now, deadline) || token <= 0 || s_Owners.Get(aircraft)) return false;
m_Mode = mode; m_Aircraft = aircraft; m_Group = group; m_Pilot = pilot; m_Seat = seat;
m_Controller = HelicopterControllerComponent.Cast(aircraft.FindComponent(HelicopterControllerComponent));
if (m_Controller) m_Simulation = m_Controller.GetSimulation();
m_Character = CharacterControllerComponent.Cast(pilot.FindComponent(CharacterControllerComponent));
if (m_Character) m_Input = m_Character.GetInputContext();
AIControlComponent control = AIControlComponent.Cast(pilot.FindComponent(AIControlComponent));
if (control) m_Agent = control.GetControlAIAgent();
if (m_Agent) m_Actions = m_Agent.GetActionManager();
m_Fuel = FuelManagerComponent.Cast(aircraft.FindComponent(FuelManagerComponent));
if (!Owns() || !Healthy() || !m_Input || m_Controller.IsEngineOn() || !ERR_CasualtyLoadingRules.AircraftReady(aircraft)) return false;
Physics physics = aircraft.GetPhysics();
if (!physics || !physics.IsDynamic() || !physics.IsActive() || !m_Simulation.HasAnyGroundContact()) return false;
SignalsManagerComponent signals = m_Controller.GetSignalsManager();
if (!signals || signals.FindSignal("MainRotorCollective") < 0) return false;
if (!qualification.Claim(mode, aircraft, pilot, token)) return false;
m_Qualification = qualification; m_Home = qualification.Home(); m_Token = token; m_Deadline = deadline;
m_Heading = aircraft.GetYawPitchRoll()[0]; m_InitialFuel = m_Fuel.GetTotalFuel();
m_OriginalMode = m_Controller.GetCollectiveMode(); m_OriginalAutohover = m_Controller.GetAutohoverEnabled(); m_OriginalLOD = m_Agent.GetPermanentLOD();
m_SettingsOwned = true; s_Owners.Set(aircraft, this);
m_Agent.SetPermanentLOD(0); m_Agent.ActivateAI();
m_Controller.SetAutohoverEnabled(false);
if (m_Controller.GetAutohoverEnabled()) { Fail("native autohover disable refused"); return false; }
m_State = ERR_AircraftFlightState.HOOKS; m_StageDeadline = Math.Min(deadline, now + 5000);
PrintFormat("ERR AIRCRAFT CONTROL: token=%1 bound qualified finite aircraft; no HQ dispatch installed", token);
return true;
}
protected bool Lease()
{
return m_Token > 0 && !Finished() && m_State != ERR_AircraftFlightState.IDLE && s_Owners.Get(m_Aircraft) == this && m_Qualification && m_Qualification.ClaimedBy(m_Aircraft, m_Pilot, m_Token);
}
// Every input entry checks the immutable deadlines even if owner Tick stalls.
protected bool ControlLease()
{
if (!Lease()) return false;
if (!Owns()) { Relinquish("ownership or world unavailable at control callback"); return false; }
float now = Now();
if (now < 0 || now >= m_Deadline || now >= m_StageDeadline) { Fail("control callback reached bounded flight deadline"); return false; }
if (!Healthy() || !m_Readback) { Fail("flight capability or input readback lost"); return false; }
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
if (!ERR_AircraftFlightRules.Envelope(transform[3][1] - m_Home[1], vector.DistanceXZ(transform[3], m_Home), transform[1][1])) { Fail("control callback exceeded qualified flight envelope"); return false; }
return true;
}
// Must be called after the native helicopter's OnPrepareControls.
void PrepareHelicopter(HelicopterControllerComponent controller)
{
if (!Lease() || controller != m_Controller) return;
if (!ControlLease()) return;
controller.SetCollectiveMode(CollectiveMode.Direct); m_HelicopterUpdates++;
}
protected bool Pair(string positive, string negative, float demand)
{
float a = Math.Clamp(demand, 0, 1), b = Math.Clamp(-demand, 0, 1);
m_Actions.SetActionValue(positive, a); m_Actions.SetActionValue(negative, b);
return Math.AbsFloat(m_Actions.GetActionValue(positive) - a) < 0.001 && Math.AbsFloat(m_Actions.GetActionValue(negative) - b) < 0.001;
}
// Must be called on the exact AI actor manager before native OnPrepareControls.
void PrepareActions(CharacterControllerComponent controller, IEntity owner, ActionManager actions, float dt)
{
if (!Lease() || controller != m_Character || owner != m_Pilot || actions != m_Actions) return;
if (!ControlLease()) return;
if (!m_Readback || dt <= 0 || dt > 0.2) return;
if (!actions.IsContextActive("HelicopterContext")) actions.ActivateContext("HelicopterContext", 1);
if (!actions.IsContextActive("HelicopterContext")) { m_Readback = false; return; }
float collective, torque, forward, aside;
if (m_State == ERR_AircraftFlightState.POSITIVE) collective = 0.05;
else if (m_State == ERR_AircraftFlightState.NEGATIVE) collective = -0.05;
else if (m_State == ERR_AircraftFlightState.SHUTDOWN) collective = -1;
else if (m_State == ERR_AircraftFlightState.CLIMB || m_State == ERR_AircraftFlightState.LANDING || m_State == ERR_AircraftFlightState.TRANSLATING)
{
if (!m_Controller.GetAutohoverEnabled()) { Fail("native autohover lost while flying"); return; }
float height = m_Aircraft.GetOrigin()[1] - m_Home[1];
float error = ERR_AircraftFlightRules.Vertical(height, m_State == ERR_AircraftFlightState.LANDING) - m_Simulation.GetVelocity()[1];
m_Collective = Math.Clamp(m_Collective + error * dt * 0.12, -0.95, 0.8);
collective = Math.Clamp(m_Collective + error * 0.12, -1, 0.9);
float headingError = m_Heading - m_Aircraft.GetYawPitchRoll()[0];
while (headingError > 180) headingError -= 360;
while (headingError < -180) headingError += 360;
torque = Math.Clamp(headingError * 0.01 - m_Simulation.GetAngularVelocityLocal()[1] * 0.3, -0.3, 0.3);
if (m_State == ERR_AircraftFlightState.TRANSLATING)
{
if (!m_TranslationStarted || !ERR_AircraftTranslationRules.Target(m_Home, m_TranslationTarget)) { Fail("immutable translation target unavailable"); return; }
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
vector demand = ERR_AircraftTranslationRules.Demand(m_TranslationTarget, transform[3], m_Simulation.GetVelocity());
forward = Math.Clamp(vector.Dot(demand, transform[2]), -0.03, 0.03);
aside = Math.Clamp(vector.Dot(demand, transform[0]), -0.03, 0.03);
m_TranslationUpdates++;
}
if (m_State == ERR_AircraftFlightState.LANDING && Grounded()) collective = -1;
}
bool a = Pair("HelicopterCollectiveIncrease", "HelicopterCollectiveDecrease", collective);
bool b = Pair("HelicopterCyclicForward", "HelicopterCyclicBack", forward);
bool c = Pair("HelicopterCyclicRight", "HelicopterCyclicLeft", aside);
bool d = Pair("HelicopterAntiTorqueRight", "HelicopterAntiTorqueLeft", torque);
m_Readback = a && b && c && d; m_ActionUpdates++; m_LastControl = Now();
}
protected void Release(bool restore)
{
// No writes after capture/group/seat change, including apparently harmless zeros.
if (m_SettingsOwned && Owns())
{
Pair("HelicopterCollectiveIncrease", "HelicopterCollectiveDecrease", 0);
Pair("HelicopterCyclicForward", "HelicopterCyclicBack", 0);
Pair("HelicopterCyclicRight", "HelicopterCyclicLeft", 0);
Pair("HelicopterAntiTorqueRight", "HelicopterAntiTorqueLeft", 0);
// Restore native modes only after verified ground completion. Airborne
// failure leaves native autohover enabled rather than disabling it midair.
if (restore && Grounded())
{
m_Controller.SetAutohoverEnabled(m_OriginalAutohover);
m_Controller.SetCollectiveMode(m_OriginalMode);
m_Agent.SetPermanentLOD(m_OriginalLOD);
}
}
if (m_Aircraft && s_Owners && s_Owners.Get(m_Aircraft) == this) s_Owners.Remove(m_Aircraft);
m_SettingsOwned = false;
}
protected void RecordState()
{
string detail = string.Format("token=%1 state=%2 reason=%3; physical availability remains with the allocating owner", m_Token, m_State, m_Reason);
Print("ERR AIRCRAFT CONTROL: " + detail);
if (m_Mode && m_Mode == ERR_GameMode.Get()) ERR_HQ.Event("AIRCRAFT CONTROL", detail);
}
protected void Relinquish(string reason)
{
if (Finished()) return;
m_Reason = reason; m_State = ERR_AircraftFlightState.RELINQUISHED; Release(false); RecordState();
}
protected void Fail(string reason)
{
if (Finished()) return;
m_Reason = reason; m_State = ERR_AircraftFlightState.FAILED; Release(true); RecordState();
}
protected bool Landing(float now)
{
m_Controller.SetAutohoverEnabled(true);
if (!m_Controller.GetAutohoverEnabled()) { Fail("native autohover enable refused"); return false; }
m_State = ERR_AircraftFlightState.LANDING; m_Stable = -1;
m_StageDeadline = Math.Min(m_Deadline, now + 65000);
return true;
}
protected void Shutdown(float now)
{
m_Controller.StopEngine(false);
m_State = ERR_AircraftFlightState.SHUTDOWN; m_StageDeadline = Math.Min(m_Deadline, now + 15000); m_Stable = -1;
}
void Cancel(string reason)
{
if (!Lease()) return;
if (m_Cancelled) return; // repeated invalid reports cannot extend a landing deadline
m_Cancelled = true; m_Reason = reason; RecordState();
// After flight begins, even a current contact sample must pass the normal
// two-second landing observation; cancellation cannot shortcut touchdown.
bool preflightGround = m_State < ERR_AircraftFlightState.CLIMB && Grounded();
int action = ERR_AircraftFlightRules.CancelAction(Owns(), preflightGround, m_Controller && m_Controller.IsEngineOn());
if (action == 0) { Relinquish(reason); return; }
if (!Healthy()) { Fail(reason + "; flight capability lost"); return; }
if (action == 3) { m_State = ERR_AircraftFlightState.CANCELLED; Release(true); RecordState(); return; }
float now = Now();
if (action == 2) { Shutdown(now); return; }
if (m_State != ERR_AircraftFlightState.LANDING && m_State != ERR_AircraftFlightState.SHUTDOWN) Landing(now);
}
protected bool BeginTranslation(float now, vector position)
{
if (!m_TranslationRequested || m_TranslationStarted || m_State != ERR_AircraftFlightState.CLIMB) { Cancel("translation stage ownership refused"); return false; }
float distance = vector.DistanceXZ(position, m_TranslationTarget);
if (!ERR_AircraftTranslationRules.Target(m_Home, m_TranslationTarget) || !ERR_AircraftTranslationRules.Reachable(distance)) { Cancel("fixed target beyond qualified four-metre translation after hover drift"); return false; }
SignalsManagerComponent signals = m_Controller.GetSignalsManager();
if (!signals || signals.FindSignal("MainRotorCyclicForward") < 0 || signals.FindSignal("MainRotorCyclicAside") < 0 || !m_Controller.GetAutohoverEnabled()) { Cancel("native autohover cyclic signals unavailable"); return false; }
m_TranslationOrigin = position; m_TranslationDistance = distance;
m_TranslationStarted = true; m_State = ERR_AircraftFlightState.TRANSLATING;
m_Stable = -1; m_StageDeadline = Math.Min(m_Deadline, now + 45000);
PrintFormat("ERR AIRCRAFT TRANSLATION: token=%1 origin=%2 immutableTarget=%3 home=%4 distance=%5 deadline=%6", m_Token, position, m_TranslationTarget, m_Home, distance, m_StageDeadline);
return true;
}
protected void CheckTranslation(float now, vector position, float height, float speed, float upright)
{
if (!m_TranslationStarted || !m_Controller.GetAutohoverEnabled() || !ERR_AircraftTranslationRules.Target(m_Home, m_TranslationTarget)) { Cancel("translation ownership or autohover lost"); return; }
vector velocity = m_Simulation.GetVelocity(); vector horizontal = velocity; horizontal[1] = 0;
bool held = m_TranslationUpdates >= 3 && ERR_AircraftTranslationRules.Held(m_Simulation.HasAnyGroundContact(), height, speed, velocity[1], upright, horizontal.Length(), vector.DistanceXZ(position, m_TranslationTarget), vector.DistanceXZ(position, m_TranslationOrigin), m_TranslationDistance);
if (!held) { m_Stable = -1; return; }
if (m_Stable < 0) m_Stable = now;
if (now - m_Stable < 5000) return;
m_TranslationProven = true;
PrintFormat("ERR AIRCRAFT TRANSLATION HELD: token=%1 targetError=%2 displacement=%3 updates=%4", m_Token, vector.DistanceXZ(position, m_TranslationTarget), vector.DistanceXZ(position, m_TranslationOrigin), m_TranslationUpdates);
Landing(now);
}
// The owner continues ticking after Cancel until Finished, including landing.
void Tick()
{
if (!Lease()) return;
float now = Now();
if (!Owns()) { Relinquish("ownership or mission changed"); return; }
if (!Healthy()) { Fail("flight capability lost"); return; }
if (!m_Readback) { Fail("native action readback failed"); return; }
if (now >= m_Deadline || now >= m_StageDeadline || now < 0) { Fail("bounded flight stage expired"); return; }
if (m_LastTick >= 0 && (now < m_LastTick || now - m_LastTick > 1500)) m_Stable = -1;
m_LastTick = now;
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
float height = transform[3][1] - m_Home[1], speed = m_Simulation.GetVelocity().Length();
if (!ERR_AircraftFlightRules.Envelope(height, vector.DistanceXZ(transform[3], m_Home), transform[1][1])) { Fail("qualified flight envelope exceeded"); return; }
if (m_State == ERR_AircraftFlightState.HOOKS)
{
if (m_ActionUpdates < 3 || m_HelicopterUpdates < 3) return;
if (m_Controller.GetCollectiveMode() != CollectiveMode.Direct || m_Input.GetVehicleCollective() != 0 || m_Controller.IsEngineOn()) { Fail("native initial control state refused"); return; }
m_Controller.StartEngine(); m_State = ERR_AircraftFlightState.STARTING; m_StageDeadline = Math.Min(m_Deadline, now + 65000); return;
}
if (m_LastControl < 0 || now - m_LastControl > 1000) { Fail("pilot callback stopped"); return; }
if (m_State == ERR_AircraftFlightState.STARTING)
{
float rpm = m_Simulation.RotorGetRPMTarget(0);
if (!m_Controller.IsEngineOn() || rpm <= 0 || m_Simulation.RotorGetRPM(0) < rpm * 0.9 || m_Fuel.GetTotalFuel() >= m_InitialFuel) return;
m_State = ERR_AircraftFlightState.POSITIVE; m_StageDeadline = Math.Min(m_Deadline, now + 3000); m_GateUpdates = m_ActionUpdates; m_Stable = -1; return;
}
if (m_State != ERR_AircraftFlightState.SHUTDOWN && !m_Controller.IsEngineOn()) { Fail("running engine lost"); return; }
if (m_State == ERR_AircraftFlightState.POSITIVE || m_State == ERR_AircraftFlightState.NEGATIVE)
{
SignalsManagerComponent signals = m_Controller.GetSignalsManager();
int index = -1; if (signals) index = signals.FindSignal("MainRotorCollective");
if (index < 0 || m_Controller.GetCollectiveMode() != CollectiveMode.Direct || m_Controller.GetAutohoverEnabled()) { Fail("native collective proof unavailable"); return; }
float expected = 0.05; if (m_State == ERR_AircraftFlightState.NEGATIVE) expected = -0.05;
bool matched = m_ActionUpdates >= m_GateUpdates + 3 && Math.AbsFloat(signals.GetSignalValue(index) - expected) <= 0.01 && Math.AbsFloat(m_Input.GetVehicleCollective() - expected) <= 0.01;
if (!matched) { m_Stable = -1; return; }
if (m_Stable < 0) m_Stable = now;
if (now - m_Stable < 500) return;
m_Stable = -1;
if (m_State == ERR_AircraftFlightState.POSITIVE) { m_State = ERR_AircraftFlightState.NEGATIVE; m_StageDeadline = Math.Min(m_Deadline, now + 3000); m_GateUpdates = m_ActionUpdates; return; }
m_Controller.SetAutohoverEnabled(true);
if (!m_Controller.GetAutohoverEnabled()) { Fail("native autohover enable refused"); return; }
m_State = ERR_AircraftFlightState.CLIMB; m_StageDeadline = Math.Min(m_Deadline, now + 65000); return;
}
if (m_State == ERR_AircraftFlightState.TRANSLATING) { CheckTranslation(now, transform[3], height, speed, transform[1][1]); return; }
if (m_State == ERR_AircraftFlightState.CLIMB || m_State == ERR_AircraftFlightState.LANDING)
{
if (!m_Controller.GetAutohoverEnabled()) { Fail("native autohover lost"); return; }
bool stable = Grounded(); float duration = 2000;
if (m_State == ERR_AircraftFlightState.CLIMB) { stable = !m_Simulation.HasAnyGroundContact() && ERR_AircraftFlightRules.Hover(height, speed, m_Simulation.GetVelocity()[1], transform[1][1]); duration = 5000; }
if (!stable) m_Stable = -1;
if (stable && m_Stable < 0) m_Stable = now;
if (m_Stable < 0 || now - m_Stable < duration) return;
if (m_State == ERR_AircraftFlightState.CLIMB)
{
if (m_TranslationRequested) BeginTranslation(now, transform[3]);
else Landing(now);
return;
}
Shutdown(now); return;
}
if (m_State == ERR_AircraftFlightState.SHUTDOWN && !m_Controller.IsEngineOn())
{
if (!Grounded()) { Fail("engine-off physical ground check failed"); return; }
if (m_Cancelled) m_State = ERR_AircraftFlightState.CANCELLED;
else m_State = ERR_AircraftFlightState.COMPLETE;
Release(true); RecordState();
}
}
void ~ERR_AircraftFlight() { Release(true); }
}
@@ -0,0 +1,236 @@
// Successor-owned measured envelope, independent of aircraft yaw/pitch/roll.
// Adapted from the separately qualified native vertical fixture; no native hook.
// All distances are around the native physics origin, not the rotor centre.
class ERR_AircraftFlightFootprintRules
{
static float CylinderSphere(float originDistance, float radius, float thickness)
{
// Triangle inequality deliberately overbounds the one-sided rotor cylinder.
return originDistance + radius + thickness;
}
static bool Fits(float radius, float initialDrift, float initialHeight)
{
if (!(radius > 0 && radius < 32 && initialDrift >= 0 && initialDrift < 32)) return false;
if (!(initialHeight >= -3 && initialHeight <= 5)) return false;
return radius + initialDrift + 8 + 1 <= 40 && radius + initialHeight + 18 + 1 <= 60;
}
}
class ERR_AircraftFlightFootprint
{
static const int MAX_ENTITIES = 256;
static const int MAX_DEPTH = 16;
static const int MAX_HITZONES = 512;
protected static bool Refuse(string reason)
{
Print("ERR AIRCRAFT FOOTPRINT: refused " + reason);
return false;
}
protected static bool PointRadius(vector point, vector origin, inout float radius)
{
float distance = vector.Distance(point, origin);
if (!(distance >= 0 && distance < 1000)) return false;
radius = Math.Max(radius, distance);
return true;
}
static bool Measure(Vehicle aircraft, VehicleHelicopterSimulation simulation, vector pad, out vector origin, out float radius)
{
radius = 0;
if (!aircraft || !simulation || !simulation.IsValid() || !aircraft.GetPrefabData()) return Refuse("missing initialized native airframe");
if (aircraft.GetPrefabData().GetPrefabName() != "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et") return Refuse("different airframe resource");
// Model bounds and damage pivots are world-space. The simulation getter
// exposes internal physics space (identity at the airport in 100422), so
// use this same physical aircraft's actual entity world transform.
vector worldTransform[4]; aircraft.GetWorldTransform(worldTransform);
origin = worldTransform[3];
for (int axis = 0; axis < 3; axis++)
if (!(Math.AbsFloat(worldTransform[axis].Length() - 1) < 0.001)) return Refuse("non-unit native entity world basis");
array<IEntity> entities = {aircraft};
array<int> depths = {0};
array<HitZone> seenZones = {};
int meshes, mainSweeps, tailSweeps;
float modelRadius, damageRadius, simulationRadius;
for (int index = 0; index < entities.Count(); index++)
{
IEntity entity = entities[index];
if (!entity) return Refuse("hierarchy changed during measurement");
if (entity.GetVObject())
{
vector mins, maxs; entity.GetWorldBounds(mins, maxs);
for (int dimension = 0; dimension < 3; dimension++)
if (!(mins[dimension] <= maxs[dimension])) return Refuse("invalid model bounds");
// A sphere around every corner encloses the complete world AABB. Its radius
// remains conservative under any rigid orientation of the whole aircraft.
for (int x = 0; x < 2; x++)
for (int y = 0; y < 2; y++)
for (int z = 0; z < 2; z++)
{
vector corner = mins;
if (x == 1) corner[0] = maxs[0];
if (y == 1) corner[1] = maxs[1];
if (z == 1) corner[2] = maxs[2];
if (!PointRadius(corner, origin, modelRadius)) return Refuse("non-finite or excessive model bounds");
}
meshes++;
}
HitZoneContainerComponent container = HitZoneContainerComponent.Cast(entity.FindComponent(HitZoneContainerComponent));
if (container)
{
int count = container.CountAllHitZones();
if (count < 0 || count + seenZones.Count() > MAX_HITZONES) return Refuse("hitzone traversal bound");
array<HitZone> zones = {};
if (container.GetAllHitZones(zones) != count || zones.Count() != count) return Refuse("hitzones changed during measurement");
foreach (HitZone zone : zones)
{
if (!zone || seenZones.Contains(zone)) continue;
seenZones.Insert(zone);
SCR_RotorHitZone rotorZone = SCR_RotorHitZone.Cast(zone);
if (!rotorZone || rotorZone.GetRotorRadius() <= 0) continue; // transmission zones have no sweep
int rotorIndex = rotorZone.GetRotorIndex();
float expectedRadius, thickness;
if (rotorIndex == 0) { expectedRadius = 12; thickness = 1.5; mainSweeps++; }
else if (rotorIndex == 1) { expectedRadius = 2.3; thickness = 0.75; tailSweeps++; }
else return Refuse("unknown rotor sweep index");
// Stock thickness is protected with no getter. Scope to the exact prefab
// and verify its public sweep radius against the audited stock constants.
if (!(Math.AbsFloat(rotorZone.GetRotorRadius() - expectedRadius) < 0.001)) return Refuse("stock damage sweep radius changed");
PointInfo point = rotorZone.GetPointInfo();
if (!point) return Refuse("missing actual rotor sweep pivot");
vector rotorWorld[4]; point.GetWorldTransform(rotorWorld);
// Frobenius norm bounds the transform's maximum stretch even with scale
// or shear; do not assume the PointInfo basis is orthonormal.
float scaleSquared = 0;
for (int basis = 0; basis < 3; basis++) scaleSquared += vector.Dot(rotorWorld[basis], rotorWorld[basis]);
float scale = Math.Sqrt(scaleSquared);
if (!(scale > 0 && scale < 4)) return Refuse("invalid damage sweep basis");
float distance = vector.Distance(origin, rotorWorld[3]);
float sweepBound = ERR_AircraftFlightFootprintRules.CylinderSphere(distance, expectedRadius * scale, thickness * scale);
if (!(sweepBound > 0 && sweepBound < 1000)) return Refuse("invalid damage sweep extent");
damageRadius = Math.Max(damageRadius, sweepBound);
PrintFormat("ERR AIRCRAFT FOOTPRINT ROTOR: index=%1 pivot=%2 damageRadius=%3 thickness=%4 scale=%5 sphereBound=%6", rotorIndex, rotorWorld[3], expectedRadius, thickness, scale, sweepBound);
}
}
IEntity child = entity.GetChildren();
while (child)
{
if (entities.Count() >= MAX_ENTITIES || depths[index] >= MAX_DEPTH || entities.Contains(child)) return Refuse("entity/depth/cycle traversal bound");
entities.Insert(child); depths.Insert(depths[index] + 1);
child = child.GetSibling();
}
}
if (meshes == 0 || mainSweeps != 1 || tailSweeps != 1) return Refuse("complete model and both actual damage sweeps required");
if (simulation.RotorCount() != 2) return Refuse("stock simulation rotor count changed");
for (int rotor = 0; rotor < 2; rotor++)
{
vector localRotor[4]; simulation.RotorGetTransform(rotor, localRotor);
float physicalRadius = simulation.RotorGetRadius(rotor);
float physicalBound = localRotor[3].Length() + physicalRadius;
if (!(physicalRadius > 0 && physicalBound > 0 && physicalBound < 1000)) return Refuse("invalid native simulation rotor extent");
simulationRadius = Math.Max(simulationRadius, physicalBound);
PrintFormat("ERR AIRCRAFT FOOTPRINT PHYSICS ROTOR: index=%1 localPivot=%2 radius=%3 sphereBound=%4", rotor, localRotor[3], physicalRadius, physicalBound);
}
radius = Math.Max(modelRadius, Math.Max(damageRadius, simulationRadius));
float initialDrift = vector.DistanceXZ(origin, pad);
float initialHeight = origin[1] - pad[1];
PrintFormat("ERR AIRCRAFT FOOTPRINT: entities=%1 meshes=%2 zones=%3 modelSphere=%4 damageSphere=%5 simulationSphere=%6 completeSphere=%7 initialDrift=%8 initialHeight=%9", entities.Count(), meshes, seenZones.Count(), modelRadius, damageRadius, simulationRadius, radius, initialDrift, initialHeight);
if (!ERR_AircraftFlightFootprintRules.Fits(radius, initialDrift, initialHeight)) return Refuse("measured footprint plus initial placement,8m drift and1m margin exceeds40m corridor or60m ceiling");
return true;
}
}
// Issued only by physical qualification; fields have no public mutators.
// One claim binds this short-lived record to one original aircraft/pilot/order.
class ERR_AircraftFlightEnvelope
{
protected ERR_GameMode m_Mode;
protected Vehicle m_Aircraft;
protected IEntity m_Pilot;
protected vector m_Home;
protected float m_Radius;
protected float m_QualifiedAt;
protected int m_Claim;
vector Home() { return m_Home; }
float Radius() { return m_Radius; }
bool ClaimedBy(Vehicle aircraft, IEntity pilot, int token)
{
return m_Claim > 0 && m_Claim == token && m_Aircraft == aircraft && m_Pilot == pilot;
}
bool Claim(ERR_GameMode mode, Vehicle aircraft, IEntity pilot, int token)
{
if (!GetGame() || !GetGame().GetWorld()) return false;
float now = GetGame().GetWorld().GetWorldTime();
if (!Replication.IsServer() || !mode || mode != m_Mode || mode != ERR_GameMode.Get() || !mode.Active() || m_Claim != 0 || token <= 0) return false;
if (aircraft != m_Aircraft || pilot != m_Pilot || !aircraft || !pilot || now < m_QualifiedAt || now - m_QualifiedAt > 2000) return false;
if (vector.Distance(aircraft.GetOrigin(), m_Home) > 0.1) return false;
m_Claim = token;
return true;
}
protected static bool Hierarchy(IEntity root, array<IEntity> excluded)
{
if (!root) return false;
array<IEntity> pending = {root};
array<int> depths = {0};
for (int i = 0; i < pending.Count(); i++)
{
IEntity entity = pending[i];
if (!entity || excluded.Count() >= 512) return false;
if (!excluded.Contains(entity)) excluded.Insert(entity);
IEntity child = entity.GetChildren();
while (child)
{
if (pending.Count() >= 256 || depths[i] >= 16 || pending.Contains(child)) return false;
pending.Insert(child); depths.Insert(depths[i] + 1); child = child.GetSibling();
}
}
return true;
}
protected static bool ClearColumn(Vehicle aircraft, IEntity pilot, vector pad)
{
array<IEntity> excluded = {};
if (!Hierarchy(aircraft, excluded) || !Hierarchy(pilot, excluded)) return false;
for (int edge = 0; edge < 4; edge++)
{
float side = edge * Math.PI / 2;
vector point = pad + Vector(Math.Cos(side) * 14, 0, Math.Sin(side) * 14);
if (!(Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(point) - pad[1]) <= 0.8)) return false;
}
// Same four finite diagonal cylinder traces as the native fixture, with
// exclusions restricted to this already-existing aircraft and seated pilot.
array<vector> diagonals = {"40 2 0", "-40 2 0", "0 2 40", "0 2 -40"};
foreach (vector diagonal : diagonals)
{
TraceParam trace = new TraceParam();
trace.Start = pad + "0 2.5 0" + diagonal;
trace.End = pad + "0 2.5 0" - diagonal;
trace.Flags = TraceFlags.ENTS; trace.ExcludeArray = excluded;
if (!(aircraft.GetWorld().TraceMove(trace, null) >= 1)) return false;
}
TraceBox clearance = new TraceBox();
clearance.Start = pad + "0 3 0"; clearance.End = pad + "0 60 0";
clearance.Mins = "-40 -1 -40"; clearance.Maxs = "40 1 40";
clearance.Flags = TraceFlags.ENTS | TraceFlags.WORLD; clearance.ExcludeArray = excluded;
return aircraft.GetWorld().TraceMove(clearance, null) >= 1;
}
static ERR_AircraftFlightEnvelope Qualify(ERR_GameMode mode, Vehicle aircraft, IEntity pilot)
{
if (!GetGame() || !GetGame().GetWorld() || !GetGame().GetPlayerManager()) return null;
if (!Replication.IsServer() || !mode || mode != ERR_GameMode.Get() || !mode.Active() || !aircraft || !pilot || ERR_Actors.Player(pilot)) return null;
if (CompartmentAccessComponent.GetVehicleIn(pilot) != aircraft || !ERR_Actors.Alive(aircraft) || !ERR_Actors.CanFight(pilot)) return null;
HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(aircraft.FindComponent(HelicopterControllerComponent));
if (!controller || controller.IsEngineOn() || !controller.GetSimulation() || !controller.GetSimulation().IsValid()) return null;
PilotCompartmentSlot seat = controller.GetPilotCompartmentSlot();
if (!seat || seat.GetOccupant() != pilot) return null;
if (!ERR_CasualtyLoadingRules.AircraftReady(aircraft)) return null;
vector pad = ERR_Actors.Ground(aircraft.GetOrigin());
if (!(pad[1] >= 2) || !ClearColumn(aircraft, pilot, pad)) return null;
vector origin; float radius;
if (!ERR_AircraftFlightFootprint.Measure(aircraft, controller.GetSimulation(), pad, origin, radius)) return null;
ERR_AircraftFlightEnvelope record = new ERR_AircraftFlightEnvelope();
record.m_Mode = mode; record.m_Aircraft = aircraft; record.m_Pilot = pilot;
record.m_Home = origin; record.m_Radius = radius;
record.m_QualifiedAt = GetGame().GetWorld().GetWorldTime();
return record;
}
}
@@ -0,0 +1,366 @@
#ifdef WORKBENCH
// Separate Chat fixture for the ACTUAL dormant AI controller. Core's original
// powered-flight probe is disabled for this outcome and retains its own evidence.
modded class SCR_CharacterControllerComponent
{
override void OnPrepareControls(IEntity owner, ActionManager am, float dt, bool player)
{
ERR_NativeAircraftControlProbe probe = ERR_NativeAircraftControlProbe.Current();
if (probe && !player) probe.PrepareActions(this, owner, am, dt);
super.OnPrepareControls(owner, am, dt, player);
}
}
modded class SCR_HelicopterControllerComponent
{
protected override event void OnPrepareControls()
{
super.OnPrepareControls();
ERR_NativeAircraftControlProbe probe = ERR_NativeAircraftControlProbe.Current();
if (probe) probe.PrepareHelicopter(this);
}
}
// Read-only native fixture observation; no production method or state override.
modded class ERR_AircraftFlight
{
float ERR_NativeControlDeadline() { return m_Deadline; }
}
class ERR_NativeAircraftControlRules
{
static bool Terminal(bool cancellation, int state, int cancellationCount)
{
if (cancellation) return state == ERR_AircraftFlightState.CANCELLED && cancellationCount == 1;
return state == ERR_AircraftFlightState.COMPLETE && cancellationCount == 0;
}
static bool CanCancel(int state, bool contact, float height)
{
return state == ERR_AircraftFlightState.CLIMB && !contact && height >= 5 && height <= 18;
}
static bool CancelProof(bool airborneHooks, bool landingHeld, bool sameDeadline, bool sameToken)
{
return airborneHooks && landingHeld && sameDeadline && sameToken;
}
static bool TranslateProof(bool proven, bool held, float requestedDistance, float displacement, float targetError)
{
return proven && held && requestedDistance >= 1.75 && requestedDistance <= 4.05 && displacement >= 1 && targetError >= 0 && targetError <= 0.75;
}
static bool PhysicalFinish(bool originalOccupancy, bool landed, bool engineOff, float fuelUsed, int wakes, int starts, int controllerStarts, int pilotHooks, int helicopterHooks)
{
if (!originalOccupancy || !landed || !engineOff) return false;
return fuelUsed > 0.05 && wakes == 1 && starts == 1 && controllerStarts == 1 && pilotHooks > 100 && helicopterHooks > 100;
}
static bool CanDispose(bool context, bool noPlayers, bool sameFaction, bool groupUnchanged, bool pilotUnchanged)
{
return context && noPlayers && sameFaction && groupUnchanged && pilotUnchanged;
}
}
class ERR_NativeAircraftControlProbe
{
protected static ERR_NativeAircraftControlProbe s_Current;
protected ERR_GameMode m_Mode;
protected Vehicle m_Aircraft;
protected SCR_AIGroup m_Group;
protected IEntity m_Pilot;
protected PilotCompartmentSlot m_Seat;
protected HelicopterControllerComponent m_Controller;
protected VehicleHelicopterSimulation m_Simulation;
protected FuelManagerComponent m_Fuel;
protected ref ERR_AircraftFlightEnvelope m_Envelope;
protected ref ERR_AircraftFlight m_Flight;
protected float m_Deadline;
protected float m_StageDeadline;
protected float m_Stable = -1;
protected float m_LastTick = -1;
protected float m_NextLog;
protected float m_InitialFuel;
protected float m_ControlDeadline;
protected float m_CancelAt = -1;
protected float m_LandingStable = -1;
protected int m_CancelCount;
protected int m_CancelPilotHooks;
protected int m_CancelHelicopterHooks;
protected bool m_CancelFixture;
protected bool m_TranslationFixture;
protected bool m_TranslationObserved;
protected bool m_TranslationHeld;
protected vector m_TranslationOrigin;
protected vector m_TranslationTarget;
protected float m_TranslationStable = -1;
protected bool m_ContinuedAirborneControl;
protected bool m_LandingHeld;
protected vector m_Pad;
protected int m_Step;
protected int m_Wakes;
protected int m_ControllerStarts;
protected int m_EngineStarts;
protected int m_PilotHooks;
protected int m_HelicopterHooks;
protected bool m_LastEngine;
protected bool m_AllocationRequested;
protected bool m_PilotRequested;
protected bool m_Stopped;
protected bool m_Complete;
static const int ORDER_TOKEN = 771001;
static const ResourceName AIRCRAFT = "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et";
static const ResourceName GROUP = "{000CD338713F2B5A}Prefabs/AI/Groups/Group_Base.et";
static const ResourceName PILOT = "{86E565387A634103}Prefabs/Characters/Campaign/Final/OPFOR/USSR_Army/Regular/Campaign_USSR_Player_MortarCrew.et";
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && (outcome == "aircraft-control" || outcome == "aircraft-cancel" || outcome == "aircraft-translate");
}
static ERR_NativeAircraftControlProbe Current() { return s_Current; }
void ERR_NativeAircraftControlProbe(ERR_GameMode mode)
{
m_Mode = mode;
if (!GetGame() || !GetGame().GetWorld() || !Enabled() || s_Current) { m_Stopped = true; return; }
string outcome; System.GetCLIParam("errSmokeOutcome", outcome); m_CancelFixture = outcome == "aircraft-cancel"; m_TranslationFixture = outcome == "aircraft-translate";
m_Deadline = GetGame().GetWorld().GetWorldTime() + 300000;
s_Current = this;
}
protected bool Check(bool passed, string detail)
{
string outcome = "aircraft-control"; if (m_CancelFixture) outcome = "aircraft-cancel"; else if (m_TranslationFixture) outcome = "aircraft-translate";
if (passed) { Print("ERR SMOKE PASS: " + outcome + " " + detail); return true; }
Print("ERR SMOKE FAIL: " + outcome + " " + detail, LogLevel.ERROR); Stop(); return false;
}
protected bool NoPlayers()
{
if (!GetGame() || !GetGame().GetPlayerManager()) return false;
if (m_Group && m_Group.GetPlayerCount(true) > 0) return false;
array<int> players = {}; GetGame().GetPlayerManager().GetPlayers(players);
foreach (int id : players)
{
IEntity actor = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
if (actor && (actor == m_Pilot || actor == m_Aircraft || (m_Aircraft && CompartmentAccessComponent.GetVehicleIn(actor) == m_Aircraft))) return false;
}
return true;
}
protected bool OriginalOccupancy()
{
if (!m_Pilot || !m_Group || !m_Aircraft || !m_Controller || !m_Seat || !NoPlayers()) return false;
if (m_Group.GetAgentsCount() != 1 || ERR_Actors.Group(m_Pilot) != m_Group || !ERR_Actors.CanFight(m_Pilot)) return false;
return m_Seat.GetOccupant() == m_Pilot && m_Controller.GetPilotCompartmentSlot() == m_Seat && CompartmentAccessComponent.GetVehicleIn(m_Pilot) == m_Aircraft;
}
void PrepareActions(CharacterControllerComponent controller, IEntity owner, ActionManager am, float dt)
{
if (m_Stopped || !m_Flight || owner != m_Pilot) return;
m_Flight.PrepareActions(controller, owner, am, dt); m_PilotHooks++;
}
void PrepareHelicopter(HelicopterControllerComponent controller)
{
if (m_Stopped || !m_Flight || controller != m_Controller) return;
m_Flight.PrepareHelicopter(controller); m_HelicopterHooks++;
}
protected bool FindPad(out vector result)
{
vector anchor = m_Mode.ExtractionVehicle().GetOrigin();
float bestSlope = float.MAX; vector bestCandidate, refinementAnchor;
int localCandidates = ERR_PoweredFlightRules.PAD_CANDIDATES + ERR_PoweredFlightRules.PAD_REFINEMENTS;
for (int index = 0; index < localCandidates + ERR_PoweredFlightRules.AIRPORT_CANDIDATES; index++)
{
if (index == ERR_PoweredFlightRules.PAD_CANDIDATES)
{
refinementAnchor = anchor; if (bestSlope != float.MAX) refinementAnchor = bestCandidate;
}
vector candidate;
if (index < ERR_PoweredFlightRules.PAD_CANDIDATES) candidate = ERR_Actors.Ground(anchor + ERR_PoweredFlightRules.PadOffset(index));
else if (index < localCandidates) candidate = ERR_Actors.Ground(refinementAnchor + ERR_PoweredFlightRules.RefineOffset(index - ERR_PoweredFlightRules.PAD_CANDIDATES));
else candidate = ERR_Actors.Ground("4900.953 0 11787.412" + ERR_PoweredFlightRules.AirportOffset(index - localCandidates));
if (candidate[1] < 2) continue;
float delta;
for (int edge = 0; edge < 4; edge++)
{
float side = edge * Math.PI / 2;
vector point = candidate + Vector(Math.Cos(side) * 14, 0, Math.Sin(side) * 14);
delta = Math.Max(delta, Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(point) - candidate[1]));
}
if (delta < bestSlope) { bestSlope = delta; bestCandidate = candidate; }
if (delta > 0.8) continue;
if (!SCR_WorldTools.TraceCilinderUtil(candidate + "0 2.5 0", 40, 4, TraceFlags.ENTS)) continue;
TraceBox clearance = new TraceBox(); clearance.Start = candidate + "0 3 0"; clearance.End = candidate + "0 60 0";
clearance.Mins = "-40 -1 -40"; clearance.Maxs = "40 1 40"; clearance.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
if (!(GetGame().GetWorld().TraceMove(clearance, null) >= 1)) continue;
PrintFormat("ERR AIRCRAFT FIXTURE PAD: candidate=%1 tested=%2 terrainDelta=%3; no fallback", candidate, index + 1, delta);
result = candidate; return true;
}
return false;
}
protected bool ReadyComponents()
{
m_Controller = HelicopterControllerComponent.Cast(m_Aircraft.FindComponent(HelicopterControllerComponent));
if (!m_Controller) return false;
m_Simulation = m_Controller.GetSimulation();
m_Fuel = FuelManagerComponent.Cast(m_Aircraft.FindComponent(FuelManagerComponent));
if (!m_Simulation || !m_Simulation.IsValid() || !m_Fuel || !m_Fuel.HasFuel()) return false;
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(m_Aircraft.FindComponent(BaseCompartmentManagerComponent));
if (!manager) return false;
array<BaseCompartmentSlot> slots = {}; manager.GetCompartments(slots);
if (slots.Count() > 64) return false;
foreach (BaseCompartmentSlot slot : slots)
{
PilotCompartmentSlot pilotSlot = PilotCompartmentSlot.Cast(slot);
if (!pilotSlot || pilotSlot.GetOwner() != m_Aircraft || pilotSlot.GetVehicle() != m_Aircraft || pilotSlot.GetCompartmentName() != "PilotCompartment") continue;
if (pilotSlot.GetType() != ECompartmentType.PILOT) continue;
if (pilotSlot.GetOccupant() || pilotSlot.IsReserved() || pilotSlot.IsGetInLocked() || !pilotSlot.IsCompartmentAccessible()) continue;
m_Seat = pilotSlot; return true;
}
return false;
}
protected void Diagnostic(float now)
{
if (!m_Aircraft || !m_Controller || !m_Simulation || now < m_NextLog) return;
m_NextLog = now + 2000;
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
vector home = m_Pad; if (m_Envelope) home = m_Envelope.Home();
int state = -1; string reason; if (m_Flight) { state = m_Flight.State(); reason = m_Flight.Reason(); }
PrintFormat("ERR AIRCRAFT FIXTURE: step=%1 state=%2 height=%3 drift=%4 speed=%5 contact=%6 engine=%7 fuelUsed=%8 reason=%9", m_Step, state, transform[3][1] - home[1], vector.DistanceXZ(transform[3], home), m_Simulation.GetVelocity().Length(), m_Simulation.HasAnyGroundContact(), m_Controller.IsEngineOn(), m_InitialFuel - m_Fuel.GetTotalFuel(), reason);
}
int Tick(float now)
{
if (m_Complete) return 1;
if (m_Stopped) return -1;
if (!Enabled() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || now >= m_Deadline) { Check(false, "active exact mission and bounded300-second fixture"); return -1; }
if (!NoPlayers()) { Check(false, "fixture remains unclaimed by any human"); return -1; }
if (m_LastTick >= 0 && (now < m_LastTick || now - m_LastTick > 1500)) { m_Stable = -1; m_LandingStable = -1; m_TranslationStable = -1; }
m_LastTick = now;
if (m_Step == 0)
{
if (!m_Mode.ExtractionVehicle()) return 0;
if (!Check(!m_AllocationRequested && FindPad(m_Pad), "independent qualified candidate found before one finite allocation")) return -1;
m_AllocationRequested = true;
m_Aircraft = Vehicle.Cast(ERR_Actors.Spawn(AIRCRAFT, m_Pad));
m_Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(GROUP, m_Pad + "20 0 0"));
if (!Check(m_Aircraft && m_Group && m_Group.InitFactionKey("USSR") && m_Group.GetFaction() == m_Mode.EnemyFaction(), "one finite native Mi8 and memberless enemy group created")) return -1;
m_Group.SetPermanentLOD(0); m_Group.ActivateAI();
m_Step = 1; m_StageDeadline = now + 30000; return 0;
}
if (!m_Aircraft || !m_Group || !ERR_Actors.Alive(m_Aircraft)) { Check(false, "original airframe and group survive without replacement"); return -1; }
if (m_Step == 1)
{
if (now >= m_StageDeadline) { Check(false, "real components and original pilot initialize within30seconds"); return -1; }
if (!ReadyComponents()) return 0;
if (!Check(!m_PilotRequested && m_Group.GetAgentsCount() == 0, "one original native pilot request only")) return -1;
m_PilotRequested = true; AIGroup originalGroup = m_Group;
m_Pilot = m_Seat.SpawnCharacterInCompartment(PILOT, originalGroup, GROUP);
AIControlComponent control; if (m_Pilot) control = AIControlComponent.Cast(m_Pilot.FindComponent(AIControlComponent));
AIAgent agent; if (control) agent = control.GetControlAIAgent();
if (!Check(m_Pilot && originalGroup == m_Group && agent, "pilot seated by native mechanism in original group")) return -1;
agent.SetPermanentLOD(0); agent.ActivateAI(); m_Step = 2; return 0;
}
Diagnostic(now);
if (m_Step == 2)
{
if (now >= m_StageDeadline) { Check(false, "actual initial seating and native ground settling within30seconds"); return -1; }
if (!OriginalOccupancy()) { m_Stable = -1; return 0; }
Physics physics = m_Aircraft.GetPhysics();
if (!physics || !physics.IsDynamic()) return 0;
if (m_Wakes == 0) { m_Wakes = 1; physics.SetActive(ActiveState.ACTIVE); m_Stable = -1; return 0; }
if (!m_Simulation.HasAnyGroundContact() || !ERR_CasualtyLoadingRules.AircraftReady(m_Aircraft)) { m_Stable = -1; return 0; }
if (m_Stable < 0) m_Stable = now;
if (now - m_Stable < 1000) return 0;
if (!Check(!m_Controller.IsEngineOn(), "post-spawn qualification precedes native engine start")) return -1;
m_Envelope = ERR_AircraftFlightEnvelope.Qualify(m_Mode, m_Aircraft, m_Pilot);
if (!Check(m_Envelope != null, "actual post-spawn column and complete rotor/model footprint issue exact qualification")) return -1;
m_InitialFuel = m_Fuel.GetTotalFuel();
m_Flight = new ERR_AircraftFlight(); m_ControllerStarts++;
m_ControlDeadline = Math.Min(m_Deadline, now + 240000);
bool started;
if (m_TranslationFixture) started = m_Flight.StartTranslation(m_Mode, m_Aircraft, m_Group, m_Pilot, m_Seat, m_Envelope, ORDER_TOKEN, m_ControlDeadline, m_Envelope.Home());
else started = m_Flight.Start(m_Mode, m_Aircraft, m_Group, m_Pilot, m_Seat, m_Envelope, ORDER_TOKEN, m_ControlDeadline);
if (!Check(started, "actual controller accepts one qualified original pilot/aircraft order")) return -1;
m_Step = 3; return 0;
}
if (m_Step == 3)
{
if (!OriginalOccupancy()) { Check(false, "original pilot remains conscious and in its exact seat/group"); return -1; }
bool engine = m_Controller.IsEngineOn(); if (engine && !m_LastEngine) m_EngineStarts++; m_LastEngine = engine;
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
vector home = m_Envelope.Home(); float height = transform[3][1] - home[1];
bool contact = m_Simulation.HasAnyGroundContact();
bool landed = ERR_AircraftFlightRules.Landed(contact, height, m_Simulation.GetVelocity().Length(), m_Simulation.GetAngularVelocity().Length(), transform[1][1]);
if (m_CancelFixture && m_CancelCount == 0 && ERR_NativeAircraftControlRules.CanCancel(m_Flight.State(), contact, height))
{
m_CancelCount = 1; m_CancelAt = now;
m_CancelPilotHooks = m_PilotHooks; m_CancelHelicopterHooks = m_HelicopterHooks;
m_Flight.Cancel("native fixture deliberate airborne cancellation");
PrintFormat("ERR AIRCRAFT CANCEL: height=%1 contact=%2 state=%3 token=%4 deadline=%5 pilotHooks=%6 helicopterHooks=%7", height, contact, m_Flight.State(), m_Flight.OrderToken(), m_Flight.ERR_NativeControlDeadline(), m_PilotHooks, m_HelicopterHooks);
if (!Check(m_Flight.State() == ERR_AircraftFlightState.LANDING && !m_Flight.Finished(), "one actual airborne Cancel enters retained LANDING control")) return -1;
}
if (m_CancelCount == 1)
{
if (m_Flight.ERR_NativeControlDeadline() != m_ControlDeadline || m_Flight.OrderToken() != ORDER_TOKEN) { Check(false, "cancel retains original overall deadline and exact order token"); return -1; }
int state = m_Flight.State();
if (state != ERR_AircraftFlightState.LANDING && state != ERR_AircraftFlightState.SHUTDOWN && state != ERR_AircraftFlightState.CANCELLED) { Check(false, "cancel retains landing or grounded shutdown states only"); return -1; }
if (state == ERR_AircraftFlightState.LANDING && !contact && height > 1 && m_PilotHooks > m_CancelPilotHooks + 10 && m_HelicopterHooks > m_CancelHelicopterHooks + 10) m_ContinuedAirborneControl = true;
if (!landed) m_LandingStable = -1;
else if (m_LandingStable < 0) m_LandingStable = now;
}
if (m_TranslationFixture && m_Flight.State() == ERR_AircraftFlightState.TRANSLATING)
{
if (!m_TranslationObserved)
{
m_TranslationObserved = true; m_TranslationOrigin = m_Flight.TranslationOrigin(); m_TranslationTarget = m_Flight.TranslationTarget();
if (!Check(vector.DistanceXZ(m_TranslationTarget, home) < 0.001, "pre-takeoff translation target remains the original qualified home")) return -1;
}
if (m_Flight.TranslationTarget() != m_TranslationTarget || m_Flight.TranslationOrigin() != m_TranslationOrigin || m_Flight.ERR_NativeControlDeadline() != m_ControlDeadline) { Check(false, "translation never retargets or renews its original deadline"); return -1; }
vector horizontal = m_Simulation.GetVelocity(); horizontal[1] = 0;
float requested = vector.DistanceXZ(m_TranslationOrigin, m_TranslationTarget);
bool held = ERR_AircraftTranslationRules.Held(contact, height, m_Simulation.GetVelocity().Length(), m_Simulation.GetVelocity()[1], transform[1][1], horizontal.Length(), vector.DistanceXZ(transform[3], m_TranslationTarget), vector.DistanceXZ(transform[3], m_TranslationOrigin), requested);
if (!held) m_TranslationStable = -1;
else if (m_TranslationStable < 0) m_TranslationStable = now;
if (m_TranslationStable >= 0 && now - m_TranslationStable >= 5000) m_TranslationHeld = true;
}
m_Flight.Tick();
if (m_CancelCount == 1 && m_Flight.State() == ERR_AircraftFlightState.SHUTDOWN)
{
if (!Check(landed && m_LandingStable >= 0 && now - m_LandingStable >= 2000, "cancel reaches shutdown only after two observed stable grounded seconds")) return -1;
m_LandingHeld = true;
}
if (!m_Flight.Finished()) return 0;
PrintFormat("ERR AIRCRAFT FIXTURE RESULT: state=%1 token=%2 engineStarts=%3 controllerStarts=%4 wakes=%5 pilotHooks=%6 helicopterHooks=%7 reason=%8", m_Flight.State(), m_Flight.OrderToken(), m_EngineStarts, m_ControllerStarts, m_Wakes, m_PilotHooks, m_HelicopterHooks, m_Flight.Reason());
bool complete = ERR_NativeAircraftControlRules.Terminal(m_CancelFixture, m_Flight.State(), m_CancelCount);
complete = complete && ERR_NativeAircraftControlRules.PhysicalFinish(OriginalOccupancy(), landed, !m_Controller.IsEngineOn(), m_InitialFuel - m_Fuel.GetTotalFuel(), m_Wakes, m_EngineStarts, m_ControllerStarts, m_PilotHooks, m_HelicopterHooks);
if (m_CancelFixture)
{
bool proof = ERR_NativeAircraftControlRules.CancelProof(m_ContinuedAirborneControl, m_LandingHeld, m_Flight.ERR_NativeControlDeadline() == m_ControlDeadline, m_Flight.OrderToken() == ORDER_TOKEN);
PrintFormat("ERR AIRCRAFT CANCEL RESULT: count=%1 elapsed=%2 airborneHooks=%3 landingHeld=%4 deadline=%5 originalDeadline=%6", m_CancelCount, now - m_CancelAt, m_ContinuedAirborneControl, m_LandingHeld, m_Flight.ERR_NativeControlDeadline(), m_ControlDeadline);
complete = complete && proof;
}
if (m_TranslationFixture)
{
float requestedDistance = vector.DistanceXZ(m_TranslationOrigin, m_TranslationTarget);
float displacement = vector.DistanceXZ(transform[3], m_TranslationOrigin);
float targetError = vector.DistanceXZ(transform[3], m_TranslationTarget);
bool translated = ERR_NativeAircraftControlRules.TranslateProof(m_Flight.TranslationProven(), m_TranslationObserved && m_TranslationHeld, requestedDistance, displacement, targetError);
PrintFormat("ERR AIRCRAFT TRANSLATION RESULT: proven=%1 held=%2 requested=%3 displacement=%4 targetError=%5 updates=%6", m_Flight.TranslationProven(), m_TranslationHeld, requestedDistance, displacement, targetError, m_Flight.TranslationUpdates());
complete = complete && translated;
}
if (!Check(complete && m_Flight.OrderToken() == ORDER_TOKEN, "actual control order completes on original physical grounded aircraft with engine off, finite fuel use and exact pilot")) return -1;
m_Complete = true; Stop(); return 1;
}
return 0;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
if (s_Current == this) s_Current = null;
if (m_Flight && !m_Flight.Finished() && m_CancelCount == 0) m_Flight.Cancel("native fixture stopped");
// End-of-test disposal is not successful flight or a production cancellation.
// These assets are fixture-owned only, never registered with mission cleanup.
// Release the controller before deleting disposable unclaimed physical assets.
m_Flight = null;
bool context = GetGame() && GetGame().GetWorld() && GetGame().GetPlayerManager() && m_Mode && m_Mode == ERR_GameMode.Get();
bool faction = context && (!m_Group || (!m_PilotRequested && m_Group.GetAgentsCount() == 0 && !m_Group.GetFaction()) || m_Group.GetFaction() == m_Mode.EnemyFaction()) && (!m_Aircraft || SCR_FactionManager.SGetFaction(m_Aircraft) == m_Mode.EnemyFaction());
bool groupUnchanged = context && (!m_Group || m_Group.GetAgentsCount() == 0 || (m_Group.GetAgentsCount() == 1 && m_Pilot && ERR_Actors.Group(m_Pilot) == m_Group));
bool pilotUnchanged = context && (!m_Pilot || (m_Group && ERR_Actors.Group(m_Pilot) == m_Group && !ERR_Actors.Player(m_Pilot)));
if (ERR_NativeAircraftControlRules.CanDispose(context, NoPlayers(), faction, groupUnchanged, pilotUnchanged))
{
ERR_Actors.DeleteSpawned(m_Group); ERR_Actors.DeleteSpawned(m_Pilot); ERR_Actors.DeleteSpawned(m_Aircraft);
}
else Print("ERR AIRCRAFT FIXTURE CLEANUP: ownership changed; captured entities preserved");
m_Envelope = null;
}
void ~ERR_NativeAircraftControlProbe() { Stop(); }
}
#endif
@@ -19,6 +19,10 @@ modded class ERR_GameMode
protected bool m_ERRTestMortar;
protected bool m_ERRTestMortarFire;
protected bool m_ERRTestMapOwnership;
protected bool m_ERRTestAircraftControl;
protected bool m_ERRTestAircraftCancel;
protected bool m_ERRTestAircraftTranslate;
protected ref ERR_NativeAircraftControlProbe m_ERRAircraftControl;
protected ref ERRM_NativeMapOwnershipProbe m_ERRMapOwnership;
protected bool m_ERRTestMortarSalvo;
protected ref ERR_NativeMortarSalvoProbe m_ERRMortarSalvo;
@@ -43,6 +47,9 @@ modded class ERR_GameMode
m_ERRTestHQResponse = outcome == "hq-response";
m_ERRTestMortar = outcome == "mortar-inventory";
m_ERRTestMapOwnership = outcome == "map-ownership";
m_ERRTestAircraftCancel = outcome == "aircraft-cancel";
m_ERRTestAircraftTranslate = outcome == "aircraft-translate";
m_ERRTestAircraftControl = outcome == "aircraft-control" || m_ERRTestAircraftCancel || m_ERRTestAircraftTranslate;
m_ERRTestMortarFire = outcome == "mortar-fire" || outcome == "mortar-expiry";
m_ERRTestMortarCourier = outcome == "mortar-courier";
m_ERRTestMortarSalvo = outcome == "mortar-salvo" || outcome == "mortar-refresh";
@@ -50,6 +57,7 @@ modded class ERR_GameMode
m_ERRTestPatrolHQ = outcome == "patrol-hq";
m_ERRTestPlayerDeath = outcome == "player-death" || m_ERRTestRadioNetwork || m_ERRTestHQResponse || m_ERRTestMortar;
m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 150000;
if (m_ERRTestAircraftControl) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
if (m_ERRTestMortarCourier) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 450000;
if (m_ERRTestMortarTransfer) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
if (m_ERRTestMortarSalvo) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
@@ -73,6 +81,7 @@ modded class ERR_GameMode
protected void ERR_StopHQPatrol()
{
if (m_ERRMapOwnership) m_ERRMapOwnership.Stop();
if (m_ERRAircraftControl) m_ERRAircraftControl.Stop();
if (m_ERRHQResponseProof) m_ERRHQResponseProof.Stop();
if (m_ERRMortarSalvo) m_ERRMortarSalvo.Stop();
if (m_ERRMortarCourier) m_ERRMortarCourier.Stop();
@@ -113,6 +122,19 @@ modded class ERR_GameMode
protected void ERR_SmokePhase(ERR_Phase previous, ERR_Phase current)
{
if (m_ERRSmokeDone || !ERR_MissionRules.Terminal(current)) return;
if (m_ERRTestAircraftControl)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 221, "actual aircraft controller completed before explicit fixture reset")) return;
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
ERR_StopHQPatrol();
if (m_ERRTestAircraftCancel)
Print("ERR SMOKE COMPLETE: actual airborne aircraft cancellation retains inputs through settled contact landing and engine-off; isolated mechanism, not HQ dispatch");
else if (m_ERRTestAircraftTranslate)
Print("ERR SMOKE COMPLETE: actual aircraft controller immutable target translation, observed target hold and contact landing; bounded mechanism, not route support or HQ dispatch");
else Print("ERR SMOKE COMPLETE: actual aircraft controller and post-spawn qualification, powered hover and contact landing; isolated physical mechanism, not HQ dispatch");
return;
}
if (m_ERRTestMapOwnership)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 201, "native map ownership probe cleaned up before explicit reset")) return;
@@ -220,6 +242,13 @@ modded class ERR_GameMode
if (m_ERRSmokeDone) return;
if (now > m_ERRSmokeDeadline) { ERR_Check(false, "timeout at step " + m_ERRSmokeStep.ToString()); return; }
if (now < m_ERRSmokeWait) return;
if (m_ERRSmokeStep == 220)
{
int result = m_ERRAircraftControl.Tick(now);
if (result < 0) { ERR_Check(false, "native actual aircraft controller fixture"); return; }
if (result == 1) { m_ERRSmokeStep = 221; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRSmokeStep == 200)
{
int result = m_ERRMapOwnership.Tick(now);
@@ -280,6 +309,13 @@ modded class ERR_GameMode
if (players.Count() != 1 || Phase() != ERR_Phase.PRISON) return;
m_ERRSmokePlayer = players[0];
if (!ERR_CheckRoster() || !ERR_NativeContractsProbe.Run(this, m_ERRSmokePlayer) || !ERR_CheckCompanionCommands()) return;
if (m_ERRTestAircraftControl)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRAircraftControl = new ERR_NativeAircraftControlProbe(this);
m_ERRSmokeStep = 220;
return;
}
if (m_ERRTestPatrolHQ)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
+10
View File
@@ -86,5 +86,15 @@ GenericEntity {
coords 0 -0.1 -6
angles 0 0 0
}
DecalEntity : "{4AD6EB80A8B1FA3A}Prefabs/Decals/Dirt/Decal_Dirt_Patch_03.et" {
ID "FCFBA935CC13E016"
components {
Hierarchy "{EF4B704D646D1759}" {}
}
coords 0 0 0
angles -90 0 0
scale 10
FarPlane 1.5
}
}
}
+29 -3
View File
@@ -3,7 +3,11 @@ class ERR_UnlockPrisonAction : ScriptedUserAction
override bool CanBePerformedScript(IEntity user)
{
ERR_GameMode mode = ERR_GameMode.Get();
return mode && mode.Phase() == ERR_Phase.PRISON && ERR_Actors.CanFight(user);
SetCannotPerformReason(string.Empty);
if (!mode) { SetCannotPerformReason("Mission is not ready"); return false; }
if (mode.Phase() != ERR_Phase.PRISON) { SetCannotPerformReason("Prison lock is not available in this mission phase"); return false; }
if (!ERR_Actors.CanFight(user)) { SetCannotPerformReason("You must be alive and conscious"); return false; }
return true;
}
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
{
@@ -28,7 +32,15 @@ class ERR_PrisonDoorAction : SCR_DoorUserAction
override bool CanBePerformedScript(IEntity user)
{
ERR_GameMode mode = ERR_GameMode.Get();
return mode && mode.Phase() > ERR_Phase.PRISON && super.CanBePerformedScript(user);
SetCannotPerformReason(string.Empty);
if (!mode) { SetCannotPerformReason("Mission is not ready"); return false; }
if (mode.Phase() <= ERR_Phase.PRISON)
{
SetCannotPerformReason("Pick the prison lock before opening the gate");
return false;
}
// Vanilla owns any obstruction/door-specific reason after unlocking.
return super.CanBePerformedScript(user);
}
}
@@ -38,7 +50,21 @@ class ERR_TuneRadioAction : ScriptedUserAction
{
ERR_GameMode mode = ERR_GameMode.Get();
ERR_LocationComponent location = ERR_LocationComponent.Cast(GetOwner().FindComponent(ERR_LocationComponent));
return mode && mode.Phase() == ERR_Phase.RADIO_SEARCH && location && location.Knowledge() == 2 && ERR_Actors.CanFight(user);
SetCannotPerformReason(string.Empty);
if (!mode) { SetCannotPerformReason("Mission is not ready"); return false; }
if (mode.Phase() != ERR_Phase.RADIO_SEARCH)
{
string reason = "Radio is not available in this mission phase";
if (mode.Phase() == ERR_Phase.PRISON) reason = "Escape the prison first";
if (mode.Phase() == ERR_Phase.RADIO_DEFENCE) reason = "Transmission is already in progress; defend the radio";
if (mode.Phase() == ERR_Phase.EXTRACTION) reason = "Transmission is complete; reach the extraction helicopter";
SetCannotPerformReason(reason);
return false;
}
if (!location) { SetCannotPerformReason("Radio location is unavailable"); return false; }
if (location.Knowledge() != 2) { SetCannotPerformReason("Identify this radio through recovered intel or close reconnaissance first"); return false; }
if (!ERR_Actors.CanFight(user)) { SetCannotPerformReason("You must be alive and conscious"); return false; }
return true;
}
override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity)
{
@@ -55,6 +55,147 @@ class ERR_OrdinaryScenarioLaunchRequest : Managed
}
// One optional capture request per world, with no camera or gameplay mutation.
// Managed callback ownership matches the ordinary launch request above.
class ERR_OrdinaryScreenshotRequest : Managed
{
protected static ref ERR_OrdinaryScreenshotRequest s_Request;
protected static bool s_Attempted;
protected ERR_GameMode m_Mode;
protected PlayerController m_Controller;
protected IEntity m_Actor;
protected ref ERR_PlayerLife m_Life;
protected UUID m_Account;
protected int m_PlayerId;
protected bool m_Finished;
protected static bool Enabled()
{
string project, screenshot;
if (!Replication.IsServer() || System.IsConsoleApp()) return false;
if (!System.GetCLIParam("errOrdinaryScenario", project) || (project != "core" && project != "chat")) return false;
return System.GetCLIParam("errOrdinaryScreenshot", screenshot) && screenshot == "1";
}
static void Queue(ERR_GameMode mode, PlayerController controller, IEntity actor)
{
if (s_Attempted || !Enabled() || !mode || !controller || !actor || !mode.Players()) return;
ERR_PlayerLife life = mode.Players().CurrentActor(actor);
if (!life) return;
ERR_OrdinaryScreenshotRequest request = new ERR_OrdinaryScreenshotRequest();
request.m_Mode = mode; request.m_Controller = controller; request.m_Actor = actor;
request.m_Life = life; request.m_Account = life.AccountId; request.m_PlayerId = controller.GetPlayerId();
if (!request.Valid()) return;
s_Attempted = true; s_Request = request;
mode.OnStop().Insert(request.Cancel);
GetGame().GetCallqueue().CallLater(request.Capture, 3000, false);
Print("ERR ORDINARY: optional prisoner screenshot queued after finalized admission");
}
protected bool Valid()
{
if (!Enabled() || !GetGame() || !m_Mode || m_Mode != ERR_GameMode.Get()) return false;
if (!GetGame().GetWorld() || m_Mode.GetWorld() != GetGame().GetWorld() || !m_Mode.Active() || m_Mode.Phase() != ERR_Phase.PRISON) return false;
if (!m_Controller || m_Controller != GetGame().GetPlayerController() || m_Controller.GetPlayerId() != m_PlayerId || m_PlayerId <= 0) return false;
PlayerManager manager = GetGame().GetPlayerManager();
if (!manager || manager.GetPlayerController(m_PlayerId) != m_Controller || manager.GetPlayerControlledEntity(m_PlayerId) != m_Actor || m_Controller.GetControlledEntity() != m_Actor) return false;
if (!ERR_Actors.Alive(m_Actor) || !m_Mode.Players()) return false;
ERR_PlayerLife current = m_Mode.Players().CurrentActor(m_Actor);
return current && current == m_Life && current.AccountId == m_Account;
}
void Cancel()
{
if (m_Finished) return;
m_Finished = true;
if (GetGame() && GetGame().GetCallqueue()) GetGame().GetCallqueue().Remove(Capture);
if (m_Mode) m_Mode.OnStop().Remove(Cancel);
m_Mode = null; m_Controller = null; m_Actor = null; m_Life = null;
}
// Read-only projection evidence, only inside the already validated capture.
// World AABB corners transformed to prison space are an envelope, not a
// promise that the material's irregular clutter mask fills that envelope.
protected void YardDiagnostic()
{
array<ERR_LocationComponent> locations = {};
m_Mode.Locations(locations);
if (locations.Count() > 64) return;
IEntity prison;
foreach (ERR_LocationComponent location : locations)
{
if (!location || location.Kind() != ERR_LocationKind.PRISON || location.Id() != "everon-prison-1") continue;
IEntity owner = location.GetOwner();
if (!owner || owner.GetWorld() != m_Mode.GetWorld() || !owner.GetPrefabData()) continue;
if (owner.GetPrefabData().GetPrefabName() != "{BE6B8D56349038A6}Prefabs/ERR/Prison.et") continue;
if (prison) { Print("ERR YARD: ambiguous registered prison; no measurement"); return; }
prison = owner;
}
if (!prison) { Print("ERR YARD: exact registered prison missing; no measurement"); return; }
vector prisonTransform[4]; prison.GetWorldTransform(prisonTransform);
PrintFormat("ERR YARD: prison world origin=%1 basisX=%2 basisY=%3 basisZ=%4", prisonTransform[3], prisonTransform[0], prisonTransform[1], prisonTransform[2]);
array<IEntity> entities = {prison};
array<int> depths = {0};
int decals;
for (int index = 0; index < entities.Count(); index++)
{
IEntity entity = entities[index];
if (!entity) { Print("ERR YARD: hierarchy changed; partial measurement"); return; }
DecalEntity decal = DecalEntity.Cast(entity);
if (decal)
{
decals++;
vector mins, maxs; decal.GetWorldBounds(mins, maxs);
vector worldTransform[4]; decal.GetWorldTransform(worldTransform);
string nativeType = decal.Type().ToString();
ResourceName prefab;
if (decal.GetPrefabData()) prefab = decal.GetPrefabData().GetPrefabName();
PrintFormat("ERR YARD: decal=%1 type=%2 prefab=%3 worldMin=%4 worldMax=%5", decals, nativeType, prefab, mins, maxs);
PrintFormat("ERR YARD: decal=%1 world origin=%2 basisX=%3 basisY=%4 basisZ=%5", decals, worldTransform[3], worldTransform[0], worldTransform[1], worldTransform[2]);
for (int x = 0; x < 2; x++)
for (int y = 0; y < 2; y++)
for (int z = 0; z < 2; z++)
{
vector corner = mins;
if (x == 1) corner[0] = maxs[0];
if (y == 1) corner[1] = maxs[1];
if (z == 1) corner[2] = maxs[2];
vector localCorner = prison.CoordToLocal(corner);
PrintFormat("ERR YARD: decal=%1 corner=%2/%3/%4 prisonLocal=%5", decals, x, y, z, localCorner);
}
}
IEntity child = entity.GetChildren();
while (child)
{
if (depths[index] >= 8 || entities.Count() >= 64 || entities.Contains(child))
{
Print("ERR YARD: hierarchy bound or duplicate reached; partial measurement");
return;
}
entities.Insert(child); depths.Insert(depths[index] + 1);
child = child.GetSibling();
}
}
PrintFormat("ERR YARD: measurement complete nodes=%1 decals=%2; bounds do not certify clutter-mask coverage", entities.Count(), decals);
}
protected void Capture()
{
if (m_Finished) return;
bool valid = Valid();
if (valid) YardDiagnostic();
Cancel();
if (!valid)
{
Print("ERR ORDINARY: optional prisoner screenshot cancelled; original admission no longer current");
return;
}
System.MakeScreenshot("$logs:err-ordinary-prison");
Print("ERR ORDINARY: optional prisoner screenshot requested without camera or input changes");
}
static void ClearWorld()
{
if (s_Request) s_Request.Cancel();
s_Request = null;
s_Attempted = false;
}
}
modded class ArmaReforgerScripted
{
protected ref ERR_OrdinaryScenarioLaunchRequest m_ERRDiagnosticLaunchRequest;
@@ -106,6 +247,7 @@ modded class ArmaReforgerScripted
override protected void OnBeforeWorldCleanup()
{
ERR_OrdinaryScreenshotRequest.ClearWorld();
if (m_ERRDiagnosticLaunchRequest) m_ERRDiagnosticLaunchRequest.Cancel();
m_ERRDiagnosticLaunchRequest = null;
super.OnBeforeWorldCleanup();
@@ -124,6 +266,7 @@ modded class ERR_GameMode
if (!local || local.GetPlayerId() != id || local.GetControlledEntity() != actor || !ERR_Actors.Alive(actor)) return;
if (!Players().CurrentActor(actor) || Phase() != ERR_Phase.PRISON) return;
PrintFormat("ERR ORDINARY: local participant verified project=%1 phase=prison", project);
ERR_OrdinaryScreenshotRequest.Queue(this, local, actor);
}
}
@@ -120,6 +120,10 @@ class ERR_SpawnLogic : SCR_SpawnLogic
Observe(playerId, reason);
return;
}
// Every active, eligible admission consumes one attempt, even when native
// prerequisites or all safe positions are unavailable. Preparing and an
// already-pending request return above without spending this budget.
m_Attempts.Set(playerId, attempts + 1);
SCR_PlayerFactionAffiliationComponent affiliation = GetPlayerFactionComponent_S(playerId);
SCR_RespawnComponent respawn = GetPlayerRespawnComponent_S(playerId);
Faction faction = mode.PlayerFaction();
@@ -127,7 +131,6 @@ class ERR_SpawnLogic : SCR_SpawnLogic
affiliation.RequestFaction(faction);
vector position;
if (!SpawnPosition(mode, position)) { RetryLater(playerId); return; }
m_Attempts.Set(playerId, attempts + 1);
SCR_FreeSpawnData request = new SCR_FreeSpawnData(mode.Factions().PrisonerPrefab, position);
if (!respawn.CanSpawn(request)) { RetryLater(playerId); return; }
m_AuthorizedPositions.Set(playerId, position);
+69 -7
View File
@@ -81,8 +81,8 @@
]
},
"clean_native_runtime": {
"status": "pending",
"scope": "Checkpoint7c53b4b passed two companion rounds (103025),96 assertions with zero errors, plus ordinary Chat102737 on the same source. The opt-in actor-local flight-input experiment changes current addon source and requires fresh qualification. Seventeen release cases remain pending.",
"status": "passed",
"scope": "Current addon sources: Core 130347 completes two scripted escape/restart rounds (612 assertions); companion 131041 completes two (96 assertions), both zero captured errors. Ordinary Chat 125906 loads the same source, admits the real canonical prisoner and settles cleanly. Actual fixed-target controller130038 also matches this source. These are local development checks, not ordinary-input full escape, multiplayer, native packaging or FPS acceptance; all seventeen release cases remain pending.",
"evidence": [
"docs/evidence/native-2026-09-19-065351/report.json",
"docs/evidence/native-2026-09-19-065351/console.log.txt",
@@ -110,10 +110,22 @@
"docs/evidence/game-2026-09-19-093115/source-sha256.json",
"docs/evidence/native-2026-09-19-103025/report.json",
"docs/evidence/native-2026-09-19-103025/console.log.txt",
"docs/evidence/native-2026-09-19-103025/source-sha256.json"
"docs/evidence/native-2026-09-19-103025/source-sha256.json",
"docs/evidence/native-2026-09-19-130347-core-loops/report.json",
"docs/evidence/native-2026-09-19-130347-core-loops/console.log.txt",
"docs/evidence/native-2026-09-19-130347-core-loops/source-sha256.json",
"docs/evidence/native-2026-09-19-131041-companion-loops/report.json",
"docs/evidence/native-2026-09-19-131041-companion-loops/console.log.txt",
"docs/evidence/native-2026-09-19-131041-companion-loops/source-sha256.json",
"docs/evidence/game-2026-09-19-125906-integrated-prison/report.json",
"docs/evidence/game-2026-09-19-125906-integrated-prison/console.log.txt",
"docs/evidence/game-2026-09-19-125906-integrated-prison/source-sha256.json",
"docs/evidence/native-2026-09-19-130038-aircraft-target/report.json",
"docs/evidence/native-2026-09-19-130038-aircraft-target/console.log.txt",
"docs/evidence/native-2026-09-19-130038-aircraft-target/source-sha256.json"
],
"source_manifest": "docs/evidence/native-2026-09-19-103025/source-sha256.json",
"native_report": "docs/evidence/native-2026-09-19-103025/report.json"
"source_manifest": "docs/evidence/native-2026-09-19-131041-companion-loops/source-sha256.json",
"native_report": "docs/evidence/native-2026-09-19-131041-companion-loops/report.json"
},
"scripted_helicopter_loss_and_restart": {
"status": "passed",
@@ -748,12 +760,16 @@
},
"packed_chat_ordinary_startup": {
"status": "passed",
"scope": "Artifact-bound ordinary Chat startup 113315: exact five successor addons plus two installed vanilla engine/data addons, all five PAK and resource database paths verified, real canonical prisoner and 15-second ready interval, zero captured errors. All fifteen runtime artifacts unchanged. Explicit noSplash; package is the archived 111731 snapshot, not current source. No build, manual escape, multiplayer or release qualification.",
"scope": "Artifact-bound current packed Chat 131942 startup: exact fifteen 1317 package artifacts, five successor plus two vanilla addons, verified PAK/RDB paths, real canonical prisoner, fifteen-second ready interval, zero captured errors and owned cleanup. All151 PAK entries match the captured183-file addon sources. Explicit noSplash; native packaging remains failed. No full player-controlled escape, multiplayer or release qualification.",
"evidence": [
"docs/evidence/packed-chat-2026-09-19-113315/report.json",
"docs/evidence/packed-chat-2026-09-19-113315/console.log.txt",
"docs/evidence/packed-chat-2026-09-19-113315/runner.json.txt",
"docs/evidence/packed-chat-2026-09-19-113315/packed-input-provenance.json"
"docs/evidence/packed-chat-2026-09-19-113315/packed-input-provenance.json",
"docs/evidence/packed-chat-2026-09-19-131942-current/report.json",
"docs/evidence/packed-chat-2026-09-19-131942-current/packed-input-provenance.json",
"docs/evidence/packed-chat-2026-09-19-131942-current/console.log.txt",
"docs/evidence/packed-chat-2026-09-19-131942-current/archive-sha256.json"
]
},
"native_powered_takeoff_hover_landing": {
@@ -792,6 +808,52 @@
"docs/evidence/native-2026-09-19-120633-map-ownership/console.log.txt",
"docs/evidence/native-2026-09-19-120633-map-ownership/source-sha256.json"
]
},
"ordinary_prisoner_view": {
"status": "passed",
"scope": "Ordinary Chat 121730 and 122353 compile without WORKBENCH, admit the genuine local canonical prisoner and settle cleanly. Opt-in read-only screenshots show the authored prison and correct objective, without camera/input changes. Native BMP and lossless viewing PNG are retained. This establishes the initial view only, not ordinary-input escape, map usability, multiplayer or FPS.",
"evidence": [
"docs/evidence/game-2026-09-19-121730-prison-view/report.json",
"docs/evidence/game-2026-09-19-121730-prison-view/err-ordinary-prison.bmp",
"docs/evidence/game-2026-09-19-121730-prison-view/image-conversion.json",
"docs/evidence/game-2026-09-19-122353-yard-candidate/report.json"
]
},
"actual_aircraft_controller_vertical": {
"status": "passed",
"scope": "Source-bound Chat Workbench 123439: actual production post-spawn envelope and controller, same physical Mi-8/original AI pilot, signed controls, finite fuel, sustained hover, contact landing, engine off; 37 assertions and zero captured errors. No operational HQ dispatch, ordinary-input piloting, route flight or multiplayer claim.",
"evidence": [
"docs/evidence/native-2026-09-19-123439-aircraft-controller/report.json",
"docs/evidence/native-2026-09-19-123439-aircraft-controller/console.log.txt",
"docs/evidence/native-2026-09-19-123439-aircraft-controller/source-sha256.json"
]
},
"actual_aircraft_airborne_cancellation": {
"status": "passed",
"scope": "Chat Workbench 124915: one actual Cancel at5.09m altitude; same production controller, original aircraft/pilot/order and deadline; continued airborne input, stable contact landing and engine-off CANCELLED. 39 assertions, zero captured errors. Isolated mechanism only; no HQ, remote capture or broader fault-recovery qualification.",
"evidence": [
"docs/evidence/native-2026-09-19-124915-aircraft-cancel/report.json",
"docs/evidence/native-2026-09-19-124915-aircraft-cancel/console.log.txt",
"docs/evidence/native-2026-09-19-124915-aircraft-cancel/source-sha256.json"
]
},
"actual_aircraft_fixed_target": {
"status": "passed",
"scope": "Source-bound Chat Workbench 130038: actual production StartTranslation with immutable pre-takeoff target, physical2.91183m displacement toward3.3096m request, independent five-second hold, contact landing and engine-off COMPLETE. 38 assertions, zero captured errors; original aircraft/pilot/order/deadline retained. No longer route, HQ dispatch, manual piloting or multiplayer claim.",
"evidence": [
"docs/evidence/native-2026-09-19-130038-aircraft-target/report.json",
"docs/evidence/native-2026-09-19-130038-aircraft-target/console.log.txt",
"docs/evidence/native-2026-09-19-130038-aircraft-target/source-sha256.json"
]
},
"packed_linux_integrated_preparation": {
"status": "passed",
"scope": "Artifact-bound empty-server compilation/preparation only: fifteen exact1317 runtime files unchanged; five successor plus two vanilla addons,5780 Game files/11202classes, Chat header/MissionSystems, seven sites117stock and prison phase. Four native asset errors also occur in vanilla control; no clean-runtime or build qualification, no real clients, graceful shutdown, FPS or release claim. Isolated90-second timeout and host cleanup verified.",
"evidence": [
"docs/evidence/dedicated-2026-09-19-132456-packed-chat/review.json",
"docs/evidence/dedicated-2026-09-19-132456-packed-chat/result.json",
"docs/evidence/dedicated-2026-09-19-132456-packed-chat/archive-sha256.json"
]
}
}
}
+52
View File
@@ -0,0 +1,52 @@
# Prepared physical aircraft controller
`ERR_AircraftFlight` is a production-code mechanism in the AI addon with no automatic force allocation, target selection or HQ dispatcher. The separate opt-in Workbench fixture now calls the actual controller: [123439](evidence/native-2026-09-19-123439-aircraft-controller/report.json) passes 37 assertions, including native post-spawn clearance qualification, signed pilot controls, takeoff, sustained hover, contact landing and engine-off completion. The ordinary game also compiles this code without Workbench. Operational HQ integration, longer routes, repeated-flight reliability and multiplayer remain unfinished. Earlier Core flight experiments use a separate implementation and retain separate evidence.
## Public integration contract
A future owner must already possess one finite, initialized, seated AI Mi-8 and keep the controller alive until its terminal state:
1. Call `ERR_AircraftFlightEnvelope.Qualify(mode, aircraft, pilot)`. Failure returns null, never an approximate or caller-asserted certificate.
2. Call `flight.Start(mode, aircraft, originalGroup, pilot, exactPilotSlot, qualification, uniqueOrderToken, absoluteDeadline)`. The deadline must be in the next 240 seconds. One controller is single-use; the exact aircraft also has an exclusive active-controller registry entry.
3. After the native helicopter's `OnPrepareControls`, call `PrepareHelicopter` for that exact controller. Before the pilot's native `OnPrepareControls`, call `PrepareActions` with its exact character, owner, AI-owned action manager and real frame interval. The global hardware input manager is rejected. No hook was added by this change.
4. Call `Tick()` regularly; it reads the native world clock itself. Continue after `Cancel(reason)` until `Finished()` and inspect `State()` and `Reason()`. Each input entry independently checks the stage/overall deadline and physical envelope, so a stalled owner tick cannot extend control indefinitely.
The allocating owner, eventually HQ, must supply and retain the order identity, asset reservation and authority to dispatch. This class does not claim that a positive token represents a delivered radio order. It never marks a physical asset available. A future caller must not treat every terminal state as successful completion or release a lost/captured/faulted asset for another task.
## Qualification and ownership
The immutable qualification record binds the exact mission, aircraft, seated pilot, measured world origin and conservative complete model/rotor radius. Issuance checks terrain and a 40m horizontal column swept to 60m, then the proven successor footprint algorithm: bounded entity/depth/hitzone traversal, native world model bounds, both real rotor damage pivots/sweeps and simulation rotor extents. It is restricted to the audited stock unarmed Mi-8 resource and sweep dimensions. The column query excludes only the already-existing aircraft/pilot hierarchies, each traversed with strict bounds; foreign objects remain obstacles. The 123439 native fixture executes these actual post-spawn exclusion traces and accepts their exact-instance certificate.
A record can be claimed once, within two seconds according to the native clock, by the same mission/aircraft/pilot and a positive order token. More than 0.1m of aircraft movement refuses the claim. A default-constructed record cannot be claimed. The caller cannot renew a record or set its validity fields.
`Start` additionally requires the exact original pilot/group/seat/controller relationship, enemy affiliation, no human in the group or aircraft, live conscious pilot, working fueled airframe/engine/rotors, initialized native simulation/input, awake dynamic physics and actual native ground contact. It does not wake, teleport or repair the aircraft. The future preparation owner must establish those physical prerequisites. All native control writes retain these identity/ownership checks. Missing game/world/player-manager context during teardown makes ownership false and prevents writes.
## Flight and cancellation states
The controller applies actor-local directional actions in Direct collective mode. It proves positive and negative collective consumption against the existing native rotor signal before enabling autohover. One normal engine start, fuel consumption, five continuous seconds of 7–9m hover, a native contact landing stable for two seconds, and engine-off verification complete the vertical operation. Native autohover supplies lateral stabilization; vertical Start has no horizontal command. The optional bounded translation described below has no attitude PD or route-following mode. Minimum collective remains applied while the stopped engine's rotors coast. Engine-off is not proof of rotor stop.
The original measured home stays fixed. Limits remain below 8m horizontal drift, -3m through 18m relative height, uprightness at least 0.85 and a maximum 240-second operation; each preparation/flight phase has its own shorter deadline. Failed or interrupted observations reset continuous holds. No position, velocity, health, fuel or signal setter exists.
Cancellation before flight can complete on verified ground or request a normal grounded engine shutdown. Once flight has begun, cancellation enters or retains the normal landing path and keeps its control lease; even a fresh ground-contact sample must pass the two-second landing observation. Repeated cancellation cannot renew the deadline. A completed grounded abort is `CANCELLED`; normal completion is `COMPLETE`.
Capability loss, envelope violation, deadline expiry or input failure is explicitly `FAILED`, with a reason printed and sent to the existing HQ journal when available. It is not normal completion. Grounded failures restore saved modes/LOD. An airborne fault clears only owned action requests and does not disable autohover, stop the engine, delete the pilot or claim that a safe landing occurred. Its allocating owner must retain/quarantine the physical asset as appropriate; this controller does not prove that an expired or damaged flight can always recover.
Human capture or any original group/seat/aircraft/controller ownership loss is `RELINQUISHED`. No zeros or settings restoration are written into the captured controller. The destructor is defensive cleanup only, not an airborne cancellation scheduler; owners must retain and tick live operations rather than drop them to cancel flight.
## Optional immutable horizontal target
`StartTranslation(mode, aircraft, originalGroup, pilot, exactPilotSlot, qualification, uniqueOrderToken, absoluteDeadline, worldTarget)` preserves `Start(...)` as the unchanged vertical default. It copies the absolute target X/Z **before** Start performs any native writes, requires it within five metres of the original qualified home, and fixes Y to the existing eight-metre hover altitude. There is no target setter, home rebasing or second command. It is a bounded migration of the separately proven Core translation mechanism, not route support or operational aircraft dispatch.
After the usual five-second physical hover, the controller measures the distance from its actual hover origin to that preselected target. A distance above 4.05 metres (four metres plus an explicit five-centimetre numerical/measurement tolerance) refuses translation and enters the normal retained cancellation/landing path. It never changes the target to make the test pass. Missing native cyclic signals also request controlled cancellation. Near-zero distances are permitted as a hold command but cannot substantiate movement in the native fixture.
The appended `TRANSLATING` state preserves all prior numeric state values and is explicitly nonterminal. Native autohover remains enabled. Horizontal error requests velocity capped at 0.5m/s, then a measured-velocity correction capped at 0.03 cyclic; actual world forward/right projections feed the existing exact pilot's action pairs. There is no attitude PD and no physics write. The target must remain within 0.75m with horizontal speed below 0.25m/s, the existing hover conditions, and measured progress consistent with the initial distance for five continuous seconds. Movement plus settling is limited to 45 seconds under the unchanged 240-second overall deadline. The original below-eight-metre drift, 18-metre height and tilt limits remain active in both Tick and native callbacks. Success enters the existing stable contact landing and engine-off path; cancellation during translation also retains that path. Deadline/capability faults retain the existing explicit `FAILED` quarantine contract, not a guarantee of recovery.
Read-only `TranslationStarted`, `TranslationOrigin`, `TranslationTarget`, `TranslationUpdates` and `TranslationProven` expose observation only. The opt-in `aircraft-translate` Chat fixture requests the original qualified home before takeoff. It can legitimately refuse if natural hover drift is outside the proven bound, or fail its evidence threshold if the initial drift is too small: no displacement is manufactured. The 130038 native run below qualifies this bounded path on its exact source. The existing [normal controller](evidence/native-2026-09-19-123439-aircraft-controller/report.json) and [airborne cancellation](evidence/native-2026-09-19-124915-aircraft-cancel/report.json) archives qualify their respective frozen pre-translation sources only. The latter records one Cancel at 5.08964m, 13.15s of continued control, an unchanged absolute deadline and engine-off `CANCELLED`, with 39 assertions and zero captured errors.
## Source-local checks
Run `python3 tests/test_aircraft_flight_policy.py`, `python3 tests/test_aircraft_flight_envelope.py` and `python3 tests/test_aircraft_translation.py`. The translation test executes the actual immutable-target Start wrapper, geometry/demand/hold policy and Begin/Check stage bodies, including excessive natural drift refusal, absent cyclic signal refusal, copied target ownership, interrupted hold, insufficient callbacks and original deadline preservation. They execute the actual policy, cancellation, landing/shutdown/tick, callback-lease, certificate-claim and release bodies with native boundaries adapted in the C++ harness. Cases include bounded deadlines independently enforced by callbacks, cancellation while airborne/on contact, repeated cancellation, interrupted hold, stale/moved/reused certificates, capture, ground failure restoration, and explicit fault rather than successful completion. The source-bound 123439 native fixture supplies collision, compilation, scoped input and physical vertical-flight evidence. Ordinary startup establishes compilation only; ordinary-game flight, operational dispatch and multiplayer remain separate requirements.
## Native fixed-target result
The [130038 fixed-target fixture](evidence/native-2026-09-19-130038-aircraft-target/report.json) passes 38 assertions and zero captured errors using this actual controller. The target was the original qualified home, fixed before takeoff. Native hover drift produced a 3.3096m requested movement; the helicopter physically moved 2.91183m and finished 0.399235m from that target after the sustained hold and contact landing. There were 484 translation updates, 1,572 callbacks on each native control path, one engine/controller start and one original aircraft/pilot. Its final state was COMPLETE with engine off. This establishes a bounded target command only; longer routes, dispatch and multiplayer remain unqualified.
+8 -8
View File
@@ -1,6 +1,6 @@
# Current development status
Updated **19 September 2026**, through refreshed mortar salvo 112900, packed five-addon startup 113315, current expiry 114751, crew registration 115232 and bounded powered translation 120245. Commit `7c53b4b` remains the clean development checkpoint: companion 103025, ordinary Chat 102737 and casualty 102037 have identical addon-source manifests. Newer flight, spectator and mortar changes mean the **current-source clean runtime gate remains pending**. Packed startup qualifies its archived artifact snapshot, which predates the latest mortar changes; it does not qualify the current source or build process. All seventeen first-release cases remain pending.
Updated **19 September 2026**. Current addon sources pass the local clean-runtime development gate: Core 130347 completes two scripted escape/restart rounds with 612 assertions; companion 131041 completes two with 96 assertions, both with zero captured errors. Ordinary Chat 125906 and actual fixed-target flight 130038 have the same addon-source manifest. The latest aircraft path adds bounded target movement to the independently authored controller; operational HQ aircraft routes remain unfinished. **All seventeen first-release acceptance cases remain pending.** The current 1317 packages pass ordinary Chat startup and isolated Linux empty-server preparation. Native packaging still fails with the known UI shutdown errors; Linux retains four installed-asset errors reproduced by its vanilla control. These are artifact-bound development observations, not release qualification.
The primary deliverable is an independently authored replacement for the abandoned Escapists base mission, with companion behavior integrated through its interfaces. Five successor addon projects have their own identities, authored Everon sites and vanilla asset references. The first release targets up to twelve players completing prison → scavenging/intel → radio defence → helicopter extraction without the original mod or dependent extensions. Scripted development checkpoints exist; a dependable, manually playable multiplayer release is still unfinished.
@@ -8,23 +8,23 @@ The primary deliverable is an independently authored replacement for the abandon
| Area | Evidence | Practical limit |
| --- | --- | --- |
| Integrated base loop | [Core 100619](evidence/native-2026-09-19-100619/report.json) completes two rounds, 612 assertions and zero errors, including canonical participant rejection/restored document recovery, rough/exact native markers, radio feedback, extraction and restart. [Companion 103025](evidence/native-2026-09-19-103025/report.json) completes two rounds, 96 assertions and zero errors at the committed checkpoint. | Scripted local integration with deliberate state/position changes, not a normal-input escape. Later addon edits require new source-bound checks. Earlier failures remain archived. |
| Ordinary game startup | [Chat 102737](evidence/game-2026-09-19-102737/report.json) loads all five addons in the regular game, verifies the header/MissionSystems and admits the real local controller to a living canonical prisoner, then observes a clean ready interval. | Explicit diagnostic noSplash and a scoped standalone round-local identity; no complete ordinary route, remote identity or manual input proof. [Admission design](local-singleplayer-admission.md). |
| Packed addon consumption | [Packed Core 110729](evidence/packed-core-2026-09-19-110729/report.json) passes ordinary startup from the archived project/PAK/resource database plus installed vanilla: exactly three expected addons, real canonical prisoner, a 15-second ready interval and zero captured errors. Package and log hashes are retained. | Artifact-bound startup with explicit noSplash; no loose successor addon was loaded. The artifact predates current mortar edits. [Packed Chat 113315](evidence/packed-chat-2026-09-19-113315/report.json) also passes startup from all five archived 111731 addons (fifteen exact artifacts), with no loose successor source, a real prisoner and zero captured errors. Native packaging remains unqualified: an [empty-addon control](native-packaging-control.md) reproduces the same UI resource-leak errors, without fixing or suppressing them. |
| Integrated base loop | [Core 130347](evidence/native-2026-09-19-130347-core-loops/report.json) completes two rounds, 612 assertions and zero errors, including canonical participant rejection/restored document recovery, rough/exact native markers, radio feedback, extraction and restart. [Companion 131041](evidence/native-2026-09-19-131041-companion-loops/report.json) completes two rounds, 96 assertions and zero errors at its source snapshot. | Scripted local integration with deliberate state/position changes, not a normal-input escape. The current Core, companion, ordinary startup and fixed-target flight archives share one exact addon-source snapshot. Future addon edits require fresh qualification. Earlier failures remain archived. |
| Ordinary game startup | [Chat 125906](evidence/game-2026-09-19-125906-integrated-prison/report.json) loads all five addons in the regular game, verifies the header/MissionSystems and admits the real local controller to a living canonical prisoner, then observes a clean ready interval. Native screenshots show the real prisoner view and the correct escape objective; the stock-scale yard decal creates a larger irregular bare patch, with substantial wheat remaining. | Explicit diagnostic noSplash and a scoped standalone round-local identity; no complete ordinary route, remote identity or manual input proof. [Admission design](local-singleplayer-admission.md). |
| Packed addon consumption | [Packed Core 110729](evidence/packed-core-2026-09-19-110729/report.json) passes ordinary startup from the archived project/PAK/resource database plus installed vanilla: exactly three expected addons, real canonical prisoner, a 15-second ready interval and zero captured errors. Package and log hashes are retained. | Artifact-bound startup with explicit noSplash; no loose successor addon was loaded. The artifact predates current mortar edits. [Current packed Chat 131942](evidence/packed-chat-2026-09-19-131942-current/report.json) passes startup from all five [1317 packages](evidence/package-2026-09-19-1317-all/review.json), with no loose successor source, a real prisoner and zero captured errors. All 151 PAK entries match the captured 183-file current addon source manifest; all fifteen runtime artifacts retain exact hashes. Native packaging remains unqualified: an [empty-addon control](native-packaging-control.md) reproduces the same UI resource-leak errors, without fixing or suppressing them. |
| Prison and scavenging | [Prisoner capacity](evidence/native-2026-09-19-063949/report.json) uses one real player plus eleven physical characters, exact starter kits and native clue transfers. [Prison boundary 100148](evidence/native-2026-09-19-100148/report.json) covers the gate/progression corrections. | Extra physical actors are not twelve connected clients. Native action completion and deliberately repositioned boundary crossings do not prove held input or walking through the gate/debris. Equipment scarcity and combat balance need playtesting. |
| Medicine and extraction casualties | [Medical treatment](evidence/native-2026-09-19-062406/report.json) demonstrates finite supplies and native recovery at its earlier snapshot. [Casualty 102037](evidence/native-2026-09-19-102037/report.json) passes 101 clean assertions at the committed checkpoint: grounded-aircraft checks, real incapacitation, helper rejection, seat/LOS constraints, one owner-handshake load and idempotent stop. [Native removal](evidence/native-2026-09-19-080058/report.json) proves the inherited exit mechanism. | Casualty loading explicitly adapts helper membership and invokes action completion. Held input, real remote ownership, competing rescuers and disconnect/death/deletion races remain separate. The older medical snapshot retains its historical catalog errors. |
| Infantry and HQ | [Actual perception response](evidence/native-2026-09-19-092237/report.json) carries an original soldier's observation through both physical radio legs and HQ to a different original squad, which physically moves. Patrol/preemption/resumption are implemented. | The prisoner is arranged and temporarily protected. Broader combat and interrupted/captured support require additional qualification; no response force is created for this test. |
| Finite support | [Mortar expiry](evidence/native-2026-09-19-091306/report.json) verifies fresh native commitments followed by stale-report withholding without duplicate orders. [Courier 095513](evidence/native-2026-09-19-095513/report.json) passes 154 clean assertions through actual radio, original-patrol walking, exact depot-shell delivery and patrol restoration. | Mortar observations are synthetic; impact accuracy and genuine late callbacks remain unproved. The new refresh now passes the bounded 112900 fixture: three actual radio updates, eight physical launches, native event completion, 19 surviving original shells and 25 seconds without replay. The current [114751 expiry regression](evidence/native-2026-09-19-114751-mortar-expiry/report.json) also passes 48 clean assertions. [115232 crew registration](evidence/native-2026-09-19-115232-intel-readiness/report.json) proves all four physical crew receive their one Intel carrying roll after setup, then exercises inventory depletion/loss; broader support-loss checks remain separate. Courier depletion is arranged by physically sequestering the original 27 rounds, not firing or replacing them. Broader losses and multiplayer remain unqualified. |
| Aircraft mechanism | [Flight 114504](evidence/native-2026-09-19-114504-powered-flight/report.json) passes 81 assertions with zero captured errors: signed actor-local collective consumption, native autohover, powered takeoff, five-second 7–9m hover, stable contact landing and an engine-off check with finite fuel. The same aircraft and living AI pilot remain inside the original physical limits. | [Translation 120245](evidence/native-2026-09-19-120245-powered-translation/report.json) additionally passes 85 clean assertions for a fixed four-metre target, five-second hold and landing. These are isolated flight mechanisms; rotors still coast after engine shutdown. Longer route navigation, operational HQ aircraft support, repeated-flight reliability and manual/multiplayer piloting remain unqualified. Earlier input, drift, hover and shutdown failures remain archived. |
| Aircraft mechanism | [Flight 114504](evidence/native-2026-09-19-114504-powered-flight/report.json) passes 81 assertions with zero captured errors: signed actor-local collective consumption, native autohover, powered takeoff, five-second 7–9m hover, stable contact landing and an engine-off check with finite fuel. The same aircraft and living AI pilot remain inside the original physical limits. | [Translation 120245](evidence/native-2026-09-19-120245-powered-translation/report.json) additionally passes 85 clean assertions for a fixed four-metre target, five-second hold and landing. The reusable production controller separately passes [123439](evidence/native-2026-09-19-123439-aircraft-controller/report.json), 37 assertions including actual post-spawn qualification and its own complete vertical operation. [Airborne cancellation 124915](evidence/native-2026-09-19-124915-aircraft-cancel/report.json) passes 39 assertions with retained control through contact landing and engine-off. The same reusable controller then passes [fixed-target movement 130038](evidence/native-2026-09-19-130038-aircraft-target/report.json), 38 assertions including a physical 2.9m translation and landing. These are isolated flight mechanisms; rotors still coast after engine shutdown. Longer route navigation, operational HQ aircraft support, repeated-flight reliability and manual/multiplayer piloting remain unqualified. Earlier input, drift, hover and shutdown failures remain archived. |
| Shared field map | [Map ownership 120633](evidence/native-2026-09-19-120633-map-ownership/report.json) passes native replacement-view, delayed-callback, mode-clear and spare/interrupted deletion checks. The fixture observes actual cleanup and waits for native component references to expire. | Camera restore authorization is observed; no public native render-state getter exists. Visible held-map usability, previously focused stock gadget callbacks and remote presentation remain separate. |
| Independent runtime controls | [Linux dedicated startup](evidence/dedicated-2026-09-19-083213/review.json) prepares the empty Core mission in an isolated network namespace. Its four resource errors reproduce in a vanilla control. [Vanilla ordinary 102533](evidence/vanilla-game-2026-09-19-102533/report.json) reproduces the initial splash unknown-class error with exactly two vanilla addons and no successor. | No real dedicated client joined. The vanilla ordinary control remains failed despite entering the menu; it does not test scenario reload or input. [Splash diagnosis](normal-splash-audit.md). |
| Independent runtime controls | [Current packed Linux Chat 132456](evidence/dedicated-2026-09-19-132456-packed-chat/review.json) compiles the integrated game scripts, loads all five packages and prepares the empty prison mission in an isolated network namespace. Its four resource errors also occur in the earlier vanilla control. All fifteen packed inputs remain unchanged. [Vanilla ordinary 102533](evidence/vanilla-game-2026-09-19-102533/report.json) reproduces the initial splash unknown-class error with exactly two vanilla addons and no successor. | No real dedicated client joined. The vanilla ordinary control remains failed despite entering the menu; it does not test scenario reload or input. [Splash diagnosis](normal-splash-audit.md). |
## Immediate work
1. **Prove the ordinary player route.** Use normal gate, inventory, document/map, radio and medical interactions; travel the authored route, board and pilot the actual extraction helicopter, and verify partial escape. Keep finite supplies, vanilla recovery and explicit round abandonment intact. Direct-completion fixtures cannot settle these usability questions.
2. **Extend the proven bounded flight mechanism.** Signed actor-local collective and native autohover now pass vertical takeoff, hover, landing and engine-off checks. A bounded four-metre translation now passes too. Longer routes and operational HQ support still need implementation and native qualification. Keep experimental controls separate from operational HQ support, retain failed observations, and re-run applicable integration checks after source changes.
2. **Extend the proven bounded flight mechanism.** Signed actor-local collective and native autohover now pass vertical takeoff, hover, landing and engine-off checks. A bounded four-metre translation now passes too. Longer routes and operational HQ support still need implementation and native qualification. The reusable controller now passes a native vertical operation; airborne cancellation also passes; optional fixed-target movement now passes too. Longer paths remain the next implementation step. Keep unqualified flight modes out of operational HQ support, retain failed observations, and re-run applicable integration checks after source changes.
3. **Establish real multiplayer behavior.** Test accepted client identities, simultaneous/late joins, preload cancellation, reserved-body reconnect/expiry, spent lives, spectator streaming, remote task/map/radio/result updates and seat contention. Local controller injections and helper actors are not substitutes for connected clients.
4. **Qualify current support and observer changes.** The refreshed full salvo now passes a source-bound native check; finish current expiry and support-loss regressions. Spectator reason messages and survivor-cycling guidance have been added and compiled; visible behavior and real-client streaming remain unverified. Exercise actual interrupted reports, captured/dead couriers, pending inventory callbacks and support losses. Preserve stock and original patrols; retain historical failures alongside source-bound successful regressions.
4. **Broaden support-loss and observer checks.** Refreshed full salvo, current expiry and late crew registration now have source-bound native evidence; wider support-loss regressions remain. Spectator reason messages and survivor-cycling guidance have been added and compiled; visible behavior and real-client streaming remain unverified. Exercise actual interrupted reports, captured/dead couriers, pending inventory callbacks and support losses. Preserve stock and original patrols; retain historical failures alongside source-bound successful regressions.
## First-release qualification
+19 -1
View File
@@ -22,6 +22,16 @@ python3 tools/dedicated_native.py \
--route header --seconds 90
```
`--project core` is the default and preserves the original Core command. Select `--project chat --route header` to load `{FA804F411BDDF2C6}` through its existing declared Core/AI/Intel/Map dependencies and start `{C1E20667DCE324ED}Missions/ERR_Everon.conf`. The regular Linux executable still receives the scenario header; no diagnostic world-system override is added. The packed 13:24:56 run below demonstrates integrated Linux compilation and empty-server preparation for its exact fifteen artifacts, with inherited asset errors retained. It does not establish genuine connected-client or multiplayer behavior.
`--route diag-world --project chat` selects the diagnostic executable with the same Chat addon and the already-authored `{B6C516F86302C679}Worlds/ERR_Everon.ent` plus `{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf`. Both projects share that Core world; their loaded scripts/resources differ through their dependency closure. Command/result metadata record the selected route and project. All network isolation, private copies, source snapshots, runtime bounds, backend disablement and owned-process cleanup remain unchanged.
`--route vanilla-everon` remains strictly vanilla: no successor addon or custom system arguments are emitted. Combining it with `--project chat` is an error before any filesystem preparation or subprocess. Vanilla metadata records no successor project.
Successor input must be runtime-resolvable, not just an authored-source bundle. Before creating the archive or invoking any process, preflight requires `addons/core/addon.gproj` and a **nonempty regular** `addons/core/resourceDatabase.rdb`. Chat additionally requires those two files for `ai`, `intel`, `map` and `chat`. Project GUIDs must match the selected successor identities; linked addon folders/files are refused. The copied private input is checked again before namespace preflight. A private input containing only `addons/<name>/{addon.gproj,data.pak,resourceDatabase.rdb}` is permitted: loose scripts are not required. This check proves file presence and project identity only; it does not establish database validity, compatibility with the PAK/source, successful native resource resolution, or a clean build. Native logs remain decisive. Vanilla control requires no successor RDBs.
The [13:14:58 Chat attempt](evidence/dedicated-2026-09-19-131458-missing-databases/result.json) exposed this distinction: the engine listed all five successor GUIDs, then warned that **all five resource databases were missing**. Game compilation remained at the installed vanilla count of 5660 files; subsequent loading reported unknown `ERR_GameMode`, wrong resource GUIDs and further errors, with no mission preparation. Its authored-only input had 183 files and zero RDB entries. Before/after manifests matched, the isolated preflight succeeded, and the bounded run ended by supervisor SIGTERM; a read-only host check found its recorded supervisor and all server executable processes absent. The attempt remains failed. This does not demonstrate that Chat's dependency flags are wrong or that integrated Linux scripts compiled. The new early preflight prevents repeating that known incomplete-input launch, and the completed packaged retry below resolves that particular input failure.
The source, archive and runtime must be separate resolved `/tmp` paths. Archive must be new. Exit zero means bounded diagnostic collection finished, **not** that compilation or acceptance passed. Inspect raw errors and positive mission evidence. The separately enumerated `vanilla-everon` control route loads installed `Missions/21_GM_Eden.conf` without successor addon arguments; see [the resource diagnosis](dedicated-resource-audit.md). The alternative `diag-world` route remains available for a different bounded question but was not needed or executed in this result; it cannot establish ordinary-header startup by itself.
## Archived observations
@@ -46,6 +56,14 @@ Earlier failed isolation checks are preserved. The sandbox-local network namespa
## Source-local regression scope
`python3 tests/test_dedicated_native.py` exercises the actual pre-exec isolation gate with substituted namespace/interface/routes and proves refusal happens before `exec`, detects source mutation and rejects linked sources, and checks header versus diagnostic-world CLI separation. It does not execute a server or simulate multiplayer. The native archive separately establishes that the observed regular server startup accepted the header and initialized the mission.
`python3 tests/test_dedicated_native.py` exercises the actual pre-exec isolation gate with substituted namespace/interface/routes and proves refusal happens before `exec`, detects source mutation and rejects linked sources, and checks exact Core/Chat identities against authored projects/metadata, their dependency closure, regular header versus diagnostic-world CLI separation, and rejection of Chat plus vanilla before filesystem or process operations, plus missing/empty/linked/wrong-identity addon inputs before archive creation or process launch. Packed-only input is tested without loose scripts; test RDB contents are explicitly synthetic and assert presence checks only. It does not execute a server or simulate multiplayer. The native archive separately establishes that the observed regular server startup accepted the header and initialized the mission.
The subsequent vanilla-only control reproduced all four initial asset errors without successor addons. This attributes those specific errors to the installed vanilla content; it does not make the first run entirely error-free or close other acceptance requirements. Exact stacks and control limitations are recorded in [the resource audit](dedicated-resource-audit.md).
## Current packed Chat retry — 13:24:56
[Independent review](evidence/dedicated-2026-09-19-132456-packed-chat/review.json) records the completed 90-second retry from the [1317 package snapshot](evidence/package-2026-09-19-1317-all/review.json). The input contains exactly fifteen successor runtime files: five project descriptors, five PAKs and five resource databases, with no loose scripts. Every artifact matches the package snapshot and remains unchanged after execution.
The regular Linux executable loads exactly five successor and two vanilla addon identities, mounts all five PAKs and opens their five databases. Native Game compilation reports 5780 files and 11202 classes; the Chat scenario header and Core MissionSystems load, seven sites and 117 stock entities prepare, and phase 0 transitions to prison phase 1. Native statistics report zero players. No script/VM-pattern failure appears.
Four native console errors remain. Each also occurs in the earlier vanilla 084040 control, whose complete error set is larger; this is subset attribution, not equality of the two complete runs. No errors are suppressed or waived. The isolated supervisor ends at its 90-second deadline with SIGTERM (`exit=-15`); a read-only process check confirms it and server executable processes are absent. This is artifact-bound integrated server startup/preparation, not a clean native build, graceful shutdown, connected-client qualification or an FPS comparison.
@@ -0,0 +1,199 @@
{
"command.json": "39664d6eddeef0998bfebdd6f2f69157f417b63f9592ee49a8ca44964261ae9f",
"isolation-preflight.json": "d39e14b6fbbbc0f7937772130c63e35a0bc23787a4d2f8918b3e4eabeacab09b",
"profile/logs/logs_2026-09-19_13-14-58/console.log": "ccc80fb88a4df681329bc4ab9e01b453b49de998593b49bd8c270e8afb22e8d8",
"profile/logs/logs_2026-09-19_13-14-58/crash.log": "10b059e914171e6df560767b24f5c04c680b867f3a6cc72fadc2408f4e5102f7",
"profile/logs/logs_2026-09-19_13-14-58/error.log": "e4dae58ce0ca06fbb95c881857aefc3e7e7e777fb24653bb473fd511717090fa",
"profile/logs/logs_2026-09-19_13-14-58/script.log": "b1bdb0efb02459d8cd0422d5d778e834a51f15cf078c4a8a523850d703f66dc2",
"profile/profile/.save/settings/ReforgerEngineSettings.conf": "7b72f3e6b6c9c47643a213914e5dcb40964a2d43328178a740bb512904157cb7",
"profile/profile/.save/settings/ReforgerGameSettings.conf": "1656680640c178239490ffbf9b3ddae04d3ac0993cd84471c0d27b28b76abf13",
"result.json": "d577ba940786dedbcdbd720c2e08305240d9ed775104b949eccfd040886ad371",
"review.json": "a3efd25c6f9f96da0184e272a8d20e0cf65f82405b5410495278da9b0d993a8d",
"runner.py": "213a5a1dafede017301f750d074c2432f7199d8fa92f3c3be5846afa654b499b",
"source/addons/ai/Prefabs/ERR/MortarCrew.et": "05d3e7353ff9fe5523218a1487b31b7b742b0be8adbcb060c7099978cf008bce",
"source/addons/ai/Prefabs/ERR/MortarCrew.et.meta": "fdfd4fcd21a67c3d698fa4aadc4e5c76e665cebf4be86013bc389e493fab63cc",
"source/addons/ai/Scripts/Game/ERR/ERR_AircraftFlight.c": "96b20b7e78612fb26befa6e26635be80dbf9ca089384db4a7839f5064ab06ccc",
"source/addons/ai/Scripts/Game/ERR/ERR_AircraftFlightEnvelope.c": "8125cef61fb283d0d7c4dd2df3c04b062b448f1101fd48dd53f4f4097df5fef3",
"source/addons/ai/Scripts/Game/ERR/ERR_HQ.c": "7ee8e7a3282fd566ec330c94854b353b2c6178e9b964f7b4d5e197e4d0b7dee9",
"source/addons/ai/Scripts/Game/ERR/ERR_HQJournal.c": "b3e402cfe92d415f37f666045afb062e6e11670ba03a0ca6f2d9fd4fc3520ac1",
"source/addons/ai/Scripts/Game/ERR/ERR_HQNetwork.c": "92ff2caca52e94c2cc9642dfc8509ce6e9349b257012bade8650bace9231128c",
"source/addons/ai/Scripts/Game/ERR/ERR_HQObservation.c": "36c978ec5f052213ac1236e1398d85063efbfd7ef60fe04f1fc11447fefe1d68",
"source/addons/ai/Scripts/Game/ERR/ERR_HQReport.c": "c4d16ccb571db4d9444c0a8d2be7318845d25ff90238b6d304e96a9ea19f1a08",
"source/addons/ai/Scripts/Game/ERR/ERR_HelicopterAvailability.c": "60eaef2f6d4b2fd44861c5a4434ef97e732e40a5ff7401e2da451ed2dd9c2762",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarAimSolution.c": "59e8343d09f7a44871d8d3335d6ad1d02216abb5deed30d38b5ec9ba3f2c0649",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarBattery.c": "a4eed96d17f239cbf353863ebe40d2282aeacd7a4d63a7b2f0ecfb3d93a2e2b2",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarCourierTransfer.c": "968e58ca700b4a11e5e04a5c159696f514a0b1bce531210172b913e495f7e2b7",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarFiring.c": "7aec51c83972610d1bcbca58b84b14f16f59952d402b12c3d88e7192a3e2ee90",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarLogistics.c": "e1ffb25fba384b449de576a5502a93d875c3f52e8da1941bbf590ad9845f9730",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarPrediction.c": "470dd196ed64800c697d24c09d1344bacd2502587fac76af4ee0edae2563092d",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarReadiness.c": "2fabe8f812fd333cea7199d1eb722c807fcc5adb285c618956bd54d31fa27ce3",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarRefresh.c": "52ae8d9d6cad92f42d68c634091b80a474d2666b6c06d63a8c30a3fab71494c4",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarReportLedger.c": "42a96040b63e56677353e55164ae23031c90f70b4d6673a9f412ad7b19e636ae",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarSalvo.c": "697c01f479df6c505a41d984a29c02002e3052114dafa2ec6252ae55e2979a2b",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarSupplyTransfer.c": "34f7eaa01dc68e97aae8c1177b7f8fd6c15d5a41c6dc86de377f00b3613a8e77",
"source/addons/ai/Scripts/Game/ERR/ERR_MortarSystem.c": "4edefc8530cc6eec7a5e1df1d0b7d56e6801186f23c61331ce0e8075b8281efb",
"source/addons/ai/Scripts/Game/ERR/ERR_NativeAircraftControlProbe.c": "e501301e683a7e36ad5a0cb56bc87a590c0c9071c9fc5c579502fb1c7a0ff143",
"source/addons/ai/Scripts/Game/ERR/ERR_NativeMortarCourierProbe.c": "740c8bebbd4128a9311b1167d1058549475d6b46f81c9b6c0d19650f4b50616f",
"source/addons/ai/Scripts/Game/ERR/ERR_NativeMortarFiringProbe.c": "ee570d21bede8baebefc799399712d9582b3e30ebda7299f6c941777f02ad2a2",
"source/addons/ai/Scripts/Game/ERR/ERR_NativeMortarSalvoProbe.c": "5fd7bb8953f6850ad79e646c0c286153108a10ee2d5ef397ccb65a07f8d2ed7c",
"source/addons/ai/Scripts/Game/ERR/ERR_NativeMortarTransferProbe.c": "399c21940ef10777b46fdb3e905430ce43404ce1de2f4a6bb62f9c61c1a0689c",
"source/addons/ai/Scripts/Game/ERR/ERR_NativePatrolHQProbe.c": "4c04aa3e1b6b000747da8e2a919a6501107880fd041134c019f516665bb6f758",
"source/addons/ai/Scripts/Game/ERR/ERR_SettlementSafety.c": "e5c0ebc95521a9a33a3f7d9cca6c551dda862a08b7af20b017877aba9584795b",
"source/addons/ai/Scripts/Game/ERR/ERR_SupportScenario.c": "71757018ec9716c4dc24f62a292a214919d3fd5daed3c16e03e56abd208e9f03",
"source/addons/ai/addon.gproj": "5f559db8f526700db11e6ae763f5245f19b7006fc68c56e4c09fb6f940bdf2a1",
"source/addons/chat/Missions/ERR_Everon.conf": "76f3ac0d21b585654beac7ddd68e3c8cbdd66dbfe909b17b04838fa813a7c29b",
"source/addons/chat/Missions/ERR_Everon.conf.meta": "0a23c98bf83873536c65fa17d38dea33a1d7b9e4ab210f90633ff688abcc551f",
"source/addons/chat/Scripts/Game/ERR/ERR_AdminCommands.c": "d7b4ed95fc32767911936cb0fcc60763233f2c31afd34a6a0fa998b39cec9f5c",
"source/addons/chat/Scripts/Game/ERR/ERR_NativeHQResponseProof.c": "0f8b09fd8753234c91fd6baaca0e2f812e409ae1c4be8f340b648ae59b69c2f3",
"source/addons/chat/Scripts/Game/ERR/ERR_NativeMortarProbe.c": "ddb892141500c192c0787b011d57dd8c07dd8c9a3ca5fdc407f6c271897bde0a",
"source/addons/chat/Scripts/Game/ERR/ERR_NativeRadioProbe.c": "84524163c63402755b4f834f74dfe6d8e5c397dc26031f532593c6f79f5a93b1",
"source/addons/chat/Scripts/Game/ERR/ERR_NativeSmoke.c": "32501d011d459e3c2d01077ef3eeb0ce8c364985e1aadfe8e8c022ba4b2054ef",
"source/addons/chat/Scripts/Game/ERRChat/ERR_OrdinaryScenarioLaunchChat.c": "dda715c9691d366e35fa2ad5e390dee1971651f137a5a9f9aeb23dcbe53ec87e",
"source/addons/chat/Scripts/WorkbenchGame/ERR/ERR_ValidateWorldPlugin.c": "81c92d408ce88cc9f98d6053f1d0c863427693ba748f00df9f1bf8079015ef94",
"source/addons/chat/addon.gproj": "ea1587e0174cf795334d306c71d99b06797fc891427cd57b6cab6e21cf0e4e0e",
"source/addons/core/Configs/ERR/EveronSites.conf": "ade92cc8d2b14a84ac378bbbb52608b351dca65d359dd7a315d64a145fa4b46f",
"source/addons/core/Configs/ERR/EveronSites.conf.meta": "d5fe68025ef4e7c1e16082cf5873b7c7172eacbfaef4ab29b7e0e3c98f8dca56",
"source/addons/core/Configs/ERR/Factions/MissionVehicles.conf": "5f74f4a7089c7e7b54ad62463159ce8f0bab25a6b85268dc3872ea6001e048fb",
"source/addons/core/Configs/ERR/Factions/MissionVehicles.conf.meta": "00c368b6271d3440cc8bf627b03a384d247635723923fc5ad2104b8b4007b035",
"source/addons/core/Configs/ERR/Factions/US.conf": "4c3da063abb6e3ee39a6e4e0c5a9ffd5eae9d209d58470ea18b45dba5528db1b",
"source/addons/core/Configs/ERR/Factions/US.conf.meta": "a05469aef028e9e17eba545e81e2af2887b59f0fef17dac51a23c7b8155e381b",
"source/addons/core/Configs/ERR/Factions/USSR.conf": "407ea27f80a64c5184c089d43cdd0bdabfc5974c84f1d2a245081fdf2afa3f30",
"source/addons/core/Configs/ERR/Factions/USSR.conf.meta": "057397d97096346791b51b7b13fbe18af218bcf78e9984d9c01b49a2403de611",
"source/addons/core/Configs/ERR/GameOverScreens.conf": "45231061911a1462d45fb6db0fecc2b0793f17b5ae71017174954ff4fbd20236",
"source/addons/core/Configs/ERR/GameOverScreens.conf.meta": "eb654b745c0b97b41c991694569ef4877a3c2315e0826f9556337cd68690843e",
"source/addons/core/Configs/ERR/VanillaFactions.conf": "db890b61b48ee15484041fd2245a862d9fa9e7ba21b1229c281ff37c448a35b1",
"source/addons/core/Configs/ERR/VanillaFactions.conf.meta": "ac77725c0c21974991ef47a466b88122e939de8b46fd23e68889e7674cbb2bfd",
"source/addons/core/Missions/ERR_Everon_Base.conf": "1e6a0e9a756d15495201f71121626890f01efe6b00154c561672d4ec157960bd",
"source/addons/core/Missions/ERR_Everon_Base.conf.meta": "9b16267c644d815bcf98a3a3aaff30b93d7224fb24d3a2bac66730a28011f317",
"source/addons/core/Prefabs/ERR/Extraction.et": "1aa286ef253c5410f09fa641e4be91a7be8ec834487a7fbb74cfb45cab60ee1e",
"source/addons/core/Prefabs/ERR/Extraction.et.meta": "c2a838633001e2e3a202d39cb33aa744ebcd996017ad032359069641b5d13853",
"source/addons/core/Prefabs/ERR/FactionManager.et": "425f916b890019c537f6b2f72b1af9e7be54b0a476320da969e1f313a7ac5ea5",
"source/addons/core/Prefabs/ERR/FactionManager.et.meta": "9bf53d5e4bcbe60dd1a4ece1409fc23c349055c860708620f5f6e9716290a97d",
"source/addons/core/Prefabs/ERR/FieldCover.et": "20f8095e69dbfe3db74ff19078e528ea75405362b335e03560ccd61f8df2e7f3",
"source/addons/core/Prefabs/ERR/FieldCover.et.meta": "83461ffa76244e1d69f20b053a9741ccdf7d127009182bd540a7e5ba6150c3d9",
"source/addons/core/Prefabs/ERR/GameMode.et": "385c97a5b8067091d92aa60c2d4fcf02002cf606b95523866ead65c96b8c0029",
"source/addons/core/Prefabs/ERR/GameMode.et.meta": "106769cd34525c9d440057072836cb7401a6752148c40edd7fe61c03bd58328c",
"source/addons/core/Prefabs/ERR/ObjectiveDocument.et": "2e72270bf186654af12d2c3ff9bf940690b03fb17fca44f873c1fde5d11384c0",
"source/addons/core/Prefabs/ERR/ObjectiveDocument.et.meta": "eaba9903be147b98a1106e2273e2270773df73b9b22f2cd4e20136a8e281382e",
"source/addons/core/Prefabs/ERR/Patrol.et": "c5685a8bc311cc5563fb3334beec9408d3d7a1069152fddeeb7df4f1b7f9e6ce",
"source/addons/core/Prefabs/ERR/Patrol.et.meta": "7c5da568fa2fcf0709ee2959253cb068d7c7ed7c2467af3b247a7e70efd96b69",
"source/addons/core/Prefabs/ERR/Prison.et": "dcd3f4dfd453303d026e68b64f7de6125bd3390822d8432dd95a3710a5b3df89",
"source/addons/core/Prefabs/ERR/Prison.et.meta": "2e426ef3de68f4107907854b51e818bc11d84649a01c538e3f18dd9ebec766bb",
"source/addons/core/Prefabs/ERR/PrisonDoor.et": "fc66be9d36f1f01084de59f1b0a1e8817f9e46547ead33d4fafaac0107446c40",
"source/addons/core/Prefabs/ERR/PrisonDoor.et.meta": "b8f3db52bf186426026668fbb9d633c3cbd4d438ab301fba880398f6a0e781c2",
"source/addons/core/Prefabs/ERR/PrisonWall.et": "8009d19365ae72a4749d7ad4fa04662575b06c65c6bf06507d380d57258b5eed",
"source/addons/core/Prefabs/ERR/PrisonWall.et.meta": "0294bc5dbb56f90f8153d62459bb0a6ad91d76a479812bbec9c7db182fdb58d6",
"source/addons/core/Prefabs/ERR/Prisoner.et": "198b5badabb12f218f86df7eb4dc268fd830769dc3de7eef6362cfaee4d6fc8d",
"source/addons/core/Prefabs/ERR/Prisoner.et.meta": "974278ba49fafeed70b619a0067bbc70ae4d534be441c812ce2bef4a5fb3c022",
"source/addons/core/Prefabs/ERR/Radio.et": "b4b5cfe125b39019855816c9a1c508ca4d65d435b5546fdaa6c1fa3c8c9b1165",
"source/addons/core/Prefabs/ERR/Radio.et.meta": "bc91137b68ba550217914fb8892bb44d7e1920e4a1086f3e506623832a44212f",
"source/addons/core/Prefabs/ERR/SpectatorCamera.et": "b5b87d5b7b707f3745741aebe7c923067c8a74f8fbb2a4711834bab03bb5a812",
"source/addons/core/Prefabs/ERR/SpectatorCamera.et.meta": "2a16558f93915e31908a674509e9c89976470f78a84a1174a64b94b68b8f77ad",
"source/addons/core/Prefabs/ERR/SupplyCache.et": "bccf7afbaae97cfcdd78dd6dbbf4be5b2c045d348f50fe4581962e556c0044d7",
"source/addons/core/Prefabs/ERR/SupplyCache.et.meta": "2f5de3a4aab1e9d0e35521f48dc57b95bd6e449c383dec4f1c258e10295904cb",
"source/addons/core/Prefabs/ERR/SupplySite.et": "dc5decce5cabf39b6116e8794ec3960cb1e35761854fdbe69740da118787ded4",
"source/addons/core/Prefabs/ERR/SupplySite.et.meta": "b41944b12eb3528ff7a3ac94258b18c66ef161aa6a38894aee300a1ac4798630",
"source/addons/core/Scripts/Game/ERR/ERR_Actions.c": "cabb21413c90b252d29708b62228d0679c51c76e39ee0e483953ad7061b2b9d6",
"source/addons/core/Scripts/Game/ERR/ERR_Actors.c": "b3c5aabb05f7cf35167bc09b5db51a4ffd89a46658805d6eedbfb80eee30273d",
"source/addons/core/Scripts/Game/ERR/ERR_AdminService.c": "9985747d5ddaeb91bf27937836a6c369f8dcb353263b77a49212ec28c10ba4eb",
"source/addons/core/Scripts/Game/ERR/ERR_CasualtyGroundSupport.c": "26f0c7ebe8563bb022f7c383674e7d8997b0cc35c7ea321d39d74f2493c1ed9d",
"source/addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingGameMode.c": "f4cf19440583ac24346831a2d5f1d7f734c031e46dc266c499972329dd0533cf",
"source/addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingOwner.c": "6346a05ddda030f7d9b6f026f977d5f018ee16002fa519e8c49dcd700c323dc7",
"source/addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingService.c": "93fe23d9e1da596886c33cf76bad92b0906ff5cdd8b5b21d58906e9270246201",
"source/addons/core/Scripts/Game/ERR/ERR_DiscoveryMap.c": "7553c8cc1388cca3404994e36f96946c6abd1fefb08cef2fbb3ea1a62b22861b",
"source/addons/core/Scripts/Game/ERR/ERR_DiscoveryService.c": "2793bb888566dd975e5bbf93fdb47a22e3b0f33a68d04992191cf6428ae942a0",
"source/addons/core/Scripts/Game/ERR/ERR_DocumentComponent.c": "17bb76b80ebd3357e46d553c44674f602c7bd6901c92aec53ac8214194aa4ff2",
"source/addons/core/Scripts/Game/ERR/ERR_EnvironmentService.c": "867e9f47adfb7a9819a8b58498688ff86127282cb89cf2529ae81f73e529e1c5",
"source/addons/core/Scripts/Game/ERR/ERR_EscapeGameOverScreenInfo.c": "b1e1afb7795cb77c0bad94c85abf705b26b9dc2a9400905d80eafd56bdd009d3",
"source/addons/core/Scripts/Game/ERR/ERR_ExtractionPlacement.c": "c30222ca25cc0e91dec4077e6b82b37a9edafcf2cbebd0be5b6bd8656b53adfe",
"source/addons/core/Scripts/Game/ERR/ERR_ExtractionService.c": "91357099b4e0401196b67d48963be54be30e56bdba57cdac67dc20b8ebb883e9",
"source/addons/core/Scripts/Game/ERR/ERR_FactionCatalog.c": "a5a8c53acf21259e14f59cb919535910f30dabff5a9179c664bc6a7a4d744d6e",
"source/addons/core/Scripts/Game/ERR/ERR_GameMode.c": "41f2247a45252eaacb580aa5ebb5a85ecfa41e0f940eb0d2c8ea5a3f6c35058e",
"source/addons/core/Scripts/Game/ERR/ERR_InitialGuardRules.c": "8c9d6b1dc4c2af96284e34f487eebb5a221091016ef108006dec60b4e3836558",
"source/addons/core/Scripts/Game/ERR/ERR_Inventory.c": "b63e679d5b650b63a7a91dfc3f42a62f4be58b0338f6df3313e66d29a93bebf6",
"source/addons/core/Scripts/Game/ERR/ERR_ItemStationTeardown.c": "102e61216e86e47ad243cc19f6f66da1fcd8cc77f8799354ceaeb4a6dc74622e",
"source/addons/core/Scripts/Game/ERR/ERR_LoadCasualtyAction.c": "5d868fb065a1a0fea7d5abfc81d1d8be96823815535f2ffd0dc4ff60d8eae794",
"source/addons/core/Scripts/Game/ERR/ERR_LocalAdmission.c": "b0e824c6880cbf9b1f368dc7ada9907bde78b22b948d419b6c861323f32258be",
"source/addons/core/Scripts/Game/ERR/ERR_Location.c": "d2035f56ded35743312ec6ca91a67f8623a466c91255e1893c570edf0954ff24",
"source/addons/core/Scripts/Game/ERR/ERR_MissionRules.c": "f7abc38ad8fdc375ce7fa63a65f0bade027d22b37e09de5e3f94ba8c3c63cc41",
"source/addons/core/Scripts/Game/ERR/ERR_NativeAdminSettingsProbe.c": "6a4df33a2b0e6b6aeed944c6895961e658d4f0db9aed9b8433a43a451fa35cae",
"source/addons/core/Scripts/Game/ERR/ERR_NativeBaseSmoke.c": "4c58290868fe4b669ff0f05835e9e7626766c13eab9633a721e0ebe5c551428e",
"source/addons/core/Scripts/Game/ERR/ERR_NativeCasualtyGroundProbe.c": "41f40db3735f8aa535c0061458d9892dcfe972222b8a17238e67b65633c46bc3",
"source/addons/core/Scripts/Game/ERR/ERR_NativeCasualtyLoadProbe.c": "94c533deea654ec546df3f1ebb7cd213e6cd79b1125d99bab5303ef8e87b018b",
"source/addons/core/Scripts/Game/ERR/ERR_NativeCasualtyRemoveProbe.c": "4cfc06f6d43b709b63caae714155ffc562502e884f49a9825623c6f46972ad36",
"source/addons/core/Scripts/Game/ERR/ERR_NativeCasualtyServiceProbe.c": "02a9d70d1f4ba20326df199ba41489cfbae12b7bb0aab441361421cee38ad9ea",
"source/addons/core/Scripts/Game/ERR/ERR_NativeContractsProbe.c": "d1556df885171cf967e96eef5d8981a2e9ee4e3938b7d32fdd0426f647bbabc0",
"source/addons/core/Scripts/Game/ERR/ERR_NativeDefinitionProbe.c": "c07048341396443ddf5307e340e2087254d65f0bddf8f7d93e6b6bd7f03ca360",
"source/addons/core/Scripts/Game/ERR/ERR_NativeDiscoveryMapProbe.c": "9ed32d391d08d937ff4f9f33bd11c3a204ce9a2230c2d72b6761e0320b31f24f",
"source/addons/core/Scripts/Game/ERR/ERR_NativeEnvironmentProbe.c": "8b1d110717aa5f07bfb8fe633847eab519109e3a8a4100970a487be05c053418",
"source/addons/core/Scripts/Game/ERR/ERR_NativeEnvironmentRestartProbe.c": "7a836a206381b8e8de24c92baf141beb30575723675dd6948d738169ed26b507",
"source/addons/core/Scripts/Game/ERR/ERR_NativeExtractionProbe.c": "4b09385f73206de004f4f3c38a6192fd658a0d2012482dc5a84dff1599ceb972",
"source/addons/core/Scripts/Game/ERR/ERR_NativeFactionCatalogProbe.c": "ee11486cf77da9070506ccc0cfd6e94240c5387c8be455bf864934a8fef65f78",
"source/addons/core/Scripts/Game/ERR/ERR_NativeFlightFootprint.c": "a8862a04ac784426f431e2e9cd3f1f19f820b38a081674d29549b8b00277c72d",
"source/addons/core/Scripts/Game/ERR/ERR_NativeFlightInputDiagnostics.c": "e231dcbdaefe81beb3bd587cb73be8fb3d3184bc86d18dbaa30a43ca4ba5dcc4",
"source/addons/core/Scripts/Game/ERR/ERR_NativeGuardCancelProbe.c": "bfa1c65cb039e19c3d1891e4a3fd33469e974c97cc7f42128efc31a8f587d514",
"source/addons/core/Scripts/Game/ERR/ERR_NativeItemStationTeardownProbe.c": "028588a49fa9207087cd27ec9396979cd932daa071e2490bd0262cd71dae30d9",
"source/addons/core/Scripts/Game/ERR/ERR_NativeLifecycleProbe.c": "b613a5dbe81c2032fd02d31070ae5631166d14d0b02cdbec3b63b782f0bbc115",
"source/addons/core/Scripts/Game/ERR/ERR_NativeLocalAdmissionProbe.c": "29c860ca3e51cdaa463f3c23a98ff45ee0499ce9bbc322046e0efbed587f1e09",
"source/addons/core/Scripts/Game/ERR/ERR_NativeMedicalProbe.c": "43fa51ffbcf9dd0cd8e905a1135bcf0756651da3af34677f3c92e7d264ed70b6",
"source/addons/core/Scripts/Game/ERR/ERR_NativeObjectiveLossProbe.c": "6b80fd317a8c6e8583bd4337870838a86251e3b8a681d0aa07e8b52e22abfc48",
"source/addons/core/Scripts/Game/ERR/ERR_NativeObjectiveProbe.c": "71fbd2479a70048f83d8b262ad1452f427be2c0afc8b071ef13bff477b9dc29b",
"source/addons/core/Scripts/Game/ERR/ERR_NativePatrolProbe.c": "02bd9fdf5454db7dd4c82240efe0d947bf8d6488ace42809c3aef6505cb6266d",
"source/addons/core/Scripts/Game/ERR/ERR_NativePoweredFlightProbe.c": "7ae5d6f85bbbebec214c66bfdf6f53b93c802f109fcd5bcfbad82c5d61d6cb9f",
"source/addons/core/Scripts/Game/ERR/ERR_NativePrisonBreachProbe.c": "22d0923cd69ba591e783011d424c97c86520272b70da9aa6a6d15558464c264c",
"source/addons/core/Scripts/Game/ERR/ERR_NativePrisonerProbe.c": "e2d1ffe5d269add55124601ff0d1dc032a398c7d51010736e6f0e4cbeb38ae86",
"source/addons/core/Scripts/Game/ERR/ERR_NativeProgressionIdentityProbe.c": "1d3fe20bce761a1082964bca9bad9ce79d1fa8b04db9b5053bae98da380ae560",
"source/addons/core/Scripts/Game/ERR/ERR_NativeRadioDefenceProbe.c": "cad54f0b43cefd72df74697dfc7e93725ff0acac6d311e963ce4f817428c272c",
"source/addons/core/Scripts/Game/ERR/ERR_NativeRecoveryProbe.c": "4de6868cd3e76afb80cf0ae5dac1e35d2c417838f640c2f18aa3343217eea2f2",
"source/addons/core/Scripts/Game/ERR/ERR_NativeResultProbe.c": "b9a117eef2d5f65fbd792041a2b3e59e38e201bda8e0df8c503e1a1242cfc415",
"source/addons/core/Scripts/Game/ERR/ERR_NativeSiteSurvey.c": "ac0203753ade0cdff6d483132797a80e4520f14546c3f9dea288281cd4cd9c4f",
"source/addons/core/Scripts/Game/ERR/ERR_NativeStationTeardownDiagnostic.c": "172248540932174341882861bb78a9720a6cf89981fcdf9dffe63f7fd4023f2c",
"source/addons/core/Scripts/Game/ERR/ERR_NativeTimeProbe.c": "c93d049785971e51246fcc07cf24804270a752a655505f2c38e48c80df5255ae",
"source/addons/core/Scripts/Game/ERR/ERR_NativeTransportProbe.c": "63ec0c5ce698114daa6314b2a6e3c3e98e76094117c477f9d0d072260686d193",
"source/addons/core/Scripts/Game/ERR/ERR_NativeTransportSurvey.c": "3e94493bf54d15be2bceb106ad18873e14e6755af09c05f07db7fb26fb7adff5",
"source/addons/core/Scripts/Game/ERR/ERR_ObjectiveDetails.c": "628ebd166dcb5544c2d18dce489619984887ee18296cf2446fc6d894f89dd5bf",
"source/addons/core/Scripts/Game/ERR/ERR_ObjectiveText.c": "d1740ddbe8a4bac04aeb0ffc68c2957bc807cbb0a197afd515f0fcb742aac04f",
"source/addons/core/Scripts/Game/ERR/ERR_Objectives.c": "88d95a52fcba103483ea751571e1afeae0e6fc82115ed50d6fc195424726960f",
"source/addons/core/Scripts/Game/ERR/ERR_Observer.c": "f417f503dcb66b4d5224d49046ab47c08fa14255d5c4af02a85047096069da33",
"source/addons/core/Scripts/Game/ERR/ERR_OrdinaryScenarioLaunch.c": "11541755401af8da68dc004d9a8e2a49578d617d4dec63b1d6e5a75b00b9c3ff",
"source/addons/core/Scripts/Game/ERR/ERR_PlayerAdminCommands.c": "a084d7dc097dfcde3d6cd9c2e326b9e2f8f2a519f77b30c4f56ab7a1429aa721",
"source/addons/core/Scripts/Game/ERR/ERR_PlayerEscapeResult.c": "3124ab4d2d6d471409a47aba0af528790734df53243c03fe0cdac3e407047c14",
"source/addons/core/Scripts/Game/ERR/ERR_PlayerGuide.c": "2c9c365b88651f8b12618443638bf52c04e497221592bd483c33447e30468f36",
"source/addons/core/Scripts/Game/ERR/ERR_PlayerRoster.c": "c038dac0f749bd29ebc35a4e952904b41a34417ba738904d5efcd1833a263e0b",
"source/addons/core/Scripts/Game/ERR/ERR_PrisonEscapeService.c": "ed6286342ed12cc9a3df618474f5647c7ac36bc65336be7a5bec908dee04421d",
"source/addons/core/Scripts/Game/ERR/ERR_RadioDefenceService.c": "5d347cf1a3b021074d560aad8db774828b797e685b63ddff3d28f55781e33ea8",
"source/addons/core/Scripts/Game/ERR/ERR_RadioFeedback.c": "545262e65e9b3645d9e514a0521e442fe08f0c2e4e7c83ba638c5105d09818db",
"source/addons/core/Scripts/Game/ERR/ERR_RecoveryService.c": "ba9ffb93679012c20a665636ef6b201cbe7d8d14fdf53e8cd3eb12ac7248943f",
"source/addons/core/Scripts/Game/ERR/ERR_Resources.c": "d0169aac6fd0c38928c47a9002ded0fd8ba558ea3314a600e792d307ac1ca3ee",
"source/addons/core/Scripts/Game/ERR/ERR_ScenarioComponent.c": "abba03af68323d5715ba1e2497fc66f3c706e6a6a983c3a386228abdfddeec89",
"source/addons/core/Scripts/Game/ERR/ERR_Settings.c": "b9390548936b386e0211d487c1b76b65410fdd2e339cd8ecd9585ef4f4ffed31",
"source/addons/core/Scripts/Game/ERR/ERR_SiteDefinition.c": "b422feeaca90084ea2bee5cde94dc9c6fcae542749b55d95f7226f922a6a68ac",
"source/addons/core/Scripts/Game/ERR/ERR_SiteDiscoveryRules.c": "50f7b3ff4f32bde37ce5ae2be7f598fc4dd8cc08f45914f6e3260d31c84baef2",
"source/addons/core/Scripts/Game/ERR/ERR_SiteService.c": "6bc9a0b5aed326822807f05e17459d0fc2da10b709e8168972b4e40fd09f87cb",
"source/addons/core/Scripts/Game/ERR/ERR_SpawnFinalization.c": "2e295b0f225208c5414728ba83333e5e0ab6eef3a708ce39eb1dfce5400c94c9",
"source/addons/core/Scripts/Game/ERR/ERR_SpawnLogic.c": "ed88279cbeb3e5e6daf8471763df87a251881f550632eddadd08153594bbd1a1",
"source/addons/core/Scripts/Game/ERR/ERR_SpawnPlacement.c": "55323dcb330c23d49dc8fa3fde86a3ae3958c0b6563d6c3cd0960da88333f93d",
"source/addons/core/Scripts/Game/ERR/ERR_Supplies.c": "752e8a56b0b8cde33cf532032cb0a203553017b686faac2e19bee06f7a9c0713",
"source/addons/core/Scripts/Game/ERR/ERR_TimeService.c": "d9f481b7da01e08d2c982a948c18ba6bde76889d7cc9d26d510708f4ba479453",
"source/addons/core/Scripts/GameLib/ERR/ERR_DiagnosticNoSplash.c": "0ea1b402f8927b6d68bf6f7e5caa2967f7e3a39147634faab147796b791933b5",
"source/addons/core/Scripts/WorkbenchGame/ERR/ERR_ValidateBaseWorldPlugin.c": "7f44aa64ea7b4f8b3640a37a4b781c43bf6adb80e4a3a702d9491131698056f6",
"source/addons/core/Worlds/ERR_Everon.ent": "22324bc7eb0d80dec8a1f3bd61a9df9201b72f749f4e751766b0c9d0b7d46b87",
"source/addons/core/Worlds/ERR_Everon.ent.meta": "19d1903ca72baa326b4724a6791d0da1104a1e53f3f31dc1ca41a856efd68e83",
"source/addons/core/Worlds/ERR_Everon_Layers/default.layer": "17105538007199bfc419aa04a64debaf2da0a7b77a1dc3ba983e92bf747c862e",
"source/addons/core/addon.gproj": "231274e6b62302f32c65f9ff31de2ce8a9e51e1c8d12c87deeec82ae1f40de46",
"source/addons/intel/Scripts/Game/ERR/ERR_IntelService.c": "5e167630a76437fd731fbb7a62a41381ea71509f3adb8a6b9c77f6816384861a",
"source/addons/intel/addon.gproj": "d0ced242cefb220a5cccd6779d61fda9bc55581701b9f78bcf13c09d92ddbcbe",
"source/addons/map/Materials/ERRM_MapRenderTarget.emat": "9877c5b175c5f30dd80bea36c711b21ab38219aa7075adf9ae79932e1d4c76bc",
"source/addons/map/Materials/ERRM_MapRenderTarget.emat.meta": "5ee1a705f8011a44020ca2691f5ccc17d44bf596fef9a36e98d1f267eb58fab0",
"source/addons/map/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et": "30dee8e9eab459ea32a7d835a9fb89d43df47708057cab829f067b25a24cd581",
"source/addons/map/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et.meta": "59093e9072c50d99c86da7609c87541016da5bcf92b98d99db4c524e1c8e0e8d",
"source/addons/map/Scripts/Game/ERR/ERR_ScenarioMap.c": "75fa1d742129ef7f33c005cd48f56db7f49ee7bde3c5428532b0b501b032b0a3",
"source/addons/map/Scripts/Game/ERRMap/ERRM_NativeMapOwnershipProbe.c": "32418602f085e3edba7343f42a6a691630b533be262002f29248e8ae87a38254",
"source/addons/map/Scripts/Game/ERRMap/ERRM_SharedMapComponent.c": "3a10f6ea25bf88a400bc3e6c339823433845c5db4f2f423835ccf99a1b1a843f",
"source/addons/map/UI/layouts/ERRMap/SharedMapRenderTarget.layout": "49a2da4181b94d42174f58c7f9b6bf2207116c99427d61ff1c212092ac7db4d2",
"source/addons/map/UI/layouts/ERRMap/SharedMapRenderTarget.layout.meta": "5c846445cc089e2e08899ba703059d4adb805ff30ab5a16409a3cd59718cbd54",
"source/addons/map/addon.gproj": "1b371e5e01ea9fa46e76570fecb8aaa3a9298e34cba7ccca8a6bdb5ca86eb46a",
"source-manifest-after.json": "86b8c474e8240667a5f5b7a0d19cdf5e26aa12be78e27e96b50d331b639648d5",
"source-manifest.json": "86b8c474e8240667a5f5b7a0d19cdf5e26aa12be78e27e96b50d331b639648d5",
"stdout.log": "64963f62de592e2b38b3485cecaf81f0707eb55c7ba8a91aa4f2818fa370a6cb"
}
@@ -0,0 +1,114 @@
{
"command": [
"bwrap",
"--unshare-net",
"--unshare-pid",
"--unshare-ipc",
"--unshare-uts",
"--die-with-parent",
"--cap-drop",
"ALL",
"--clearenv",
"--ro-bind",
"/",
"/",
"--tmpfs",
"/home",
"--tmpfs",
"/tmp",
"--tmpfs",
"/run",
"--proc",
"/proc",
"--dev",
"/dev",
"--ro-bind",
"/tmp/erri-dedicated-runtime/server",
"/tmp/runtime",
"--bind",
"/tmp/erri-dedicated-chat-20260919-1314/source",
"/tmp/source",
"--bind",
"/tmp/erri-dedicated-chat-20260919-1314",
"/tmp/work",
"--setenv",
"HOME",
"/tmp/work/home",
"--setenv",
"TMPDIR",
"/tmp/work/tmp",
"--setenv",
"XDG_CONFIG_HOME",
"/tmp/work/home/.config",
"--setenv",
"XDG_DATA_HOME",
"/tmp/work/home/.local/share",
"--setenv",
"XDG_CACHE_HOME",
"/tmp/work/home/.cache",
"--setenv",
"PATH",
"/usr/bin:/bin",
"--setenv",
"LANG",
"C.UTF-8",
"--chdir",
"/tmp/runtime",
"/usr/bin/python3",
"-c",
"import json,os,socket,sys\nstate={\"netns\":os.readlink(\"/proc/self/ns/net\"),\"interfaces\":socket.if_nameindex(),\"routes\":open(\"/proc/net/route\").read(),\"ipv6Routes\":open(\"/proc/net/ipv6_route\").read()}\nprint(\"ERR ISOLATION \"+json.dumps(state),flush=True)\nassert state[\"netns\"] != sys.argv[1], \"network namespace was not isolated\"\nassert [name for _,name in state[\"interfaces\"]] == [\"lo\"], \"non-loopback interface present\"\nassert len(state[\"routes\"].splitlines()) == 1, \"IPv4 route present\"\nassert all(line.split()[-1] == \"lo\" for line in state[\"ipv6Routes\"].splitlines()), \"non-loopback IPv6 route present\"\nif len(sys.argv)>2: os.execv(sys.argv[2],sys.argv[2:])\n",
"net:[4026531833]",
"/tmp/runtime/ArmaReforgerServer",
"-profile",
"/tmp/work/profile",
"-noBackend",
"-noSound",
"-noSplash",
"-noThrow",
"-bindIP",
"127.0.0.1",
"-bindPort",
"2001",
"-maxFPS",
"30",
"-logStats",
"5000",
"-addonsDir",
"/tmp/source/addons",
"-addons",
"FA804F411BDDF2C6",
"-server",
"{C1E20667DCE324ED}Missions/ERR_Everon.conf"
],
"engine": [
"/tmp/runtime/ArmaReforgerServer",
"-profile",
"/tmp/work/profile",
"-noBackend",
"-noSound",
"-noSplash",
"-noThrow",
"-bindIP",
"127.0.0.1",
"-bindPort",
"2001",
"-maxFPS",
"30",
"-logStats",
"5000",
"-addonsDir",
"/tmp/source/addons",
"-addons",
"FA804F411BDDF2C6",
"-server",
"{C1E20667DCE324ED}Missions/ERR_Everon.conf"
],
"seconds": 90,
"route": "header",
"project": "chat",
"binarySHA256": "91fdf333ec49c266d309be358aabab4244a93a2f9cfa11853a600e026104001f",
"serverLaunched": false,
"realPlayers": 0,
"multiplayerProven": false,
"scope": "successor chat empty-server startup"
}
@@ -0,0 +1,65 @@
{
"command": [
"bwrap",
"--unshare-net",
"--unshare-pid",
"--unshare-ipc",
"--unshare-uts",
"--die-with-parent",
"--cap-drop",
"ALL",
"--clearenv",
"--ro-bind",
"/",
"/",
"--tmpfs",
"/home",
"--tmpfs",
"/tmp",
"--tmpfs",
"/run",
"--proc",
"/proc",
"--dev",
"/dev",
"--ro-bind",
"/tmp/erri-dedicated-runtime/server",
"/tmp/runtime",
"--bind",
"/tmp/erri-dedicated-chat-20260919-1314/source",
"/tmp/source",
"--bind",
"/tmp/erri-dedicated-chat-20260919-1314",
"/tmp/work",
"--setenv",
"HOME",
"/tmp/work/home",
"--setenv",
"TMPDIR",
"/tmp/work/tmp",
"--setenv",
"XDG_CONFIG_HOME",
"/tmp/work/home/.config",
"--setenv",
"XDG_DATA_HOME",
"/tmp/work/home/.local/share",
"--setenv",
"XDG_CACHE_HOME",
"/tmp/work/home/.cache",
"--setenv",
"PATH",
"/usr/bin:/bin",
"--setenv",
"LANG",
"C.UTF-8",
"--chdir",
"/tmp/runtime",
"/usr/bin/python3",
"-c",
"import json,os,socket,sys\nstate={\"netns\":os.readlink(\"/proc/self/ns/net\"),\"interfaces\":socket.if_nameindex(),\"routes\":open(\"/proc/net/route\").read(),\"ipv6Routes\":open(\"/proc/net/ipv6_route\").read()}\nprint(\"ERR ISOLATION \"+json.dumps(state),flush=True)\nassert state[\"netns\"] != sys.argv[1], \"network namespace was not isolated\"\nassert [name for _,name in state[\"interfaces\"]] == [\"lo\"], \"non-loopback interface present\"\nassert len(state[\"routes\"].splitlines()) == 1, \"IPv4 route present\"\nassert all(line.split()[-1] == \"lo\" for line in state[\"ipv6Routes\"].splitlines()), \"non-loopback IPv6 route present\"\nif len(sys.argv)>2: os.execv(sys.argv[2],sys.argv[2:])\n",
"net:[4026531833]"
],
"exit": 0,
"stdout": "ERR ISOLATION {\"netns\": \"net:[4026534801]\", \"interfaces\": [[1, \"lo\"]], \"routes\": \"Iface\\tDestination\\tGateway \\tFlags\\tRefCnt\\tUse\\tMetric\\tMask\\t\\tMTU\\tWindow\\tIRTT \\n\", \"ipv6Routes\": \"00000000000000000000000000000000 00 00000000000000000000000000000000 00 00000000000000000000000000000000 ffffffff 00000001 00000000 00200200 lo\\n00000000000000000000000000000001 80 00000000000000000000000000000000 00 00000000000000000000000000000000 00000000 00000002 00000000 80200001 lo\\n00000000000000000000000000000000 00 00000000000000000000000000000000 00 00000000000000000000000000000000 ffffffff 00000001 00000000 00200200 lo\\n\"}\n",
"stderr": ""
}
@@ -0,0 +1,304 @@
---------------------------------------------
Log /tmp/work/profile/logs/logs_2026-09-19_13-14-58/console.log started at 2026-09-19 13:14:58 (2026-09-19 11:14:58 UTC)
13:14:58.032 ENGINE : FileSystem: Adding relative directory '/tmp/work/profile/logs/logs_2026-09-19_13-14-58' to filesystem under name logs
13:14:58.032 ENGINE : FileSystem: Adding relative directory '/tmp/work/profile/profile' to filesystem under name profile
13:14:58.032 ENGINE : FileSystem: Current working directory: '/tmp/runtime/'
13:14:58.033 ENGINE : Initializing engine, version 192142
13:14:58.033 ENGINE : CLI Params: -profile /tmp/work/profile -noBackend -noSound -noSplash -noThrow -bindIP 127.0.0.1 -bindPort 2001 -maxFPS 30 -logStats 5000 -addonsDir /tmp/source/addons -addons FA804F411BDDF2C6 -server {C1E20667DCE324ED}Missions/ERR_Everon.conf
13:14:58.034 ENGINE : Addon dirs:
13:14:58.034 ENGINE : dir: '/tmp/source/addons'
13:14:58.034 ENGINE : dir: './addons'
13:14:58.034 ENGINE : dir: '/tmp/work/profile/addons'
13:14:58.034 ENGINE : Available addons:
13:14:58.034 ENGINE : gproj: '/tmp/source/addons/ai/addon.gproj' guid: '5192138CB9D5B911'
13:14:58.034 ENGINE : gproj: '/tmp/source/addons/chat/addon.gproj' guid: 'FA804F411BDDF2C6'
13:14:58.034 ENGINE : gproj: '/tmp/source/addons/core/addon.gproj' guid: '2528466DE220D91C'
13:14:58.034 ENGINE : gproj: '/tmp/source/addons/intel/addon.gproj' guid: 'D31039C7C53E348A'
13:14:58.034 ENGINE : gproj: '/tmp/source/addons/map/addon.gproj' guid: '9AAEE9041D79809E'
13:14:58.034 ENGINE : gproj: './addons/data/ArmaReforger.gproj' guid: '58D0FB3206B6F859' (packed)
13:14:58.034 ENGINE : gproj: './addons/core/core.gproj' guid: '5614BBCCBB55ED1C' (packed)
13:14:58.034 ENGINE : FileSystem: Adding relative directory '/tmp/source/addons/chat' to filesystem under name ERR_Chat
13:14:58.034 ENGINE : FileSystem: Adding relative directory '/tmp/source/addons/map' to filesystem under name ERR_Map
13:14:58.034 ENGINE : FileSystem: Adding relative directory '/tmp/source/addons/intel' to filesystem under name ERR_Intel
13:14:58.034 ENGINE : FileSystem: Adding relative directory '/tmp/source/addons/ai' to filesystem under name ERR_Ai
13:14:58.034 ENGINE : FileSystem: Adding relative directory '/tmp/source/addons/core' to filesystem under name ERR_Core
13:14:58.123 ENGINE : FileSystem: Adding package './addons/data/' (pak count: 6) to filesystem under name ArmaReforger
13:14:58.123 ENGINE : FileSystem: Adding package './addons/core/' (pak count: 1) to filesystem under name core
13:14:58.123 ENGINE : CPU vendor : AuthenticAMD
13:14:58.123 ENGINE : CPU type : 2
13:14:58.123 ENGINE : CPU name : AMD Ryzen 7 3800XT 8-Core Processor
13:14:58.123 ENGINE : CPU cache info : Unknown cache
13:14:58.123 ENGINE : CPU count logical : 16
13:14:58.123 ENGINE : CPU count physical : 8
13:14:58.123 ENGINE : CPU features : mmx sse sse2 sse3 ssse3 sse4_1 sse4_2 popcnt aesni pclmulqdq osxsave avx sha avx2
13:14:58.123 RESOURCES : ResourceDB: loading cache (id=0 name=ERR_Chat path=/tmp/source/addons/chat/resourceDatabase.rdb)
13:14:58.124 RESOURCES (W): ResourceDB: could not open the cache file '/tmp/source/addons/chat/resourceDatabase.rdb'
13:14:58.124 RESOURCES : ResourceDB: loading cache (id=1 name=ERR_Map path=/tmp/source/addons/map/resourceDatabase.rdb)
13:14:58.124 RESOURCES (W): ResourceDB: could not open the cache file '/tmp/source/addons/map/resourceDatabase.rdb'
13:14:58.124 RESOURCES : ResourceDB: loading cache (id=2 name=ERR_Intel path=/tmp/source/addons/intel/resourceDatabase.rdb)
13:14:58.124 RESOURCES (W): ResourceDB: could not open the cache file '/tmp/source/addons/intel/resourceDatabase.rdb'
13:14:58.124 RESOURCES : ResourceDB: loading cache (id=3 name=ERR_Ai path=/tmp/source/addons/ai/resourceDatabase.rdb)
13:14:58.124 RESOURCES (W): ResourceDB: could not open the cache file '/tmp/source/addons/ai/resourceDatabase.rdb'
13:14:58.124 RESOURCES : ResourceDB: loading cache (id=4 name=ERR_Core path=/tmp/source/addons/core/resourceDatabase.rdb)
13:14:58.124 RESOURCES (W): ResourceDB: could not open the cache file '/tmp/source/addons/core/resourceDatabase.rdb'
13:14:58.124 RESOURCES : ResourceDB: loading cache (id=5 name=ArmaReforger path=./addons/data/resourceDatabase.rdb)
13:14:58.253 RESOURCES : ResourceDB: loading cache (id=6 name=core path=./addons/core/resourceDatabase.rdb)
13:14:58.254 RESOURCES : ResourceDB: loading cache (id=7 name=profile path=/tmp/work/profile/profile/resourceDatabase.rdb)
13:14:58.254 RESOURCES : ResourceDB: loading cache (id=8 name=logs path=/tmp/work/profile/logs/logs_2026-09-19_13-14-58/resourceDatabase.rdb)
13:14:58.394 ENGINE : GameProject load
13:14:58.394 ENGINE : Loaded addons:
13:14:58.394 ENGINE : gproj: './addons/core/core.gproj' guid: '5614BBCCBB55ED1C'
13:14:58.394 ENGINE : gproj: './addons/data/ArmaReforger.gproj' guid: '58D0FB3206B6F859'
13:14:58.394 ENGINE : gproj: '/tmp/source/addons/core/addon.gproj' guid: '2528466DE220D91C'
13:14:58.394 ENGINE : gproj: '/tmp/source/addons/ai/addon.gproj' guid: '5192138CB9D5B911'
13:14:58.394 ENGINE : gproj: '/tmp/source/addons/intel/addon.gproj' guid: 'D31039C7C53E348A'
13:14:58.394 ENGINE : gproj: '/tmp/source/addons/map/addon.gproj' guid: '9AAEE9041D79809E'
13:14:58.394 ENGINE : gproj: '/tmp/source/addons/chat/addon.gproj' guid: 'FA804F411BDDF2C6'
13:14:58.395 ENGINE : Loading custom engine user config '$profile:.save/settings/ReforgerEngineSettings.conf'
13:14:58.395 ENGINE : No Enfusion settings file loaded. Continuing with defaults
13:14:58.395 RENDER : Basic screen setup:
13:14:58.395 RENDER : Back buffer width x height: 0x0
13:14:58.395 RENDER : Back buffer format : TEXFMT_UNKNOWN (0)
13:14:58.395 RENDER : MSAA : none
13:14:58.395 RENDER : GPU_DUMMY (0xFFFF) (Enfusion dummy render) with 1 GPU detected, VRAM 128MB (fast 128MB), shared 128MB, driver version Unknown version
13:14:58.395 RENDER : Detected output devices:
13:14:58.395 RENDER : * 1 device/s connected to Dummy:
13:14:58.395 RENDER : Dummy output (Dummy device for our dummy render.), 1920x1080 (offset 0x0), 60Hz
13:14:58.395 RENDER : Concurrent frames limit 2
13:14:58.395 ENGINE : Job system settings: coreCount=16, shortJobContextCount=16, shortJobThreadCount=15, longJobThreadCount=7
13:14:58.408 PROFILING : Settings changed took: 0.025000 ms
13:14:58.411 ENGINE : Initializing inputs.
13:14:58.419 NETWORK : Initializing networking.
13:14:58.419 NETWORK : Using new GameRplScheduler.
13:14:58.419 SCRIPT : SCRIPT : Initializing scripts
13:14:58.419 SCRIPT : SCRIPT : ScriptProjectManager init
13:14:58.419 PROFILING : ScriptProjectManager init took: 0.009000 ms
13:14:58.440 PROFILING : Initializing scripts took: 20.561000 ms
13:14:58.440 BACKEND : Backend Services are disabled.
13:14:58.440 ENGINE : Enfusion engine successfully created.
13:14:58.442 GUI : Using default language (en_us)
13:14:58.442 GUI : Loading 'en_us' localization file.
13:14:58.442 INIT : INIT : Loading StringTable
13:14:58.463 PROFILING : Loading StringTable took: 20.762000 ms
13:14:58.463 SCRIPT : SCRIPT : Compiling GameLib scripts
13:14:58.496 SCRIPT : Module: GameLib; loaded 465x files; 607x classes; used 654K of static memory; defines: "PLATFORM_LINUX,ENF_ENABLE_VHC_OVERRIDE,ENF_ENABLE_RPLCOMPONENT_IN_GAMECODE"; CRC32: 4507af04
13:14:58.496 PROFILING : Compiling GameLib scripts took: 33.527000 ms
13:14:58.496 SCRIPT : SCRIPT : Compiling Game scripts
13:14:59.284 SCRIPT : Compiling Game scripts
13:14:59.284 SCRIPT (W): @"scripts/Game/Editor/Components/EditableEntity/SCR_EditableEntityComponent.c,785": 'GetPlayerPlatformKind' is obsolete
13:14:59.284 SCRIPT (W): @"scripts/Game/Editor/Components/EditableEntity/SCR_EditableEntityComponent.c,786": 'GetPlayerPlatformId' is obsolete
13:14:59.510 SCRIPT (W): @"scripts/Game/Workshop/SCR_AddonManager.c,876": 'GetRefCount' is obsolete: Will be removed
13:14:59.536 SCRIPT (W): @"scripts/Game/Campaign/SCR_PlayerProfileManagerComponent.c,142": 'PlayerData' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,159": 'GetDSSession' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,162": 'RoomID' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,197": 'GetDSSession' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,201": 'RoomID' is obsolete
13:14:59.706 SCRIPT (W): @"scripts/Game/DataCollection/SCR_DataCollectorComponent.c,481": 'GetStorage' is obsolete
13:14:59.706 SCRIPT (W): @"scripts/Game/DataCollection/SCR_DataCollectorComponent.c,483": 'GetOnlineWritePrivilege' is obsolete
13:14:59.707 SCRIPT (W): @"scripts/Game/Utilities/SCR_PlayerIdentityUtils.c,20": 'GetPlayerIdentityId' is obsolete
13:14:59.744 SCRIPT (W): @"scripts/Game/UI/Menu/MainMenuUI.c,643": 'GetNotifyCount' is obsolete: Will be substitued by Notification Api.
13:14:59.744 SCRIPT (W): @"scripts/Game/UI/Menu/MainMenuUI.c,645": 'GetNotifyItem' is obsolete: Will be substitued by Notification Api.
13:14:59.799 SCRIPT (W): @"scripts/Game/DataCollection/PlayerData/SCR_PlayerData.c,210": 'PlayerData' is obsolete
13:14:59.906 SCRIPT : Module: Game; loaded 5660x files; 11004x classes; used 25508K of static memory; defines: "PLATFORM_LINUX,ENF_ENABLE_VHC_OVERRIDE,ENF_ENABLE_RPLCOMPONENT_IN_GAMECODE,ENABLE_BASE_DESTRUCTION,BUDGET_OPTIMIZATION_CHECKS,DEBUG,RESPAWN_TIMER_COMPONENT_DEBUG,DEBUG_CONFIGURABLE_DIALOGS,DISABLE_WEAPON_SWITCHING,SCRIPTED_AIM_MODIFIER_DEBUG"; CRC32: 931a2d4d
13:14:59.922 PROFILING : Compiling Game scripts took: 1425.231000 ms
13:14:59.922 INIT : Creating game instance(ArmaReforgerScripted), version 1.8.0.13 built 2026-08-21 14:30:18 UTC.
13:14:59.943 ENGINE : Game successfully created.
13:15:00.028 PLATFORM : Save data from container 'settings' have been loaded
13:15:00.127 PLATFORM : Save data from container 'sessions' have been loaded
13:15:00.127 PLATFORM : System language differs from the saved one - using new language {new: 'en_us'; old: ''}
13:15:00.127 PLATFORM : Save data transaction to 'settings' started.
13:15:00.127 NETWORK : Starting local multiplayer server using command line args.
13:15:00.127 RESOURCES : GetResourceObject @"{C1E20667DCE324ED}Missions/ERR_Everon.conf"
13:15:00.127 DEFAULT : Config load @"{C1E20667DCE324ED}Missions/ERR_Everon.conf"
13:15:00.127 RESOURCES (E): Wrong GUID/name for resource @"{B6C516F86302C679}Worlds/ERR_Everon.ent" in property "World"
13:15:00.226 DEFAULT : [SaveGameManager] Starting new playthrough nr.0 '' for mission '{C1E20667DCE324ED}Missions/ERR_Everon.conf'.
13:15:00.227 WORLD : WORLD : Entities load 'Worlds/ERR_Everon.ent'
13:15:00.341 RESOURCES : GetResourceObject @"{58E62C5D17281925}Prefabs/Particles/Blasts/Blast_small_Invisible_Trigger.et"
13:15:00.341 WORLD : Entity prefab load @"{58E62C5D17281925}Prefabs/Particles/Blasts/Blast_small_Invisible_Trigger.et"
13:15:00.341 RESOURCES (E): Wrong GUID/name for resource @"{FF35BE9795BA3797}system/wbdata/Gizmo3D/sphere.xob" in property "Object"
13:15:00.816 WORLD : Entities load 'Worlds/ERR_Everon.ent'
13:15:00.816 WORLD : Subscene load @"{853E92315D1D9EFE}worlds/Eden/Eden.ent"
13:15:00.816 RESOURCES : GetResourceObject @"{C8FAB83670BCF7C3}Prefabs/Structures/Cultural/Calvaries/CalvaryLarge_01.et"
13:15:00.816 WORLD : Entity prefab load @"{C8FAB83670BCF7C3}Prefabs/Structures/Cultural/Calvaries/CalvaryLarge_01.et"
13:15:00.816 RESOURCES (E): Wrong GUID/name for resource @"{45ECF6B1797D3CD9}system/wbdata/PreviewWindow/TransparentMat.emat" in property "AssignedMaterial"
13:15:00.963 WORLD : Entity layer load @"Worlds/ERR_Everon_Layers/default.layer"
13:15:00.963 RESOURCES (E): Wrong GUID/name for resource @"{978E045E53EF36C8}Prefabs/ERR/GameMode.et" in property "inherited-name"
13:15:00.963 WORLD (E): Unknown class 'ERR_GameMode' at offset 73(0x49)
13:15:00.963 RESOURCES (E): Wrong GUID/name for resource @"{5414041A15DC4B54}Prefabs/ERR/FactionManager.et" in property "inherited-name"
13:15:00.963 RESOURCES : GetResourceObject @"{5414041A15DC4B54}Prefabs/ERR/FactionManager.et"
13:15:00.963 WORLD : Entity prefab load @"{5414041A15DC4B54}Prefabs/ERR/FactionManager.et"
13:15:00.963 RESOURCES (E): Wrong GUID/name for resource @"{5C67A1DDE0044806}Configs/ERR/Factions/US.conf" in property "inherited-name"
13:15:00.964 RESOURCES : GetResourceObject @"{5C67A1DDE0044806}Configs/ERR/Factions/US.conf"
13:15:00.964 DEFAULT : Config load @"{5C67A1DDE0044806}Configs/ERR/Factions/US.conf"
13:15:00.964 RESOURCES (E): Wrong GUID/name for resource @"{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf" in property "inherited-name"
13:15:00.965 RESOURCES (E): Wrong GUID/name for resource @"{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf" in property "inherited-name"
13:15:00.965 RESOURCES : GetResourceObject @"{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf"
13:15:00.965 DEFAULT : Config load @"{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf"
13:15:00.965 RESOURCES (E): Wrong GUID/name for resource @"{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf" in property "inherited-name"
13:15:00.966 PROFILING : Entities load took: 739.507000 ms
13:15:00.967 WORLD : WORLD : Game::LoadEntities
13:15:00.967 WORLD : WORLD : Initializing world systems @"{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf"
13:15:00.968 NETWORK : Enabled lag compensation, 29 frames with 100 FPS and 280 ms rewind limits.
13:15:00.969 WORLD : WORLD : CreateEntities
13:15:00.969 WORLD : WORLD : CreateEntities
13:15:00.969 WORLD : WORLD : NotifyBeforeEntitiesCreated
13:15:00.969 WORLD : Game::LoadEntities
13:15:00.969 WORLD : CreateEntities
13:15:00.969 WORLD : CreateEntities
13:15:00.969 WORLD : NotifyBeforeEntitiesCreated
13:15:00.969 RESOURCES : GetResourceObject '$ERR_Chat:Missions/ERR_Everon.conf'
13:15:00.969 DEFAULT : Config load '$ERR_Chat:Missions/ERR_Everon.conf'
13:15:00.969 RESOURCES (E): Wrong GUID/name for resource @"{B6C516F86302C679}Worlds/ERR_Everon.ent" in property "World"
13:15:00.969 RESOURCES : GetResourceObject '$ERR_Core:Missions/ERR_Everon_Base.conf'
13:15:00.969 DEFAULT : Config load '$ERR_Core:Missions/ERR_Everon_Base.conf'
13:15:00.969 RESOURCES (E): Wrong GUID/name for resource @"{B6C516F86302C679}Worlds/ERR_Everon.ent" in property "World"
13:15:00.970 RESOURCES : Loaded mission headers from 7 addon(s)!
13:15:00.970 PROFILING : NotifyBeforeEntitiesCreated took: 1.522000 ms
13:15:00.970 WORLD : WORLD : Wait for preloading data
13:15:02.042 PROFILING : Wait for preloading data took: 1072.274000 ms
13:15:02.105 ENTITY : Create entity @"ENTITY:2" ('Eden', GenericTerrainEntity) at <0.000000 0.000000 0.000000> @"{5AD924374E8C7AF6}Prefabs/World/Game/Eden/GenericTerrain_Eden.et"
13:15:02.105 PROFILING : PROFILING : Terrain load
13:15:03.298 PROFILING : Terrain load took: 1193.704000 ms
13:15:05.817 ENTITY : Create entity @"ENTITY:610406" ('StaticModelEntity','Assets/Structures/Infrastructure/Towers/TransmitterTower_01/TransmitterTower_01_small.xob') at <1931.796021 81.777000 5138.725098> @"{6A004A8F0571D456}Prefabs/Structures/Infrastructure/Towers/TransmitterTower_01/TransmitterTower_01_small.et"
13:15:05.817 ENTITY : CreateComponent 'ScriptedRadioComponent'
13:15:05.817 DEFAULT (W): World doesn't contain RadioManagerEntity to support any BaseRadioComponent.
13:15:07.939 ENTITY : Create entity @"ENTITY:1104061" ('StaticModelEntity') at <8831.291016 95.934265 2673.487061>
13:15:07.939 ENTITY : EntityPrefabUse @"{C8FAB83670BCF7C3}Prefabs/Structures/Cultural/Calvaries/CalvaryLarge_01.et"
13:15:07.939 RESOURCES : CreateComponentData @"{904345715F67BBAC}Assets/Structures/Cultural/Calvaries/CalvaryLarge_01/CalvaryLarge_01.xob"
13:15:07.939 MATERIAL : Material load @"{45ECF6B1797D3CD9}system/wbdata/PreviewWindow/TransparentMat.emat"
13:15:07.939 MATERIAL (E): Material file not found
13:15:07.939 MATERIAL (E): Cannot load material @"system/wbdata/PreviewWindow/TransparentMat.emat", creating default one (class MatPBRBasic)
13:15:10.631 WORLD : WORLD : NotifyBeforeEntitiesInitialized
13:15:10.631 PROFILING : NotifyBeforeEntitiesInitialized took: 0.034000 ms
13:15:10.631 WORLD : InitEntities 'world'
13:15:10.631 ENTITY : Init entity @"ENTITY:3" ('SCR_MapEntity1', SCR_MapEntity) at <6400.000000 0.000000 6400.000000> @"{E1B88C66BF6CA1F9}Prefabs/World/Game/Eden/MapEntity_Everon.et"
13:15:10.631 SCRIPT (W): SCR_MapEntity: Cannot get the size from terrain. Using default.
13:15:11.148 ENTITY : Init entity @"ENTITY:2305843009213693955" ('Factions', SCR_FactionManager) at <0.000000 0.000000 0.000000> @"{5414041A15DC4B54}Prefabs/ERR/FactionManager.et"
13:15:11.148 RPL (E): Attempting to insert an entity with invalid prefab resource GUID @"{5414041A15DC4B54}".
13:15:11.149 SCRIPT (E): Virtual Machine Exception
Reason: NULL pointer to instance. Variable 'm_OnPlayerDisconnected'
Class: 'SCR_FactionManager'
Entity name:'Factions' id:2305843009213693955
Function: 'EOnInit'
Stack trace:
scripts/Game/GameMode/FactionManager/SCR_FactionManager.c:439 Function EOnInit
13:15:11.149 WORLD : WORLD : NotifyEntitiesInitialized
13:15:11.149 TERRAIN : TERRAIN : Initializing road networks...
13:15:11.151 PROFILING : Initializing road networks... took: 1.845000 ms
13:15:11.151 DEFAULT : DEFAULT : WorldVolumesCollection
13:15:11.207 PROFILING : WorldVolumesCollection took: 56.663000 ms
13:15:11.207 INIT : INIT : SoundWorldInit
13:15:11.215 PROFILING : SoundWorldInit took: 7.453000 ms
13:15:11.215 PROFILING : NotifyEntitiesInitialized took: 66.060000 ms
13:15:11.215 PROFILING : CreateEntities took: 10246.394000 ms
13:15:11.215 PROFILING : CreateEntities took: 10246.400000 ms
13:15:11.215 WORLD : WORLD : EOnActivate
13:15:11.248 PROFILING : EOnActivate took: 33.366000 ms
13:15:11.248 PROFILING : Game::LoadEntities took: 10280.968000 ms
13:15:11.365 NETWORK : Starting RPL server, listening on address 0.0.0.0:2001, fastValidation=false
13:15:11.841 DEFAULT : Entered online game state.
13:15:11.842 PROFILING : Settings changed took: 0.491000 ms
13:15:11.842 DEFAULT (W): No GameMode present in the world, using fallback logic!
13:15:11.842 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!
13:15:11.842 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:16.854 WORLD : UpdateEntities
13:15:16.854 WORLD : Frame
13:15:16.854 DEFAULT : FPS: 29.7, frame time (avg: 33.6 ms, min: 23.4 ms, max: 96.0 ms, mean: 33.3 ms, median: 33.5 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:15:21.854 WORLD : UpdateEntities
13:15:21.854 WORLD : Frame
13:15:21.854 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 31.0 ms, max: 35.2 ms, mean: 33.3 ms, median: 33.4 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:15:26.887 WORLD : UpdateEntities
13:15:26.887 WORLD : Frame
13:15:26.887 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 32.4 ms, max: 34.3 ms, mean: 33.3 ms, median: 33.4 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:15:31.920 WORLD : UpdateEntities
13:15:31.920 WORLD : Frame
13:15:31.920 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 30.8 ms, max: 36.3 ms, mean: 33.3 ms, median: 33.4 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:15:36.954 WORLD : UpdateEntities
13:15:36.954 WORLD : Frame
13:15:36.954 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 32.2 ms, max: 34.3 ms, mean: 33.3 ms, median: 33.4 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:15:41.987 WORLD : UpdateEntities
13:15:41.987 WORLD : Frame
13:15:41.987 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 32.4 ms, max: 34.3 ms, mean: 33.3 ms, median: 33.4 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:15:46.987 WORLD : UpdateEntities
13:15:46.987 WORLD : Frame
13:15:46.987 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 32.4 ms, max: 34.2 ms, mean: 33.3 ms, median: 33.4 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:15:52.021 WORLD : UpdateEntities
13:15:52.021 WORLD : Frame
13:15:52.021 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 32.3 ms, max: 34.4 ms, mean: 33.3 ms, median: 33.4 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:15:57.053 WORLD : UpdateEntities
13:15:57.053 WORLD : Frame
13:15:57.053 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 31.0 ms, max: 35.3 ms, mean: 33.3 ms, median: 33.5 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:16:02.054 WORLD : UpdateEntities
13:16:02.054 WORLD : Frame
13:16:02.054 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 31.5 ms, max: 34.6 ms, mean: 33.3 ms, median: 33.5 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:16:07.087 WORLD : UpdateEntities
13:16:07.087 WORLD : Frame
13:16:07.087 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 31.7 ms, max: 34.6 ms, mean: 33.3 ms, median: 33.5 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:16:12.120 WORLD : UpdateEntities
13:16:12.120 WORLD : Frame
13:16:12.120 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 32.4 ms, max: 34.7 ms, mean: 33.3 ms, median: 33.5 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:16:17.121 WORLD : UpdateEntities
13:16:17.121 WORLD : Frame
13:16:17.121 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 32.4 ms, max: 34.4 ms, mean: 33.3 ms, median: 33.5 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:16:22.154 WORLD : UpdateEntities
13:16:22.154 WORLD : Frame
13:16:22.154 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 32.4 ms, max: 33.9 ms, mean: 33.3 ms, median: 33.5 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
13:16:27.187 WORLD : UpdateEntities
13:16:27.187 WORLD : Frame
13:16:27.187 DEFAULT : FPS: 30.0, frame time (avg: 33.3 ms, min: 32.3 ms, max: 34.0 ms, mean: 33.3 ms, median: 33.5 ms), Mem: 985260 kB, Player: 0, AI: 0, AIChar: 0, Veh: 0 (0), Proj (S: 0, M: 0, G: 0 | 0), Streaming(Dynam: 509, Static: 57589, Disabled: 16 | New: 0, Del: 0, Bump: 16)
@@ -0,0 +1,20 @@
---------------------------------------------
Log /tmp/work/profile/logs/logs_2026-09-19_13-14-58/crash.log started at 2026-09-19 13:15:11 (2026-09-19 11:15:11 UTC)
------------------------------------
19.09 2026 13:15:11
Virtual Machine Exception
Reason: NULL pointer to instance. Variable 'm_OnPlayerDisconnected'
Class: 'SCR_FactionManager'
Entity name:'Factions' id:2305843009213693955
Function: 'EOnInit'
Stack trace:
scripts/Game/GameMode/FactionManager/SCR_FactionManager.c:439 Function EOnInit
Runtime mode
CLI params: profile /tmp/work/profile noBackend noSound noSplash noThrow bindIP 127.0.0.1 bindPort 2001 maxFPS 30 logStats 5000 addonsDir /tmp/source/addons addons FA804F411BDDF2C6 server {C1E20667DCE324ED}Missions/ERR_Everon.conf
@@ -0,0 +1,137 @@
---------------------------------------------
Log /tmp/work/profile/logs/logs_2026-09-19_13-14-58/error.log started at 2026-09-19 13:14:58 (2026-09-19 11:14:58 UTC)
13:14:58.124 RESOURCES (W): ResourceDB: could not open the cache file '/tmp/source/addons/chat/resourceDatabase.rdb'
13:14:58.124 RESOURCES (W): ResourceDB: could not open the cache file '/tmp/source/addons/map/resourceDatabase.rdb'
13:14:58.124 RESOURCES (W): ResourceDB: could not open the cache file '/tmp/source/addons/intel/resourceDatabase.rdb'
13:14:58.124 RESOURCES (W): ResourceDB: could not open the cache file '/tmp/source/addons/ai/resourceDatabase.rdb'
13:14:58.124 RESOURCES (W): ResourceDB: could not open the cache file '/tmp/source/addons/core/resourceDatabase.rdb'
13:14:59.284 SCRIPT : Compiling Game scripts
13:14:59.284 SCRIPT (W): @"scripts/Game/Editor/Components/EditableEntity/SCR_EditableEntityComponent.c,785": 'GetPlayerPlatformKind' is obsolete
13:14:59.284 SCRIPT (W): @"scripts/Game/Editor/Components/EditableEntity/SCR_EditableEntityComponent.c,786": 'GetPlayerPlatformId' is obsolete
13:14:59.510 SCRIPT (W): @"scripts/Game/Workshop/SCR_AddonManager.c,876": 'GetRefCount' is obsolete: Will be removed
13:14:59.536 SCRIPT (W): @"scripts/Game/Campaign/SCR_PlayerProfileManagerComponent.c,142": 'PlayerData' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,159": 'GetDSSession' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,162": 'RoomID' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,197": 'GetDSSession' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,201": 'RoomID' is obsolete
13:14:59.705 SCRIPT (W): @"scripts/Game/DataCollection/SCR_DataCollectorComponent.c,481": 'GetStorage' is obsolete
13:14:59.706 SCRIPT (W): @"scripts/Game/DataCollection/SCR_DataCollectorComponent.c,483": 'GetOnlineWritePrivilege' is obsolete
13:14:59.707 SCRIPT (W): @"scripts/Game/Utilities/SCR_PlayerIdentityUtils.c,20": 'GetPlayerIdentityId' is obsolete
13:14:59.744 SCRIPT (W): @"scripts/Game/UI/Menu/MainMenuUI.c,643": 'GetNotifyCount' is obsolete: Will be substitued by Notification Api.
13:14:59.744 SCRIPT (W): @"scripts/Game/UI/Menu/MainMenuUI.c,645": 'GetNotifyItem' is obsolete: Will be substitued by Notification Api.
13:14:59.799 SCRIPT (W): @"scripts/Game/DataCollection/PlayerData/SCR_PlayerData.c,210": 'PlayerData' is obsolete
13:15:00.127 RESOURCES : GetResourceObject @"{C1E20667DCE324ED}Missions/ERR_Everon.conf"
13:15:00.127 DEFAULT : Config load @"{C1E20667DCE324ED}Missions/ERR_Everon.conf"
13:15:00.127 RESOURCES (E): Wrong GUID/name for resource @"{B6C516F86302C679}Worlds/ERR_Everon.ent" in property "World"
13:15:00.341 RESOURCES : GetResourceObject @"{58E62C5D17281925}Prefabs/Particles/Blasts/Blast_small_Invisible_Trigger.et"
13:15:00.341 WORLD : Entity prefab load @"{58E62C5D17281925}Prefabs/Particles/Blasts/Blast_small_Invisible_Trigger.et"
13:15:00.341 RESOURCES (E): Wrong GUID/name for resource @"{FF35BE9795BA3797}system/wbdata/Gizmo3D/sphere.xob" in property "Object"
13:15:00.816 WORLD : Entities load 'Worlds/ERR_Everon.ent'
13:15:00.816 WORLD : Subscene load @"{853E92315D1D9EFE}worlds/Eden/Eden.ent"
13:15:00.816 RESOURCES : GetResourceObject @"{C8FAB83670BCF7C3}Prefabs/Structures/Cultural/Calvaries/CalvaryLarge_01.et"
13:15:00.816 WORLD : Entity prefab load @"{C8FAB83670BCF7C3}Prefabs/Structures/Cultural/Calvaries/CalvaryLarge_01.et"
13:15:00.816 RESOURCES (E): Wrong GUID/name for resource @"{45ECF6B1797D3CD9}system/wbdata/PreviewWindow/TransparentMat.emat" in property "AssignedMaterial"
13:15:00.963 WORLD : Entity layer load @"Worlds/ERR_Everon_Layers/default.layer"
13:15:00.963 RESOURCES (E): Wrong GUID/name for resource @"{978E045E53EF36C8}Prefabs/ERR/GameMode.et" in property "inherited-name"
13:15:00.963 WORLD (E): Unknown class 'ERR_GameMode' at offset 73(0x49)
13:15:00.963 RESOURCES (E): Wrong GUID/name for resource @"{5414041A15DC4B54}Prefabs/ERR/FactionManager.et" in property "inherited-name"
13:15:00.963 RESOURCES : GetResourceObject @"{5414041A15DC4B54}Prefabs/ERR/FactionManager.et"
13:15:00.963 WORLD : Entity prefab load @"{5414041A15DC4B54}Prefabs/ERR/FactionManager.et"
13:15:00.963 RESOURCES (E): Wrong GUID/name for resource @"{5C67A1DDE0044806}Configs/ERR/Factions/US.conf" in property "inherited-name"
13:15:00.964 RESOURCES : GetResourceObject @"{5C67A1DDE0044806}Configs/ERR/Factions/US.conf"
13:15:00.964 DEFAULT : Config load @"{5C67A1DDE0044806}Configs/ERR/Factions/US.conf"
13:15:00.964 RESOURCES (E): Wrong GUID/name for resource @"{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf" in property "inherited-name"
13:15:00.965 RESOURCES (E): Wrong GUID/name for resource @"{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf" in property "inherited-name"
13:15:00.965 RESOURCES : GetResourceObject @"{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf"
13:15:00.965 DEFAULT : Config load @"{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf"
13:15:00.965 RESOURCES (E): Wrong GUID/name for resource @"{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf" in property "inherited-name"
13:15:00.969 WORLD : Game::LoadEntities
13:15:00.969 WORLD : CreateEntities
13:15:00.969 WORLD : CreateEntities
13:15:00.969 WORLD : NotifyBeforeEntitiesCreated
13:15:00.969 RESOURCES : GetResourceObject '$ERR_Chat:Missions/ERR_Everon.conf'
13:15:00.969 DEFAULT : Config load '$ERR_Chat:Missions/ERR_Everon.conf'
13:15:00.969 RESOURCES (E): Wrong GUID/name for resource @"{B6C516F86302C679}Worlds/ERR_Everon.ent" in property "World"
13:15:00.969 RESOURCES : GetResourceObject '$ERR_Core:Missions/ERR_Everon_Base.conf'
13:15:00.969 DEFAULT : Config load '$ERR_Core:Missions/ERR_Everon_Base.conf'
13:15:00.969 RESOURCES (E): Wrong GUID/name for resource @"{B6C516F86302C679}Worlds/ERR_Everon.ent" in property "World"
13:15:05.817 ENTITY : Create entity @"ENTITY:610406" ('StaticModelEntity','Assets/Structures/Infrastructure/Towers/TransmitterTower_01/TransmitterTower_01_small.xob') at <1931.796021 81.777000 5138.725098> @"{6A004A8F0571D456}Prefabs/Structures/Infrastructure/Towers/TransmitterTower_01/TransmitterTower_01_small.et"
13:15:05.817 ENTITY : CreateComponent 'ScriptedRadioComponent'
13:15:05.817 DEFAULT (W): World doesn't contain RadioManagerEntity to support any BaseRadioComponent.
13:15:07.939 ENTITY : Create entity @"ENTITY:1104061" ('StaticModelEntity') at <8831.291016 95.934265 2673.487061>
13:15:07.939 ENTITY : EntityPrefabUse @"{C8FAB83670BCF7C3}Prefabs/Structures/Cultural/Calvaries/CalvaryLarge_01.et"
13:15:07.939 RESOURCES : CreateComponentData @"{904345715F67BBAC}Assets/Structures/Cultural/Calvaries/CalvaryLarge_01/CalvaryLarge_01.xob"
13:15:07.939 MATERIAL : Material load @"{45ECF6B1797D3CD9}system/wbdata/PreviewWindow/TransparentMat.emat"
13:15:07.939 MATERIAL (E): Material file not found
13:15:07.939 MATERIAL (E): Cannot load material @"system/wbdata/PreviewWindow/TransparentMat.emat", creating default one (class MatPBRBasic)
13:15:10.631 WORLD : InitEntities 'world'
13:15:10.631 ENTITY : Init entity @"ENTITY:3" ('SCR_MapEntity1', SCR_MapEntity) at <6400.000000 0.000000 6400.000000> @"{E1B88C66BF6CA1F9}Prefabs/World/Game/Eden/MapEntity_Everon.et"
13:15:10.631 SCRIPT (W): SCR_MapEntity: Cannot get the size from terrain. Using default.
13:15:11.148 ENTITY : Init entity @"ENTITY:2305843009213693955" ('Factions', SCR_FactionManager) at <0.000000 0.000000 0.000000> @"{5414041A15DC4B54}Prefabs/ERR/FactionManager.et"
13:15:11.148 RPL (E): Attempting to insert an entity with invalid prefab resource GUID @"{5414041A15DC4B54}".
13:15:11.149 SCRIPT (E): Virtual Machine Exception
Reason: NULL pointer to instance. Variable 'm_OnPlayerDisconnected'
Class: 'SCR_FactionManager'
Entity name:'Factions' id:2305843009213693955
Function: 'EOnInit'
Stack trace:
scripts/Game/GameMode/FactionManager/SCR_FactionManager.c:439 Function EOnInit
13:15:11.842 DEFAULT (W): No GameMode present in the world, using fallback logic!
13:15:11.842 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!
13:15:11.842 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
@@ -0,0 +1,89 @@
---------------------------------------------
Log /tmp/work/profile/logs/logs_2026-09-19_13-14-58/script.log started at 2026-09-19 13:14:58 (2026-09-19 11:14:58 UTC)
13:14:58.419 SCRIPT : SCRIPT : Initializing scripts
13:14:58.419 SCRIPT : SCRIPT : ScriptProjectManager init
13:14:58.463 SCRIPT : SCRIPT : Compiling GameLib scripts
13:14:58.496 SCRIPT : Module: GameLib; loaded 465x files; 607x classes; used 654K of static memory; defines: "PLATFORM_LINUX,ENF_ENABLE_VHC_OVERRIDE,ENF_ENABLE_RPLCOMPONENT_IN_GAMECODE"; CRC32: 4507af04
13:14:58.496 SCRIPT : SCRIPT : Compiling Game scripts
13:14:59.284 SCRIPT : Compiling Game scripts
13:14:59.284 SCRIPT (W): @"scripts/Game/Editor/Components/EditableEntity/SCR_EditableEntityComponent.c,785": 'GetPlayerPlatformKind' is obsolete
13:14:59.284 SCRIPT (W): @"scripts/Game/Editor/Components/EditableEntity/SCR_EditableEntityComponent.c,786": 'GetPlayerPlatformId' is obsolete
13:14:59.510 SCRIPT (W): @"scripts/Game/Workshop/SCR_AddonManager.c,876": 'GetRefCount' is obsolete: Will be removed
13:14:59.536 SCRIPT (W): @"scripts/Game/Campaign/SCR_PlayerProfileManagerComponent.c,142": 'PlayerData' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,159": 'GetDSSession' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,162": 'RoomID' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,197": 'GetDSSession' is obsolete
13:14:59.624 SCRIPT (W): @"scripts/Game/UI/Backend/CareerBackend.c,201": 'RoomID' is obsolete
13:14:59.706 SCRIPT (W): @"scripts/Game/DataCollection/SCR_DataCollectorComponent.c,481": 'GetStorage' is obsolete
13:14:59.706 SCRIPT (W): @"scripts/Game/DataCollection/SCR_DataCollectorComponent.c,483": 'GetOnlineWritePrivilege' is obsolete
13:14:59.707 SCRIPT (W): @"scripts/Game/Utilities/SCR_PlayerIdentityUtils.c,20": 'GetPlayerIdentityId' is obsolete
13:14:59.744 SCRIPT (W): @"scripts/Game/UI/Menu/MainMenuUI.c,643": 'GetNotifyCount' is obsolete: Will be substitued by Notification Api.
13:14:59.744 SCRIPT (W): @"scripts/Game/UI/Menu/MainMenuUI.c,645": 'GetNotifyItem' is obsolete: Will be substitued by Notification Api.
13:14:59.799 SCRIPT (W): @"scripts/Game/DataCollection/PlayerData/SCR_PlayerData.c,210": 'PlayerData' is obsolete
13:14:59.906 SCRIPT : Module: Game; loaded 5660x files; 11004x classes; used 25508K of static memory; defines: "PLATFORM_LINUX,ENF_ENABLE_VHC_OVERRIDE,ENF_ENABLE_RPLCOMPONENT_IN_GAMECODE,ENABLE_BASE_DESTRUCTION,BUDGET_OPTIMIZATION_CHECKS,DEBUG,RESPAWN_TIMER_COMPONENT_DEBUG,DEBUG_CONFIGURABLE_DIALOGS,DISABLE_WEAPON_SWITCHING,SCRIPTED_AIM_MODIFIER_DEBUG"; CRC32: 931a2d4d
13:15:10.631 SCRIPT (W): SCR_MapEntity: Cannot get the size from terrain. Using default.
13:15:11.149 SCRIPT (E): Virtual Machine Exception
Reason: NULL pointer to instance. Variable 'm_OnPlayerDisconnected'
Class: 'SCR_FactionManager'
Entity name:'Factions' id:2305843009213693955
Function: 'EOnInit'
Stack trace:
scripts/Game/GameMode/FactionManager/SCR_FactionManager.c:439 Function EOnInit
13:15:11.842 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!
13:15:11.842 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
13:15:11.847 SCRIPT (W): 'SCR_BaseSupportStationComponent' Support station on entity that exists in world when loaded has range but no 'SCR_SupportStationManagerComponent' availible on GameMode. So it won't work!
@@ -0,0 +1,96 @@
EngineUserSettings {
GameProjectUserSettings GameProjectUserSettings {
}
GraphicsQualitySettings GraphicsQualitySettings {
}
GrassMaterialSettings GrassMaterialSettings {
}
InputDeviceUserSettings InputDeviceUserSettings {
GamepadProfile InputProfileGamepad "{6A66B3BA49B3DB26}" {
LeftThumbHorizontal InputProfileKey "{6A66B3BA49B3DB20}" {
Transition CurveCubicSplineFloat "{6A66B3BA49B3DB21}" {
}
}
LeftThumbVertical InputProfileKey "{6A66B3BA49B3DB23}" {
Transition CurveCubicSplineFloat "{6A66B3BA49B228E5}" {
}
}
RightThumbHorizontal InputProfileKey "{6A66B3BA49B254AB}" {
Transition CurveCubicSplineFloat "{6A66B3BA49B254A4}" {
}
}
RightThumbVertical InputProfileKey "{6A66B3BA49B2C432}" {
Transition CurveCubicSplineFloat "{6A66B3BA49AD465D}" {
}
}
LeftTrigger InputProfileKey "{6A66B3BA49AD5EA0}" {
Transition CurveCubicSplineFloat "{6A66B3BA49AD9B6F}" {
}
}
RightTrigger InputProfileKey "{6A66B3BA49AD9B6E}" {
Transition CurveCubicSplineFloat "{6A66B3BA49ADFE10}" {
}
}
}
JoystickProfile InputProfileJoystick "{6A66B3BA49ADFE13}" {
Axis00 InputProfileKey "{6A66B3BA49AC2CF5}" {
Transition CurveCubicSplineFloat "{6A66B3BA49AC7F87}" {
}
}
Axis01 InputProfileKey "{6A66B3BA49AC9E02}" {
Transition CurveCubicSplineFloat "{6A66B3BA49ACC774}" {
}
}
Axis02 InputProfileKey "{6A66B3BA49ACC775}" {
Transition CurveCubicSplineFloat "{6A66B3BA49ACF67B}" {
}
}
Axis03 InputProfileKey "{6A66B3BA49AF233F}" {
Transition CurveCubicSplineFloat "{6A66B3BA49AF66E8}" {
}
}
Axis04 InputProfileKey "{6A66B3BA49AFBED3}" {
Transition CurveCubicSplineFloat "{6A66B3BA49AFEF1C}" {
}
}
Axis05 InputProfileKey "{6A66B3BA49AE34D2}" {
Transition CurveCubicSplineFloat "{6A66B3BA49AE6934}" {
}
}
Axis06 InputProfileKey "{6A66B3BA49AE6933}" {
Transition CurveCubicSplineFloat "{6A66B3BA49AE99CD}" {
}
}
Axis07 InputProfileKey "{6A66B3BA49AEBC2D}" {
Transition CurveCubicSplineFloat "{6A66B3BA49AEC52D}" {
}
}
Axis08 InputProfileKey "{6A66B3BA49AEC514}" {
Transition CurveCubicSplineFloat "{6A66B3BA49AED910}" {
}
}
Axis09 InputProfileKey "{6A66B3BA49AEF812}" {
Transition CurveCubicSplineFloat "{6A66B3BA49A97831}" {
}
}
}
}
MaterialSystemUserSettings MaterialSystemUserSettings {
}
PostprocessUserSettings PostprocessUserSettings {
}
ResourceManagerUserSettings ResourceManagerUserSettings {
}
TerrainGenMaterialSettings TerrainGenMaterialSettings {
}
UserInterfaceSettings UserInterfaceSettings {
LanguageCode "en_us"
SystemLanguageCode "en_us"
}
VideoUserSettings VideoUserSettings {
WindowPosX -640
WindowPosY -360
}
WaterPoolMaterialSettings WaterPoolMaterialSettings {
}
}
@@ -0,0 +1,119 @@
{
"command": [
"bwrap",
"--unshare-net",
"--unshare-pid",
"--unshare-ipc",
"--unshare-uts",
"--die-with-parent",
"--cap-drop",
"ALL",
"--clearenv",
"--ro-bind",
"/",
"/",
"--tmpfs",
"/home",
"--tmpfs",
"/tmp",
"--tmpfs",
"/run",
"--proc",
"/proc",
"--dev",
"/dev",
"--ro-bind",
"/tmp/erri-dedicated-runtime/server",
"/tmp/runtime",
"--bind",
"/tmp/erri-dedicated-chat-20260919-1314/source",
"/tmp/source",
"--bind",
"/tmp/erri-dedicated-chat-20260919-1314",
"/tmp/work",
"--setenv",
"HOME",
"/tmp/work/home",
"--setenv",
"TMPDIR",
"/tmp/work/tmp",
"--setenv",
"XDG_CONFIG_HOME",
"/tmp/work/home/.config",
"--setenv",
"XDG_DATA_HOME",
"/tmp/work/home/.local/share",
"--setenv",
"XDG_CACHE_HOME",
"/tmp/work/home/.cache",
"--setenv",
"PATH",
"/usr/bin:/bin",
"--setenv",
"LANG",
"C.UTF-8",
"--chdir",
"/tmp/runtime",
"/usr/bin/python3",
"-c",
"import json,os,socket,sys\nstate={\"netns\":os.readlink(\"/proc/self/ns/net\"),\"interfaces\":socket.if_nameindex(),\"routes\":open(\"/proc/net/route\").read(),\"ipv6Routes\":open(\"/proc/net/ipv6_route\").read()}\nprint(\"ERR ISOLATION \"+json.dumps(state),flush=True)\nassert state[\"netns\"] != sys.argv[1], \"network namespace was not isolated\"\nassert [name for _,name in state[\"interfaces\"]] == [\"lo\"], \"non-loopback interface present\"\nassert len(state[\"routes\"].splitlines()) == 1, \"IPv4 route present\"\nassert all(line.split()[-1] == \"lo\" for line in state[\"ipv6Routes\"].splitlines()), \"non-loopback IPv6 route present\"\nif len(sys.argv)>2: os.execv(sys.argv[2],sys.argv[2:])\n",
"net:[4026531833]",
"/tmp/runtime/ArmaReforgerServer",
"-profile",
"/tmp/work/profile",
"-noBackend",
"-noSound",
"-noSplash",
"-noThrow",
"-bindIP",
"127.0.0.1",
"-bindPort",
"2001",
"-maxFPS",
"30",
"-logStats",
"5000",
"-addonsDir",
"/tmp/source/addons",
"-addons",
"FA804F411BDDF2C6",
"-server",
"{C1E20667DCE324ED}Missions/ERR_Everon.conf"
],
"engine": [
"/tmp/runtime/ArmaReforgerServer",
"-profile",
"/tmp/work/profile",
"-noBackend",
"-noSound",
"-noSplash",
"-noThrow",
"-bindIP",
"127.0.0.1",
"-bindPort",
"2001",
"-maxFPS",
"30",
"-logStats",
"5000",
"-addonsDir",
"/tmp/source/addons",
"-addons",
"FA804F411BDDF2C6",
"-server",
"{C1E20667DCE324ED}Missions/ERR_Everon.conf"
],
"seconds": 90,
"route": "header",
"project": "chat",
"binarySHA256": "91fdf333ec49c266d309be358aabab4244a93a2f9cfa11853a600e026104001f",
"serverLaunched": true,
"realPlayers": 0,
"multiplayerProven": false,
"scope": "successor chat empty-server startup",
"supervisorPid": 3553742,
"startedUTC": "2026-09-19T11:14:57Z",
"timeout": true,
"exit": -15,
"finishedUTC": "2026-09-19T11:16:27Z"
}
@@ -0,0 +1,37 @@
{
"result": "failed-input-missing-resource-databases",
"scope": "Isolated native Linux Chat attempt; no successful successor compilation/preparation, clients or multiplayer proof.",
"source_files": 183,
"source_unchanged": true,
"input_resource_databases": [],
"missing_database_warnings": [
"/tmp/source/addons/chat/resourceDatabase.rdb",
"/tmp/source/addons/map/resourceDatabase.rdb",
"/tmp/source/addons/intel/resourceDatabase.rdb",
"/tmp/source/addons/ai/resourceDatabase.rdb",
"/tmp/source/addons/core/resourceDatabase.rdb"
],
"errors": [
"13:15:00.127 RESOURCES (E): Wrong GUID/name for resource @\"{B6C516F86302C679}Worlds/ERR_Everon.ent\" in property \"World\"",
"13:15:00.341 RESOURCES (E): Wrong GUID/name for resource @\"{FF35BE9795BA3797}system/wbdata/Gizmo3D/sphere.xob\" in property \"Object\"",
"13:15:00.816 RESOURCES (E): Wrong GUID/name for resource @\"{45ECF6B1797D3CD9}system/wbdata/PreviewWindow/TransparentMat.emat\" in property \"AssignedMaterial\"",
"13:15:00.963 RESOURCES (E): Wrong GUID/name for resource @\"{978E045E53EF36C8}Prefabs/ERR/GameMode.et\" in property \"inherited-name\"",
"13:15:00.963 WORLD (E): Unknown class 'ERR_GameMode' at offset 73(0x49)",
"13:15:00.963 RESOURCES (E): Wrong GUID/name for resource @\"{5414041A15DC4B54}Prefabs/ERR/FactionManager.et\" in property \"inherited-name\"",
"13:15:00.963 RESOURCES (E): Wrong GUID/name for resource @\"{5C67A1DDE0044806}Configs/ERR/Factions/US.conf\" in property \"inherited-name\"",
"13:15:00.964 RESOURCES (E): Wrong GUID/name for resource @\"{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf\" in property \"inherited-name\"",
"13:15:00.965 RESOURCES (E): Wrong GUID/name for resource @\"{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf\" in property \"inherited-name\"",
"13:15:00.965 RESOURCES (E): Wrong GUID/name for resource @\"{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf\" in property \"inherited-name\"",
"13:15:00.969 RESOURCES (E): Wrong GUID/name for resource @\"{B6C516F86302C679}Worlds/ERR_Everon.ent\" in property \"World\"",
"13:15:00.969 RESOURCES (E): Wrong GUID/name for resource @\"{B6C516F86302C679}Worlds/ERR_Everon.ent\" in property \"World\"",
"13:15:07.939 MATERIAL (E): Material file not found",
"13:15:07.939 MATERIAL (E): Cannot load material @\"system/wbdata/PreviewWindow/TransparentMat.emat\", creating default one (class MatPBRBasic)",
"13:15:11.148 RPL (E): Attempting to insert an entity with invalid prefab resource GUID @\"{5414041A15DC4B54}\".",
"13:15:11.149 SCRIPT (E): Virtual Machine Exception",
"13:15:11.842 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!",
"13:15:11.842 SCRIPT (E): 'SCR_BaseResupplySupportStationComponent' needs a entity catalog manager!"
],
"raw_console": "profile/logs/logs_2026-09-19_13-14-58/console.log",
"multiplayer_proven": false,
"release_qualified": false
}
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""Bounded Linux server diagnostic: isolated network, frozen sources, no clients."""
import argparse
import fcntl
import hashlib
import json
import os
from pathlib import Path
import shutil
import signal
import subprocess
import time
PROBE = '''import json,os,socket,sys
state={"netns":os.readlink("/proc/self/ns/net"),"interfaces":socket.if_nameindex(),"routes":open("/proc/net/route").read(),"ipv6Routes":open("/proc/net/ipv6_route").read()}
print("ERR ISOLATION "+json.dumps(state),flush=True)
assert state["netns"] != sys.argv[1], "network namespace was not isolated"
assert [name for _,name in state["interfaces"]] == ["lo"], "non-loopback interface present"
assert len(state["routes"].splitlines()) == 1, "IPv4 route present"
assert all(line.split()[-1] == "lo" for line in state["ipv6Routes"].splitlines()), "non-loopback IPv6 route present"
if len(sys.argv)>2: os.execv(sys.argv[2],sys.argv[2:])
'''
def manifest(source):
result = {}
for path in sorted(source.rglob('*')):
if path.is_symlink():
raise ValueError('Linked source entry is not allowed: ' + str(path))
if path.is_file():
result[str(path.relative_to(source))] = hashlib.sha256(path.read_bytes()).hexdigest()
return result
def sandbox(runtime, source, run):
return ['bwrap', '--unshare-net', '--unshare-pid', '--unshare-ipc', '--unshare-uts',
'--die-with-parent', '--cap-drop', 'ALL', '--clearenv',
'--ro-bind', '/', '/', '--tmpfs', '/home', '--tmpfs', '/tmp', '--tmpfs', '/run',
'--proc', '/proc', '--dev', '/dev', '--ro-bind', str(runtime), '/tmp/runtime',
'--bind', str(source), '/tmp/source', '--bind', str(run), '/tmp/work',
'--setenv', 'HOME', '/tmp/work/home', '--setenv', 'TMPDIR', '/tmp/work/tmp',
'--setenv', 'XDG_CONFIG_HOME', '/tmp/work/home/.config',
'--setenv', 'XDG_DATA_HOME', '/tmp/work/home/.local/share',
'--setenv', 'XDG_CACHE_HOME', '/tmp/work/home/.cache',
'--setenv', 'PATH', '/usr/bin:/bin', '--setenv', 'LANG', 'C.UTF-8',
'--chdir', '/tmp/runtime']
PROJECTS = {
'core': ('2528466DE220D91C', '{AD62322D5213C440}Missions/ERR_Everon_Base.conf'),
'chat': ('FA804F411BDDF2C6', '{C1E20667DCE324ED}Missions/ERR_Everon.conf'),
}
WORLD = '{B6C516F86302C679}Worlds/ERR_Everon.ent'
SYSTEMS = '{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf'
def engine_args(route, project='core'):
if route not in ('header', 'diag-world', 'vanilla-everon') or project not in PROJECTS:
raise ValueError('Unsupported diagnostic route or project')
if route == 'vanilla-everon' and project != 'core':
raise ValueError('vanilla-everon cannot be combined with --project chat')
args = ['/tmp/runtime/ArmaReforgerServer' + ('Diag' if route == 'diag-world' else ''),
'-profile', '/tmp/work/profile', '-noBackend', '-noSound', '-noSplash', '-noThrow',
'-bindIP', '127.0.0.1', '-bindPort', '2001', '-maxFPS', '30', '-logStats', '5000']
if route == 'vanilla-everon':
return args + ['-server', 'Missions/21_GM_Eden.conf']
addon, header = PROJECTS[project]
args += ['-addonsDir', '/tmp/source/addons', '-addons', addon]
if route == 'header':
return args + ['-server', header]
return args + ['-server', WORLD, '-worldSystemsConfig', SYSTEMS]
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--source', type=Path, required=True)
parser.add_argument('--runtime', type=Path, default=Path('/tmp/erri-dedicated-runtime/server'))
parser.add_argument('--archive', type=Path, required=True)
parser.add_argument('--route', choices=['header', 'diag-world', 'vanilla-everon'], default='header')
parser.add_argument('--project', choices=['core', 'chat'], default='core',
help='Core base (default) or Chat with all five declared successor addons')
parser.add_argument('--seconds', type=int, default=90)
args = parser.parse_args()
if args.route == 'vanilla-everon' and args.project != 'core':
parser.error('vanilla-everon cannot be combined with --project chat')
if not 1 <= args.seconds <= 110:
parser.error('Runtime bound must be 1..110 seconds (plus at most 10 seconds owned cleanup)')
source, runtime, archive = args.source.resolve(), args.runtime.resolve(), args.archive.resolve()
if not source.is_relative_to('/tmp') or not runtime.is_relative_to('/tmp') or not archive.is_relative_to('/tmp'):
parser.error('Sources, runtime and archive must be isolated /tmp paths')
if archive == source or source in archive.parents or archive in source.parents or archive == runtime or runtime in archive.parents or archive in runtime.parents:
parser.error('Source, archive and runtime must not overlap')
if archive.exists() or archive.is_symlink():
parser.error('Archive must be new')
origin = runtime.parent / 'runtime-origin.json'
if not origin.is_file() or json.loads(origin.read_text()).get('app_id') != 1874900:
parser.error('Stable private runtime origin missing')
binary = runtime / Path(engine_args(args.route, args.project)[0]).name
with binary.open('rb') as stream:
elf = stream.read(20)
if binary.is_symlink() or len(elf) != 20 or elf[:6] != b'\x7fELF\x02\x01' or int.from_bytes(elf[18:20], 'little') != 62:
parser.error('Expected real native x86-64 ELF server')
archive.mkdir(mode=0o700, parents=True)
(archive / 'runner.py').write_bytes(Path(__file__).read_bytes())
for child in ['home', 'profile', 'tmp']:
(archive / child).mkdir()
# Reject links before copytree follows them into the isolated source snapshot.
manifest(source)
frozen = archive / 'source'
shutil.copytree(source, frozen, ignore=shutil.ignore_patterns('.git', '__pycache__', 'evidence', 'generated'))
initial = manifest(frozen)
(archive / 'source-manifest.json').write_text(json.dumps(initial, indent=2)+'\n')
base = sandbox(runtime, frozen, archive)
netns = os.readlink('/proc/self/ns/net')
check = base + ['/usr/bin/python3', '-c', PROBE, netns]
preflight = subprocess.run(check, capture_output=True, text=True, timeout=15)
(archive / 'isolation-preflight.json').write_text(json.dumps({'command':check,'exit':preflight.returncode,'stdout':preflight.stdout,'stderr':preflight.stderr}, indent=2)+'\n')
if preflight.returncode:
print('Isolation refused; no server launched:', archive)
return 2
command = base + ['/usr/bin/python3', '-c', PROBE, netns] + engine_args(args.route, args.project)
record = {'command': command, 'engine': engine_args(args.route, args.project), 'seconds':args.seconds,
'route': args.route, 'project': None if args.route == 'vanilla-everon' else args.project,
'binarySHA256':hashlib.sha256(binary.read_bytes()).hexdigest(),
'serverLaunched':False,'realPlayers':0,'multiplayerProven':False,
'scope':'vanilla Everon control; no successor loaded' if args.route == 'vanilla-everon' else f'successor {args.project} empty-server startup'}
(archive / 'command.json').write_text(json.dumps(record, indent=2)+'\n')
lock_fd = os.open('/tmp/erri-dedicated-native-diagnostic.lock', os.O_WRONLY | os.O_CREAT | os.O_NOFOLLOW, 0o600)
with os.fdopen(lock_fd, 'a') as lock:
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
with (archive / 'stdout.log').open('wb') as log:
process = subprocess.Popen(command, stdout=log, stderr=subprocess.STDOUT, start_new_session=True)
record['serverLaunched'] = True
record['supervisorPid'] = process.pid
record['startedUTC'] = time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime())
try:
record['exit'] = process.wait(timeout=args.seconds)
record['timeout'] = False
except subprocess.TimeoutExpired:
record['timeout'] = True
finally:
if process.poll() is None:
os.killpg(process.pid, signal.SIGTERM)
try: process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait(timeout=5)
record['exit'] = process.returncode
record['finishedUTC'] = time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime())
(archive / 'result.json').write_text(json.dumps(record, indent=2)+'\n')
(archive / 'source-manifest-after.json').write_text(json.dumps(manifest(frozen), indent=2)+'\n')
print('Diagnostic archived; inspect actual compilation/header/systems/errors:',archive)
return 0
if __name__ == '__main__':
raise SystemExit(main())
@@ -0,0 +1,185 @@
{
"addons/ai/Prefabs/ERR/MortarCrew.et": "05d3e7353ff9fe5523218a1487b31b7b742b0be8adbcb060c7099978cf008bce",
"addons/ai/Prefabs/ERR/MortarCrew.et.meta": "fdfd4fcd21a67c3d698fa4aadc4e5c76e665cebf4be86013bc389e493fab63cc",
"addons/ai/Scripts/Game/ERR/ERR_AircraftFlight.c": "96b20b7e78612fb26befa6e26635be80dbf9ca089384db4a7839f5064ab06ccc",
"addons/ai/Scripts/Game/ERR/ERR_AircraftFlightEnvelope.c": "8125cef61fb283d0d7c4dd2df3c04b062b448f1101fd48dd53f4f4097df5fef3",
"addons/ai/Scripts/Game/ERR/ERR_HQ.c": "7ee8e7a3282fd566ec330c94854b353b2c6178e9b964f7b4d5e197e4d0b7dee9",
"addons/ai/Scripts/Game/ERR/ERR_HQJournal.c": "b3e402cfe92d415f37f666045afb062e6e11670ba03a0ca6f2d9fd4fc3520ac1",
"addons/ai/Scripts/Game/ERR/ERR_HQNetwork.c": "92ff2caca52e94c2cc9642dfc8509ce6e9349b257012bade8650bace9231128c",
"addons/ai/Scripts/Game/ERR/ERR_HQObservation.c": "36c978ec5f052213ac1236e1398d85063efbfd7ef60fe04f1fc11447fefe1d68",
"addons/ai/Scripts/Game/ERR/ERR_HQReport.c": "c4d16ccb571db4d9444c0a8d2be7318845d25ff90238b6d304e96a9ea19f1a08",
"addons/ai/Scripts/Game/ERR/ERR_HelicopterAvailability.c": "60eaef2f6d4b2fd44861c5a4434ef97e732e40a5ff7401e2da451ed2dd9c2762",
"addons/ai/Scripts/Game/ERR/ERR_MortarAimSolution.c": "59e8343d09f7a44871d8d3335d6ad1d02216abb5deed30d38b5ec9ba3f2c0649",
"addons/ai/Scripts/Game/ERR/ERR_MortarBattery.c": "a4eed96d17f239cbf353863ebe40d2282aeacd7a4d63a7b2f0ecfb3d93a2e2b2",
"addons/ai/Scripts/Game/ERR/ERR_MortarCourierTransfer.c": "968e58ca700b4a11e5e04a5c159696f514a0b1bce531210172b913e495f7e2b7",
"addons/ai/Scripts/Game/ERR/ERR_MortarFiring.c": "7aec51c83972610d1bcbca58b84b14f16f59952d402b12c3d88e7192a3e2ee90",
"addons/ai/Scripts/Game/ERR/ERR_MortarLogistics.c": "e1ffb25fba384b449de576a5502a93d875c3f52e8da1941bbf590ad9845f9730",
"addons/ai/Scripts/Game/ERR/ERR_MortarPrediction.c": "470dd196ed64800c697d24c09d1344bacd2502587fac76af4ee0edae2563092d",
"addons/ai/Scripts/Game/ERR/ERR_MortarReadiness.c": "2fabe8f812fd333cea7199d1eb722c807fcc5adb285c618956bd54d31fa27ce3",
"addons/ai/Scripts/Game/ERR/ERR_MortarRefresh.c": "52ae8d9d6cad92f42d68c634091b80a474d2666b6c06d63a8c30a3fab71494c4",
"addons/ai/Scripts/Game/ERR/ERR_MortarReportLedger.c": "42a96040b63e56677353e55164ae23031c90f70b4d6673a9f412ad7b19e636ae",
"addons/ai/Scripts/Game/ERR/ERR_MortarSalvo.c": "697c01f479df6c505a41d984a29c02002e3052114dafa2ec6252ae55e2979a2b",
"addons/ai/Scripts/Game/ERR/ERR_MortarSupplyTransfer.c": "34f7eaa01dc68e97aae8c1177b7f8fd6c15d5a41c6dc86de377f00b3613a8e77",
"addons/ai/Scripts/Game/ERR/ERR_MortarSystem.c": "4edefc8530cc6eec7a5e1df1d0b7d56e6801186f23c61331ce0e8075b8281efb",
"addons/ai/Scripts/Game/ERR/ERR_NativeAircraftControlProbe.c": "e501301e683a7e36ad5a0cb56bc87a590c0c9071c9fc5c579502fb1c7a0ff143",
"addons/ai/Scripts/Game/ERR/ERR_NativeMortarCourierProbe.c": "740c8bebbd4128a9311b1167d1058549475d6b46f81c9b6c0d19650f4b50616f",
"addons/ai/Scripts/Game/ERR/ERR_NativeMortarFiringProbe.c": "ee570d21bede8baebefc799399712d9582b3e30ebda7299f6c941777f02ad2a2",
"addons/ai/Scripts/Game/ERR/ERR_NativeMortarSalvoProbe.c": "5fd7bb8953f6850ad79e646c0c286153108a10ee2d5ef397ccb65a07f8d2ed7c",
"addons/ai/Scripts/Game/ERR/ERR_NativeMortarTransferProbe.c": "399c21940ef10777b46fdb3e905430ce43404ce1de2f4a6bb62f9c61c1a0689c",
"addons/ai/Scripts/Game/ERR/ERR_NativePatrolHQProbe.c": "4c04aa3e1b6b000747da8e2a919a6501107880fd041134c019f516665bb6f758",
"addons/ai/Scripts/Game/ERR/ERR_SettlementSafety.c": "e5c0ebc95521a9a33a3f7d9cca6c551dda862a08b7af20b017877aba9584795b",
"addons/ai/Scripts/Game/ERR/ERR_SupportScenario.c": "71757018ec9716c4dc24f62a292a214919d3fd5daed3c16e03e56abd208e9f03",
"addons/ai/addon.gproj": "5f559db8f526700db11e6ae763f5245f19b7006fc68c56e4c09fb6f940bdf2a1",
"addons/chat/Missions/ERR_Everon.conf": "76f3ac0d21b585654beac7ddd68e3c8cbdd66dbfe909b17b04838fa813a7c29b",
"addons/chat/Missions/ERR_Everon.conf.meta": "0a23c98bf83873536c65fa17d38dea33a1d7b9e4ab210f90633ff688abcc551f",
"addons/chat/Scripts/Game/ERR/ERR_AdminCommands.c": "d7b4ed95fc32767911936cb0fcc60763233f2c31afd34a6a0fa998b39cec9f5c",
"addons/chat/Scripts/Game/ERR/ERR_NativeHQResponseProof.c": "0f8b09fd8753234c91fd6baaca0e2f812e409ae1c4be8f340b648ae59b69c2f3",
"addons/chat/Scripts/Game/ERR/ERR_NativeMortarProbe.c": "ddb892141500c192c0787b011d57dd8c07dd8c9a3ca5fdc407f6c271897bde0a",
"addons/chat/Scripts/Game/ERR/ERR_NativeRadioProbe.c": "84524163c63402755b4f834f74dfe6d8e5c397dc26031f532593c6f79f5a93b1",
"addons/chat/Scripts/Game/ERR/ERR_NativeSmoke.c": "32501d011d459e3c2d01077ef3eeb0ce8c364985e1aadfe8e8c022ba4b2054ef",
"addons/chat/Scripts/Game/ERRChat/ERR_OrdinaryScenarioLaunchChat.c": "dda715c9691d366e35fa2ad5e390dee1971651f137a5a9f9aeb23dcbe53ec87e",
"addons/chat/Scripts/WorkbenchGame/ERR/ERR_ValidateWorldPlugin.c": "81c92d408ce88cc9f98d6053f1d0c863427693ba748f00df9f1bf8079015ef94",
"addons/chat/addon.gproj": "ea1587e0174cf795334d306c71d99b06797fc891427cd57b6cab6e21cf0e4e0e",
"addons/core/Configs/ERR/EveronSites.conf": "ade92cc8d2b14a84ac378bbbb52608b351dca65d359dd7a315d64a145fa4b46f",
"addons/core/Configs/ERR/EveronSites.conf.meta": "d5fe68025ef4e7c1e16082cf5873b7c7172eacbfaef4ab29b7e0e3c98f8dca56",
"addons/core/Configs/ERR/Factions/MissionVehicles.conf": "5f74f4a7089c7e7b54ad62463159ce8f0bab25a6b85268dc3872ea6001e048fb",
"addons/core/Configs/ERR/Factions/MissionVehicles.conf.meta": "00c368b6271d3440cc8bf627b03a384d247635723923fc5ad2104b8b4007b035",
"addons/core/Configs/ERR/Factions/US.conf": "4c3da063abb6e3ee39a6e4e0c5a9ffd5eae9d209d58470ea18b45dba5528db1b",
"addons/core/Configs/ERR/Factions/US.conf.meta": "a05469aef028e9e17eba545e81e2af2887b59f0fef17dac51a23c7b8155e381b",
"addons/core/Configs/ERR/Factions/USSR.conf": "407ea27f80a64c5184c089d43cdd0bdabfc5974c84f1d2a245081fdf2afa3f30",
"addons/core/Configs/ERR/Factions/USSR.conf.meta": "057397d97096346791b51b7b13fbe18af218bcf78e9984d9c01b49a2403de611",
"addons/core/Configs/ERR/GameOverScreens.conf": "45231061911a1462d45fb6db0fecc2b0793f17b5ae71017174954ff4fbd20236",
"addons/core/Configs/ERR/GameOverScreens.conf.meta": "eb654b745c0b97b41c991694569ef4877a3c2315e0826f9556337cd68690843e",
"addons/core/Configs/ERR/VanillaFactions.conf": "db890b61b48ee15484041fd2245a862d9fa9e7ba21b1229c281ff37c448a35b1",
"addons/core/Configs/ERR/VanillaFactions.conf.meta": "ac77725c0c21974991ef47a466b88122e939de8b46fd23e68889e7674cbb2bfd",
"addons/core/Missions/ERR_Everon_Base.conf": "1e6a0e9a756d15495201f71121626890f01efe6b00154c561672d4ec157960bd",
"addons/core/Missions/ERR_Everon_Base.conf.meta": "9b16267c644d815bcf98a3a3aaff30b93d7224fb24d3a2bac66730a28011f317",
"addons/core/Prefabs/ERR/Extraction.et": "1aa286ef253c5410f09fa641e4be91a7be8ec834487a7fbb74cfb45cab60ee1e",
"addons/core/Prefabs/ERR/Extraction.et.meta": "c2a838633001e2e3a202d39cb33aa744ebcd996017ad032359069641b5d13853",
"addons/core/Prefabs/ERR/FactionManager.et": "425f916b890019c537f6b2f72b1af9e7be54b0a476320da969e1f313a7ac5ea5",
"addons/core/Prefabs/ERR/FactionManager.et.meta": "9bf53d5e4bcbe60dd1a4ece1409fc23c349055c860708620f5f6e9716290a97d",
"addons/core/Prefabs/ERR/FieldCover.et": "20f8095e69dbfe3db74ff19078e528ea75405362b335e03560ccd61f8df2e7f3",
"addons/core/Prefabs/ERR/FieldCover.et.meta": "83461ffa76244e1d69f20b053a9741ccdf7d127009182bd540a7e5ba6150c3d9",
"addons/core/Prefabs/ERR/GameMode.et": "385c97a5b8067091d92aa60c2d4fcf02002cf606b95523866ead65c96b8c0029",
"addons/core/Prefabs/ERR/GameMode.et.meta": "106769cd34525c9d440057072836cb7401a6752148c40edd7fe61c03bd58328c",
"addons/core/Prefabs/ERR/ObjectiveDocument.et": "2e72270bf186654af12d2c3ff9bf940690b03fb17fca44f873c1fde5d11384c0",
"addons/core/Prefabs/ERR/ObjectiveDocument.et.meta": "eaba9903be147b98a1106e2273e2270773df73b9b22f2cd4e20136a8e281382e",
"addons/core/Prefabs/ERR/Patrol.et": "c5685a8bc311cc5563fb3334beec9408d3d7a1069152fddeeb7df4f1b7f9e6ce",
"addons/core/Prefabs/ERR/Patrol.et.meta": "7c5da568fa2fcf0709ee2959253cb068d7c7ed7c2467af3b247a7e70efd96b69",
"addons/core/Prefabs/ERR/Prison.et": "dcd3f4dfd453303d026e68b64f7de6125bd3390822d8432dd95a3710a5b3df89",
"addons/core/Prefabs/ERR/Prison.et.meta": "2e426ef3de68f4107907854b51e818bc11d84649a01c538e3f18dd9ebec766bb",
"addons/core/Prefabs/ERR/PrisonDoor.et": "fc66be9d36f1f01084de59f1b0a1e8817f9e46547ead33d4fafaac0107446c40",
"addons/core/Prefabs/ERR/PrisonDoor.et.meta": "b8f3db52bf186426026668fbb9d633c3cbd4d438ab301fba880398f6a0e781c2",
"addons/core/Prefabs/ERR/PrisonWall.et": "8009d19365ae72a4749d7ad4fa04662575b06c65c6bf06507d380d57258b5eed",
"addons/core/Prefabs/ERR/PrisonWall.et.meta": "0294bc5dbb56f90f8153d62459bb0a6ad91d76a479812bbec9c7db182fdb58d6",
"addons/core/Prefabs/ERR/Prisoner.et": "198b5badabb12f218f86df7eb4dc268fd830769dc3de7eef6362cfaee4d6fc8d",
"addons/core/Prefabs/ERR/Prisoner.et.meta": "974278ba49fafeed70b619a0067bbc70ae4d534be441c812ce2bef4a5fb3c022",
"addons/core/Prefabs/ERR/Radio.et": "b4b5cfe125b39019855816c9a1c508ca4d65d435b5546fdaa6c1fa3c8c9b1165",
"addons/core/Prefabs/ERR/Radio.et.meta": "bc91137b68ba550217914fb8892bb44d7e1920e4a1086f3e506623832a44212f",
"addons/core/Prefabs/ERR/SpectatorCamera.et": "b5b87d5b7b707f3745741aebe7c923067c8a74f8fbb2a4711834bab03bb5a812",
"addons/core/Prefabs/ERR/SpectatorCamera.et.meta": "2a16558f93915e31908a674509e9c89976470f78a84a1174a64b94b68b8f77ad",
"addons/core/Prefabs/ERR/SupplyCache.et": "bccf7afbaae97cfcdd78dd6dbbf4be5b2c045d348f50fe4581962e556c0044d7",
"addons/core/Prefabs/ERR/SupplyCache.et.meta": "2f5de3a4aab1e9d0e35521f48dc57b95bd6e449c383dec4f1c258e10295904cb",
"addons/core/Prefabs/ERR/SupplySite.et": "dc5decce5cabf39b6116e8794ec3960cb1e35761854fdbe69740da118787ded4",
"addons/core/Prefabs/ERR/SupplySite.et.meta": "b41944b12eb3528ff7a3ac94258b18c66ef161aa6a38894aee300a1ac4798630",
"addons/core/Scripts/Game/ERR/ERR_Actions.c": "cabb21413c90b252d29708b62228d0679c51c76e39ee0e483953ad7061b2b9d6",
"addons/core/Scripts/Game/ERR/ERR_Actors.c": "b3c5aabb05f7cf35167bc09b5db51a4ffd89a46658805d6eedbfb80eee30273d",
"addons/core/Scripts/Game/ERR/ERR_AdminService.c": "9985747d5ddaeb91bf27937836a6c369f8dcb353263b77a49212ec28c10ba4eb",
"addons/core/Scripts/Game/ERR/ERR_CasualtyGroundSupport.c": "26f0c7ebe8563bb022f7c383674e7d8997b0cc35c7ea321d39d74f2493c1ed9d",
"addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingGameMode.c": "f4cf19440583ac24346831a2d5f1d7f734c031e46dc266c499972329dd0533cf",
"addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingOwner.c": "6346a05ddda030f7d9b6f026f977d5f018ee16002fa519e8c49dcd700c323dc7",
"addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingService.c": "93fe23d9e1da596886c33cf76bad92b0906ff5cdd8b5b21d58906e9270246201",
"addons/core/Scripts/Game/ERR/ERR_DiscoveryMap.c": "7553c8cc1388cca3404994e36f96946c6abd1fefb08cef2fbb3ea1a62b22861b",
"addons/core/Scripts/Game/ERR/ERR_DiscoveryService.c": "2793bb888566dd975e5bbf93fdb47a22e3b0f33a68d04992191cf6428ae942a0",
"addons/core/Scripts/Game/ERR/ERR_DocumentComponent.c": "17bb76b80ebd3357e46d553c44674f602c7bd6901c92aec53ac8214194aa4ff2",
"addons/core/Scripts/Game/ERR/ERR_EnvironmentService.c": "867e9f47adfb7a9819a8b58498688ff86127282cb89cf2529ae81f73e529e1c5",
"addons/core/Scripts/Game/ERR/ERR_EscapeGameOverScreenInfo.c": "b1e1afb7795cb77c0bad94c85abf705b26b9dc2a9400905d80eafd56bdd009d3",
"addons/core/Scripts/Game/ERR/ERR_ExtractionPlacement.c": "c30222ca25cc0e91dec4077e6b82b37a9edafcf2cbebd0be5b6bd8656b53adfe",
"addons/core/Scripts/Game/ERR/ERR_ExtractionService.c": "91357099b4e0401196b67d48963be54be30e56bdba57cdac67dc20b8ebb883e9",
"addons/core/Scripts/Game/ERR/ERR_FactionCatalog.c": "a5a8c53acf21259e14f59cb919535910f30dabff5a9179c664bc6a7a4d744d6e",
"addons/core/Scripts/Game/ERR/ERR_GameMode.c": "41f2247a45252eaacb580aa5ebb5a85ecfa41e0f940eb0d2c8ea5a3f6c35058e",
"addons/core/Scripts/Game/ERR/ERR_InitialGuardRules.c": "8c9d6b1dc4c2af96284e34f487eebb5a221091016ef108006dec60b4e3836558",
"addons/core/Scripts/Game/ERR/ERR_Inventory.c": "b63e679d5b650b63a7a91dfc3f42a62f4be58b0338f6df3313e66d29a93bebf6",
"addons/core/Scripts/Game/ERR/ERR_ItemStationTeardown.c": "102e61216e86e47ad243cc19f6f66da1fcd8cc77f8799354ceaeb4a6dc74622e",
"addons/core/Scripts/Game/ERR/ERR_LoadCasualtyAction.c": "5d868fb065a1a0fea7d5abfc81d1d8be96823815535f2ffd0dc4ff60d8eae794",
"addons/core/Scripts/Game/ERR/ERR_LocalAdmission.c": "b0e824c6880cbf9b1f368dc7ada9907bde78b22b948d419b6c861323f32258be",
"addons/core/Scripts/Game/ERR/ERR_Location.c": "d2035f56ded35743312ec6ca91a67f8623a466c91255e1893c570edf0954ff24",
"addons/core/Scripts/Game/ERR/ERR_MissionRules.c": "f7abc38ad8fdc375ce7fa63a65f0bade027d22b37e09de5e3f94ba8c3c63cc41",
"addons/core/Scripts/Game/ERR/ERR_NativeAdminSettingsProbe.c": "6a4df33a2b0e6b6aeed944c6895961e658d4f0db9aed9b8433a43a451fa35cae",
"addons/core/Scripts/Game/ERR/ERR_NativeBaseSmoke.c": "4c58290868fe4b669ff0f05835e9e7626766c13eab9633a721e0ebe5c551428e",
"addons/core/Scripts/Game/ERR/ERR_NativeCasualtyGroundProbe.c": "41f40db3735f8aa535c0061458d9892dcfe972222b8a17238e67b65633c46bc3",
"addons/core/Scripts/Game/ERR/ERR_NativeCasualtyLoadProbe.c": "94c533deea654ec546df3f1ebb7cd213e6cd79b1125d99bab5303ef8e87b018b",
"addons/core/Scripts/Game/ERR/ERR_NativeCasualtyRemoveProbe.c": "4cfc06f6d43b709b63caae714155ffc562502e884f49a9825623c6f46972ad36",
"addons/core/Scripts/Game/ERR/ERR_NativeCasualtyServiceProbe.c": "02a9d70d1f4ba20326df199ba41489cfbae12b7bb0aab441361421cee38ad9ea",
"addons/core/Scripts/Game/ERR/ERR_NativeContractsProbe.c": "d1556df885171cf967e96eef5d8981a2e9ee4e3938b7d32fdd0426f647bbabc0",
"addons/core/Scripts/Game/ERR/ERR_NativeDefinitionProbe.c": "c07048341396443ddf5307e340e2087254d65f0bddf8f7d93e6b6bd7f03ca360",
"addons/core/Scripts/Game/ERR/ERR_NativeDiscoveryMapProbe.c": "9ed32d391d08d937ff4f9f33bd11c3a204ce9a2230c2d72b6761e0320b31f24f",
"addons/core/Scripts/Game/ERR/ERR_NativeEnvironmentProbe.c": "8b1d110717aa5f07bfb8fe633847eab519109e3a8a4100970a487be05c053418",
"addons/core/Scripts/Game/ERR/ERR_NativeEnvironmentRestartProbe.c": "7a836a206381b8e8de24c92baf141beb30575723675dd6948d738169ed26b507",
"addons/core/Scripts/Game/ERR/ERR_NativeExtractionProbe.c": "4b09385f73206de004f4f3c38a6192fd658a0d2012482dc5a84dff1599ceb972",
"addons/core/Scripts/Game/ERR/ERR_NativeFactionCatalogProbe.c": "ee11486cf77da9070506ccc0cfd6e94240c5387c8be455bf864934a8fef65f78",
"addons/core/Scripts/Game/ERR/ERR_NativeFlightFootprint.c": "a8862a04ac784426f431e2e9cd3f1f19f820b38a081674d29549b8b00277c72d",
"addons/core/Scripts/Game/ERR/ERR_NativeFlightInputDiagnostics.c": "e231dcbdaefe81beb3bd587cb73be8fb3d3184bc86d18dbaa30a43ca4ba5dcc4",
"addons/core/Scripts/Game/ERR/ERR_NativeGuardCancelProbe.c": "bfa1c65cb039e19c3d1891e4a3fd33469e974c97cc7f42128efc31a8f587d514",
"addons/core/Scripts/Game/ERR/ERR_NativeItemStationTeardownProbe.c": "028588a49fa9207087cd27ec9396979cd932daa071e2490bd0262cd71dae30d9",
"addons/core/Scripts/Game/ERR/ERR_NativeLifecycleProbe.c": "b613a5dbe81c2032fd02d31070ae5631166d14d0b02cdbec3b63b782f0bbc115",
"addons/core/Scripts/Game/ERR/ERR_NativeLocalAdmissionProbe.c": "29c860ca3e51cdaa463f3c23a98ff45ee0499ce9bbc322046e0efbed587f1e09",
"addons/core/Scripts/Game/ERR/ERR_NativeMedicalProbe.c": "43fa51ffbcf9dd0cd8e905a1135bcf0756651da3af34677f3c92e7d264ed70b6",
"addons/core/Scripts/Game/ERR/ERR_NativeObjectiveLossProbe.c": "6b80fd317a8c6e8583bd4337870838a86251e3b8a681d0aa07e8b52e22abfc48",
"addons/core/Scripts/Game/ERR/ERR_NativeObjectiveProbe.c": "71fbd2479a70048f83d8b262ad1452f427be2c0afc8b071ef13bff477b9dc29b",
"addons/core/Scripts/Game/ERR/ERR_NativePatrolProbe.c": "02bd9fdf5454db7dd4c82240efe0d947bf8d6488ace42809c3aef6505cb6266d",
"addons/core/Scripts/Game/ERR/ERR_NativePoweredFlightProbe.c": "7ae5d6f85bbbebec214c66bfdf6f53b93c802f109fcd5bcfbad82c5d61d6cb9f",
"addons/core/Scripts/Game/ERR/ERR_NativePrisonBreachProbe.c": "22d0923cd69ba591e783011d424c97c86520272b70da9aa6a6d15558464c264c",
"addons/core/Scripts/Game/ERR/ERR_NativePrisonerProbe.c": "e2d1ffe5d269add55124601ff0d1dc032a398c7d51010736e6f0e4cbeb38ae86",
"addons/core/Scripts/Game/ERR/ERR_NativeProgressionIdentityProbe.c": "1d3fe20bce761a1082964bca9bad9ce79d1fa8b04db9b5053bae98da380ae560",
"addons/core/Scripts/Game/ERR/ERR_NativeRadioDefenceProbe.c": "cad54f0b43cefd72df74697dfc7e93725ff0acac6d311e963ce4f817428c272c",
"addons/core/Scripts/Game/ERR/ERR_NativeRecoveryProbe.c": "4de6868cd3e76afb80cf0ae5dac1e35d2c417838f640c2f18aa3343217eea2f2",
"addons/core/Scripts/Game/ERR/ERR_NativeResultProbe.c": "b9a117eef2d5f65fbd792041a2b3e59e38e201bda8e0df8c503e1a1242cfc415",
"addons/core/Scripts/Game/ERR/ERR_NativeSiteSurvey.c": "ac0203753ade0cdff6d483132797a80e4520f14546c3f9dea288281cd4cd9c4f",
"addons/core/Scripts/Game/ERR/ERR_NativeStationTeardownDiagnostic.c": "172248540932174341882861bb78a9720a6cf89981fcdf9dffe63f7fd4023f2c",
"addons/core/Scripts/Game/ERR/ERR_NativeTimeProbe.c": "c93d049785971e51246fcc07cf24804270a752a655505f2c38e48c80df5255ae",
"addons/core/Scripts/Game/ERR/ERR_NativeTransportProbe.c": "63ec0c5ce698114daa6314b2a6e3c3e98e76094117c477f9d0d072260686d193",
"addons/core/Scripts/Game/ERR/ERR_NativeTransportSurvey.c": "3e94493bf54d15be2bceb106ad18873e14e6755af09c05f07db7fb26fb7adff5",
"addons/core/Scripts/Game/ERR/ERR_ObjectiveDetails.c": "628ebd166dcb5544c2d18dce489619984887ee18296cf2446fc6d894f89dd5bf",
"addons/core/Scripts/Game/ERR/ERR_ObjectiveText.c": "d1740ddbe8a4bac04aeb0ffc68c2957bc807cbb0a197afd515f0fcb742aac04f",
"addons/core/Scripts/Game/ERR/ERR_Objectives.c": "88d95a52fcba103483ea751571e1afeae0e6fc82115ed50d6fc195424726960f",
"addons/core/Scripts/Game/ERR/ERR_Observer.c": "f417f503dcb66b4d5224d49046ab47c08fa14255d5c4af02a85047096069da33",
"addons/core/Scripts/Game/ERR/ERR_OrdinaryScenarioLaunch.c": "11541755401af8da68dc004d9a8e2a49578d617d4dec63b1d6e5a75b00b9c3ff",
"addons/core/Scripts/Game/ERR/ERR_PlayerAdminCommands.c": "a084d7dc097dfcde3d6cd9c2e326b9e2f8f2a519f77b30c4f56ab7a1429aa721",
"addons/core/Scripts/Game/ERR/ERR_PlayerEscapeResult.c": "3124ab4d2d6d471409a47aba0af528790734df53243c03fe0cdac3e407047c14",
"addons/core/Scripts/Game/ERR/ERR_PlayerGuide.c": "2c9c365b88651f8b12618443638bf52c04e497221592bd483c33447e30468f36",
"addons/core/Scripts/Game/ERR/ERR_PlayerRoster.c": "c038dac0f749bd29ebc35a4e952904b41a34417ba738904d5efcd1833a263e0b",
"addons/core/Scripts/Game/ERR/ERR_PrisonEscapeService.c": "ed6286342ed12cc9a3df618474f5647c7ac36bc65336be7a5bec908dee04421d",
"addons/core/Scripts/Game/ERR/ERR_RadioDefenceService.c": "5d347cf1a3b021074d560aad8db774828b797e685b63ddff3d28f55781e33ea8",
"addons/core/Scripts/Game/ERR/ERR_RadioFeedback.c": "545262e65e9b3645d9e514a0521e442fe08f0c2e4e7c83ba638c5105d09818db",
"addons/core/Scripts/Game/ERR/ERR_RecoveryService.c": "ba9ffb93679012c20a665636ef6b201cbe7d8d14fdf53e8cd3eb12ac7248943f",
"addons/core/Scripts/Game/ERR/ERR_Resources.c": "d0169aac6fd0c38928c47a9002ded0fd8ba558ea3314a600e792d307ac1ca3ee",
"addons/core/Scripts/Game/ERR/ERR_ScenarioComponent.c": "abba03af68323d5715ba1e2497fc66f3c706e6a6a983c3a386228abdfddeec89",
"addons/core/Scripts/Game/ERR/ERR_Settings.c": "b9390548936b386e0211d487c1b76b65410fdd2e339cd8ecd9585ef4f4ffed31",
"addons/core/Scripts/Game/ERR/ERR_SiteDefinition.c": "b422feeaca90084ea2bee5cde94dc9c6fcae542749b55d95f7226f922a6a68ac",
"addons/core/Scripts/Game/ERR/ERR_SiteDiscoveryRules.c": "50f7b3ff4f32bde37ce5ae2be7f598fc4dd8cc08f45914f6e3260d31c84baef2",
"addons/core/Scripts/Game/ERR/ERR_SiteService.c": "6bc9a0b5aed326822807f05e17459d0fc2da10b709e8168972b4e40fd09f87cb",
"addons/core/Scripts/Game/ERR/ERR_SpawnFinalization.c": "2e295b0f225208c5414728ba83333e5e0ab6eef3a708ce39eb1dfce5400c94c9",
"addons/core/Scripts/Game/ERR/ERR_SpawnLogic.c": "ed88279cbeb3e5e6daf8471763df87a251881f550632eddadd08153594bbd1a1",
"addons/core/Scripts/Game/ERR/ERR_SpawnPlacement.c": "55323dcb330c23d49dc8fa3fde86a3ae3958c0b6563d6c3cd0960da88333f93d",
"addons/core/Scripts/Game/ERR/ERR_Supplies.c": "752e8a56b0b8cde33cf532032cb0a203553017b686faac2e19bee06f7a9c0713",
"addons/core/Scripts/Game/ERR/ERR_TimeService.c": "d9f481b7da01e08d2c982a948c18ba6bde76889d7cc9d26d510708f4ba479453",
"addons/core/Scripts/GameLib/ERR/ERR_DiagnosticNoSplash.c": "0ea1b402f8927b6d68bf6f7e5caa2967f7e3a39147634faab147796b791933b5",
"addons/core/Scripts/WorkbenchGame/ERR/ERR_ValidateBaseWorldPlugin.c": "7f44aa64ea7b4f8b3640a37a4b781c43bf6adb80e4a3a702d9491131698056f6",
"addons/core/Worlds/ERR_Everon.ent": "22324bc7eb0d80dec8a1f3bd61a9df9201b72f749f4e751766b0c9d0b7d46b87",
"addons/core/Worlds/ERR_Everon.ent.meta": "19d1903ca72baa326b4724a6791d0da1104a1e53f3f31dc1ca41a856efd68e83",
"addons/core/Worlds/ERR_Everon_Layers/default.layer": "17105538007199bfc419aa04a64debaf2da0a7b77a1dc3ba983e92bf747c862e",
"addons/core/addon.gproj": "231274e6b62302f32c65f9ff31de2ce8a9e51e1c8d12c87deeec82ae1f40de46",
"addons/intel/Scripts/Game/ERR/ERR_IntelService.c": "5e167630a76437fd731fbb7a62a41381ea71509f3adb8a6b9c77f6816384861a",
"addons/intel/addon.gproj": "d0ced242cefb220a5cccd6779d61fda9bc55581701b9f78bcf13c09d92ddbcbe",
"addons/map/Materials/ERRM_MapRenderTarget.emat": "9877c5b175c5f30dd80bea36c711b21ab38219aa7075adf9ae79932e1d4c76bc",
"addons/map/Materials/ERRM_MapRenderTarget.emat.meta": "5ee1a705f8011a44020ca2691f5ccc17d44bf596fef9a36e98d1f267eb58fab0",
"addons/map/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et": "30dee8e9eab459ea32a7d835a9fb89d43df47708057cab829f067b25a24cd581",
"addons/map/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et.meta": "59093e9072c50d99c86da7609c87541016da5bcf92b98d99db4c524e1c8e0e8d",
"addons/map/Scripts/Game/ERR/ERR_ScenarioMap.c": "75fa1d742129ef7f33c005cd48f56db7f49ee7bde3c5428532b0b501b032b0a3",
"addons/map/Scripts/Game/ERRMap/ERRM_NativeMapOwnershipProbe.c": "32418602f085e3edba7343f42a6a691630b533be262002f29248e8ae87a38254",
"addons/map/Scripts/Game/ERRMap/ERRM_SharedMapComponent.c": "3a10f6ea25bf88a400bc3e6c339823433845c5db4f2f423835ccf99a1b1a843f",
"addons/map/UI/layouts/ERRMap/SharedMapRenderTarget.layout": "49a2da4181b94d42174f58c7f9b6bf2207116c99427d61ff1c212092ac7db4d2",
"addons/map/UI/layouts/ERRMap/SharedMapRenderTarget.layout.meta": "5c846445cc089e2e08899ba703059d4adb805ff30ab5a16409a3cd59718cbd54",
"addons/map/addon.gproj": "1b371e5e01ea9fa46e76570fecb8aaa3a9298e34cba7ccca8a6bdb5ca86eb46a"
}
@@ -0,0 +1,185 @@
{
"addons/ai/Prefabs/ERR/MortarCrew.et": "05d3e7353ff9fe5523218a1487b31b7b742b0be8adbcb060c7099978cf008bce",
"addons/ai/Prefabs/ERR/MortarCrew.et.meta": "fdfd4fcd21a67c3d698fa4aadc4e5c76e665cebf4be86013bc389e493fab63cc",
"addons/ai/Scripts/Game/ERR/ERR_AircraftFlight.c": "96b20b7e78612fb26befa6e26635be80dbf9ca089384db4a7839f5064ab06ccc",
"addons/ai/Scripts/Game/ERR/ERR_AircraftFlightEnvelope.c": "8125cef61fb283d0d7c4dd2df3c04b062b448f1101fd48dd53f4f4097df5fef3",
"addons/ai/Scripts/Game/ERR/ERR_HQ.c": "7ee8e7a3282fd566ec330c94854b353b2c6178e9b964f7b4d5e197e4d0b7dee9",
"addons/ai/Scripts/Game/ERR/ERR_HQJournal.c": "b3e402cfe92d415f37f666045afb062e6e11670ba03a0ca6f2d9fd4fc3520ac1",
"addons/ai/Scripts/Game/ERR/ERR_HQNetwork.c": "92ff2caca52e94c2cc9642dfc8509ce6e9349b257012bade8650bace9231128c",
"addons/ai/Scripts/Game/ERR/ERR_HQObservation.c": "36c978ec5f052213ac1236e1398d85063efbfd7ef60fe04f1fc11447fefe1d68",
"addons/ai/Scripts/Game/ERR/ERR_HQReport.c": "c4d16ccb571db4d9444c0a8d2be7318845d25ff90238b6d304e96a9ea19f1a08",
"addons/ai/Scripts/Game/ERR/ERR_HelicopterAvailability.c": "60eaef2f6d4b2fd44861c5a4434ef97e732e40a5ff7401e2da451ed2dd9c2762",
"addons/ai/Scripts/Game/ERR/ERR_MortarAimSolution.c": "59e8343d09f7a44871d8d3335d6ad1d02216abb5deed30d38b5ec9ba3f2c0649",
"addons/ai/Scripts/Game/ERR/ERR_MortarBattery.c": "a4eed96d17f239cbf353863ebe40d2282aeacd7a4d63a7b2f0ecfb3d93a2e2b2",
"addons/ai/Scripts/Game/ERR/ERR_MortarCourierTransfer.c": "968e58ca700b4a11e5e04a5c159696f514a0b1bce531210172b913e495f7e2b7",
"addons/ai/Scripts/Game/ERR/ERR_MortarFiring.c": "7aec51c83972610d1bcbca58b84b14f16f59952d402b12c3d88e7192a3e2ee90",
"addons/ai/Scripts/Game/ERR/ERR_MortarLogistics.c": "e1ffb25fba384b449de576a5502a93d875c3f52e8da1941bbf590ad9845f9730",
"addons/ai/Scripts/Game/ERR/ERR_MortarPrediction.c": "470dd196ed64800c697d24c09d1344bacd2502587fac76af4ee0edae2563092d",
"addons/ai/Scripts/Game/ERR/ERR_MortarReadiness.c": "2fabe8f812fd333cea7199d1eb722c807fcc5adb285c618956bd54d31fa27ce3",
"addons/ai/Scripts/Game/ERR/ERR_MortarRefresh.c": "52ae8d9d6cad92f42d68c634091b80a474d2666b6c06d63a8c30a3fab71494c4",
"addons/ai/Scripts/Game/ERR/ERR_MortarReportLedger.c": "42a96040b63e56677353e55164ae23031c90f70b4d6673a9f412ad7b19e636ae",
"addons/ai/Scripts/Game/ERR/ERR_MortarSalvo.c": "697c01f479df6c505a41d984a29c02002e3052114dafa2ec6252ae55e2979a2b",
"addons/ai/Scripts/Game/ERR/ERR_MortarSupplyTransfer.c": "34f7eaa01dc68e97aae8c1177b7f8fd6c15d5a41c6dc86de377f00b3613a8e77",
"addons/ai/Scripts/Game/ERR/ERR_MortarSystem.c": "4edefc8530cc6eec7a5e1df1d0b7d56e6801186f23c61331ce0e8075b8281efb",
"addons/ai/Scripts/Game/ERR/ERR_NativeAircraftControlProbe.c": "e501301e683a7e36ad5a0cb56bc87a590c0c9071c9fc5c579502fb1c7a0ff143",
"addons/ai/Scripts/Game/ERR/ERR_NativeMortarCourierProbe.c": "740c8bebbd4128a9311b1167d1058549475d6b46f81c9b6c0d19650f4b50616f",
"addons/ai/Scripts/Game/ERR/ERR_NativeMortarFiringProbe.c": "ee570d21bede8baebefc799399712d9582b3e30ebda7299f6c941777f02ad2a2",
"addons/ai/Scripts/Game/ERR/ERR_NativeMortarSalvoProbe.c": "5fd7bb8953f6850ad79e646c0c286153108a10ee2d5ef397ccb65a07f8d2ed7c",
"addons/ai/Scripts/Game/ERR/ERR_NativeMortarTransferProbe.c": "399c21940ef10777b46fdb3e905430ce43404ce1de2f4a6bb62f9c61c1a0689c",
"addons/ai/Scripts/Game/ERR/ERR_NativePatrolHQProbe.c": "4c04aa3e1b6b000747da8e2a919a6501107880fd041134c019f516665bb6f758",
"addons/ai/Scripts/Game/ERR/ERR_SettlementSafety.c": "e5c0ebc95521a9a33a3f7d9cca6c551dda862a08b7af20b017877aba9584795b",
"addons/ai/Scripts/Game/ERR/ERR_SupportScenario.c": "71757018ec9716c4dc24f62a292a214919d3fd5daed3c16e03e56abd208e9f03",
"addons/ai/addon.gproj": "5f559db8f526700db11e6ae763f5245f19b7006fc68c56e4c09fb6f940bdf2a1",
"addons/chat/Missions/ERR_Everon.conf": "76f3ac0d21b585654beac7ddd68e3c8cbdd66dbfe909b17b04838fa813a7c29b",
"addons/chat/Missions/ERR_Everon.conf.meta": "0a23c98bf83873536c65fa17d38dea33a1d7b9e4ab210f90633ff688abcc551f",
"addons/chat/Scripts/Game/ERR/ERR_AdminCommands.c": "d7b4ed95fc32767911936cb0fcc60763233f2c31afd34a6a0fa998b39cec9f5c",
"addons/chat/Scripts/Game/ERR/ERR_NativeHQResponseProof.c": "0f8b09fd8753234c91fd6baaca0e2f812e409ae1c4be8f340b648ae59b69c2f3",
"addons/chat/Scripts/Game/ERR/ERR_NativeMortarProbe.c": "ddb892141500c192c0787b011d57dd8c07dd8c9a3ca5fdc407f6c271897bde0a",
"addons/chat/Scripts/Game/ERR/ERR_NativeRadioProbe.c": "84524163c63402755b4f834f74dfe6d8e5c397dc26031f532593c6f79f5a93b1",
"addons/chat/Scripts/Game/ERR/ERR_NativeSmoke.c": "32501d011d459e3c2d01077ef3eeb0ce8c364985e1aadfe8e8c022ba4b2054ef",
"addons/chat/Scripts/Game/ERRChat/ERR_OrdinaryScenarioLaunchChat.c": "dda715c9691d366e35fa2ad5e390dee1971651f137a5a9f9aeb23dcbe53ec87e",
"addons/chat/Scripts/WorkbenchGame/ERR/ERR_ValidateWorldPlugin.c": "81c92d408ce88cc9f98d6053f1d0c863427693ba748f00df9f1bf8079015ef94",
"addons/chat/addon.gproj": "ea1587e0174cf795334d306c71d99b06797fc891427cd57b6cab6e21cf0e4e0e",
"addons/core/Configs/ERR/EveronSites.conf": "ade92cc8d2b14a84ac378bbbb52608b351dca65d359dd7a315d64a145fa4b46f",
"addons/core/Configs/ERR/EveronSites.conf.meta": "d5fe68025ef4e7c1e16082cf5873b7c7172eacbfaef4ab29b7e0e3c98f8dca56",
"addons/core/Configs/ERR/Factions/MissionVehicles.conf": "5f74f4a7089c7e7b54ad62463159ce8f0bab25a6b85268dc3872ea6001e048fb",
"addons/core/Configs/ERR/Factions/MissionVehicles.conf.meta": "00c368b6271d3440cc8bf627b03a384d247635723923fc5ad2104b8b4007b035",
"addons/core/Configs/ERR/Factions/US.conf": "4c3da063abb6e3ee39a6e4e0c5a9ffd5eae9d209d58470ea18b45dba5528db1b",
"addons/core/Configs/ERR/Factions/US.conf.meta": "a05469aef028e9e17eba545e81e2af2887b59f0fef17dac51a23c7b8155e381b",
"addons/core/Configs/ERR/Factions/USSR.conf": "407ea27f80a64c5184c089d43cdd0bdabfc5974c84f1d2a245081fdf2afa3f30",
"addons/core/Configs/ERR/Factions/USSR.conf.meta": "057397d97096346791b51b7b13fbe18af218bcf78e9984d9c01b49a2403de611",
"addons/core/Configs/ERR/GameOverScreens.conf": "45231061911a1462d45fb6db0fecc2b0793f17b5ae71017174954ff4fbd20236",
"addons/core/Configs/ERR/GameOverScreens.conf.meta": "eb654b745c0b97b41c991694569ef4877a3c2315e0826f9556337cd68690843e",
"addons/core/Configs/ERR/VanillaFactions.conf": "db890b61b48ee15484041fd2245a862d9fa9e7ba21b1229c281ff37c448a35b1",
"addons/core/Configs/ERR/VanillaFactions.conf.meta": "ac77725c0c21974991ef47a466b88122e939de8b46fd23e68889e7674cbb2bfd",
"addons/core/Missions/ERR_Everon_Base.conf": "1e6a0e9a756d15495201f71121626890f01efe6b00154c561672d4ec157960bd",
"addons/core/Missions/ERR_Everon_Base.conf.meta": "9b16267c644d815bcf98a3a3aaff30b93d7224fb24d3a2bac66730a28011f317",
"addons/core/Prefabs/ERR/Extraction.et": "1aa286ef253c5410f09fa641e4be91a7be8ec834487a7fbb74cfb45cab60ee1e",
"addons/core/Prefabs/ERR/Extraction.et.meta": "c2a838633001e2e3a202d39cb33aa744ebcd996017ad032359069641b5d13853",
"addons/core/Prefabs/ERR/FactionManager.et": "425f916b890019c537f6b2f72b1af9e7be54b0a476320da969e1f313a7ac5ea5",
"addons/core/Prefabs/ERR/FactionManager.et.meta": "9bf53d5e4bcbe60dd1a4ece1409fc23c349055c860708620f5f6e9716290a97d",
"addons/core/Prefabs/ERR/FieldCover.et": "20f8095e69dbfe3db74ff19078e528ea75405362b335e03560ccd61f8df2e7f3",
"addons/core/Prefabs/ERR/FieldCover.et.meta": "83461ffa76244e1d69f20b053a9741ccdf7d127009182bd540a7e5ba6150c3d9",
"addons/core/Prefabs/ERR/GameMode.et": "385c97a5b8067091d92aa60c2d4fcf02002cf606b95523866ead65c96b8c0029",
"addons/core/Prefabs/ERR/GameMode.et.meta": "106769cd34525c9d440057072836cb7401a6752148c40edd7fe61c03bd58328c",
"addons/core/Prefabs/ERR/ObjectiveDocument.et": "2e72270bf186654af12d2c3ff9bf940690b03fb17fca44f873c1fde5d11384c0",
"addons/core/Prefabs/ERR/ObjectiveDocument.et.meta": "eaba9903be147b98a1106e2273e2270773df73b9b22f2cd4e20136a8e281382e",
"addons/core/Prefabs/ERR/Patrol.et": "c5685a8bc311cc5563fb3334beec9408d3d7a1069152fddeeb7df4f1b7f9e6ce",
"addons/core/Prefabs/ERR/Patrol.et.meta": "7c5da568fa2fcf0709ee2959253cb068d7c7ed7c2467af3b247a7e70efd96b69",
"addons/core/Prefabs/ERR/Prison.et": "dcd3f4dfd453303d026e68b64f7de6125bd3390822d8432dd95a3710a5b3df89",
"addons/core/Prefabs/ERR/Prison.et.meta": "2e426ef3de68f4107907854b51e818bc11d84649a01c538e3f18dd9ebec766bb",
"addons/core/Prefabs/ERR/PrisonDoor.et": "fc66be9d36f1f01084de59f1b0a1e8817f9e46547ead33d4fafaac0107446c40",
"addons/core/Prefabs/ERR/PrisonDoor.et.meta": "b8f3db52bf186426026668fbb9d633c3cbd4d438ab301fba880398f6a0e781c2",
"addons/core/Prefabs/ERR/PrisonWall.et": "8009d19365ae72a4749d7ad4fa04662575b06c65c6bf06507d380d57258b5eed",
"addons/core/Prefabs/ERR/PrisonWall.et.meta": "0294bc5dbb56f90f8153d62459bb0a6ad91d76a479812bbec9c7db182fdb58d6",
"addons/core/Prefabs/ERR/Prisoner.et": "198b5badabb12f218f86df7eb4dc268fd830769dc3de7eef6362cfaee4d6fc8d",
"addons/core/Prefabs/ERR/Prisoner.et.meta": "974278ba49fafeed70b619a0067bbc70ae4d534be441c812ce2bef4a5fb3c022",
"addons/core/Prefabs/ERR/Radio.et": "b4b5cfe125b39019855816c9a1c508ca4d65d435b5546fdaa6c1fa3c8c9b1165",
"addons/core/Prefabs/ERR/Radio.et.meta": "bc91137b68ba550217914fb8892bb44d7e1920e4a1086f3e506623832a44212f",
"addons/core/Prefabs/ERR/SpectatorCamera.et": "b5b87d5b7b707f3745741aebe7c923067c8a74f8fbb2a4711834bab03bb5a812",
"addons/core/Prefabs/ERR/SpectatorCamera.et.meta": "2a16558f93915e31908a674509e9c89976470f78a84a1174a64b94b68b8f77ad",
"addons/core/Prefabs/ERR/SupplyCache.et": "bccf7afbaae97cfcdd78dd6dbbf4be5b2c045d348f50fe4581962e556c0044d7",
"addons/core/Prefabs/ERR/SupplyCache.et.meta": "2f5de3a4aab1e9d0e35521f48dc57b95bd6e449c383dec4f1c258e10295904cb",
"addons/core/Prefabs/ERR/SupplySite.et": "dc5decce5cabf39b6116e8794ec3960cb1e35761854fdbe69740da118787ded4",
"addons/core/Prefabs/ERR/SupplySite.et.meta": "b41944b12eb3528ff7a3ac94258b18c66ef161aa6a38894aee300a1ac4798630",
"addons/core/Scripts/Game/ERR/ERR_Actions.c": "cabb21413c90b252d29708b62228d0679c51c76e39ee0e483953ad7061b2b9d6",
"addons/core/Scripts/Game/ERR/ERR_Actors.c": "b3c5aabb05f7cf35167bc09b5db51a4ffd89a46658805d6eedbfb80eee30273d",
"addons/core/Scripts/Game/ERR/ERR_AdminService.c": "9985747d5ddaeb91bf27937836a6c369f8dcb353263b77a49212ec28c10ba4eb",
"addons/core/Scripts/Game/ERR/ERR_CasualtyGroundSupport.c": "26f0c7ebe8563bb022f7c383674e7d8997b0cc35c7ea321d39d74f2493c1ed9d",
"addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingGameMode.c": "f4cf19440583ac24346831a2d5f1d7f734c031e46dc266c499972329dd0533cf",
"addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingOwner.c": "6346a05ddda030f7d9b6f026f977d5f018ee16002fa519e8c49dcd700c323dc7",
"addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingService.c": "93fe23d9e1da596886c33cf76bad92b0906ff5cdd8b5b21d58906e9270246201",
"addons/core/Scripts/Game/ERR/ERR_DiscoveryMap.c": "7553c8cc1388cca3404994e36f96946c6abd1fefb08cef2fbb3ea1a62b22861b",
"addons/core/Scripts/Game/ERR/ERR_DiscoveryService.c": "2793bb888566dd975e5bbf93fdb47a22e3b0f33a68d04992191cf6428ae942a0",
"addons/core/Scripts/Game/ERR/ERR_DocumentComponent.c": "17bb76b80ebd3357e46d553c44674f602c7bd6901c92aec53ac8214194aa4ff2",
"addons/core/Scripts/Game/ERR/ERR_EnvironmentService.c": "867e9f47adfb7a9819a8b58498688ff86127282cb89cf2529ae81f73e529e1c5",
"addons/core/Scripts/Game/ERR/ERR_EscapeGameOverScreenInfo.c": "b1e1afb7795cb77c0bad94c85abf705b26b9dc2a9400905d80eafd56bdd009d3",
"addons/core/Scripts/Game/ERR/ERR_ExtractionPlacement.c": "c30222ca25cc0e91dec4077e6b82b37a9edafcf2cbebd0be5b6bd8656b53adfe",
"addons/core/Scripts/Game/ERR/ERR_ExtractionService.c": "91357099b4e0401196b67d48963be54be30e56bdba57cdac67dc20b8ebb883e9",
"addons/core/Scripts/Game/ERR/ERR_FactionCatalog.c": "a5a8c53acf21259e14f59cb919535910f30dabff5a9179c664bc6a7a4d744d6e",
"addons/core/Scripts/Game/ERR/ERR_GameMode.c": "41f2247a45252eaacb580aa5ebb5a85ecfa41e0f940eb0d2c8ea5a3f6c35058e",
"addons/core/Scripts/Game/ERR/ERR_InitialGuardRules.c": "8c9d6b1dc4c2af96284e34f487eebb5a221091016ef108006dec60b4e3836558",
"addons/core/Scripts/Game/ERR/ERR_Inventory.c": "b63e679d5b650b63a7a91dfc3f42a62f4be58b0338f6df3313e66d29a93bebf6",
"addons/core/Scripts/Game/ERR/ERR_ItemStationTeardown.c": "102e61216e86e47ad243cc19f6f66da1fcd8cc77f8799354ceaeb4a6dc74622e",
"addons/core/Scripts/Game/ERR/ERR_LoadCasualtyAction.c": "5d868fb065a1a0fea7d5abfc81d1d8be96823815535f2ffd0dc4ff60d8eae794",
"addons/core/Scripts/Game/ERR/ERR_LocalAdmission.c": "b0e824c6880cbf9b1f368dc7ada9907bde78b22b948d419b6c861323f32258be",
"addons/core/Scripts/Game/ERR/ERR_Location.c": "d2035f56ded35743312ec6ca91a67f8623a466c91255e1893c570edf0954ff24",
"addons/core/Scripts/Game/ERR/ERR_MissionRules.c": "f7abc38ad8fdc375ce7fa63a65f0bade027d22b37e09de5e3f94ba8c3c63cc41",
"addons/core/Scripts/Game/ERR/ERR_NativeAdminSettingsProbe.c": "6a4df33a2b0e6b6aeed944c6895961e658d4f0db9aed9b8433a43a451fa35cae",
"addons/core/Scripts/Game/ERR/ERR_NativeBaseSmoke.c": "4c58290868fe4b669ff0f05835e9e7626766c13eab9633a721e0ebe5c551428e",
"addons/core/Scripts/Game/ERR/ERR_NativeCasualtyGroundProbe.c": "41f40db3735f8aa535c0061458d9892dcfe972222b8a17238e67b65633c46bc3",
"addons/core/Scripts/Game/ERR/ERR_NativeCasualtyLoadProbe.c": "94c533deea654ec546df3f1ebb7cd213e6cd79b1125d99bab5303ef8e87b018b",
"addons/core/Scripts/Game/ERR/ERR_NativeCasualtyRemoveProbe.c": "4cfc06f6d43b709b63caae714155ffc562502e884f49a9825623c6f46972ad36",
"addons/core/Scripts/Game/ERR/ERR_NativeCasualtyServiceProbe.c": "02a9d70d1f4ba20326df199ba41489cfbae12b7bb0aab441361421cee38ad9ea",
"addons/core/Scripts/Game/ERR/ERR_NativeContractsProbe.c": "d1556df885171cf967e96eef5d8981a2e9ee4e3938b7d32fdd0426f647bbabc0",
"addons/core/Scripts/Game/ERR/ERR_NativeDefinitionProbe.c": "c07048341396443ddf5307e340e2087254d65f0bddf8f7d93e6b6bd7f03ca360",
"addons/core/Scripts/Game/ERR/ERR_NativeDiscoveryMapProbe.c": "9ed32d391d08d937ff4f9f33bd11c3a204ce9a2230c2d72b6761e0320b31f24f",
"addons/core/Scripts/Game/ERR/ERR_NativeEnvironmentProbe.c": "8b1d110717aa5f07bfb8fe633847eab519109e3a8a4100970a487be05c053418",
"addons/core/Scripts/Game/ERR/ERR_NativeEnvironmentRestartProbe.c": "7a836a206381b8e8de24c92baf141beb30575723675dd6948d738169ed26b507",
"addons/core/Scripts/Game/ERR/ERR_NativeExtractionProbe.c": "4b09385f73206de004f4f3c38a6192fd658a0d2012482dc5a84dff1599ceb972",
"addons/core/Scripts/Game/ERR/ERR_NativeFactionCatalogProbe.c": "ee11486cf77da9070506ccc0cfd6e94240c5387c8be455bf864934a8fef65f78",
"addons/core/Scripts/Game/ERR/ERR_NativeFlightFootprint.c": "a8862a04ac784426f431e2e9cd3f1f19f820b38a081674d29549b8b00277c72d",
"addons/core/Scripts/Game/ERR/ERR_NativeFlightInputDiagnostics.c": "e231dcbdaefe81beb3bd587cb73be8fb3d3184bc86d18dbaa30a43ca4ba5dcc4",
"addons/core/Scripts/Game/ERR/ERR_NativeGuardCancelProbe.c": "bfa1c65cb039e19c3d1891e4a3fd33469e974c97cc7f42128efc31a8f587d514",
"addons/core/Scripts/Game/ERR/ERR_NativeItemStationTeardownProbe.c": "028588a49fa9207087cd27ec9396979cd932daa071e2490bd0262cd71dae30d9",
"addons/core/Scripts/Game/ERR/ERR_NativeLifecycleProbe.c": "b613a5dbe81c2032fd02d31070ae5631166d14d0b02cdbec3b63b782f0bbc115",
"addons/core/Scripts/Game/ERR/ERR_NativeLocalAdmissionProbe.c": "29c860ca3e51cdaa463f3c23a98ff45ee0499ce9bbc322046e0efbed587f1e09",
"addons/core/Scripts/Game/ERR/ERR_NativeMedicalProbe.c": "43fa51ffbcf9dd0cd8e905a1135bcf0756651da3af34677f3c92e7d264ed70b6",
"addons/core/Scripts/Game/ERR/ERR_NativeObjectiveLossProbe.c": "6b80fd317a8c6e8583bd4337870838a86251e3b8a681d0aa07e8b52e22abfc48",
"addons/core/Scripts/Game/ERR/ERR_NativeObjectiveProbe.c": "71fbd2479a70048f83d8b262ad1452f427be2c0afc8b071ef13bff477b9dc29b",
"addons/core/Scripts/Game/ERR/ERR_NativePatrolProbe.c": "02bd9fdf5454db7dd4c82240efe0d947bf8d6488ace42809c3aef6505cb6266d",
"addons/core/Scripts/Game/ERR/ERR_NativePoweredFlightProbe.c": "7ae5d6f85bbbebec214c66bfdf6f53b93c802f109fcd5bcfbad82c5d61d6cb9f",
"addons/core/Scripts/Game/ERR/ERR_NativePrisonBreachProbe.c": "22d0923cd69ba591e783011d424c97c86520272b70da9aa6a6d15558464c264c",
"addons/core/Scripts/Game/ERR/ERR_NativePrisonerProbe.c": "e2d1ffe5d269add55124601ff0d1dc032a398c7d51010736e6f0e4cbeb38ae86",
"addons/core/Scripts/Game/ERR/ERR_NativeProgressionIdentityProbe.c": "1d3fe20bce761a1082964bca9bad9ce79d1fa8b04db9b5053bae98da380ae560",
"addons/core/Scripts/Game/ERR/ERR_NativeRadioDefenceProbe.c": "cad54f0b43cefd72df74697dfc7e93725ff0acac6d311e963ce4f817428c272c",
"addons/core/Scripts/Game/ERR/ERR_NativeRecoveryProbe.c": "4de6868cd3e76afb80cf0ae5dac1e35d2c417838f640c2f18aa3343217eea2f2",
"addons/core/Scripts/Game/ERR/ERR_NativeResultProbe.c": "b9a117eef2d5f65fbd792041a2b3e59e38e201bda8e0df8c503e1a1242cfc415",
"addons/core/Scripts/Game/ERR/ERR_NativeSiteSurvey.c": "ac0203753ade0cdff6d483132797a80e4520f14546c3f9dea288281cd4cd9c4f",
"addons/core/Scripts/Game/ERR/ERR_NativeStationTeardownDiagnostic.c": "172248540932174341882861bb78a9720a6cf89981fcdf9dffe63f7fd4023f2c",
"addons/core/Scripts/Game/ERR/ERR_NativeTimeProbe.c": "c93d049785971e51246fcc07cf24804270a752a655505f2c38e48c80df5255ae",
"addons/core/Scripts/Game/ERR/ERR_NativeTransportProbe.c": "63ec0c5ce698114daa6314b2a6e3c3e98e76094117c477f9d0d072260686d193",
"addons/core/Scripts/Game/ERR/ERR_NativeTransportSurvey.c": "3e94493bf54d15be2bceb106ad18873e14e6755af09c05f07db7fb26fb7adff5",
"addons/core/Scripts/Game/ERR/ERR_ObjectiveDetails.c": "628ebd166dcb5544c2d18dce489619984887ee18296cf2446fc6d894f89dd5bf",
"addons/core/Scripts/Game/ERR/ERR_ObjectiveText.c": "d1740ddbe8a4bac04aeb0ffc68c2957bc807cbb0a197afd515f0fcb742aac04f",
"addons/core/Scripts/Game/ERR/ERR_Objectives.c": "88d95a52fcba103483ea751571e1afeae0e6fc82115ed50d6fc195424726960f",
"addons/core/Scripts/Game/ERR/ERR_Observer.c": "f417f503dcb66b4d5224d49046ab47c08fa14255d5c4af02a85047096069da33",
"addons/core/Scripts/Game/ERR/ERR_OrdinaryScenarioLaunch.c": "11541755401af8da68dc004d9a8e2a49578d617d4dec63b1d6e5a75b00b9c3ff",
"addons/core/Scripts/Game/ERR/ERR_PlayerAdminCommands.c": "a084d7dc097dfcde3d6cd9c2e326b9e2f8f2a519f77b30c4f56ab7a1429aa721",
"addons/core/Scripts/Game/ERR/ERR_PlayerEscapeResult.c": "3124ab4d2d6d471409a47aba0af528790734df53243c03fe0cdac3e407047c14",
"addons/core/Scripts/Game/ERR/ERR_PlayerGuide.c": "2c9c365b88651f8b12618443638bf52c04e497221592bd483c33447e30468f36",
"addons/core/Scripts/Game/ERR/ERR_PlayerRoster.c": "c038dac0f749bd29ebc35a4e952904b41a34417ba738904d5efcd1833a263e0b",
"addons/core/Scripts/Game/ERR/ERR_PrisonEscapeService.c": "ed6286342ed12cc9a3df618474f5647c7ac36bc65336be7a5bec908dee04421d",
"addons/core/Scripts/Game/ERR/ERR_RadioDefenceService.c": "5d347cf1a3b021074d560aad8db774828b797e685b63ddff3d28f55781e33ea8",
"addons/core/Scripts/Game/ERR/ERR_RadioFeedback.c": "545262e65e9b3645d9e514a0521e442fe08f0c2e4e7c83ba638c5105d09818db",
"addons/core/Scripts/Game/ERR/ERR_RecoveryService.c": "ba9ffb93679012c20a665636ef6b201cbe7d8d14fdf53e8cd3eb12ac7248943f",
"addons/core/Scripts/Game/ERR/ERR_Resources.c": "d0169aac6fd0c38928c47a9002ded0fd8ba558ea3314a600e792d307ac1ca3ee",
"addons/core/Scripts/Game/ERR/ERR_ScenarioComponent.c": "abba03af68323d5715ba1e2497fc66f3c706e6a6a983c3a386228abdfddeec89",
"addons/core/Scripts/Game/ERR/ERR_Settings.c": "b9390548936b386e0211d487c1b76b65410fdd2e339cd8ecd9585ef4f4ffed31",
"addons/core/Scripts/Game/ERR/ERR_SiteDefinition.c": "b422feeaca90084ea2bee5cde94dc9c6fcae542749b55d95f7226f922a6a68ac",
"addons/core/Scripts/Game/ERR/ERR_SiteDiscoveryRules.c": "50f7b3ff4f32bde37ce5ae2be7f598fc4dd8cc08f45914f6e3260d31c84baef2",
"addons/core/Scripts/Game/ERR/ERR_SiteService.c": "6bc9a0b5aed326822807f05e17459d0fc2da10b709e8168972b4e40fd09f87cb",
"addons/core/Scripts/Game/ERR/ERR_SpawnFinalization.c": "2e295b0f225208c5414728ba83333e5e0ab6eef3a708ce39eb1dfce5400c94c9",
"addons/core/Scripts/Game/ERR/ERR_SpawnLogic.c": "ed88279cbeb3e5e6daf8471763df87a251881f550632eddadd08153594bbd1a1",
"addons/core/Scripts/Game/ERR/ERR_SpawnPlacement.c": "55323dcb330c23d49dc8fa3fde86a3ae3958c0b6563d6c3cd0960da88333f93d",
"addons/core/Scripts/Game/ERR/ERR_Supplies.c": "752e8a56b0b8cde33cf532032cb0a203553017b686faac2e19bee06f7a9c0713",
"addons/core/Scripts/Game/ERR/ERR_TimeService.c": "d9f481b7da01e08d2c982a948c18ba6bde76889d7cc9d26d510708f4ba479453",
"addons/core/Scripts/GameLib/ERR/ERR_DiagnosticNoSplash.c": "0ea1b402f8927b6d68bf6f7e5caa2967f7e3a39147634faab147796b791933b5",
"addons/core/Scripts/WorkbenchGame/ERR/ERR_ValidateBaseWorldPlugin.c": "7f44aa64ea7b4f8b3640a37a4b781c43bf6adb80e4a3a702d9491131698056f6",
"addons/core/Worlds/ERR_Everon.ent": "22324bc7eb0d80dec8a1f3bd61a9df9201b72f749f4e751766b0c9d0b7d46b87",
"addons/core/Worlds/ERR_Everon.ent.meta": "19d1903ca72baa326b4724a6791d0da1104a1e53f3f31dc1ca41a856efd68e83",
"addons/core/Worlds/ERR_Everon_Layers/default.layer": "17105538007199bfc419aa04a64debaf2da0a7b77a1dc3ba983e92bf747c862e",
"addons/core/addon.gproj": "231274e6b62302f32c65f9ff31de2ce8a9e51e1c8d12c87deeec82ae1f40de46",
"addons/intel/Scripts/Game/ERR/ERR_IntelService.c": "5e167630a76437fd731fbb7a62a41381ea71509f3adb8a6b9c77f6816384861a",
"addons/intel/addon.gproj": "d0ced242cefb220a5cccd6779d61fda9bc55581701b9f78bcf13c09d92ddbcbe",
"addons/map/Materials/ERRM_MapRenderTarget.emat": "9877c5b175c5f30dd80bea36c711b21ab38219aa7075adf9ae79932e1d4c76bc",
"addons/map/Materials/ERRM_MapRenderTarget.emat.meta": "5ee1a705f8011a44020ca2691f5ccc17d44bf596fef9a36e98d1f267eb58fab0",
"addons/map/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et": "30dee8e9eab459ea32a7d835a9fb89d43df47708057cab829f067b25a24cd581",
"addons/map/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et.meta": "59093e9072c50d99c86da7609c87541016da5bcf92b98d99db4c524e1c8e0e8d",
"addons/map/Scripts/Game/ERR/ERR_ScenarioMap.c": "75fa1d742129ef7f33c005cd48f56db7f49ee7bde3c5428532b0b501b032b0a3",
"addons/map/Scripts/Game/ERRMap/ERRM_NativeMapOwnershipProbe.c": "32418602f085e3edba7343f42a6a691630b533be262002f29248e8ae87a38254",
"addons/map/Scripts/Game/ERRMap/ERRM_SharedMapComponent.c": "3a10f6ea25bf88a400bc3e6c339823433845c5db4f2f423835ccf99a1b1a843f",
"addons/map/UI/layouts/ERRMap/SharedMapRenderTarget.layout": "49a2da4181b94d42174f58c7f9b6bf2207116c99427d61ff1c212092ac7db4d2",
"addons/map/UI/layouts/ERRMap/SharedMapRenderTarget.layout.meta": "5c846445cc089e2e08899ba703059d4adb805ff30ab5a16409a3cd59718cbd54",
"addons/map/addon.gproj": "1b371e5e01ea9fa46e76570fecb8aaa3a9298e34cba7ccca8a6bdb5ca86eb46a"
}
@@ -0,0 +1,8 @@
SCR_AIGroup : "{8DE0C0830FE0C33D}Prefabs/Groups/OPFOR/Group_USSR_Base.et" {
m_aUnitPrefabSlots {
"{86E565387A634103}Prefabs/Characters/Campaign/Final/OPFOR/USSR_Army/Regular/Campaign_USSR_Player_MortarCrew.et"
"{86E565387A634103}Prefabs/Characters/Campaign/Final/OPFOR/USSR_Army/Regular/Campaign_USSR_Player_MortarCrew.et"
"{86E565387A634103}Prefabs/Characters/Campaign/Final/OPFOR/USSR_Army/Regular/Campaign_USSR_Player_MortarCrew.et"
"{612F43A4D5AE765F}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_RTO.et"
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{B84C05941C4C81B8}Prefabs/ERR/MortarCrew.et"
Configurations {
EntityTemplateResourceClass PC {}
EntityTemplateResourceClass HEADLESS {}
}
}
@@ -0,0 +1,436 @@
// Prepared control mechanism only. No global hooks, force allocation or HQ selection.
enum ERR_AircraftFlightState
{
IDLE, HOOKS, STARTING, POSITIVE, NEGATIVE, CLIMB, LANDING, SHUTDOWN,
COMPLETE, CANCELLED, FAILED, RELINQUISHED, TRANSLATING
}
class ERR_AircraftFlightRules
{
static bool Terminal(int state) { return state >= ERR_AircraftFlightState.COMPLETE && state <= ERR_AircraftFlightState.RELINQUISHED; }
static bool Deadline(float now, float deadline) { return now >= 0 && deadline > now && deadline - now <= 240000; }
static bool Envelope(float height, float drift, float upright)
{
return height >= -3 && height <= 18 && drift >= 0 && drift < 8 && upright >= 0.85;
}
static bool Hover(float height, float speed, float vertical, float upright)
{
return height >= 7 && height <= 9 && speed >= 0 && speed < 1.5 && Math.AbsFloat(vertical) < 0.5 && upright > 0.98;
}
static bool Landed(bool contact, float height, float speed, float angular, float upright)
{
return contact && height > -1 && height < 1 && speed >= 0 && speed < 0.5 && angular >= 0 && angular < 0.1 && upright > 0.98;
}
static float Vertical(float height, bool landing)
{
float target = 8; if (landing) target = 0;
return Math.Clamp((target - height) * 0.6, -0.8, 1.5);
}
// Ground aborts may stop the engine; airborne cancellation retains the flight lease.
static int CancelAction(bool ownershipValid, bool landed, bool engineOn)
{
if (!ownershipValid) return 0;
if (!landed) return 1;
if (engineOn) return 2;
return 3;
}
}
// Bounded migration of the separately proven native-autohover translation.
class ERR_AircraftTranslationRules
{
static bool Target(vector home, vector target)
{
float distance = vector.DistanceXZ(home, target);
return distance >= 0 && distance <= 5;
}
static bool Reachable(float distance) { return distance >= 0 && distance <= 4.05; }
static vector Demand(vector target, vector position, vector velocity)
{
vector error = target - position; error[1] = 0;
vector desired = error * 0.25;
float speed = desired.Length();
if (speed > 0.5) desired = desired * (0.5 / speed);
velocity[1] = 0;
vector demand = (desired - velocity) * 0.05;
float magnitude = demand.Length();
if (magnitude > 0.03) demand = demand * (0.03 / magnitude);
return demand;
}
static bool Held(bool contact, float height, float speed, float vertical, float upright, float horizontalSpeed, float targetError, float displacement, float distance)
{
if (contact || !ERR_AircraftFlightRules.Hover(height, speed, vertical, upright)) return false;
if (!(horizontalSpeed >= 0 && horizontalSpeed < 0.25 && targetError >= 0 && targetError <= 0.75)) return false;
return Reachable(distance) && displacement >= Math.Max(0, distance - 0.75);
}
}
class ERR_AircraftFlight
{
protected static ref map<Vehicle, ERR_AircraftFlight> s_Owners = new map<Vehicle, ERR_AircraftFlight>();
protected ERR_GameMode m_Mode;
protected Vehicle m_Aircraft;
protected SCR_AIGroup m_Group;
protected IEntity m_Pilot;
protected PilotCompartmentSlot m_Seat;
protected HelicopterControllerComponent m_Controller;
protected VehicleHelicopterSimulation m_Simulation;
protected CharacterControllerComponent m_Character;
protected CharacterInputContext m_Input;
protected AIAgent m_Agent;
protected ActionManager m_Actions;
protected FuelManagerComponent m_Fuel;
protected ref ERR_AircraftFlightEnvelope m_Qualification;
protected ERR_AircraftFlightState m_State;
protected int m_Token;
protected vector m_Home;
protected float m_Heading;
protected float m_Deadline;
protected float m_StageDeadline;
protected float m_Stable = -1;
protected float m_LastTick = -1;
protected float m_LastControl = -1;
protected float m_InitialFuel;
protected float m_Collective = -0.8;
protected int m_ActionUpdates;
protected int m_HelicopterUpdates;
protected int m_GateUpdates;
protected int m_OriginalLOD;
protected CollectiveMode m_OriginalMode;
protected bool m_OriginalAutohover;
protected bool m_SettingsOwned;
protected bool m_Readback = true;
protected bool m_Cancelled;
protected bool m_StartAttempted;
protected string m_Reason;
protected bool m_TranslationRequested;
protected bool m_TranslationStarted;
protected bool m_TranslationProven;
protected vector m_TranslationTarget;
protected vector m_TranslationOrigin;
protected float m_TranslationDistance;
protected int m_TranslationUpdates;
ERR_AircraftFlightState State() { return m_State; }
string Reason() { return m_Reason; }
int OrderToken() { return m_Token; }
bool Finished() { return ERR_AircraftFlightRules.Terminal(m_State); }
Vehicle Aircraft() { return m_Aircraft; }
bool TranslationStarted() { return m_TranslationStarted; }
bool TranslationProven() { return m_TranslationProven; }
vector TranslationTarget() { return m_TranslationTarget; }
vector TranslationOrigin() { return m_TranslationOrigin; }
int TranslationUpdates() { return m_TranslationUpdates; }
// Absolute horizontal target fixed before takeoff. Its altitude is the existing
// eight-metre hover, not a caller-supplied route or a new qualification home.
bool StartTranslation(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftFlightEnvelope qualification, int token, float deadline, vector target)
{
if (m_StartAttempted || m_TranslationRequested || !qualification || !ERR_AircraftTranslationRules.Target(qualification.Home(), target)) return false;
m_TranslationRequested = true; m_TranslationTarget = target;
vector home = qualification.Home(); m_TranslationTarget[1] = home[1] + 8;
return Start(mode, aircraft, group, pilot, seat, qualification, token, deadline);
}
protected float Now()
{
if (!GetGame() || !GetGame().GetWorld()) return -1;
return GetGame().GetWorld().GetWorldTime();
}
protected bool NoPlayers()
{
if (!GetGame() || !GetGame().GetPlayerManager()) return false;
if (!m_Group || m_Group.GetPlayerCount(true) > 0 || ERR_Actors.Player(m_Pilot)) return false;
array<int> players = {}; GetGame().GetPlayerManager().GetPlayers(players);
foreach (int id : players)
{
IEntity actor = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
if (actor && (actor == m_Pilot || CompartmentAccessComponent.GetVehicleIn(actor) == m_Aircraft)) return false;
}
return true;
}
protected bool Owns()
{
if (!GetGame() || !GetGame().GetWorld() || !GetGame().GetPlayerManager()) return false;
if (!Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || !m_Aircraft || !m_Group || !m_Pilot) return false;
if (!m_Controller || m_Controller.GetOwner() != m_Aircraft || !m_Seat || m_Controller.GetPilotCompartmentSlot() != m_Seat || m_Seat.GetOccupant() != m_Pilot) return false;
if (CompartmentAccessComponent.GetVehicleIn(m_Pilot) != m_Aircraft || ERR_Actors.Group(m_Pilot) != m_Group || m_Group.GetFaction() != m_Mode.EnemyFaction()) return false;
if (SCR_FactionManager.SGetFaction(m_Aircraft) != m_Mode.EnemyFaction() || !NoPlayers()) return false;
return m_Agent && m_Agent.GetControlledEntity() == m_Pilot && m_Agent.GetActionManager() == m_Actions && m_Actions && m_Actions != GetGame().GetInputManager();
}
protected bool Healthy()
{
if (!ERR_Actors.Alive(m_Aircraft) || !ERR_Actors.CanFight(m_Pilot) || !m_Simulation || !m_Simulation.IsValid() || !m_Fuel || !m_Fuel.HasFuel()) return false;
SCR_VehicleDamageManagerComponent damage = SCR_VehicleDamageManagerComponent.Cast(m_Aircraft.FindComponent(SCR_VehicleDamageManagerComponent));
if (!damage || !damage.GetEngineFunctional() || damage.GetMovementDamage() >= 1 || damage.IsOnFire() || m_Simulation.RotorCount() != 2) return false;
for (int rotor = 0; rotor < 2; rotor++) if (m_Simulation.RotorGetState(rotor) == RotorState.DESTROYED) return false;
return true;
}
protected bool Grounded()
{
if (!GetGame() || !GetGame().GetWorld() || !m_Aircraft || !m_Simulation) return false;
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
return ERR_AircraftFlightRules.Landed(m_Simulation.HasAnyGroundContact(), transform[3][1] - m_Home[1], m_Simulation.GetVelocity().Length(), m_Simulation.GetAngularVelocity().Length(), transform[1][1]);
}
// Single use. A future HQ owner supplies its immutable unique order token and
// retains this instance, calling Tick and the two scoped callbacks until terminal.
bool Start(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftFlightEnvelope qualification, int token, float deadline)
{
if (m_StartAttempted || m_State != ERR_AircraftFlightState.IDLE || m_Token != 0 || !Replication.IsServer() || !GetGame() || !qualification || !aircraft || !pilot || !group || !seat) return false;
m_StartAttempted = true;
float now = Now();
if (!ERR_AircraftFlightRules.Deadline(now, deadline) || token <= 0 || s_Owners.Get(aircraft)) return false;
m_Mode = mode; m_Aircraft = aircraft; m_Group = group; m_Pilot = pilot; m_Seat = seat;
m_Controller = HelicopterControllerComponent.Cast(aircraft.FindComponent(HelicopterControllerComponent));
if (m_Controller) m_Simulation = m_Controller.GetSimulation();
m_Character = CharacterControllerComponent.Cast(pilot.FindComponent(CharacterControllerComponent));
if (m_Character) m_Input = m_Character.GetInputContext();
AIControlComponent control = AIControlComponent.Cast(pilot.FindComponent(AIControlComponent));
if (control) m_Agent = control.GetControlAIAgent();
if (m_Agent) m_Actions = m_Agent.GetActionManager();
m_Fuel = FuelManagerComponent.Cast(aircraft.FindComponent(FuelManagerComponent));
if (!Owns() || !Healthy() || !m_Input || m_Controller.IsEngineOn() || !ERR_CasualtyLoadingRules.AircraftReady(aircraft)) return false;
Physics physics = aircraft.GetPhysics();
if (!physics || !physics.IsDynamic() || !physics.IsActive() || !m_Simulation.HasAnyGroundContact()) return false;
SignalsManagerComponent signals = m_Controller.GetSignalsManager();
if (!signals || signals.FindSignal("MainRotorCollective") < 0) return false;
if (!qualification.Claim(mode, aircraft, pilot, token)) return false;
m_Qualification = qualification; m_Home = qualification.Home(); m_Token = token; m_Deadline = deadline;
m_Heading = aircraft.GetYawPitchRoll()[0]; m_InitialFuel = m_Fuel.GetTotalFuel();
m_OriginalMode = m_Controller.GetCollectiveMode(); m_OriginalAutohover = m_Controller.GetAutohoverEnabled(); m_OriginalLOD = m_Agent.GetPermanentLOD();
m_SettingsOwned = true; s_Owners.Set(aircraft, this);
m_Agent.SetPermanentLOD(0); m_Agent.ActivateAI();
m_Controller.SetAutohoverEnabled(false);
if (m_Controller.GetAutohoverEnabled()) { Fail("native autohover disable refused"); return false; }
m_State = ERR_AircraftFlightState.HOOKS; m_StageDeadline = Math.Min(deadline, now + 5000);
PrintFormat("ERR AIRCRAFT CONTROL: token=%1 bound qualified finite aircraft; no HQ dispatch installed", token);
return true;
}
protected bool Lease()
{
return m_Token > 0 && !Finished() && m_State != ERR_AircraftFlightState.IDLE && s_Owners.Get(m_Aircraft) == this && m_Qualification && m_Qualification.ClaimedBy(m_Aircraft, m_Pilot, m_Token);
}
// Every input entry checks the immutable deadlines even if owner Tick stalls.
protected bool ControlLease()
{
if (!Lease()) return false;
if (!Owns()) { Relinquish("ownership or world unavailable at control callback"); return false; }
float now = Now();
if (now < 0 || now >= m_Deadline || now >= m_StageDeadline) { Fail("control callback reached bounded flight deadline"); return false; }
if (!Healthy() || !m_Readback) { Fail("flight capability or input readback lost"); return false; }
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
if (!ERR_AircraftFlightRules.Envelope(transform[3][1] - m_Home[1], vector.DistanceXZ(transform[3], m_Home), transform[1][1])) { Fail("control callback exceeded qualified flight envelope"); return false; }
return true;
}
// Must be called after the native helicopter's OnPrepareControls.
void PrepareHelicopter(HelicopterControllerComponent controller)
{
if (!Lease() || controller != m_Controller) return;
if (!ControlLease()) return;
controller.SetCollectiveMode(CollectiveMode.Direct); m_HelicopterUpdates++;
}
protected bool Pair(string positive, string negative, float demand)
{
float a = Math.Clamp(demand, 0, 1), b = Math.Clamp(-demand, 0, 1);
m_Actions.SetActionValue(positive, a); m_Actions.SetActionValue(negative, b);
return Math.AbsFloat(m_Actions.GetActionValue(positive) - a) < 0.001 && Math.AbsFloat(m_Actions.GetActionValue(negative) - b) < 0.001;
}
// Must be called on the exact AI actor manager before native OnPrepareControls.
void PrepareActions(CharacterControllerComponent controller, IEntity owner, ActionManager actions, float dt)
{
if (!Lease() || controller != m_Character || owner != m_Pilot || actions != m_Actions) return;
if (!ControlLease()) return;
if (!m_Readback || dt <= 0 || dt > 0.2) return;
if (!actions.IsContextActive("HelicopterContext")) actions.ActivateContext("HelicopterContext", 1);
if (!actions.IsContextActive("HelicopterContext")) { m_Readback = false; return; }
float collective, torque, forward, aside;
if (m_State == ERR_AircraftFlightState.POSITIVE) collective = 0.05;
else if (m_State == ERR_AircraftFlightState.NEGATIVE) collective = -0.05;
else if (m_State == ERR_AircraftFlightState.SHUTDOWN) collective = -1;
else if (m_State == ERR_AircraftFlightState.CLIMB || m_State == ERR_AircraftFlightState.LANDING || m_State == ERR_AircraftFlightState.TRANSLATING)
{
if (!m_Controller.GetAutohoverEnabled()) { Fail("native autohover lost while flying"); return; }
float height = m_Aircraft.GetOrigin()[1] - m_Home[1];
float error = ERR_AircraftFlightRules.Vertical(height, m_State == ERR_AircraftFlightState.LANDING) - m_Simulation.GetVelocity()[1];
m_Collective = Math.Clamp(m_Collective + error * dt * 0.12, -0.95, 0.8);
collective = Math.Clamp(m_Collective + error * 0.12, -1, 0.9);
float headingError = m_Heading - m_Aircraft.GetYawPitchRoll()[0];
while (headingError > 180) headingError -= 360;
while (headingError < -180) headingError += 360;
torque = Math.Clamp(headingError * 0.01 - m_Simulation.GetAngularVelocityLocal()[1] * 0.3, -0.3, 0.3);
if (m_State == ERR_AircraftFlightState.TRANSLATING)
{
if (!m_TranslationStarted || !ERR_AircraftTranslationRules.Target(m_Home, m_TranslationTarget)) { Fail("immutable translation target unavailable"); return; }
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
vector demand = ERR_AircraftTranslationRules.Demand(m_TranslationTarget, transform[3], m_Simulation.GetVelocity());
forward = Math.Clamp(vector.Dot(demand, transform[2]), -0.03, 0.03);
aside = Math.Clamp(vector.Dot(demand, transform[0]), -0.03, 0.03);
m_TranslationUpdates++;
}
if (m_State == ERR_AircraftFlightState.LANDING && Grounded()) collective = -1;
}
bool a = Pair("HelicopterCollectiveIncrease", "HelicopterCollectiveDecrease", collective);
bool b = Pair("HelicopterCyclicForward", "HelicopterCyclicBack", forward);
bool c = Pair("HelicopterCyclicRight", "HelicopterCyclicLeft", aside);
bool d = Pair("HelicopterAntiTorqueRight", "HelicopterAntiTorqueLeft", torque);
m_Readback = a && b && c && d; m_ActionUpdates++; m_LastControl = Now();
}
protected void Release(bool restore)
{
// No writes after capture/group/seat change, including apparently harmless zeros.
if (m_SettingsOwned && Owns())
{
Pair("HelicopterCollectiveIncrease", "HelicopterCollectiveDecrease", 0);
Pair("HelicopterCyclicForward", "HelicopterCyclicBack", 0);
Pair("HelicopterCyclicRight", "HelicopterCyclicLeft", 0);
Pair("HelicopterAntiTorqueRight", "HelicopterAntiTorqueLeft", 0);
// Restore native modes only after verified ground completion. Airborne
// failure leaves native autohover enabled rather than disabling it midair.
if (restore && Grounded())
{
m_Controller.SetAutohoverEnabled(m_OriginalAutohover);
m_Controller.SetCollectiveMode(m_OriginalMode);
m_Agent.SetPermanentLOD(m_OriginalLOD);
}
}
if (m_Aircraft && s_Owners && s_Owners.Get(m_Aircraft) == this) s_Owners.Remove(m_Aircraft);
m_SettingsOwned = false;
}
protected void RecordState()
{
string detail = string.Format("token=%1 state=%2 reason=%3; physical availability remains with the allocating owner", m_Token, m_State, m_Reason);
Print("ERR AIRCRAFT CONTROL: " + detail);
if (m_Mode && m_Mode == ERR_GameMode.Get()) ERR_HQ.Event("AIRCRAFT CONTROL", detail);
}
protected void Relinquish(string reason)
{
if (Finished()) return;
m_Reason = reason; m_State = ERR_AircraftFlightState.RELINQUISHED; Release(false); RecordState();
}
protected void Fail(string reason)
{
if (Finished()) return;
m_Reason = reason; m_State = ERR_AircraftFlightState.FAILED; Release(true); RecordState();
}
protected bool Landing(float now)
{
m_Controller.SetAutohoverEnabled(true);
if (!m_Controller.GetAutohoverEnabled()) { Fail("native autohover enable refused"); return false; }
m_State = ERR_AircraftFlightState.LANDING; m_Stable = -1;
m_StageDeadline = Math.Min(m_Deadline, now + 65000);
return true;
}
protected void Shutdown(float now)
{
m_Controller.StopEngine(false);
m_State = ERR_AircraftFlightState.SHUTDOWN; m_StageDeadline = Math.Min(m_Deadline, now + 15000); m_Stable = -1;
}
void Cancel(string reason)
{
if (!Lease()) return;
if (m_Cancelled) return; // repeated invalid reports cannot extend a landing deadline
m_Cancelled = true; m_Reason = reason; RecordState();
// After flight begins, even a current contact sample must pass the normal
// two-second landing observation; cancellation cannot shortcut touchdown.
bool preflightGround = m_State < ERR_AircraftFlightState.CLIMB && Grounded();
int action = ERR_AircraftFlightRules.CancelAction(Owns(), preflightGround, m_Controller && m_Controller.IsEngineOn());
if (action == 0) { Relinquish(reason); return; }
if (!Healthy()) { Fail(reason + "; flight capability lost"); return; }
if (action == 3) { m_State = ERR_AircraftFlightState.CANCELLED; Release(true); RecordState(); return; }
float now = Now();
if (action == 2) { Shutdown(now); return; }
if (m_State != ERR_AircraftFlightState.LANDING && m_State != ERR_AircraftFlightState.SHUTDOWN) Landing(now);
}
protected bool BeginTranslation(float now, vector position)
{
if (!m_TranslationRequested || m_TranslationStarted || m_State != ERR_AircraftFlightState.CLIMB) { Cancel("translation stage ownership refused"); return false; }
float distance = vector.DistanceXZ(position, m_TranslationTarget);
if (!ERR_AircraftTranslationRules.Target(m_Home, m_TranslationTarget) || !ERR_AircraftTranslationRules.Reachable(distance)) { Cancel("fixed target beyond qualified four-metre translation after hover drift"); return false; }
SignalsManagerComponent signals = m_Controller.GetSignalsManager();
if (!signals || signals.FindSignal("MainRotorCyclicForward") < 0 || signals.FindSignal("MainRotorCyclicAside") < 0 || !m_Controller.GetAutohoverEnabled()) { Cancel("native autohover cyclic signals unavailable"); return false; }
m_TranslationOrigin = position; m_TranslationDistance = distance;
m_TranslationStarted = true; m_State = ERR_AircraftFlightState.TRANSLATING;
m_Stable = -1; m_StageDeadline = Math.Min(m_Deadline, now + 45000);
PrintFormat("ERR AIRCRAFT TRANSLATION: token=%1 origin=%2 immutableTarget=%3 home=%4 distance=%5 deadline=%6", m_Token, position, m_TranslationTarget, m_Home, distance, m_StageDeadline);
return true;
}
protected void CheckTranslation(float now, vector position, float height, float speed, float upright)
{
if (!m_TranslationStarted || !m_Controller.GetAutohoverEnabled() || !ERR_AircraftTranslationRules.Target(m_Home, m_TranslationTarget)) { Cancel("translation ownership or autohover lost"); return; }
vector velocity = m_Simulation.GetVelocity(); vector horizontal = velocity; horizontal[1] = 0;
bool held = m_TranslationUpdates >= 3 && ERR_AircraftTranslationRules.Held(m_Simulation.HasAnyGroundContact(), height, speed, velocity[1], upright, horizontal.Length(), vector.DistanceXZ(position, m_TranslationTarget), vector.DistanceXZ(position, m_TranslationOrigin), m_TranslationDistance);
if (!held) { m_Stable = -1; return; }
if (m_Stable < 0) m_Stable = now;
if (now - m_Stable < 5000) return;
m_TranslationProven = true;
PrintFormat("ERR AIRCRAFT TRANSLATION HELD: token=%1 targetError=%2 displacement=%3 updates=%4", m_Token, vector.DistanceXZ(position, m_TranslationTarget), vector.DistanceXZ(position, m_TranslationOrigin), m_TranslationUpdates);
Landing(now);
}
// The owner continues ticking after Cancel until Finished, including landing.
void Tick()
{
if (!Lease()) return;
float now = Now();
if (!Owns()) { Relinquish("ownership or mission changed"); return; }
if (!Healthy()) { Fail("flight capability lost"); return; }
if (!m_Readback) { Fail("native action readback failed"); return; }
if (now >= m_Deadline || now >= m_StageDeadline || now < 0) { Fail("bounded flight stage expired"); return; }
if (m_LastTick >= 0 && (now < m_LastTick || now - m_LastTick > 1500)) m_Stable = -1;
m_LastTick = now;
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
float height = transform[3][1] - m_Home[1], speed = m_Simulation.GetVelocity().Length();
if (!ERR_AircraftFlightRules.Envelope(height, vector.DistanceXZ(transform[3], m_Home), transform[1][1])) { Fail("qualified flight envelope exceeded"); return; }
if (m_State == ERR_AircraftFlightState.HOOKS)
{
if (m_ActionUpdates < 3 || m_HelicopterUpdates < 3) return;
if (m_Controller.GetCollectiveMode() != CollectiveMode.Direct || m_Input.GetVehicleCollective() != 0 || m_Controller.IsEngineOn()) { Fail("native initial control state refused"); return; }
m_Controller.StartEngine(); m_State = ERR_AircraftFlightState.STARTING; m_StageDeadline = Math.Min(m_Deadline, now + 65000); return;
}
if (m_LastControl < 0 || now - m_LastControl > 1000) { Fail("pilot callback stopped"); return; }
if (m_State == ERR_AircraftFlightState.STARTING)
{
float rpm = m_Simulation.RotorGetRPMTarget(0);
if (!m_Controller.IsEngineOn() || rpm <= 0 || m_Simulation.RotorGetRPM(0) < rpm * 0.9 || m_Fuel.GetTotalFuel() >= m_InitialFuel) return;
m_State = ERR_AircraftFlightState.POSITIVE; m_StageDeadline = Math.Min(m_Deadline, now + 3000); m_GateUpdates = m_ActionUpdates; m_Stable = -1; return;
}
if (m_State != ERR_AircraftFlightState.SHUTDOWN && !m_Controller.IsEngineOn()) { Fail("running engine lost"); return; }
if (m_State == ERR_AircraftFlightState.POSITIVE || m_State == ERR_AircraftFlightState.NEGATIVE)
{
SignalsManagerComponent signals = m_Controller.GetSignalsManager();
int index = -1; if (signals) index = signals.FindSignal("MainRotorCollective");
if (index < 0 || m_Controller.GetCollectiveMode() != CollectiveMode.Direct || m_Controller.GetAutohoverEnabled()) { Fail("native collective proof unavailable"); return; }
float expected = 0.05; if (m_State == ERR_AircraftFlightState.NEGATIVE) expected = -0.05;
bool matched = m_ActionUpdates >= m_GateUpdates + 3 && Math.AbsFloat(signals.GetSignalValue(index) - expected) <= 0.01 && Math.AbsFloat(m_Input.GetVehicleCollective() - expected) <= 0.01;
if (!matched) { m_Stable = -1; return; }
if (m_Stable < 0) m_Stable = now;
if (now - m_Stable < 500) return;
m_Stable = -1;
if (m_State == ERR_AircraftFlightState.POSITIVE) { m_State = ERR_AircraftFlightState.NEGATIVE; m_StageDeadline = Math.Min(m_Deadline, now + 3000); m_GateUpdates = m_ActionUpdates; return; }
m_Controller.SetAutohoverEnabled(true);
if (!m_Controller.GetAutohoverEnabled()) { Fail("native autohover enable refused"); return; }
m_State = ERR_AircraftFlightState.CLIMB; m_StageDeadline = Math.Min(m_Deadline, now + 65000); return;
}
if (m_State == ERR_AircraftFlightState.TRANSLATING) { CheckTranslation(now, transform[3], height, speed, transform[1][1]); return; }
if (m_State == ERR_AircraftFlightState.CLIMB || m_State == ERR_AircraftFlightState.LANDING)
{
if (!m_Controller.GetAutohoverEnabled()) { Fail("native autohover lost"); return; }
bool stable = Grounded(); float duration = 2000;
if (m_State == ERR_AircraftFlightState.CLIMB) { stable = !m_Simulation.HasAnyGroundContact() && ERR_AircraftFlightRules.Hover(height, speed, m_Simulation.GetVelocity()[1], transform[1][1]); duration = 5000; }
if (!stable) m_Stable = -1;
if (stable && m_Stable < 0) m_Stable = now;
if (m_Stable < 0 || now - m_Stable < duration) return;
if (m_State == ERR_AircraftFlightState.CLIMB)
{
if (m_TranslationRequested) BeginTranslation(now, transform[3]);
else Landing(now);
return;
}
Shutdown(now); return;
}
if (m_State == ERR_AircraftFlightState.SHUTDOWN && !m_Controller.IsEngineOn())
{
if (!Grounded()) { Fail("engine-off physical ground check failed"); return; }
if (m_Cancelled) m_State = ERR_AircraftFlightState.CANCELLED;
else m_State = ERR_AircraftFlightState.COMPLETE;
Release(true); RecordState();
}
}
void ~ERR_AircraftFlight() { Release(true); }
}
@@ -0,0 +1,236 @@
// Successor-owned measured envelope, independent of aircraft yaw/pitch/roll.
// Adapted from the separately qualified native vertical fixture; no native hook.
// All distances are around the native physics origin, not the rotor centre.
class ERR_AircraftFlightFootprintRules
{
static float CylinderSphere(float originDistance, float radius, float thickness)
{
// Triangle inequality deliberately overbounds the one-sided rotor cylinder.
return originDistance + radius + thickness;
}
static bool Fits(float radius, float initialDrift, float initialHeight)
{
if (!(radius > 0 && radius < 32 && initialDrift >= 0 && initialDrift < 32)) return false;
if (!(initialHeight >= -3 && initialHeight <= 5)) return false;
return radius + initialDrift + 8 + 1 <= 40 && radius + initialHeight + 18 + 1 <= 60;
}
}
class ERR_AircraftFlightFootprint
{
static const int MAX_ENTITIES = 256;
static const int MAX_DEPTH = 16;
static const int MAX_HITZONES = 512;
protected static bool Refuse(string reason)
{
Print("ERR AIRCRAFT FOOTPRINT: refused " + reason);
return false;
}
protected static bool PointRadius(vector point, vector origin, inout float radius)
{
float distance = vector.Distance(point, origin);
if (!(distance >= 0 && distance < 1000)) return false;
radius = Math.Max(radius, distance);
return true;
}
static bool Measure(Vehicle aircraft, VehicleHelicopterSimulation simulation, vector pad, out vector origin, out float radius)
{
radius = 0;
if (!aircraft || !simulation || !simulation.IsValid() || !aircraft.GetPrefabData()) return Refuse("missing initialized native airframe");
if (aircraft.GetPrefabData().GetPrefabName() != "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et") return Refuse("different airframe resource");
// Model bounds and damage pivots are world-space. The simulation getter
// exposes internal physics space (identity at the airport in 100422), so
// use this same physical aircraft's actual entity world transform.
vector worldTransform[4]; aircraft.GetWorldTransform(worldTransform);
origin = worldTransform[3];
for (int axis = 0; axis < 3; axis++)
if (!(Math.AbsFloat(worldTransform[axis].Length() - 1) < 0.001)) return Refuse("non-unit native entity world basis");
array<IEntity> entities = {aircraft};
array<int> depths = {0};
array<HitZone> seenZones = {};
int meshes, mainSweeps, tailSweeps;
float modelRadius, damageRadius, simulationRadius;
for (int index = 0; index < entities.Count(); index++)
{
IEntity entity = entities[index];
if (!entity) return Refuse("hierarchy changed during measurement");
if (entity.GetVObject())
{
vector mins, maxs; entity.GetWorldBounds(mins, maxs);
for (int dimension = 0; dimension < 3; dimension++)
if (!(mins[dimension] <= maxs[dimension])) return Refuse("invalid model bounds");
// A sphere around every corner encloses the complete world AABB. Its radius
// remains conservative under any rigid orientation of the whole aircraft.
for (int x = 0; x < 2; x++)
for (int y = 0; y < 2; y++)
for (int z = 0; z < 2; z++)
{
vector corner = mins;
if (x == 1) corner[0] = maxs[0];
if (y == 1) corner[1] = maxs[1];
if (z == 1) corner[2] = maxs[2];
if (!PointRadius(corner, origin, modelRadius)) return Refuse("non-finite or excessive model bounds");
}
meshes++;
}
HitZoneContainerComponent container = HitZoneContainerComponent.Cast(entity.FindComponent(HitZoneContainerComponent));
if (container)
{
int count = container.CountAllHitZones();
if (count < 0 || count + seenZones.Count() > MAX_HITZONES) return Refuse("hitzone traversal bound");
array<HitZone> zones = {};
if (container.GetAllHitZones(zones) != count || zones.Count() != count) return Refuse("hitzones changed during measurement");
foreach (HitZone zone : zones)
{
if (!zone || seenZones.Contains(zone)) continue;
seenZones.Insert(zone);
SCR_RotorHitZone rotorZone = SCR_RotorHitZone.Cast(zone);
if (!rotorZone || rotorZone.GetRotorRadius() <= 0) continue; // transmission zones have no sweep
int rotorIndex = rotorZone.GetRotorIndex();
float expectedRadius, thickness;
if (rotorIndex == 0) { expectedRadius = 12; thickness = 1.5; mainSweeps++; }
else if (rotorIndex == 1) { expectedRadius = 2.3; thickness = 0.75; tailSweeps++; }
else return Refuse("unknown rotor sweep index");
// Stock thickness is protected with no getter. Scope to the exact prefab
// and verify its public sweep radius against the audited stock constants.
if (!(Math.AbsFloat(rotorZone.GetRotorRadius() - expectedRadius) < 0.001)) return Refuse("stock damage sweep radius changed");
PointInfo point = rotorZone.GetPointInfo();
if (!point) return Refuse("missing actual rotor sweep pivot");
vector rotorWorld[4]; point.GetWorldTransform(rotorWorld);
// Frobenius norm bounds the transform's maximum stretch even with scale
// or shear; do not assume the PointInfo basis is orthonormal.
float scaleSquared = 0;
for (int basis = 0; basis < 3; basis++) scaleSquared += vector.Dot(rotorWorld[basis], rotorWorld[basis]);
float scale = Math.Sqrt(scaleSquared);
if (!(scale > 0 && scale < 4)) return Refuse("invalid damage sweep basis");
float distance = vector.Distance(origin, rotorWorld[3]);
float sweepBound = ERR_AircraftFlightFootprintRules.CylinderSphere(distance, expectedRadius * scale, thickness * scale);
if (!(sweepBound > 0 && sweepBound < 1000)) return Refuse("invalid damage sweep extent");
damageRadius = Math.Max(damageRadius, sweepBound);
PrintFormat("ERR AIRCRAFT FOOTPRINT ROTOR: index=%1 pivot=%2 damageRadius=%3 thickness=%4 scale=%5 sphereBound=%6", rotorIndex, rotorWorld[3], expectedRadius, thickness, scale, sweepBound);
}
}
IEntity child = entity.GetChildren();
while (child)
{
if (entities.Count() >= MAX_ENTITIES || depths[index] >= MAX_DEPTH || entities.Contains(child)) return Refuse("entity/depth/cycle traversal bound");
entities.Insert(child); depths.Insert(depths[index] + 1);
child = child.GetSibling();
}
}
if (meshes == 0 || mainSweeps != 1 || tailSweeps != 1) return Refuse("complete model and both actual damage sweeps required");
if (simulation.RotorCount() != 2) return Refuse("stock simulation rotor count changed");
for (int rotor = 0; rotor < 2; rotor++)
{
vector localRotor[4]; simulation.RotorGetTransform(rotor, localRotor);
float physicalRadius = simulation.RotorGetRadius(rotor);
float physicalBound = localRotor[3].Length() + physicalRadius;
if (!(physicalRadius > 0 && physicalBound > 0 && physicalBound < 1000)) return Refuse("invalid native simulation rotor extent");
simulationRadius = Math.Max(simulationRadius, physicalBound);
PrintFormat("ERR AIRCRAFT FOOTPRINT PHYSICS ROTOR: index=%1 localPivot=%2 radius=%3 sphereBound=%4", rotor, localRotor[3], physicalRadius, physicalBound);
}
radius = Math.Max(modelRadius, Math.Max(damageRadius, simulationRadius));
float initialDrift = vector.DistanceXZ(origin, pad);
float initialHeight = origin[1] - pad[1];
PrintFormat("ERR AIRCRAFT FOOTPRINT: entities=%1 meshes=%2 zones=%3 modelSphere=%4 damageSphere=%5 simulationSphere=%6 completeSphere=%7 initialDrift=%8 initialHeight=%9", entities.Count(), meshes, seenZones.Count(), modelRadius, damageRadius, simulationRadius, radius, initialDrift, initialHeight);
if (!ERR_AircraftFlightFootprintRules.Fits(radius, initialDrift, initialHeight)) return Refuse("measured footprint plus initial placement,8m drift and1m margin exceeds40m corridor or60m ceiling");
return true;
}
}
// Issued only by physical qualification; fields have no public mutators.
// One claim binds this short-lived record to one original aircraft/pilot/order.
class ERR_AircraftFlightEnvelope
{
protected ERR_GameMode m_Mode;
protected Vehicle m_Aircraft;
protected IEntity m_Pilot;
protected vector m_Home;
protected float m_Radius;
protected float m_QualifiedAt;
protected int m_Claim;
vector Home() { return m_Home; }
float Radius() { return m_Radius; }
bool ClaimedBy(Vehicle aircraft, IEntity pilot, int token)
{
return m_Claim > 0 && m_Claim == token && m_Aircraft == aircraft && m_Pilot == pilot;
}
bool Claim(ERR_GameMode mode, Vehicle aircraft, IEntity pilot, int token)
{
if (!GetGame() || !GetGame().GetWorld()) return false;
float now = GetGame().GetWorld().GetWorldTime();
if (!Replication.IsServer() || !mode || mode != m_Mode || mode != ERR_GameMode.Get() || !mode.Active() || m_Claim != 0 || token <= 0) return false;
if (aircraft != m_Aircraft || pilot != m_Pilot || !aircraft || !pilot || now < m_QualifiedAt || now - m_QualifiedAt > 2000) return false;
if (vector.Distance(aircraft.GetOrigin(), m_Home) > 0.1) return false;
m_Claim = token;
return true;
}
protected static bool Hierarchy(IEntity root, array<IEntity> excluded)
{
if (!root) return false;
array<IEntity> pending = {root};
array<int> depths = {0};
for (int i = 0; i < pending.Count(); i++)
{
IEntity entity = pending[i];
if (!entity || excluded.Count() >= 512) return false;
if (!excluded.Contains(entity)) excluded.Insert(entity);
IEntity child = entity.GetChildren();
while (child)
{
if (pending.Count() >= 256 || depths[i] >= 16 || pending.Contains(child)) return false;
pending.Insert(child); depths.Insert(depths[i] + 1); child = child.GetSibling();
}
}
return true;
}
protected static bool ClearColumn(Vehicle aircraft, IEntity pilot, vector pad)
{
array<IEntity> excluded = {};
if (!Hierarchy(aircraft, excluded) || !Hierarchy(pilot, excluded)) return false;
for (int edge = 0; edge < 4; edge++)
{
float side = edge * Math.PI / 2;
vector point = pad + Vector(Math.Cos(side) * 14, 0, Math.Sin(side) * 14);
if (!(Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(point) - pad[1]) <= 0.8)) return false;
}
// Same four finite diagonal cylinder traces as the native fixture, with
// exclusions restricted to this already-existing aircraft and seated pilot.
array<vector> diagonals = {"40 2 0", "-40 2 0", "0 2 40", "0 2 -40"};
foreach (vector diagonal : diagonals)
{
TraceParam trace = new TraceParam();
trace.Start = pad + "0 2.5 0" + diagonal;
trace.End = pad + "0 2.5 0" - diagonal;
trace.Flags = TraceFlags.ENTS; trace.ExcludeArray = excluded;
if (!(aircraft.GetWorld().TraceMove(trace, null) >= 1)) return false;
}
TraceBox clearance = new TraceBox();
clearance.Start = pad + "0 3 0"; clearance.End = pad + "0 60 0";
clearance.Mins = "-40 -1 -40"; clearance.Maxs = "40 1 40";
clearance.Flags = TraceFlags.ENTS | TraceFlags.WORLD; clearance.ExcludeArray = excluded;
return aircraft.GetWorld().TraceMove(clearance, null) >= 1;
}
static ERR_AircraftFlightEnvelope Qualify(ERR_GameMode mode, Vehicle aircraft, IEntity pilot)
{
if (!GetGame() || !GetGame().GetWorld() || !GetGame().GetPlayerManager()) return null;
if (!Replication.IsServer() || !mode || mode != ERR_GameMode.Get() || !mode.Active() || !aircraft || !pilot || ERR_Actors.Player(pilot)) return null;
if (CompartmentAccessComponent.GetVehicleIn(pilot) != aircraft || !ERR_Actors.Alive(aircraft) || !ERR_Actors.CanFight(pilot)) return null;
HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(aircraft.FindComponent(HelicopterControllerComponent));
if (!controller || controller.IsEngineOn() || !controller.GetSimulation() || !controller.GetSimulation().IsValid()) return null;
PilotCompartmentSlot seat = controller.GetPilotCompartmentSlot();
if (!seat || seat.GetOccupant() != pilot) return null;
if (!ERR_CasualtyLoadingRules.AircraftReady(aircraft)) return null;
vector pad = ERR_Actors.Ground(aircraft.GetOrigin());
if (!(pad[1] >= 2) || !ClearColumn(aircraft, pilot, pad)) return null;
vector origin; float radius;
if (!ERR_AircraftFlightFootprint.Measure(aircraft, controller.GetSimulation(), pad, origin, radius)) return null;
ERR_AircraftFlightEnvelope record = new ERR_AircraftFlightEnvelope();
record.m_Mode = mode; record.m_Aircraft = aircraft; record.m_Pilot = pilot;
record.m_Home = origin; record.m_Radius = radius;
record.m_QualifiedAt = GetGame().GetWorld().GetWorldTime();
return record;
}
}
@@ -0,0 +1,391 @@
class ERR_HQUnit
{
int Id;
SCR_AIGroup Group;
SCR_AIGroupUtilityComponent Utility;
IEntity Radio;
float NextReport;
ref ERR_HQRoute Uplink;
ref ERR_HQReport PendingReport;
ref ERR_HQRoute Downlink;
ref ERR_HQReport PendingOrder;
ref ERR_HQReport Assignment;
AIWaypoint Waypoint;
bool ReservedForSupport;
int LogisticsReservation;
ref ERR_MortarSupplyStatus PendingSupplyStatus;
bool Eligible(ERR_GameMode mode)
{
if (ReservedForSupport || LogisticsReservation > 0 || !Group || !Utility || Group.GetPlayerCount(true) > 0 || Group.IsSlave() || Group.GetFaction() != mode.EnemyFaction()) return false;
array<AIAgent> agents = {};
Group.GetAgents(agents);
bool survivor;
foreach (AIAgent agent : agents)
{
if (!agent || !ERR_Actors.CanFight(agent.GetControlledEntity())) continue;
survivor = true;
if (CompartmentAccessComponent.GetVehicleIn(agent.GetControlledEntity())) return false;
}
return survivor;
}
void Release()
{
if (Waypoint)
{
if (Group) Group.RemoveWaypoint(Waypoint);
SCR_EntityHelper.DeleteEntityAndChildren(Waypoint);
}
Waypoint = null;
Assignment = null;
PendingOrder = null;
Downlink = null;
}
}
class ERR_HQ : ERR_CommandService
{
protected ERR_GameMode m_Mode;
protected ref ERR_MortarSystem m_Mortars;
protected ref ERR_MortarLogistics m_Logistics;
protected ref array<ref ERR_HQUnit> m_Units = {};
protected ref array<ref ERR_HQReport> m_Contacts = {};
protected ref ERR_HQNetwork m_Network = new ERR_HQNetwork();
protected ref ERR_HQJournal m_Journal = new ERR_HQJournal();
protected int m_NextId;
protected float m_NextNetwork;
protected float m_NextAssignment;
void ERR_HQ(ERR_GameMode mode) { m_Mode = mode; m_Mortars = new ERR_MortarSystem(this); m_Logistics = new ERR_MortarLogistics(this, mode); }
static ERR_HQ Get()
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode) return null;
return ERR_HQ.Cast(mode.Command());
}
static bool MissionActive()
{
ERR_GameMode mode = ERR_GameMode.Get();
return mode && mode.Active();
}
static void Event(string kind, string message, int messageId = 0)
{
ERR_HQ hq = Get();
if (hq) hq.Journal().Write(kind, message, messageId);
}
ERR_MortarSystem Mortars() { return m_Mortars; }
bool PrepareSupport() { return m_Mortars.Prepare() && m_Logistics.Prepare(); }
ERR_MortarLogistics Logistics() { return m_Logistics; }
void ReserveGroup(SCR_AIGroup group)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group) continue;
unit.Release();
unit.ReservedForSupport = true;
}
}
// Temporary reservations never alter the permanent support reservation.
int LogisticsToken(SCR_AIGroup group)
{
foreach (ERR_HQUnit unit : m_Units) if (unit.Group == group) return unit.LogisticsReservation;
return 0;
}
bool ReserveLogistics(SCR_AIGroup group, int token)
{
if (!Replication.IsServer() || token <= 0 || !MissionActive()) return false;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group || !unit.Eligible(m_Mode) || !unit.Radio || unit.Assignment || unit.PendingOrder || unit.Downlink || unit.Waypoint) continue;
if (unit.Utility.m_Perception && !unit.Utility.m_Perception.m_aTargets.IsEmpty()) continue;
unit.LogisticsReservation = token;
return true;
}
return false;
}
void ReleaseLogistics(SCR_AIGroup group, int token)
{
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == group && unit.LogisticsReservation == token) unit.LogisticsReservation = 0;
}
bool QueueSupplyStatus(ERR_MortarBattery battery, float now)
{
if (!Replication.IsServer() || !MissionActive() || !battery || !battery.StartupActivated || !battery.Operational() || !m_Logistics.CanSample(battery) || !ERR_MortarLogisticsRules.Need(battery.CountShells())) return false;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != battery.Group || unit.Uplink || !unit.Radio || unit.Radio != battery.RadioOperator) continue;
ERR_HQRoute route = m_Network.Route(unit.Radio);
if (!route) return false;
ERR_MortarSupplyStatus report = new ERR_MortarSupplyStatus();
report.Group = battery.Group; report.Gun = battery.Gun; report.Position = battery.Gun.GetOrigin();
report.Shells = battery.CountShells(); report.ObservedAt = now; report.MessageId = m_Journal.Message();
route.MessageId = report.MessageId;
route.Payload = string.Format("mortar supply status; usable HE=%1; position=%2; sampled=%3", report.Shells, report.Position, report.ObservedAt);
unit.PendingSupplyStatus = report; unit.Uplink = route;
m_Journal.Write("SUPPLY REPORT", route.Payload, route.MessageId);
return true;
}
return false;
}
void PhysicalForces(out array<IEntity> entities)
{
entities.Clear();
foreach (ERR_HQUnit unit : m_Units)
{
if (!unit.Group) continue;
array<AIAgent> agents = {};
unit.Group.GetAgents(agents);
foreach (AIAgent agent : agents)
{
if (!agent || !agent.GetControlledEntity()) continue;
IEntity actor = agent.GetControlledEntity();
if (!entities.Contains(actor)) entities.Insert(actor);
IEntity vehicle = CompartmentAccessComponent.GetVehicleIn(actor);
if (vehicle && !entities.Contains(vehicle)) entities.Insert(vehicle);
}
}
m_Mortars.AppendGuns(entities);
}
bool MortarConnected(SCR_AIGroup group)
{
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == group && unit.Radio) return m_Network.Route(unit.Radio) != null;
return false;
}
ERR_HQRoute Downlink(SCR_AIGroup group, string kind, string detail, int sourceId)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group || !unit.Radio) continue;
ERR_HQRoute route = m_Network.Route(unit.Radio, true);
if (!route) return null;
route.MessageId = m_Journal.Message();
route.Payload = string.Format("%1 from report M%2; %3", kind, sourceId, detail);
m_Journal.Write("ORDER QUEUED", route.Payload, route.MessageId);
return route;
}
return null;
}
void SupportContacts(out array<ref ERR_HQReport> contacts, float now)
{
contacts.Clear();
foreach (ERR_HQReport contact : m_Contacts)
if (ERR_MortarPrediction.Fresh(contact, now)) contacts.Insert(contact.Snapshot());
}
ERR_HQJournal Journal() { return m_Journal; }
string Status()
{
return string.Format("HQ: %1 groups, %2 delivered contacts, %3 assignments", m_Units.Count(), m_Contacts.Count(), Assigned()) + "; " + m_Mortars.Status() + "; " + m_Logistics.Status();
}
override void RegisterGroup(SCR_AIGroup group)
{
if (!Replication.IsServer() || !group || m_Units.Count() >= 128) return;
foreach (ERR_HQUnit known : m_Units) if (known.Group == group) return;
SCR_AIGroupUtilityComponent utility = SCR_AIGroupUtilityComponent.Cast(group.FindComponent(SCR_AIGroupUtilityComponent));
if (!utility || group.GetFaction() != m_Mode.EnemyFaction()) return;
ERR_HQUnit unit = new ERR_HQUnit();
unit.Id = ++m_NextId;
unit.Group = group;
unit.Utility = utility;
int phase = unit.Id % 5;
unit.NextReport = GetGame().GetWorld().GetWorldTime() + phase * 1000;
m_Units.Insert(unit);
m_Journal.Write("REGISTER", string.Format("group %1", unit.Id));
}
override void UnregisterGroup(SCR_AIGroup group)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group) continue;
unit.Release();
unit.Group = null;
unit.Utility = null;
}
}
protected int Assigned()
{
int total;
foreach (ERR_HQUnit unit : m_Units) if (unit.Assignment || unit.PendingOrder) total++;
return total;
}
protected void IndexRadios()
{
m_Network.Clear();
foreach (ERR_HQUnit unit : m_Units)
{
unit.Radio = null;
if (!unit.Group || unit.Group.GetPlayerCount(true) > 0 || unit.Group.GetFaction() != m_Mode.EnemyFaction()) continue;
array<AIAgent> agents = {};
unit.Group.GetAgents(agents);
int best;
foreach (AIAgent agent : agents)
{
if (!agent) continue;
IEntity actor = agent.GetControlledEntity();
int capability = ERR_HQRadio.Capability(actor);
if (capability <= 0) continue;
m_Network.Add(actor, unit.Group, unit.Id, capability);
if (capability > best) { unit.Radio = actor; best = capability; }
}
}
m_Network.Build();
}
protected void Observe(ERR_HQUnit unit, float now)
{
if (!unit.Utility || !unit.Radio || unit.Uplink || now < unit.NextReport) return;
unit.NextReport = now + m_Mode.Settings().Get("reportIntervalSeconds") * 1000;
array<ref ERR_HQVisualContact> samples = {};
ERR_HQObservation.Gather(unit.Utility, now, samples);
if (samples.IsEmpty()) return;
ERR_HQVisualContact seen = samples[0];
ERR_HQRoute route = m_Network.Route(unit.Radio);
if (!route) return;
ERR_HQReport report = new ERR_HQReport();
report.MessageId = m_Journal.Message();
report.Target = seen.Entity;
report.Source = unit.Group;
report.Position = seen.Position;
report.ObservedAt = seen.ObservedAt;
ERR_HQObservation.Describe(report, samples, now);
route.MessageId = report.MessageId;
route.Payload = report.Description(now);
unit.PendingReport = report;
unit.Uplink = route;
m_Journal.Write("REPORT", route.Payload, report.MessageId);
}
protected void Accept(ERR_HQReport report, float now)
{
if (!report || !ERR_MissionRules.ContactFresh(report.ObservedAt, now)) return;
for (int i = 0; i < m_Contacts.Count(); i++)
{
ERR_HQReport previous = m_Contacts[i];
if (previous.Target != report.Target) continue;
if (previous.ObservedAt >= report.ObservedAt) return;
float seconds = (report.ObservedAt - previous.ObservedAt) / 1000;
if (seconds > 0 && seconds < 30 && vector.DistanceXZ(previous.Position, report.Position) < 500)
{
report.Velocity = (report.Position - previous.Position) / seconds;
report.HasVelocity = report.Velocity.Length() <= 100;
}
m_Contacts[i] = report.Snapshot();
return;
}
if (m_Contacts.Count() >= 64) m_Contacts.Remove(0);
m_Contacts.Insert(report.Snapshot());
}
protected void DeliverOrder(ERR_HQUnit unit, float now)
{
ERR_HQReport order = unit.PendingOrder;
if (!order || !ERR_MissionRules.ContactFresh(order.ObservedAt, now) || !unit.Eligible(m_Mode)) { unit.Release(); return; }
SCR_AIGroupPerception perception = unit.Utility.m_Perception;
if (perception && !perception.m_aTargets.IsEmpty()) { unit.Release(); return; }
int deliveryId;
if (unit.Downlink) deliveryId = unit.Downlink.MessageId;
unit.Release();
vector position = order.Position;
if (order.HasVelocity) position = ERR_HQReport.Predict(position, order.Velocity, (now - order.ObservedAt) / 1000);
AIWaypoint waypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.SearchWaypoint(), ERR_Actors.Ground(position)));
if (!waypoint) { m_Journal.Write("ORDER FAILED", "waypoint spawn failed", order.MessageId); return; }
unit.Waypoint = waypoint;
unit.Assignment = order;
unit.Group.AddWaypointAt(waypoint, 0);
m_Journal.Write("ORDER DELIVERED", string.Format("group %1 from report M%2: %3", unit.Id, order.MessageId, order.Description(now)), deliveryId);
}
protected void Assign(float now)
{
int limit = m_Mode.Settings().Get("supportSquadLimit");
int radius = m_Mode.Settings().Get("responseRadiusMetres");
int assigned = Assigned();
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Assignment && (!ERR_MissionRules.ContactFresh(unit.Assignment.ObservedAt, now) || !unit.Eligible(m_Mode) || assigned > limit))
{
unit.Release(); assigned--;
}
if (unit.PendingOrder && (!ERR_MissionRules.ContactFresh(unit.PendingOrder.ObservedAt, now) || !unit.Eligible(m_Mode) || assigned > limit))
{
unit.Release(); assigned--;
}
}
foreach (ERR_HQReport report : m_Contacts)
{
if (!ERR_MissionRules.ContactFresh(report.ObservedAt, now)) continue;
foreach (ERR_HQUnit unit : m_Units)
{
if (assigned >= limit) return;
if (unit.Assignment || unit.PendingOrder || !unit.Eligible(m_Mode) || !unit.Radio) continue;
if (vector.DistanceXZ(unit.Group.GetOrigin(), report.Position) > radius) continue;
SCR_AIGroupPerception perception = unit.Utility.m_Perception;
if (perception && !perception.m_aTargets.IsEmpty()) continue;
ERR_HQRoute route = m_Network.Route(unit.Radio, true);
if (!route) continue;
unit.PendingOrder = report.Snapshot();
unit.Downlink = route;
route.MessageId = m_Journal.Message();
route.Payload = "order from report M" + report.MessageId.ToString();
assigned++;
}
}
}
override void Tick(float now)
{
if (!Replication.IsServer()) return;
if (now >= m_NextNetwork) { IndexRadios(); m_NextNetwork = now + 5000; }
for (int i = m_Contacts.Count() - 1; i >= 0; i--)
if (!ERR_MissionRules.ContactFresh(m_Contacts[i].ObservedAt, now)) m_Contacts.Remove(i);
for (int i = m_Units.Count() - 1; i >= 0; i--)
{
ERR_HQUnit unit = m_Units[i];
if (unit.Uplink)
{
int delivered = unit.Uplink.Advance(now, m_Journal);
if (delivered == 1)
{
if (unit.PendingSupplyStatus) m_Logistics.Receive(unit.PendingSupplyStatus, now);
else Accept(unit.PendingReport, now);
}
if (delivered != 0) { unit.Uplink = null; unit.PendingReport = null; unit.PendingSupplyStatus = null; }
}
if (unit.Downlink)
{
int delivered = unit.Downlink.Advance(now, m_Journal);
if (delivered == 1) DeliverOrder(unit, now);
else if (delivered < 0) unit.Release();
}
if (!unit.Group && !unit.Uplink) { unit.Release(); m_Units.Remove(i); continue; }
Observe(unit, now);
}
if (now >= m_NextAssignment) { Assign(now); m_NextAssignment = now + 5000; }
m_Logistics.Tick(now);
m_Mortars.Tick(now);
m_Journal.Flush();
}
override void Stop()
{
m_Logistics.Stop();
m_Mortars.Stop();
foreach (ERR_HQUnit unit : m_Units) unit.Release();
m_Units.Clear();
m_Contacts.Clear();
m_Network.Clear();
m_Journal.Write("STOP", "HQ stopped; tactical orders released");
m_Journal.Flush();
}
}
modded class ERR_GameMode
{
protected override ERR_CommandService CreateCommandService() { return new ERR_HQ(this); }
}
@@ -0,0 +1,101 @@
// Server-owned audit stream. The file contains every event; chat reads a bounded
// recent-history window with explicit gap notices for clients that fall behind.
class ERR_HQJournal
{
protected ref array<string> m_Recent = {};
protected ref array<string> m_Pending = {};
protected int m_Sequence;
protected int m_Message;
protected static int s_Part;
protected static int s_Session;
protected string m_Session;
void ERR_HQJournal()
{
s_Session++;
m_Session = string.Format("%1-%2", System.GetUnixTime(), s_Session);
}
protected float m_RetryAt;
protected int m_Lost;
string Path()
{
if (s_Part == 0)
return "$logs:rereintroduced-hq.log";
return "$logs:rereintroduced-hq-" + s_Part.ToString() + ".log";
}
int Message()
{
return ++m_Message;
}
int Sequence()
{
return m_Sequence;
}
void Write(string kind, string message, int messageId = 0)
{
if (!Replication.IsServer())
return;
float seconds = GetGame().GetWorld().GetWorldTime() / 1000;
string line = string.Format("#%1 t=%2s M%3 %4 %5", ++m_Sequence, Math.Round(seconds * 10) / 10.0, messageId, kind, message) + " [session=" + m_Session + "]";
if (m_Recent.Count() >= 300)
m_Recent.Remove(0);
m_Recent.Insert(line);
if (m_Pending.Count() < 4096)
m_Pending.Insert(line);
else
m_Lost++;
}
void Read(int after, out array<string> lines, out int cursor, int limit = 8)
{
lines.Clear();
cursor = Math.Clamp(after, 0, m_Sequence);
int first = m_Sequence - m_Recent.Count();
if (cursor < first)
{
lines.Insert(string.Format("HQ log: %1 older entries are outside chat history; see %2", first - cursor, Path()));
cursor = first;
}
int characters;
for (int i = cursor - first; i < m_Recent.Count() && lines.Count() < Math.Clamp(limit, 1, 40); i++)
{
// Bound each menu RPC without skipping unread records. Chat still
// requests eight; the live menu can drain a busy journal faster.
if (characters + m_Recent[i].Length() > 16000 && !lines.IsEmpty())
break;
lines.Insert(m_Recent[i]);
characters += m_Recent[i].Length();
cursor++;
}
}
void Flush()
{
if (m_Pending.IsEmpty() || !Replication.IsServer())
return;
float now = GetGame().GetWorld().GetWorldTime();
if (now < m_RetryAt)
return;
FileHandle file = FileIO.OpenFile(Path(), FileMode.APPEND);
if (!file)
{
m_RetryAt = now + 30000;
PrintFormat("Escapists AI [HQ log]: cannot open %1; retry in 30s, chat history remains available", Path(), level: LogLevel.ERROR);
return;
}
foreach (string line : m_Pending)
file.WriteLine(line);
if (m_Lost > 0)
file.WriteLine(string.Format("HQ log: %1 entries lost while file was unavailable and buffer full", m_Lost));
bool rotate = file.GetLength() >= 5242880;
file.Close();
m_Pending.Clear();
m_Lost = 0;
if (rotate)
s_Part++;
}
}
@@ -0,0 +1,241 @@
// Radio propagation is simulated using actual living carriers and inventory.
// A manpack is the HQ uplink; short-range hops are limited to 500 m and three
// retransmissions. No virtual relay is fabricated when the network is broken.
class ERR_HQRadioNode
{
IEntity Carrier;
SCR_AIGroup Group;
int UnitId;
int Capability;
protected float m_CheckedAt = -1;
protected int m_LiveCapability;
bool Valid(bool manpack = false)
{
ERR_GameMode factions = ERR_GameMode.Get();
if (!Carrier || !Group || Group.GetPlayerCount(true) > 0 || !factions || Group.GetFaction() != factions.EnemyFaction() || ERR_Actors.Group(Carrier) != Group)
return false;
int required = 1;
if (manpack)
required = 2;
float now = GetGame().GetWorld().GetWorldTime();
if (m_CheckedAt < 0 || now < m_CheckedAt || now - m_CheckedAt >= 1000)
{
m_CheckedAt = now;
m_LiveCapability = ERR_HQRadio.Capability(Carrier);
}
return m_LiveCapability >= required;
}
string Label()
{
return "unit " + UnitId.ToString();
}
}
class ERR_HQRoute
{
ref array<ref ERR_HQRadioNode> Nodes = {};
bool Downlink;
int Cursor;
bool Sending;
float Due;
vector SentFrom;
float StartedAt = -1;
int MessageId;
string Payload;
bool Failed;
// -1 failed, 0 travelling, 1 delivered. Each hop really gates delivery.
int Advance(float now, ERR_HQJournal journal)
{
if (Failed)
return -1;
if (StartedAt < 0)
{
StartedAt = now;
if (Downlink)
Cursor = -1; // first leg is HQ to the manpack
}
if (now - StartedAt > 30000)
return Fail(journal, "route timed out");
int target = Cursor + 1;
if (Sending)
{
if (now < Due)
return 0;
if (target < Nodes.Count())
{
if (!Nodes[target].Valid(Downlink && target == 0))
return Fail(journal, "receiving carrier lost radio, died or changed ownership");
if (Cursor >= 0 && vector.DistanceXZ(SentFrom, Nodes[target].Carrier.GetOrigin()) > 500)
return Fail(journal, "receiver moved beyond the 500m link");
journal.Write("RECEIVE", Nodes[target].Label() + " received from " + SenderLabel(), MessageId);
}
else
journal.Write("RECEIVE", "HQ received from " + SenderLabel(), MessageId);
Cursor++;
Sending = false;
if ((!Downlink && Cursor >= Nodes.Count()) || (Downlink && Cursor == Nodes.Count() - 1))
return 1;
target = Cursor + 1;
}
if (Nodes.IsEmpty())
return Fail(journal, "no physical radio route");
if (Cursor >= 0 && !Nodes[Cursor].Valid(!Downlink && Cursor == Nodes.Count() - 1))
return Fail(journal, "transmitting carrier lost radio, died or changed ownership");
string recipient = "HQ";
string link = "long-range";
if (target < Nodes.Count())
{
if (!Nodes[target].Valid(Downlink && target == 0))
return Fail(journal, "next relay unavailable");
recipient = Nodes[target].Label();
if (Cursor >= 0)
{
SentFrom = Nodes[Cursor].Carrier.GetOrigin();
float distance = vector.DistanceXZ(SentFrom, Nodes[target].Carrier.GetOrigin());
if (distance > 500)
return Fail(journal, "next relay beyond 500m");
link = Math.Round(distance).ToString() + "m short-range";
}
}
journal.Write("TRANSMIT", SenderLabel() + " -> " + recipient + " (" + link + ")", MessageId);
Sending = true;
Due = now + 2000;
return 0;
}
protected string SenderLabel()
{
if (Cursor < 0)
return "HQ";
return Nodes[Cursor].Label();
}
protected int Fail(ERR_HQJournal journal, string reason)
{
Failed = true;
journal.Write("LOST", reason + "; " + Payload, MessageId);
return -1;
}
}
class ERR_HQNetwork
{
protected ref array<ref ERR_HQRadioNode> m_Nodes = {};
protected ref array<int> m_Parents = {};
protected ref array<int> m_Depths = {};
protected ref map<IEntity, int> m_Indices = new map<IEntity, int>();
protected ref map<string, ref array<int>> m_Cells = new map<string, ref array<int>>();
bool Overflow;
void Clear()
{
m_Nodes.Clear();
m_Parents.Clear();
m_Depths.Clear();
m_Indices.Clear();
m_Cells.Clear();
Overflow = false;
}
protected string Cell(int x, int z)
{
return string.Format("%1:%2", x, z);
}
void Add(IEntity carrier, SCR_AIGroup group, int unitId, int capability)
{
if (m_Indices.Contains(carrier))
return;
if (m_Nodes.Count() >= 2048)
{
Overflow = true;
return;
}
ERR_HQRadioNode node = new ERR_HQRadioNode();
node.Carrier = carrier;
node.Group = group;
node.UnitId = unitId;
node.Capability = capability;
int index = m_Nodes.Count();
m_Nodes.Insert(node);
m_Indices.Insert(carrier, index);
m_Parents.Insert(-2);
m_Depths.Insert(99);
vector position = carrier.GetOrigin();
string key = Cell(Math.Floor(position[0] / 500), Math.Floor(position[2] / 500));
array<int> cell = m_Cells.Get(key);
if (!cell)
{
cell = {};
m_Cells.Insert(key, cell);
}
cell.Insert(index);
}
void Build()
{
array<int> queue = {};
int checks;
for (int root = 0; root < m_Nodes.Count(); root++)
{
if (m_Nodes[root].Capability == 2)
{
m_Parents[root] = -1;
m_Depths[root] = 0;
queue.Insert(root);
}
}
for (int head = 0; head < queue.Count(); head++)
{
int from = queue[head];
if (m_Depths[from] >= 3)
continue;
vector position = m_Nodes[from].Carrier.GetOrigin();
int x = Math.Floor(position[0] / 500);
int z = Math.Floor(position[2] / 500);
for (int dx = -1; dx <= 1; dx++)
{
for (int dz = -1; dz <= 1; dz++)
{
array<int> neighbors = m_Cells.Get(Cell(x + dx, z + dz));
if (!neighbors)
continue;
foreach (int candidate : neighbors)
{
if (++checks > 100000)
{
Overflow = true;
return;
}
if (m_Parents[candidate] != -2 || vector.DistanceXZ(position, m_Nodes[candidate].Carrier.GetOrigin()) > 500)
continue;
m_Parents[candidate] = from;
m_Depths[candidate] = m_Depths[from] + 1;
queue.Insert(candidate);
}
}
}
}
}
ERR_HQRoute Route(IEntity caller, bool downlink = false)
{
int index;
if (!m_Indices.Find(caller, index) || m_Parents[index] == -2)
return null;
ERR_HQRoute route = new ERR_HQRoute();
route.Downlink = downlink;
while (index >= 0)
{
if (downlink)
route.Nodes.InsertAt(m_Nodes[index], 0);
else
route.Nodes.Insert(m_Nodes[index]);
index = m_Parents[index];
}
return route;
}
}
@@ -0,0 +1,199 @@
// These samples exist only at intake. HQ never revisits their entity loadouts.
class ERR_HQVisualContact
{
IEntity Entity;
vector Position;
float ObservedAt;
bool Escapist;
string Travel = "unknown";
string Weapon = "unknown";
}
class ERR_HQObservation
{
// Require current native line of sight before reading visible equipment.
static bool IsClearSighting(float age, float traceFraction, float exposure)
{
return age >= 0 && age <= 0.5 && traceFraction >= 1 && exposure > 0;
}
static bool CanIdentifyWeapon(float distance, float exposure)
{
return distance >= 0 && distance <= 150 && exposure >= 0.5;
}
static string WeaponCategory(EWeaponType type)
{
switch (type)
{
case EWeaponType.WT_HANDGUN: return "pistol";
case EWeaponType.WT_RIFLE: return "rifle";
case EWeaponType.WT_SNIPERRIFLE: return "sniper rifle";
case EWeaponType.WT_MACHINEGUN: return "machine gun";
case EWeaponType.WT_ROCKETLAUNCHER: return "rocket launcher";
case EWeaponType.WT_GRENADELAUNCHER: return "grenade launcher";
case EWeaponType.WT_FRAGGRENADE: return "grenade";
case EWeaponType.WT_SMOKEGRENADE: return "smoke grenade";
}
return "unidentified weapon";
}
static ERR_HQVisualContact Capture(BaseTarget target, float now)
{
if (!target || target.GetTargetCategory() != ETargetCategory.ENEMY || !IsClearSighting(target.GetTimeSinceSeen(), target.GetTraceFraction(), target.GetExposure()))
return null;
DamageManagerComponent damage = target.GetDamageManagerComponent();
if (damage && damage.IsDestroyed())
return null;
IEntity entity = target.GetTargetEntity();
if (!entity)
return null;
ERR_HQVisualContact seen = new ERR_HQVisualContact();
seen.Entity = entity;
seen.Position = target.GetLastSeenPosition();
seen.ObservedAt = now - target.GetTimeSinceSeen() * 1000;
SCR_ChimeraCharacter character = SCR_ChimeraCharacter.Cast(entity);
if (character)
{
if (GetGame().GetPlayerManager())
seen.Escapist = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(entity) > 0;
seen.Travel = "on foot";
if (CompartmentAccessComponent.GetVehicleIn(entity))
seen.Travel = "in vehicle";
// Read only the drawn weapon; never enumerate weapons or inventory.
if (CanIdentifyWeapon(target.GetDistance(), target.GetExposure()))
{
CharacterControllerComponent controller = CharacterControllerComponent.Cast(entity.FindComponent(CharacterControllerComponent));
if (controller)
{
IEntity heldItem = controller.GetCurrentItemInHands();
BaseWeaponComponent weapon;
if (heldItem)
weapon = BaseWeaponComponent.Cast(heldItem.FindComponent(BaseWeaponComponent));
seen.Weapon = "none drawn";
if (weapon)
seen.Weapon = WeaponCategory(weapon.GetWeaponType());
}
}
}
else if (Vehicle.Cast(entity))
{
seen.Travel = "vehicle";
if (target.GetUnitType() == EAIUnitType.UnitType_Aircraft)
seen.Travel = "aircraft";
}
return seen;
}
static void Gather(SCR_AIGroupUtilityComponent utility, float now, out array<ref ERR_HQVisualContact> samples)
{
samples.Clear();
if (!utility)
return;
array<BaseTarget> visible = {};
utility.ERR_HQGetVisualTargets(visible);
foreach (BaseTarget target : visible)
{
ERR_HQVisualContact seen = Capture(target, now);
if (!seen)
continue;
ERR_HQVisualContact existing;
foreach (ERR_HQVisualContact sample : samples)
{
if (sample.Entity == seen.Entity)
{
existing = sample;
break;
}
}
if (!existing)
{
if (samples.Count() < 64)
samples.Insert(seen);
}
else if (seen.ObservedAt > existing.ObservedAt || (seen.ObservedAt == existing.ObservedAt && existing.Weapon == "unknown" && seen.Weapon != "unknown"))
{
existing.Position = seen.Position;
existing.ObservedAt = seen.ObservedAt;
existing.Travel = seen.Travel;
existing.Weapon = seen.Weapon;
}
}
}
static void Describe(ERR_HQReport report, array<ref ERR_HQVisualContact> samples, float now)
{
// New observations must not silently inherit old equipment/travel details.
report.SeenEscapists = -1;
report.TravelMode = "unknown";
report.SeenWeapons = "unknown";
report.DetailsObservedAt = -1;
if (report.Distress)
return;
ERR_HQVisualContact anchor;
foreach (ERR_HQVisualContact sample : samples)
{
if (sample.Entity == report.Target && now - sample.ObservedAt >= 0 && now - sample.ObservedAt <= 500)
{
anchor = sample;
break;
}
}
if (!anchor)
return;
report.DetailsObservedAt = anchor.ObservedAt;
report.TravelMode = anchor.Travel;
report.SeenWeapons = anchor.Weapon;
array<IEntity> counted = {};
array<string> weapons = {};
string travel;
foreach (ERR_HQVisualContact sighting : samples)
{
if (!sighting.Escapist || !sighting.Entity || counted.Contains(sighting.Entity) || now - sighting.ObservedAt < 0 || now - sighting.ObservedAt > 500 || vector.DistanceXZ(anchor.Position, sighting.Position) > 100)
continue;
counted.Insert(sighting.Entity);
if (travel.IsEmpty())
travel = sighting.Travel;
else if (travel != sighting.Travel)
travel = "mixed travel";
if (sighting.Weapon != "unknown" && !weapons.Contains(sighting.Weapon) && weapons.Count() < 4)
weapons.Insert(sighting.Weapon);
}
if (counted.IsEmpty())
return; // A visible vehicle does not reveal its hidden occupants.
report.SeenEscapists = counted.Count();
// A vehicle anchor remains a vehicle report, even with people near it.
if (anchor.Escapist)
report.TravelMode = travel;
report.SeenWeapons = "unknown";
if (!weapons.IsEmpty())
{
report.SeenWeapons = weapons[0];
for (int i = 1; i < weapons.Count(); i++)
report.SeenWeapons += ", " + weapons[i];
}
}
}
modded class SCR_AIGroupUtilityComponent
{
void ERR_HQGetVisualTargets(out array<BaseTarget> targets)
{
targets.Clear();
foreach (SCR_AIInfoComponent info : m_aInfoComponents)
{
if (!info || !info.m_Perception)
continue;
AIAgent observer = AIAgent.Cast(info.GetOwner());
if (!observer || !ERR_Actors.CanFight(observer.GetControlledEntity()))
continue;
array<BaseTarget> own = {};
info.m_Perception.GetTargetsList(own, ETargetCategory.ENEMY);
foreach (BaseTarget target : own)
{
if (target && ERR_HQObservation.IsClearSighting(target.GetTimeSinceSeen(), target.GetTraceFraction(), target.GetExposure()))
targets.Insert(target);
}
}
}
}
@@ -0,0 +1,144 @@
// A radio's category is protected in the stock gadget component.
modded class SCR_RadioComponent
{
bool ERR_IsHQManpack()
{
return m_iRadioCategory == ERadioCategory.MANPACK;
}
}
class ERR_HQRadio
{
// 0: no radio, 1: basic radio, 2: manpack. Inventory is checked live.
static int Capability(IEntity character)
{
if (!ERR_Actors.CanFight(character))
return 0;
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(character.FindComponent(InventoryStorageManagerComponent));
if (!inventory)
return 0;
array<IEntity> items = {};
inventory.GetItems(items);
int result;
foreach (IEntity item : items)
{
if (!item || !item.FindComponent(BaseRadioComponent))
continue;
result = 1;
SCR_RadioComponent gadget = SCR_RadioComponent.Cast(item.FindComponent(SCR_RadioComponent));
if (gadget && gadget.ERR_IsHQManpack())
return 2;
}
return result;
}
}
// HQ uses entity identity only for matching observations, never for its origin.
// Visible details are captured at intake and copied as scalar snapshots.
// Reports and orders contain immutable observed coordinates and timestamps.
class ERR_HQReport
{
int MessageId;
IEntity Target;
SCR_AIGroup Source;
vector Position;
vector Velocity;
float ObservedAt = -1;
float LastSent = -60000;
float PerceptionStamp = -1;
bool HasVelocity;
bool Engaged;
bool Distress;
int SeenEscapists = -1;
string TravelMode = "unknown";
string SeenWeapons = "unknown";
float DetailsObservedAt = -1;
ERR_HQReport Snapshot()
{
ERR_HQReport copy = new ERR_HQReport();
copy.MessageId = MessageId;
copy.Target = Target;
copy.Source = Source;
copy.Position = Position;
copy.Velocity = Velocity;
copy.ObservedAt = ObservedAt;
copy.Engaged = Engaged;
copy.Distress = Distress;
copy.HasVelocity = HasVelocity;
copy.SeenEscapists = SeenEscapists;
copy.TravelMode = TravelMode;
copy.SeenWeapons = SeenWeapons;
copy.DetailsObservedAt = DetailsObservedAt;
return copy;
}
string TravelDirection()
{
if (!HasVelocity)
return "unknown";
if (Velocity.Length() < 0.5)
return "stationary";
float x = Math.AbsFloat(Velocity[0]);
float z = Math.AbsFloat(Velocity[2]);
string northSouth = "N";
string eastWest = "E";
if (Velocity[2] < 0)
northSouth = "S";
if (Velocity[0] < 0)
eastWest = "W";
if (x < z * 0.414214)
return northSouth;
if (z < x * 0.414214)
return eastWest;
return northSouth + eastWest;
}
string Description(float now, bool brief = false)
{
if (Distress)
return string.Format("Distress at friendly X=%1 Z=%2; enemy details unknown (%3s ago)", Math.Round(Position[0]), Math.Round(Position[2]), Math.Round(Math.Max(0, now - ObservedAt) / 1000));
string count = "count unknown";
if (SeenEscapists >= 0)
count = "at least " + SeenEscapists.ToString() + " spotted nearby";
string motion = "speed/direction unknown";
if (HasVelocity)
{
float speed = Math.Round(Velocity.Length() * 10) / 10.0;
float azimuth = Math.Atan2(Velocity[0], Velocity[2]) * Math.RAD2DEG;
if (azimuth < 0)
azimuth += 360;
int heading = Math.Round(azimuth);
motion = string.Format("%1 km/h, azimuth %2 degrees (%3)", Math.Round(speed * 3.6), heading % 360, TravelDirection());
}
string details = count + "; " + TravelMode + "; " + motion + "; seen weapons: " + SeenWeapons;
if (brief)
return details;
string velocity = "unknown";
if (HasVelocity)
velocity = string.Format("[%1,%2] m/s", Math.Round(Velocity[0] * 10) / 10.0, Math.Round(Velocity[2] * 10) / 10.0);
return string.Format("X=%1 Z=%2: %3; vector X/Z=%4 (%5s ago)", Math.Round(Position[0]), Math.Round(Position[2]), details, velocity, Math.Round(Math.Max(0, now - ObservedAt) / 1000));
}
static vector Predict(vector position, vector velocity, float seconds)
{
vector offset = velocity * Math.Clamp(seconds, 0, 20);
offset[1] = 0;
if (offset.Length() > 300)
offset = offset.Normalized() * 300;
return position + offset;
}
int Priority(float now)
{
if (Distress)
return 0;
if (now - ObservedAt <= 15000)
{
if (Engaged)
return 3;
return 2;
}
return 1;
}
}
@@ -0,0 +1,31 @@
// An idle rotor or zero speed alone is normal during takeoff, landing and reload.
// Confirm prolonged grounding, but recognize actual loss of flight capability.
class ERR_HelicopterAvailability
{
protected float m_fGroundedSince = -1;
string Check(float now, float spawnedAt, bool destroyed, bool disabled, bool livingPilot, bool playerControlled, bool servicing, float height, float speed)
{
if (destroyed) return "destroyed";
if (now - spawnedAt < 20000)
{
m_fGroundedSince = -1;
return "";
}
if (disabled) return "engine, rotor or airframe disabled";
if (playerControlled)
{
m_fGroundedSince = -1;
return "";
}
if (!livingPilot) return "no living pilot";
if (servicing || height > 5 || speed > 2)
{
m_fGroundedSince = -1;
return "";
}
if (m_fGroundedSince < 0) m_fGroundedSince = now;
if (now - m_fGroundedSince >= 60000) return "grounded for 60 seconds";
return "";
}
}
@@ -0,0 +1,32 @@
// The stored geometry is the command actually emitted to the native aiming
// tree, never a chain of refreshed approximations. Entity/report/node links
// are weak; cancellation and physical projectile observation clear ownership.
class ERR_MortarAimSolution
{
IEntity Gun;
// Last delivered observation proved equivalent to this original geometry.
ERR_HQReport Report;
int OrderId;
int Shot;
vector GunOrigin;
vector Impact;
vector AimTarget;
vector SalvoOffset;
int Charge;
float FlightTime;
bool Matches(ERR_MortarBattery battery, ERR_HQReport report)
{
return battery && Gun && Gun == battery.Gun && Report && Report == report && OrderId == battery.OrderId && Shot == battery.OrderShots;
}
}
class ERR_MortarAimRules
{
// Three-dimensional tolerances include terrain/aim-height changes. These are
// equivalence limits, not permission to move the gun or redirect its barrel.
static bool Equivalent(int commandedCharge, int updatedCharge, vector commandedOrigin, vector updatedOrigin, vector commandedImpact, vector updatedImpact, vector commandedAim, vector updatedAim, float commandedFlight, float updatedFlight)
{
return commandedCharge >= 0 && commandedCharge == updatedCharge && commandedFlight > 0 && updatedFlight > 0 && vector.Distance(commandedOrigin, updatedOrigin) <= 0.05 && vector.Distance(commandedImpact, updatedImpact) <= 0.1 && vector.Distance(commandedAim, updatedAim) <= 0.1 && Math.AbsFloat(commandedFlight - updatedFlight) <= 0.05;
}
}
@@ -0,0 +1,557 @@
// Migrated from the independently authored AI extension; successor-owned physical groups.
// Each site/phase has its own repeat limiter. Changed reasons and lifecycle
// events are immediate; only identical polling results are summarized.
class ERR_MortarTraceState
{
string LastReason;
float NextLog;
int Suppressed;
bool Due(string reason, float now)
{
if (reason != LastReason || now >= NextLog)
{
LastReason = reason;
NextLog = now + 30000;
return true;
}
Suppressed++;
return false;
}
}
class ERR_MortarBattery
{
static const ResourceName GUN = "{D1FFE458E8AC4BDB}Prefabs/Weapons/Mortars/2B14/Mortar_2B14.et";
static const ResourceName SHELL = "{98EC9C526AFBA282}Prefabs/Weapons/Ammo/Ammo_Shell_82mm_HE_O832DU.et";
static const ResourceName CREW = "{86E565387A634103}Prefabs/Characters/Campaign/Final/OPFOR/USSR_Army/Regular/Campaign_USSR_Player_MortarCrew.et";
static const ResourceName RADIO_OPERATOR = "{612F43A4D5AE765F}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_RTO.et";
IEntity RadioOperator;
IEntity Gun;
SCR_AIGroup Group;
ref SCR_AIStaticArtilleryActivity Activity;
ref ERR_HQReport Report;
ref ERR_HQRoute RadioOrder;
ref ERR_HQReport PendingRadioReport;
ref ERR_HQReport PendingSupplyReport;
ref ERR_MortarRefresh Refresh;
bool RefreshRejected; // latched through an already committed projectile
protected ref ERR_MortarReadiness m_Readiness;
protected ref ERR_MortarSupplyTransfer m_Transfer;
protected ref array<IEntity> m_AttemptedShells = {};
protected bool m_Stopped;
protected SCR_AIStaticArtilleryActivity m_ObservedActivity;
protected int m_ObservedActivityOrder;
protected EAIActionState m_ActivityTerminal = EAIActionState.EVALUATED;
protected ref ERR_MortarReportLedger m_ReportLedger = new ERR_MortarReportLedger();
vector Position;
vector Impact;
vector AimOffset;
int AimedShot = -1;
float FlightTime;
float AimedAt = -1;
float AimValidatedAt = -1;
SCR_AIGetArtilleryAimDistanceCompensation AimSolver;
ref ERR_MortarAimSolution AimSolution;
float AirspaceUntil;
vector AirspaceImpact;
float AirspaceRadius = 600;
float OrderedAt;
float NextOrder;
float SpawnedAt;
bool StartupActivated;
bool Released;
bool Rejected;
bool Claimed; // keeps the world slot occupied after the battery is destroyed
int SiteId;
int OrderId;
int RadioId;
int SourceId;
int OrderShots;
int TotalShots;
bool CommandOpen;
string LastFailure;
string SpawnFailure;
vector LastCandidate;
ref map<string, int> SearchCounts = new map<string, int>();
ref map<string, ref ERR_MortarTraceState> Traces = new map<string, ref ERR_MortarTraceState>();
void Audit(string kind, string detail)
{
ERR_HQ.Event("MORTAR " + kind, string.Format("site=%1 order=%2 radio=M%3 source=M%4 position=%5; %6", SiteId, OrderId, RadioId, SourceId, Position, detail), RadioId);
}
void Trace(string phase, string reason, string detail)
{
ERR_MortarTraceState state;
if (!Traces.Find(phase, state))
{
state = new ERR_MortarTraceState();
Traces.Insert(phase, state);
}
string previous = state.LastReason;
if (!state.Due(reason, GetGame().GetWorld().GetWorldTime()))
return;
Audit(phase, string.Format("reason=%1; %2; previous=%3 repeated polls omitted=%4", reason, detail, previous, state.Suppressed));
state.Suppressed = 0;
}
void FlushTraces()
{
foreach (string phase, ERR_MortarTraceState state : Traces)
{
if (state.Suppressed > 0)
Audit("REPEATS", string.Format("phase=%1 reason=%2 repeated polls omitted=%3", phase, state.LastReason, state.Suppressed));
}
Traces.Clear();
}
string OperationalReason()
{
if (m_Stopped) return "battery stopped";
if (!Gun) return "gun missing";
if (!SpawnFailure.IsEmpty()) return SpawnFailure;
if (!Group || !Group.GetGroupUtilityComponent()) return "crew group missing";
if (Group.GetFaction() != ERR_GameMode.Get().EnemyFaction()) return "crew changed faction";
if (Group.GetPlayerCount(true) > 0) return "player in crew group";
if (Group.GetAgentsCount() == 0) return "crew group empty";
DamageManagerComponent damage = DamageManagerComponent.Cast(Gun.FindComponent(DamageManagerComponent));
if (damage && damage.IsDestroyed()) return "gun destroyed";
return "";
}
bool CanRequest(ERR_HQReport report, float now)
{
return !m_Stopped && ERR_MortarPrediction.Fresh(report, now) && m_ReportLedger.Available(report.MessageId, report.ObservedAt, now);
}
bool BeginRequest(ERR_HQReport report, float now)
{
if (!CanRequest(report, now) || !m_ReportLedger.Claim(report.MessageId, report.ObservedAt, now)) return false;
Cancel("superseded by new HQ order");
FlushTraces();
OrderId++;
RadioId = 0;
SourceId = report.MessageId;
OrderShots = 0;
Released = false;
CommandOpen = true;
OrderedAt = now;
LastFailure = "";
Audit("COMMAND", string.Format("requested rounds=%1 HE radius=%2m; %3", ERR_MortarSalvo.ROUNDS, ERR_MortarSalvo.RADIUS_METRES, report.Description(now)));
return true;
}
bool CanRefresh(ERR_HQReport report, float now)
{
if (m_Stopped || !CommandOpen || !Activity || Refresh || RefreshRejected || Rejected || !Operational()) return false;
return ERR_MortarRefreshRules.Newer(Report, report, now) && m_ReportLedger.Available(report.MessageId, report.ObservedAt, now);
}
bool BeginRefresh(ERR_HQReport report, ERR_HQRoute route, float now)
{
if (!route || !CanRefresh(report, now) || !m_ReportLedger.Claim(report.MessageId, report.ObservedAt, now)) return false;
Refresh = new ERR_MortarRefresh(this, report, route);
Audit("UPDATE QUEUED", string.Format("same order; update radio=M%1 newer observation=M%2 observed=%3", route.MessageId, report.MessageId, report.ObservedAt));
return true;
}
void RejectRefresh(string reason)
{
// Never clear the commit latch or steal the physical projectile callback.
// AllowShot refuses further loads now; HQ cancels after it is observed.
RefreshRejected = true;
Refresh = null;
LastFailure = reason;
Audit("UPDATE REJECTED", reason);
}
void ObserveProjectile(IEntity projectileEntity, IEntity firingCharacter, bool playerGunner)
{
if (!projectileEntity)
{
Audit("FIRE CALLBACK", "native OnWeaponFired had no projectile; no confirmed shot counted");
return;
}
TotalShots++;
if (CommandOpen && Released && !playerGunner)
{
OrderShots++;
// Only a physical projectile unlocks the next load. It must be aimed again.
Released = false;
AimedAt = -1;
AimValidatedAt = -1;
AimSolver = null;
AimSolution = null;
Audit("FIRED", string.Format("physical projectile=%1 origin=%2 order shots=%3 total shots=%4 predicted impact=%5 predicted flight=%6s gunner=%7 (impact not observed)", projectileEntity, projectileEntity.GetOrigin(), OrderShots, TotalShots, Impact, FlightTime, firingCharacter));
}
else
Audit("FIRED UNASSIGNED", string.Format("physical projectile=%1 origin=%2 player gunner=%3 order open=%4 committed=%5 total shots=%6; no active HQ order attribution", projectileEntity, projectileEntity.GetOrigin(), playerGunner, CommandOpen, Released, TotalShots));
}
void ReserveShellAirspace(float now)
{
// Enclose every still-airborne shell, including an earlier, longer flight.
// A later round must not move/shrink the existing exclusion or deadline.
if (now >= AirspaceUntil)
{
AirspaceImpact = Impact;
AirspaceRadius = 600;
}
AirspaceRadius = Math.Max(AirspaceRadius, vector.DistanceXZ(AirspaceImpact, Impact) + 600);
AirspaceUntil = Math.Max(AirspaceUntil, now + (FlightTime + 10) * 1000);
}
bool SpawnFailed(string reason)
{
SpawnFailure = reason;
Audit("DEPLOY FAILED", string.Format("reason=%1 claimed=%2 gun=%3 group=%4", reason, Claimed, Gun, Group));
return false;
}
void SearchResult(vector candidate, string reason)
{
LastCandidate = candidate;
int count;
SearchCounts.Find(reason, count);
SearchCounts.Set(reason, count + 1);
}
void SiteStatus(float now)
{
if (!SearchCounts.IsEmpty())
{
string counts;
foreach (string reason, int count : SearchCounts) counts += string.Format("%1=%2; ", reason, count);
Audit("SITE SEARCH", string.Format("candidate counts since last summary: %1 last candidate=%2", counts, LastCandidate));
SearchCounts.Clear();
}
int agents, living;
int actionState = -1;
if (Group)
{
array<AIAgent> crew = {};
Group.GetAgents(crew);
agents = crew.Count();
foreach (AIAgent agent : crew)
{
if (agent && ERR_Actors.CanFight(agent.GetControlledEntity())) living++;
}
}
if (Activity) actionState = Activity.GetActionState();
bool connected;
ERR_HQ hq = ERR_HQ.Get();
if (hq && Group) connected = hq.MortarConnected(Group);
Audit("SITE STATUS", string.Format("claimed=%1 startup=%2 operational=%3 crew agents=%4 fighting=%5 inventory HE=%6 radio connected=%7 cooldown remaining=%8s", Claimed, StartupActivated, Operational(), agents, living, CountShells(), connected, Math.Max(0, NextOrder - now) / 1000));
Audit("ORDER STATUS", string.Format("open=%1 route pending=%2 native action=%3 shell committed=%4 order shots=%5 total shots=%6 age=%7s last failure=%8", CommandOpen, RadioOrder != null, actionState, Released, OrderShots, TotalShots, Math.Max(0, now - OrderedAt) / 1000, LastFailure));
if (!SpawnFailure.IsEmpty()) Audit("PARTIAL DEPLOYMENT", SpawnFailure);
if (!Operational()) Trace("SITE BLOCK", OperationalReason(), "spawn failure=" + SpawnFailure);
}
bool Operational()
{
return OperationalReason().IsEmpty();
}
int CountShells()
{
if (!Group)
return 0;
int count;
array<IEntity> items = {};
array<AIAgent> agents = {};
Group.GetAgents(agents);
foreach (AIAgent agent : agents)
{
if (!agent || !ERR_Actors.CanFight(agent.GetControlledEntity()))
continue;
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(agent.GetControlledEntity().FindComponent(InventoryStorageManagerComponent));
if (!inventory)
continue;
ERR_Inventory.AppendUnique(inventory, items);
}
foreach (IEntity item : items)
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == SHELL) count++;
return count;
}
void Bind(int siteId, SCR_AIGroup group, IEntity gun, float now)
{
SiteId = siteId;
Group = group;
Gun = gun;
Position = gun.GetOrigin();
SpawnedAt = now;
Claimed = true;
m_Readiness = new ERR_MortarReadiness(this);
}
int InventoryShells(InventoryStorageManagerComponent inventory)
{
if (!inventory)
return 0;
int count;
array<IEntity> items = {};
ERR_Inventory.Items(inventory, items);
foreach (IEntity item : items)
{
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == SHELL)
count++;
}
return count;
}
bool Stopped() { return m_Stopped; }
bool SupplyPending() { return m_Transfer && m_Transfer.Unresolved(); }
bool SupplyAuthorized(float now)
{
if (!Replication.IsServer() || m_Stopped || !StartupActivated || !Operational() || !CommandOpen || !ERR_HQ.MissionActive()) return false;
ERR_MortarSystem system = ERR_MortarSystem.Get();
if (!system || system.FindGun(Gun) != this) return false;
ERR_HQReport suppliedReport = Report;
if (!suppliedReport) suppliedReport = PendingSupplyReport;
return ERR_MortarPrediction.Fresh(suppliedReport, now);
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
Cancel("HQ stopped");
if (m_Readiness) m_Readiness.Stop();
}
ERR_MortarSupplyState SupplyState(float now)
{
// Observe an existing native operation even after its order was canceled.
// No second request may race an unresolved transfer on this battery.
if (m_Transfer)
{
ERR_MortarSupplyState observed = m_Transfer.Poll(now);
if (m_Transfer.CapturedOrder() == OrderId)
{
if (observed == ERR_MortarSupplyState.READY) m_Transfer = null;
return observed;
}
if (m_Transfer.Unresolved()) return ERR_MortarSupplyState.FAILED;
m_Transfer = null;
}
if (!SupplyAuthorized(now)) return SupplyStateFailed("current HQ order, crew or report is unavailable");
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(Gun.FindComponent(SCR_AIVehicleUsageComponent));
IEntity gunner;
if (usage && usage.GetTurretCompartmentSlot())
gunner = usage.GetTurretCompartmentSlot().GetOccupant();
array<AIAgent> agents = {};
Group.GetAgents(agents);
float nearest = float.MAX;
if (!gunner)
{
foreach (AIAgent candidate : agents)
{
if (!candidate || candidate.GetControlledEntity() == RadioOperator || !ERR_Actors.CanFight(candidate.GetControlledEntity()))
continue;
float distance = vector.DistanceXZ(Gun.GetOrigin(), candidate.GetControlledEntity().GetOrigin());
if (distance < nearest)
{
nearest = distance;
gunner = candidate.GetControlledEntity();
}
}
}
if (!gunner) return SupplyStateFailed("no fighting gunner found");
if (ERR_Actors.Group(gunner) != Group) return SupplyStateFailed("turret occupant belongs to another group");
if (!ERR_Actors.CanFight(gunner)) return SupplyStateFailed("gunner cannot fight");
InventoryStorageManagerComponent destination = InventoryStorageManagerComponent.Cast(gunner.FindComponent(InventoryStorageManagerComponent));
if (!destination)
return SupplyStateFailed("gunner inventory missing");
if (InventoryShells(destination) > 0)
return ERR_MortarSupplyState.READY;
int nearbyDonors, donorShells, storageRejected;
array<IEntity> consideredShells = {};
foreach (AIAgent bearer : agents)
{
if (!bearer || !ERR_Actors.CanFight(bearer.GetControlledEntity()))
continue;
IEntity donor = bearer.GetControlledEntity();
if (donor == gunner || vector.DistanceXZ(donor.GetOrigin(), gunner.GetOrigin()) > 12)
continue;
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(donor.FindComponent(InventoryStorageManagerComponent));
if (!inventory)
continue;
nearbyDonors++;
array<IEntity> items = {};
ERR_Inventory.Items(inventory, items);
foreach (IEntity item : items)
{
if (!item || !item.GetPrefabData() || item.GetPrefabData().GetPrefabName() != SHELL)
continue;
if (consideredShells.Contains(item) || m_AttemptedShells.Contains(item)) continue;
consideredShells.Insert(item);
donorShells++;
BaseInventoryStorageComponent storage = destination.FindStorageForItem(item, EStoragePurpose.PURPOSE_DEPOSIT);
if (!storage) { storageRejected++; continue; }
m_AttemptedShells.Insert(item); // even rejected/ambiguous requests are never retried
m_Transfer = new ERR_MortarSupplyTransfer(this, donor, gunner, item, storage, now);
if (!m_Transfer.Begin(now)) return SupplyStateFailed("exact shell transfer request rejected; no retry");
Audit("AMMO TRANSFER", string.Format("one exact-item move pending donor=%1 gunner=%2 shell=%3 order=%4; awaiting callback AND physical destination", donor, gunner, item, OrderId));
return ERR_MortarSupplyState.PENDING;
}
}
return SupplyStateFailed(string.Format("gunner empty; fighting donors within 12m=%1 unattempted HE=%2 destination storage rejected=%3", nearbyDonors, donorShells, storageRejected));
}
// Compatibility for existing native callers; request acceptance is NOT ready.
bool SupplyGunner() { return SupplyState(GetGame().GetWorld().GetWorldTime()) == ERR_MortarSupplyState.READY; }
protected ERR_MortarSupplyState SupplyStateFailed(string reason)
{
SupplyFailed(reason);
return ERR_MortarSupplyState.FAILED;
}
bool SupplyFailed(string reason)
{
LastFailure = reason;
Audit("AMMO FAILED", reason);
return false;
}
protected void WatchActivity()
{
DetachActivity();
if (!Activity || !CommandOpen || m_Stopped) return;
m_ObservedActivity = Activity;
m_ObservedActivityOrder = OrderId;
Activity.m_OnActionCompleted.Insert(OnActivityCompleted);
Activity.m_OnActionFailed.Insert(OnActivityFailed);
}
protected void DetachActivity()
{
if (m_ObservedActivity)
{
m_ObservedActivity.m_OnActionCompleted.Remove(OnActivityCompleted);
m_ObservedActivity.m_OnActionFailed.Remove(OnActivityFailed);
}
m_ObservedActivity = null;
m_ObservedActivityOrder = 0;
m_ActivityTerminal = EAIActionState.EVALUATED;
}
protected void OnActivityCompleted(SCR_AIActionBase action)
{
ObserveActivityTerminal(action, EAIActionState.COMPLETED);
}
protected void OnActivityFailed(SCR_AIActionBase action)
{
ObserveActivityTerminal(action, EAIActionState.FAILED);
}
protected void ObserveActivityTerminal(SCR_AIActionBase action, EAIActionState terminal)
{
if (m_Stopped || !CommandOpen || !Activity || action != Activity || action != m_ObservedActivity || OrderId != m_ObservedActivityOrder) return;
if (m_ActivityTerminal != EAIActionState.EVALUATED) return;
if (terminal != EAIActionState.COMPLETED && terminal != EAIActionState.FAILED) return;
// Vanilla deselection can overwrite COMPLETED/FAILED with EVALUATED
// before the next HQ tick. Record its actual event, never infer completion
// from projectile totals, and never cancel from inside the native callback.
m_ActivityTerminal = terminal;
Audit("ACTIVITY EVENT", string.Format("native terminal event=%1 current state=%2 captured order=%3 physical order shots=%4 total shots=%5 committed=%6", terminal, Activity.GetActionState(), m_ObservedActivityOrder, OrderShots, TotalShots, Released));
}
protected EAIActionState ActivityTerminal()
{
if (Activity == m_ObservedActivity && OrderId == m_ObservedActivityOrder)
if (m_ActivityTerminal == EAIActionState.COMPLETED || m_ActivityTerminal == EAIActionState.FAILED) return m_ActivityTerminal;
return Activity.GetActionState();
}
void Cancel(string reason = "cancel requested")
{
if (CommandOpen)
{
FlushTraces();
Audit("ORDER END", string.Format("reason=%1 observed shots=%2 shell committed=%3 elapsed=%4s last failure=%5", reason, OrderShots, Released, (GetGame().GetWorld().GetWorldTime() - OrderedAt) / 1000, LastFailure));
}
CommandOpen = false;
// Remove before our own cleanup calls Complete; that is not a native
// successful fire mission and must not manufacture an observed event.
DetachActivity();
RadioOrder = null;
PendingRadioReport = null;
PendingSupplyReport = null;
Refresh = null;
RefreshRejected = false;
if (m_Transfer) m_Transfer.Invalidate();
Report = null;
AimedAt = -1;
AimValidatedAt = -1;
AimSolver = null;
AimSolution = null;
if (Activity)
Activity.Complete();
Activity = null;
}
// Idle distant batteries still report through HQ's scheduler. Their native
// AI needs full detail while firing or when players can interact with them.
protected void UpdateCrewDetail(float now, bool firing = false)
{
if (!Group || Group.GetPlayerCount(true) > 0)
return;
bool active = firing || Activity != null || ERR_MortarSystem.NearPlayer(Position);
int lod = -1;
if (active)
lod = 0;
if (Group.GetPermanentLOD() != lod)
Group.SetPermanentLOD(lod);
if (active && !Group.IsAIActivated())
Group.ActivateAI();
array<AIAgent> agents = {};
Group.GetAgents(agents);
foreach (AIAgent agent : agents)
{
if (!agent || EntityUtils.IsPlayer(agent.GetControlledEntity()))
continue;
if (agent.GetPermanentLOD() != lod)
agent.SetPermanentLOD(lod);
if (active && !agent.IsAIActivated() && ERR_Actors.CanFight(agent.GetControlledEntity()))
agent.ActivateAI();
}
}
void Update(float now)
{
if (m_Stopped) return;
if (!StartupActivated)
{
if (!SpawnFailure.IsEmpty()) return;
if (!m_Readiness) { SpawnFailed("initial support readiness owner missing"); return; }
ERR_MortarSupplyState startup = m_Readiness.Poll(now);
if (startup != ERR_MortarSupplyState.READY) return;
StartupActivated = true;
}
if (m_Transfer) m_Transfer.Poll(now);
if (!Operational())
{
Cancel(OperationalReason());
return;
}
if (Activity)
{
Trace("ACTIVITY", string.Format("native state=%1", Activity.GetActionState()), string.Format("age=%1s aim age=%2s committed=%3 shots=%4", (now - OrderedAt) / 1000, (now - AimedAt) / 1000, Released, OrderShots));
EAIActionState terminal = ActivityTerminal();
if (terminal == EAIActionState.FAILED) Cancel("native artillery activity FAILED");
else if (terminal == EAIActionState.COMPLETED) Cancel("native artillery activity COMPLETED (shot count is separate)");
else if (now - OrderedAt > ERR_MortarSalvo.TIMEOUT_MS) Cancel("120s artillery salvo timeout");
}
if (StartupActivated)
UpdateCrewDetail(now);
}
void Order(ERR_HQReport report, float now)
{
if (m_Stopped || !StartupActivated || !Operational() || !CommandOpen || Activity || !ERR_MortarPrediction.Fresh(report, now)) return;
Report = report.Snapshot();
OrderedAt = now;
NextOrder = now + 30000;
Released = false;
UpdateCrewDetail(now, true);
Activity = new SCR_AIStaticArtilleryActivity(Group.GetGroupUtilityComponent(), null, report.Position, SCR_EAIArtilleryAmmoType.HIGH_EXPLOSIVE, ERR_MortarSalvo.ROUNDS);
WatchActivity();
Group.GetGroupUtilityComponent().AddAction(Activity);
Audit("AIM", string.Format("native activity created; latest source=M%1 observed=%2", report.MessageId, report.Description(now)));
}
}
@@ -0,0 +1,68 @@
class ERR_MortarCourierCallback : ScriptedInventoryOperationCallback
{
ERR_MortarCourierTransfer Transfer;
protected override void OnComplete() { if (Transfer) Transfer.Acknowledge(true); }
protected override void OnFailed() { if (Transfer) Transfer.Acknowledge(false); }
}
// One exact native move belonging to one courier trip. Never retries ambiguous work.
class ERR_MortarCourierTransfer
{
protected ERR_MortarLogistics m_Service;
protected IEntity m_Shell;
protected IEntity m_From;
protected IEntity m_To;
protected InventoryStorageManagerComponent m_Source;
protected InventoryStorageManagerComponent m_Destination;
protected BaseInventoryStorageComponent m_Storage;
protected ref ERR_MortarCourierCallback m_Callback;
protected int m_Trip;
protected int m_Acknowledgement;
protected float m_Deadline;
protected bool m_Accepted;
protected bool m_Invalidated;
protected bool m_Resolved;
void ERR_MortarCourierTransfer(ERR_MortarLogistics service, int trip, IEntity shell, IEntity from, IEntity to, BaseInventoryStorageComponent storage, float now)
{
m_Service = service; m_Trip = trip; m_Shell = shell; m_From = from; m_To = to; m_Storage = storage; m_Deadline = now + 8000;
m_Source = InventoryStorageManagerComponent.Cast(from.FindComponent(InventoryStorageManagerComponent));
m_Destination = InventoryStorageManagerComponent.Cast(to.FindComponent(InventoryStorageManagerComponent));
}
static bool Proven(bool complete, bool source, bool destination, bool exact) { return complete && !source && destination && exact; }
protected bool Contains(InventoryStorageManagerComponent inventory)
{
if (!inventory || !m_Shell) return false;
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items); return items.Contains(m_Shell);
}
void Acknowledge(bool success)
{
if (m_Acknowledgement != 0) return;
if (success) m_Acknowledgement = 1;
else m_Acknowledgement = -1;
}
bool Begin(float now)
{
if (m_Callback || !Replication.IsServer() || !m_Service.TransferAllowed(m_Trip, m_Shell, m_From, m_To, now) || !Contains(m_Source) || Contains(m_Destination)) return false;
m_Callback = new ERR_MortarCourierCallback(); m_Callback.Transfer = this;
m_Accepted = m_Source.TryMoveItemToStorage(m_Shell, m_Storage, -1, m_Callback);
if (!m_Accepted) { Acknowledge(false); m_Resolved = true; }
return m_Accepted;
}
void Invalidate() { m_Invalidated = true; }
bool Unresolved() { return m_Accepted && !m_Resolved; }
IEntity Shell() { return m_Shell; }
int Poll(float now)
{
// A deleted exact entity cannot complete later; close as a permanent loss.
if (!m_Shell) { m_Resolved = true; m_Invalidated = true; return -1; }
bool exact;
InventoryItemComponent item;
if (m_Shell) item = InventoryItemComponent.Cast(m_Shell.FindComponent(InventoryItemComponent));
if (item && item.GetParentSlot()) exact = item.GetParentSlot().GetStorage() == m_Storage;
bool proven = Proven(m_Acknowledgement == 1, Contains(m_Source), Contains(m_Destination), exact);
if (proven || m_Acknowledgement < 0) m_Resolved = true;
if (m_Invalidated || now >= m_Deadline || !m_Service.TransferAllowed(m_Trip, m_Shell, m_From, m_To, now)) { m_Invalidated = true; return -1; }
if (proven) return 1;
if (m_Acknowledgement < 0 || !m_Accepted) return -1;
return 0;
}
}
@@ -0,0 +1,308 @@
// The dedicated manpack operator has no mortar-shell backpack. Preserve the
// native chooser and its caller's original index for every eligible gunner.
modded class SCR_AIStaticArtilleryActivity
{
protected override AIAgent FindBestAgentForArtillery(notnull array<AIAgent> agents, notnull IEntity artilleryEntity, out int outIndex)
{
ERR_MortarSystem system = ERR_MortarSystem.Get();
ERR_MortarBattery battery;
if (system) battery = system.FindGun(artilleryEntity);
if (!battery) return super.FindBestAgentForArtillery(agents, artilleryEntity, outIndex);
array<AIAgent> gunners = {};
foreach (AIAgent agent : agents)
{
if (agent && agent.GetControlledEntity() != battery.RadioOperator && ERR_Actors.CanFight(agent.GetControlledEntity()))
gunners.Insert(agent);
}
int filteredIndex;
AIAgent selected = super.FindBestAgentForArtillery(gunners, artilleryEntity, filteredIndex);
outIndex = -1;
if (selected) outIndex = agents.Find(selected);
return selected;
}
}
// Leave all other artillery (including player-operated captured guns) native.
modded class SCR_AIGetArtilleryAimDistanceCompensation
{
override ENodeResult EOnTaskSimulate(AIAgent owner, float dt)
{
IEntity gun;
GetVariableIn(PORT_ARTILLERY_ENTITY, gun);
ERR_MortarSystem system = ERR_MortarSystem.Get();
ERR_MortarBattery battery;
if (system)
battery = system.FindGun(gun);
if (!battery)
return super.EOnTaskSimulate(owner, dt);
battery.AimedAt = -1;
battery.AimValidatedAt = -1;
battery.AimSolver = null;
battery.AimSolution = null;
float now = GetGame().GetWorld().GetWorldTime();
ERR_HQReport report = system.Latest(battery, now);
if (!ERR_HQ.MissionActive()) { system.Withhold(battery, "aim: mission inactive"); return ENodeResult.FAIL; }
if (!battery.CommandOpen || battery.OrderShots >= ERR_MortarSalvo.ROUNDS) { system.Withhold(battery, "aim: salvo complete or order closed"); return ENodeResult.FAIL; }
if (battery.Released) { system.Withhold(battery, "aim: waiting for committed projectile"); return ENodeResult.FAIL; }
if (!report) { system.Withhold(battery, "aim: " + system.ContactFailure(battery)); return ENodeResult.FAIL; }
battery.Trace("AIM START", "solving moving intercept", string.Format("latest source=M%1; %2", report.MessageId, report.Description(now)));
ERR_MortarAimSolution solution = ERR_Solve(battery, system, report, now);
if (!solution) return ENodeResult.FAIL;
battery.Impact = solution.Impact;
battery.AimOffset = solution.SalvoOffset;
battery.AimedShot = battery.OrderShots;
battery.FlightTime = solution.FlightTime;
battery.AimSolution = solution;
battery.AimSolver = this; // weak: do not retain a destroyed native behavior tree
battery.AimedAt = now;
battery.AimValidatedAt = now;
battery.LastFailure = "";
battery.Trace("AIM SOLUTION", "ready", string.Format("predicted impact=%1 flight=%2s charge index=%3 aim target=%4 latest source=M%5", solution.Impact, solution.FlightTime, solution.Charge, solution.AimTarget, report.MessageId));
SetVariableOut(PORT_AIM_POS, solution.AimTarget);
SetVariableOut(PORT_INIT_SPEED_ID, solution.Charge);
return ENodeResult.SUCCESS;
}
// Reuse this exact native node's coefficient/elevation APIs. A slow native
// barrel can keep its command only if a NEW solve still matches that command.
bool ERR_RevalidateAim(ERR_MortarBattery battery, ERR_MortarSystem system, ERR_HQReport report, float now)
{
IEntity gun;
ResourceName ammo;
GetVariableIn(PORT_ARTILLERY_ENTITY, gun);
GetVariableIn(PORT_AMMO_PREFAB, ammo);
ERR_MortarAimSolution commanded = battery.AimSolution;
if (battery.AimSolver != this || !commanded || !commanded.Matches(battery, report) || gun != battery.Gun || ammo != ERR_MortarBattery.SHELL)
return system.Withhold(battery, "original aim solver or command no longer available");
if (!ERR_MortarPrediction.Fresh(report, now)) return system.Withhold(battery, system.ContactFailure(battery));
ERR_MortarAimSolution updated = ERR_Solve(battery, system, report, now);
if (!updated) return false;
if (!ERR_MortarAimRules.Equivalent(commanded.Charge, updated.Charge, commanded.GunOrigin, updated.GunOrigin, commanded.Impact, updated.Impact, commanded.AimTarget, updated.AimTarget, commanded.FlightTime, updated.FlightTime))
return system.Withhold(battery, "new ballistic solution requires native re-aim", string.Format("gun drift=%1m impact drift=%2m aim drift=%3m charge=%4/%5 flight=%6/%7s", vector.Distance(commanded.GunOrigin, updated.GunOrigin), vector.Distance(commanded.Impact, updated.Impact), vector.Distance(commanded.AimTarget, updated.AimTarget), commanded.Charge, updated.Charge, commanded.FlightTime, updated.FlightTime));
// Keep the ORIGINAL command/impact/flight. Comparing successive refreshed
// solutions would allow a moving target to drift away in small increments.
battery.AimValidatedAt = now;
battery.Trace("AIM REVALIDATED", "same native command", string.Format("fresh ballistic solve matched original; source=M%1 observed age=%2s", report.MessageId, (now - report.ObservedAt) / 1000));
return true;
}
// The report may change only after a real HQ downlink. Re-solve against the
// ORIGINAL geometry; never replace the commanded solution with an
// approximately equivalent successor (which would permit tolerance creep).
bool ERR_AdoptReport(ERR_MortarBattery battery, ERR_MortarSystem system, ERR_HQReport report, float now)
{
IEntity gun;
ResourceName ammo;
GetVariableIn(PORT_ARTILLERY_ENTITY, gun);
GetVariableIn(PORT_AMMO_PREFAB, ammo);
ERR_MortarAimSolution commanded = battery.AimSolution;
if (battery.Released || battery.RefreshRejected || !battery.CommandOpen || !battery.Refresh || !battery.Refresh.DeliveredFor(battery, report))
return system.Withhold(battery, "target update lacks completed delivery for this uncommitted order");
if (battery.AimSolver != this || !commanded || !commanded.Matches(battery, battery.Report) || gun != battery.Gun || ammo != ERR_MortarBattery.SHELL)
return system.Withhold(battery, "target update lost its original native aim owner");
if (battery.AimedAt < 0 || now < battery.AimedAt) return system.Withhold(battery, "original native aim timestamp invalid");
if (!ERR_MortarRefreshRules.Newer(battery.Report, report, now)) return system.Withhold(battery, "target update is not a newer fresh same-target observation");
ERR_MortarAimSolution updated = ERR_Solve(battery, system, report, now);
if (!updated) return false;
if (!ERR_MortarAimRules.Equivalent(commanded.Charge, updated.Charge, commanded.GunOrigin, updated.GunOrigin, commanded.Impact, updated.Impact, commanded.AimTarget, updated.AimTarget, commanded.FlightTime, updated.FlightTime))
return system.Withhold(battery, "target update requires a different native aim command");
// Synchronous adoption changes accepted observation identity together.
// Original command time/geometry, order deadline and shot count survive.
commanded.Report = report;
battery.Report = report;
battery.AimValidatedAt = now;
return true;
}
protected ERR_MortarAimSolution ERR_Solve(ERR_MortarBattery battery, ERR_MortarSystem system, ERR_HQReport report, float now)
{
IEntity gun = battery.Gun;
vector gunOrigin = gun.GetOrigin();
ResourceName ammo;
GetVariableIn(PORT_AMMO_PREFAB, ammo);
if (ammo != ERR_MortarBattery.SHELL) { system.Withhold(battery, "unsupported ammunition prefab", ammo); return null; }
Resource resource = Resource.Load(ammo);
if (!resource || !resource.IsValid() || !resource.GetResource()) { system.Withhold(battery, "shell resource load failed", ammo); return null; }
IEntitySource source = resource.GetResource().ToEntitySource();
if (!source) { system.Withhold(battery, "shell resource has no entity source", ammo); return null; }
array<float> coefficients = {};
GetAmmoInitialSpeedCoefficients(ammo, coefficients);
if (coefficients.IsEmpty())
coefficients.Insert(1.0);
float minimum, maximum;
GetVerticalAimLimits(gun, minimum, maximum);
float flight = 30;
float offset;
int charge;
vector impact;
vector salvoOffset = ERR_MortarSalvo.Offset(battery.SiteId, battery.OrderId, battery.OrderShots);
bool converged;
// Iterate the moving intercept using the native shell's flight time.
// Reject non-convergence rather than fire with an arbitrary lead time.
for (int iteration = 0; iteration < 4; iteration++)
{
if (!ERR_MortarPrediction.Predict(report, now, flight + 2, impact)) { system.Withhold(battery, "aim prediction: " + ERR_MortarPrediction.FailureReason, string.Format("flight lead=%1s; %2", flight + 2, report.Description(now))); return null; }
if (!system.OutsideSettlements(impact)) { system.Withhold(battery, "aim target inside settlement exclusion", system.SettlementReason); return null; }
impact += salvoOffset;
impact[1] = SCR_TerrainHelper.GetTerrainY(impact);
float distance = vector.DistanceXZ(gunOrigin, impact);
float altitude = impact[1] - gunOrigin[1];
if (distance < 350 || distance > 4000) { system.Withhold(battery, "predicted impact out of range", string.Format("iteration=%1 distance=%2m impact=%3 allowed=350..4000m", iteration, distance, impact)); return null; }
bool reachable;
float calculated;
for (int i = 0; i < coefficients.Count(); i++)
{
if (!BallisticTable.GetAimHeightOfProjectileAltitudeFromSource(distance, offset, calculated, source, altitude, coefficients[i]))
continue;
float tangent = (offset + altitude) / distance;
if (calculated <= 0 || calculated > 75 || tangent < Math.Tan(minimum) || tangent > Math.Tan(maximum))
continue;
charge = i;
reachable = true;
break;
}
if (!reachable) { system.Withhold(battery, "no native ballistic solution within elevation/flight-time limits", string.Format("iteration=%1 distance=%2 altitude=%3 charges=%4 elevation radians=%5..%6 flight limit=75s", iteration, distance, altitude, coefficients.Count(), minimum, maximum)); return null; }
converged = Math.AbsFloat(calculated - flight) < 1;
flight = calculated;
if (converged)
break;
}
if (!converged) { system.Withhold(battery, "moving intercept did not converge in four iterations"); return null; }
if (!system.Safe(battery, impact, flight + 2)) { system.Withhold(battery, "aim impact safety rejected", system.SafetyReason); return null; }
ERR_MortarAimSolution solution = new ERR_MortarAimSolution();
solution.Gun = gun;
solution.Report = report;
solution.OrderId = battery.OrderId;
solution.Shot = battery.OrderShots;
solution.GunOrigin = gunOrigin;
solution.Impact = impact;
solution.AimTarget = impact + Vector(0, offset, 0);
solution.SalvoOffset = salvoOffset;
solution.Charge = charge;
solution.FlightTime = flight;
return solution;
}
}
modded class SCR_AIShootStaticArtillery
{
override ENodeResult EOnTaskSimulate(AIAgent owner, float dt)
{
IEntity gun;
GetVariableIn(PORT_ARTILLERY_ENTITY, gun);
ERR_MortarSystem system = ERR_MortarSystem.Get();
ERR_MortarBattery battery;
if (system)
battery = system.FindGun(gun);
if (battery && !system.AllowShot(battery))
{
battery.Trace("SHOOT NODE", "authorization rejected", battery.LastFailure);
return ENodeResult.FAIL;
}
ENodeResult result = super.EOnTaskSimulate(owner, dt);
if (battery)
battery.Trace("SHOOT NODE", string.Format("native result=%1", result), "authorization passed; node result does not confirm a physical shot");
return result;
}
}
modded class SCR_MortarMuzzleComponent
{
protected override void TransferShellToMortar()
{
ERR_MortarSystem system = ERR_MortarSystem.Get();
ERR_MortarBattery battery;
if (system && m_CharController && !EntityUtils.IsPlayer(m_CharController.GetOwner()))
battery = system.FindGun(GetOwner());
if (battery)
{
// The loading animation takes time. Recheck at the actual transfer,
// before native inventory insertion triggers the physical projectile.
if (!system.AllowShot(battery))
{
if (m_ShellComponent)
{
m_ShellComponent.GetOnShellUsed().Remove(TransferShellToMortar);
m_ShellComponent.SetLoadedState(false);
}
CharacterAnimationComponent animation = m_CharController.GetAnimationComponent();
if (animation && animation.GetCommandHandler())
animation.GetCommandHandler().FinishItemUse(false);
battery.Audit("ABORT", "final shell transfer rejected; " + battery.LastFailure);
return;
}
battery.Released = true;
battery.ReserveShellAirspace(GetGame().GetWorld().GetWorldTime());
battery.Audit("SHELL COMMIT", string.Format("final safety passed; gunner=%1 predicted impact=%2 flight=%3s; invoking native transfer, awaiting projectile event", m_CharController.GetOwner(), battery.Impact, battery.FlightTime));
}
super.TransferShellToMortar();
}
override void OnWeaponFired(IEntity effectEntity, BaseMuzzleComponent muzzle, IEntity projectileEntity)
{
IEntity firingCharacter;
if (m_CharController) firingCharacter = m_CharController.GetOwner();
super.OnWeaponFired(effectEntity, muzzle, projectileEntity);
ERR_MortarSystem system = ERR_MortarSystem.Get();
ERR_MortarBattery battery;
if (system)
battery = system.FindGun(GetOwner());
if (!battery)
return;
bool playerGunner;
if (firingCharacter) playerGunner = EntityUtils.IsPlayer(firingCharacter);
battery.ObserveProjectile(projectileEntity, firingCharacter, playerGunner);
}
}
// The native tree normally re-aims only every five shots (and skips the first
// re-aim). Each salvo shell needs its own spread point and fresh authorization.
modded class SCR_AIStaticArtilleryBehavior
{
ERR_MortarBattery ERR_GetMortarBattery()
{
ERR_MortarSystem system = ERR_MortarSystem.Get();
if (!system) return null;
return system.FindGun(m_ArtilleryEntity.m_Value);
}
}
modded class SCR_AIStaticArtilleryBehavior_ShootingLogic
{
override ENodeResult EOnTaskSimulate(AIAgent owner, float dt)
{
ERR_MortarBattery battery;
SCR_AIStaticArtilleryBehavior behavior;
if (m_Utility)
{
behavior = SCR_AIStaticArtilleryBehavior.Cast(m_Utility.GetExecutedAction());
if (behavior) battery = behavior.ERR_GetMortarBattery();
}
if (battery)
{
int gate = ERR_MortarSupplyRules.CycleGate(battery.CommandOpen, battery.OrderShots, behavior.GetShotCount(), ERR_MortarSalvo.ROUNDS, ERR_MortarSupplyState.READY);
if (gate < 0) return ENodeResult.FAIL;
if (gate == 0) return ENodeResult.RUNNING;
// Native super increments the behavior counter. Wait BEFORE calling it,
// so repeated pending ticks cannot count the same projectile twice.
if (battery.OrderShots < ERR_MortarSalvo.ROUNDS)
{
float now = GetGame().GetWorld().GetWorldTime();
ERR_MortarSupplyState supply = battery.SupplyState(now);
if (supply == ERR_MortarSupplyState.PENDING) return ENodeResult.RUNNING;
if (supply == ERR_MortarSupplyState.FAILED)
{
ERR_MortarSystem system = ERR_MortarSystem.Get();
if (system) system.Withhold(battery, system.SupplyFailure(battery, now));
return ENodeResult.FAIL;
}
}
}
ENodeResult result = super.EOnTaskSimulate(owner, dt);
if (battery && result == ENodeResult.SUCCESS)
{
SetVariableOut(PORT_AIMING_NEEDED, true);
}
return result;
}
}
@@ -0,0 +1,347 @@
class ERR_MortarSupplyStatus
{
SCR_AIGroup Group;
IEntity Gun;
vector Position;
float ObservedAt;
int MessageId;
int Shells;
}
class ERR_MortarLogisticsRules
{
static bool Fresh(float observedAt, float now) { return observedAt >= 0 && now >= observedAt && now - observedAt <= 180000; }
static bool Need(int shells) { return shells >= 0 && shells <= 6; }
static bool NewRequest(int message, int consumed, bool fresh) { return fresh && message > 0 && message > consumed; }
static bool Near(float distance) { return distance >= 0 && distance <= 12; }
static bool ActiveTrip(bool server, bool stopped, int trip, int current, bool fresh, bool ownershipValid) { return server && !stopped && trip > 0 && trip == current && fresh && ownershipValid; }
}
// One finite western-cache courier, owned and ticked exclusively by HQ.
class ERR_MortarLogistics
{
protected ERR_HQ m_HQ;
protected ERR_GameMode m_Mode;
protected ERR_SiteRuntime m_Site;
protected IEntity m_Cache;
protected SCR_AIGroup m_Group;
protected ERR_MortarBattery m_Battery;
protected IEntity m_OriginalGun;
protected SCR_AIGroup m_OriginalBatteryGroup;
protected ref array<IEntity> m_BatteryMembers = {};
protected ref array<IEntity> m_OriginalMembers = {};
protected ref array<IEntity> m_Stock = {};
protected ref array<IEntity> m_Cargo = {};
protected ref array<IEntity> m_AttemptedPickup = {};
protected ref array<IEntity> m_AttemptedUnload = {};
protected ref array<ref ERR_MortarInitialItem> m_Initial = {};
protected ref ERR_MortarSupplyStatus m_Report;
protected ref ERR_HQRoute m_Order;
protected ref ERR_MortarCourierTransfer m_Move;
protected IEntity m_Courier;
protected AIWaypoint m_Waypoint;
protected AIWaypoint m_HoldWaypoint;
protected float m_InitialDeadline;
protected float m_NextReport;
protected float m_LastDeliveryAt = -1;
protected int m_ConsumedMessage;
protected int m_Trip;
protected int m_State; // 0 initial, 1 idle, 2 downlink, 3 pickup, 4 travel, 5 unload
protected int m_Delivered;
protected int m_CompletedTrips;
protected bool m_Started;
protected bool m_Stopped;
protected string m_Reason;
static const int INITIAL_SHELLS = 12;
static const int TRIP_SHELLS = 6;
void ERR_MortarLogistics(ERR_HQ hq, ERR_GameMode mode) { m_HQ = hq; m_Mode = mode; }
int State() { return m_State; }
int Trip() { return m_Trip; }
int Delivered() { return m_Delivered; }
int CompletedTrips() { return m_CompletedTrips; }
IEntity Cache() { return m_Cache; }
SCR_AIGroup Group() { return m_Group; }
IEntity Courier() { return m_Courier; }
ERR_MortarBattery Battery() { return m_Battery; }
ERR_MortarSupplyStatus Report() { return m_Report; }
bool Stopped() { return m_Stopped; }
void InitialStock(out array<IEntity> items) { items.Copy(m_Stock); }
protected void Audit(string kind, string reason)
{
int message; if (m_Report) message = m_Report.MessageId;
m_Reason = reason;
m_HQ.Journal().Write("COURIER " + kind, string.Format("trip=%1 state=%2 delivered=%3; %4", m_Trip, m_State, m_Delivered, reason), message);
}
protected InventoryStorageManagerComponent Inventory(IEntity entity)
{
if (!entity) return null;
return InventoryStorageManagerComponent.Cast(entity.FindComponent(InventoryStorageManagerComponent));
}
protected bool Contains(IEntity owner, IEntity shell)
{
InventoryStorageManagerComponent inventory = Inventory(owner);
if (!inventory || !shell) return false;
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items); return items.Contains(shell);
}
protected void Shells(IEntity owner, out array<IEntity> shells)
{
shells.Clear(); InventoryStorageManagerComponent inventory = Inventory(owner); if (!inventory) return;
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
foreach (IEntity item : items)
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_MortarBattery.SHELL) shells.Insert(item);
}
bool Prepare()
{
if (!Replication.IsServer() || m_Started || m_Stopped) return false;
m_Started = true;
ERR_ScenarioComponent scenario = ERR_ScenarioComponent.Cast(m_Mode.FindComponent(ERR_ScenarioComponent));
if (!scenario || !scenario.Sites()) return false;
m_Site = scenario.Sites().Find("everon-supply-2");
if (!m_Site || !m_Site.Group || !m_Site.PatrolCycle || !scenario.Sites().Owns(m_Site.Container)) return false;
m_Cache = m_Site.Container; m_Group = m_Site.Group;
array<ref ERR_MortarBattery> batteries = {}; m_HQ.Mortars().Batteries(batteries);
if (batteries.Count() != 1) return false;
m_Battery = batteries[0]; m_OriginalGun = m_Battery.Gun; m_OriginalBatteryGroup = m_Battery.Group;
InventoryStorageManagerComponent inventory = Inventory(m_Cache);
if (!inventory || !inventory.CanInsertResource(ERR_MortarBattery.SHELL, EStoragePurpose.PURPOSE_DEPOSIT)) return false;
array<IEntity> existing = {}; Shells(m_Cache, existing); if (!existing.IsEmpty()) return false;
array<AIAgent> agents = {}; m_Group.GetAgents(agents);
foreach (AIAgent agent : agents)
{
if (!agent || !ERR_Actors.CanFight(agent.GetControlledEntity())) return false;
m_OriginalMembers.Insert(agent.GetControlledEntity());
}
if (m_OriginalMembers.Count() != m_Site.InitialGuardCount) return false;
m_InitialDeadline = GetGame().GetWorld().GetWorldTime() + 15000;
// SiteService owns this existing cache and its contents through rollback/world teardown.
// This bounded startup allocation is never reopened, including partial failure/theft.
for (int i = 0; i < INITIAL_SHELLS; i++)
{
IEntity shell = ERR_Actors.Spawn(ERR_MortarBattery.SHELL, m_Cache.GetOrigin());
if (!shell) return false;
// Keep the exact created identity; counts cannot prove an external item was
// not substituted while an asynchronous insertion was pending.
m_Mode.Own(shell); m_Stock.Insert(shell);
ERR_MortarInitialItem operation = new ERR_MortarInitialItem(); m_Initial.Insert(operation);
if (!inventory.TryInsertItem(shell, EStoragePurpose.PURPOSE_DEPOSIT, operation)) { operation.Rejected(); return false; }
}
return true;
}
protected bool OriginalGroup()
{
if (!m_Site || m_Site.Group != m_Group || m_Site.Container != m_Cache || !m_Group || !m_Cache || m_Group.GetPlayerCount(true) > 0 || m_Group.GetFaction() != m_Mode.EnemyFaction()) return false;
array<AIAgent> agents = {}; m_Group.GetAgents(agents); bool living;
foreach (AIAgent agent : agents)
{
if (!agent || !agent.GetControlledEntity()) continue;
IEntity actor = agent.GetControlledEntity();
if (!m_OriginalMembers.Contains(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != m_Group || SCR_FactionManager.SGetFaction(actor) != m_Mode.EnemyFaction()) return false;
if (ERR_Actors.CanFight(actor)) living = true;
}
return living;
}
protected bool BindBatteryMembers()
{
if (!m_Battery || !m_Battery.StartupActivated || m_Battery.Group != m_OriginalBatteryGroup || m_Battery.Gun != m_OriginalGun) return false;
if (!m_BatteryMembers.IsEmpty()) return true;
array<AIAgent> agents = {}; m_Battery.Group.GetAgents(agents);
foreach (AIAgent agent : agents)
if (agent && agent.GetControlledEntity() && !m_BatteryMembers.Contains(agent.GetControlledEntity())) m_BatteryMembers.Insert(agent.GetControlledEntity());
return !m_BatteryMembers.IsEmpty();
}
bool TargetAvailable()
{
if (!m_Battery || m_Battery.Gun != m_OriginalGun || m_Battery.Group != m_OriginalBatteryGroup || !m_OriginalGun || !m_Battery.StartupActivated || m_Battery.Stopped() || !m_Battery.Operational() || m_HQ.Mortars().FindGun(m_OriginalGun) != m_Battery || m_BatteryMembers.IsEmpty()) return false;
if (SCR_FactionManager.SGetFaction(m_OriginalGun) != m_Mode.EnemyFaction()) return false;
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_OriginalGun.FindComponent(SCR_AIVehicleUsageComponent));
if (!usage || !usage.GetTurretCompartmentSlot()) return false;
IEntity occupant = usage.GetTurretCompartmentSlot().GetOccupant();
if (!occupant) return true;
return m_BatteryMembers.Contains(occupant) && ERR_Actors.CanFight(occupant) && !ERR_Actors.Player(occupant) && ERR_Actors.Group(occupant) == m_OriginalBatteryGroup && SCR_FactionManager.SGetFaction(occupant) == m_Mode.EnemyFaction();
}
bool CanSample(ERR_MortarBattery battery) { return battery == m_Battery && TargetAvailable(); }
protected bool Bearer(IEntity actor)
{
return actor && m_OriginalMembers.Contains(actor) && ERR_Actors.Group(actor) == m_Group && ERR_Actors.CanFight(actor) && !ERR_Actors.Player(actor) && !CompartmentAccessComponent.GetVehicleIn(actor) && SCR_FactionManager.SGetFaction(actor) == m_Mode.EnemyFaction() && ERR_HQRadio.Capability(actor) != 2;
}
bool AcceptsStatus(ERR_MortarSupplyStatus report, float now)
{
return !m_Stopped && m_State == 1 && report && m_Battery && report.Group == m_Battery.Group && report.Gun == m_Battery.Gun && ERR_MortarLogisticsRules.Need(report.Shells) && ERR_MortarLogisticsRules.NewRequest(report.MessageId, m_ConsumedMessage, ERR_MortarLogisticsRules.Fresh(report.ObservedAt, now));
}
void Receive(ERR_MortarSupplyStatus report, float now)
{
if (!AcceptsStatus(report, now)) return;
m_Report = report;
Audit("RECEIVED", "immutable low-ammunition status delivered by native physical radio route");
}
protected bool Authorized(float now)
{
if (!m_Mode || !m_Mode.Active() || !m_Report || !m_Battery) return false;
return ERR_MortarLogisticsRules.ActiveTrip(Replication.IsServer(), m_Stopped, m_Trip, m_HQ.LogisticsToken(m_Group), ERR_MortarLogisticsRules.Fresh(m_Report.ObservedAt, now), OriginalGroup()) && TargetAvailable() && m_Report.Gun == m_Battery.Gun && m_Report.Group == m_Battery.Group && vector.Distance(m_Report.Position, m_Battery.Gun.GetOrigin()) <= 5;
}
bool TransferAllowed(int trip, IEntity shell, IEntity from, IEntity to, float now)
{
if (trip != m_Trip || !Authorized(now) || !shell || !m_Stock.Contains(shell) || !Bearer(m_Courier)) return false;
if (m_State == 3) return from == m_Cache && to == m_Courier && ERR_MortarLogisticsRules.Near(vector.Distance(from.GetOrigin(), to.GetOrigin()));
if (m_State != 5 || from != m_Courier || !m_Cargo.Contains(shell) || !ERR_Actors.CanFight(to) || ERR_Actors.Player(to) || to == m_Battery.RadioOperator || !m_BatteryMembers.Contains(to) || ERR_Actors.Group(to) != m_Battery.Group || SCR_FactionManager.SGetFaction(to) != m_Mode.EnemyFaction()) return false;
return !CompartmentAccessComponent.GetVehicleIn(to) && ERR_MortarLogisticsRules.Near(vector.Distance(from.GetOrigin(), to.GetOrigin()));
}
protected void ClearWaypoint()
{
if (m_Waypoint) { if (m_Group) m_Group.RemoveWaypoint(m_Waypoint); ERR_Actors.DeleteSpawned(m_Waypoint); }
m_Waypoint = null;
if (m_HoldWaypoint) { if (m_Group) m_Group.RemoveWaypoint(m_HoldWaypoint); ERR_Actors.DeleteSpawned(m_HoldWaypoint); }
m_HoldWaypoint = null;
}
protected bool MoveTo(vector position)
{
ClearWaypoint();
m_Waypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), ERR_Actors.Ground(position)));
if (!m_Waypoint) return false;
// A completed Move alone would let the original patrol resume during the
// asynchronous item operation. This owned, non-completing native Defend
// keeps that queue below us until release; its original entries are untouched.
m_HoldWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.DefendWaypoint(), ERR_Actors.Ground(position)));
if (!m_HoldWaypoint) { ClearWaypoint(); return false; }
m_HoldWaypoint.SetCompletionRadius(8);
m_Group.AddWaypointAt(m_HoldWaypoint, 0); m_Group.AddWaypointAt(m_Waypoint, 0); m_Group.ActivateAI(); return true;
}
protected void Finish(string reason)
{
Audit("END", reason);
if (m_Move) m_Move.Invalidate();
ClearWaypoint(); m_HQ.ReleaseLogistics(m_Group, m_Trip);
m_Order = null; m_Report = null; m_Courier = null; m_Cargo.Clear(); m_State = 1;
// Native requests cannot be canceled. A pending move remains held until its
// physical outcome resolves; callbacks cannot authorize a later trip.
m_NextReport = GetGame().GetWorld().GetWorldTime() + 60000;
}
protected bool BeginMove(IEntity shell, IEntity from, IEntity to, float now)
{
InventoryStorageManagerComponent destination = Inventory(to);
BaseInventoryStorageComponent storage; if (destination) storage = destination.FindStorageForItem(shell, EStoragePurpose.PURPOSE_DEPOSIT);
if (!storage) return false;
m_Move = new ERR_MortarCourierTransfer(this, m_Trip, shell, from, to, storage, now);
return m_Move.Begin(now);
}
protected bool PollMove(float now)
{
if (!m_Move) return true;
int result = m_Move.Poll(now);
if (result == 0) return false;
if (result < 0) { Finish("native item operation rejected, expired or lost authority; physical items retained"); return false; }
IEntity shell = m_Move.Shell(); m_Move = null;
if (m_State == 3) { m_Cargo.Insert(shell); Audit("PICKUP", "exact depot shell confirmed in original bearer inventory"); }
else if (m_State == 5) { m_Cargo.RemoveItem(shell); m_Delivered++; m_LastDeliveryAt = now; Audit("DELIVERED", "exact original shell confirmed in living mortar crew inventory"); }
return true;
}
protected void Pickup(float now)
{
if (!m_Courier)
{
foreach (IEntity candidate : m_OriginalMembers)
if (Bearer(candidate) && Inventory(candidate) && Inventory(candidate).CanInsertResource(ERR_MortarBattery.SHELL, EStoragePurpose.PURPOSE_DEPOSIT) && ERR_MortarLogisticsRules.Near(vector.Distance(candidate.GetOrigin(), m_Cache.GetOrigin()))) { m_Courier = candidate; break; }
if (!m_Courier) return;
}
if (!Bearer(m_Courier)) { Finish("original bearer lost or captured"); return; }
if (!PollMove(now)) return;
if (m_Cargo.Count() < TRIP_SHELLS)
{
foreach (IEntity shell : m_Stock)
{
if (!shell || !Contains(m_Cache, shell) || m_AttemptedPickup.Contains(shell)) continue;
InventoryStorageManagerComponent destination = Inventory(m_Courier);
if (!destination || !destination.FindStorageForItem(shell, EStoragePurpose.PURPOSE_DEPOSIT)) break;
m_AttemptedPickup.Insert(shell);
if (!BeginMove(shell, m_Cache, m_Courier, now)) Finish("native pickup rejected; no second request for that shell");
return;
}
}
if (m_Cargo.IsEmpty()) { Finish("finite cache empty or bearer native storage full"); return; }
m_State = 4;
if (!MoveTo(m_Report.Position)) { Finish("native delivery waypoint unavailable"); return; }
Audit("OUTBOUND", "original patrol walking with verified finite cargo to reported battery position");
}
protected void Unload(float now)
{
if (!PollMove(now)) return;
if (m_Cargo.IsEmpty()) { m_CompletedTrips++; Finish("finite delivery complete; original patrol restored"); return; }
array<AIAgent> agents = {}; m_Battery.Group.GetAgents(agents);
foreach (IEntity shell : m_Cargo)
{
if (!shell || !Contains(m_Courier, shell)) { Finish("carried shell lost or stolen; no replacement"); return; }
if (m_AttemptedUnload.Contains(shell)) { Finish("previous unload unresolved or failed; no retry"); return; }
foreach (AIAgent agent : agents)
{
if (!agent) continue;
IEntity recipient = agent.GetControlledEntity();
if (!TransferAllowed(m_Trip, shell, m_Courier, recipient, now)) continue;
InventoryStorageManagerComponent destination = Inventory(recipient);
if (!destination || !destination.FindStorageForItem(shell, EStoragePurpose.PURPOSE_DEPOSIT)) continue;
m_AttemptedUnload.Insert(shell);
if (!BeginMove(shell, m_Courier, recipient, now)) Finish("native recipient move rejected; cargo remains physical");
return;
}
}
}
void Tick(float now)
{
if (m_Stopped || !m_Started || !Replication.IsServer() || !m_Mode.Active()) return;
if (m_State == 0)
{
bool failed; foreach (ERR_MortarInitialItem operation : m_Initial) { if (!operation.Finished) { if (now < m_InitialDeadline) return; failed = true; } else if (!operation.Succeeded) failed = true; }
if (!OriginalGroup()) failed = true;
array<IEntity> shells = {}; Shells(m_Cache, shells);
if (shells.Count() != INITIAL_SHELLS) { if (now < m_InitialDeadline && !failed) return; failed = true; }
foreach (IEntity originalShell : m_Stock) if (!originalShell || !shells.Contains(originalShell)) failed = true;
if (m_Stock.Count() != INITIAL_SHELLS) failed = true;
if (failed) { Audit("STARTUP FAILED", "one-time native stock allocation incomplete; no refill"); Stop(); return; }
m_State = 1; Audit("READY", "12 distinct original HE shells physically confirmed in existing western cache");
}
if (m_State == 1)
{
if (m_Move) { m_Move.Poll(now); if (m_Move.Unresolved()) return; m_Move = null; }
if (!OriginalGroup() || !BindBatteryMembers() || !TargetAvailable()) return;
bool available; foreach (IEntity stocked : m_Stock) if (Contains(m_Cache, stocked) && !m_AttemptedPickup.Contains(stocked)) available = true;
if (!available) return;
if (now >= m_NextReport && ERR_MortarLogisticsRules.Need(m_Battery.CountShells())) { if (m_HQ.QueueSupplyStatus(m_Battery, now)) m_NextReport = now + 60000; }
if (!m_Report || !ERR_MortarLogisticsRules.NewRequest(m_Report.MessageId, m_ConsumedMessage, ERR_MortarLogisticsRules.Fresh(m_Report.ObservedAt, now))) return;
int nextTrip = m_Trip + 1;
if (!m_HQ.ReserveLogistics(m_Group, nextTrip)) return;
m_Trip = nextTrip; m_ConsumedMessage = m_Report.MessageId;
m_Order = m_HQ.Downlink(m_Group, "MORTAR SUPPLY", "collect finite western-cache HE; walk to reported battery", m_Report.MessageId);
m_State = 2;
if (!m_Order) { Finish("no native courier downlink"); return; }
Audit("QUEUED", "original patrol reserved pending physical radio command delivery");
}
if (!Authorized(now)) { Finish("supply report expired or original ownership/gun/group changed"); return; }
if (m_Courier && !Bearer(m_Courier)) { Finish("original bearer unavailable; surviving cargo retained"); return; }
if (m_State == 2)
{
int delivered = m_Order.Advance(now, m_HQ.Journal());
if (delivered == 0) return;
if (delivered < 0) { Finish("courier command interrupted"); return; }
m_Order = null; m_State = 3;
if (!MoveTo(m_Cache.GetOrigin())) { Finish("native pickup waypoint unavailable"); return; }
Audit("DISPATCH", "same original patrol receives delivered order and walks to finite cache");
}
if (m_State == 3) { Pickup(now); return; }
if (m_State == 4)
{
if (!ERR_MortarLogisticsRules.Near(vector.Distance(m_Courier.GetOrigin(), m_Report.Position))) return;
m_State = 5; Audit("ARRIVED", "physical bearer reached reported gun area; exact recipient checks still required");
}
if (m_State == 5) Unload(now);
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
if (m_Move) m_Move.Invalidate();
ClearWaypoint(); m_HQ.ReleaseLogistics(m_Group, m_Trip); m_Order = null; m_Report = null;
}
#ifdef WORKBENCH
ERR_HQRoute ProbeOrder() { return m_Order; }
int ProbeSourceMessage() { return m_ConsumedMessage; }
float ProbeLastDelivery() { return m_LastDeliveryAt; }
#endif
string Status() { return string.Format("courier state=%1 finite initial=%2 delivered=%3 stopped=%4 (%5)", m_State, m_Stock.Count(), m_Delivered, m_Stopped, m_Reason); }
}
@@ -0,0 +1,131 @@
// All target coordinates come from delivered HQ observations, never a player's
// current transform. Road projection stops at an unknown junction/road end.
class ERR_MortarPrediction
{
// Read immediately after a failed synchronous prediction; no extra world scan.
static string FailureReason;
// Includes observation uplink, HQ downlink and native aiming. All stages
// retain the original observation time; radio delivery never renews it.
static const float MAX_REPORT_AGE_MS = 60000;
static bool Fresh(ERR_HQReport report, float now)
{
return report && report.Target && !report.Distress && now >= report.ObservedAt && now - report.ObservedAt <= MAX_REPORT_AGE_MS && report.TravelMode != "aircraft";
}
static bool Reject(string reason)
{
FailureReason = reason;
return false;
}
static vector ClosestOnSegment(vector point, vector start, vector end)
{
vector segment = end - start;
segment[1] = 0;
vector offset = point - start;
offset[1] = 0;
float lengthSq = segment.LengthSq();
if (lengthSq < 0.01)
return start;
return vector.Lerp(start, end, Math.Clamp(vector.Dot(offset, segment) / lengthSq, 0, 1));
}
static bool FollowRoad(array<vector> points, vector observed, vector velocity, float seconds, out vector impact)
{
if (!points || points.Count() < 2)
return Reject("road polyline has fewer than two points");
int segment = -1;
float closest = 20;
vector projected;
for (int i = 0; i < points.Count() - 1; i++)
{
vector candidate = ClosestOnSegment(observed, points[i], points[i + 1]);
float distance = vector.DistanceXZ(candidate, observed);
if (distance < closest)
{
closest = distance;
segment = i;
projected = candidate;
}
}
if (segment < 0)
return Reject("observed vehicle is not within 20m of a road segment");
vector forward = points[segment + 1] - points[segment];
forward[1] = 0;
forward.Normalize();
vector travel = velocity;
travel[1] = 0;
float speed = travel.Length();
travel.Normalize();
float alignment = vector.Dot(forward, travel);
if (Math.AbsFloat(alignment) < 0.7)
return Reject("vehicle heading crosses road; alignment below 0.7");
int step = 1;
int next = segment + 1;
if (alignment < 0)
{
step = -1;
next = segment;
}
float remaining = speed * seconds;
vector position = projected;
while (next >= 0 && next < points.Count())
{
float length = vector.DistanceXZ(position, points[next]);
if (length >= remaining && length > 0.01)
{
impact = vector.Lerp(position, points[next], remaining / length);
return true;
}
remaining -= length;
position = points[next];
next += step;
}
return Reject("prediction reaches unknown road end/junction");
}
static bool Predict(ERR_HQReport report, float now, float flightSeconds, out vector impact)
{
FailureReason = "";
if (!report) return Reject("target report missing");
if (report.Distress) return Reject("distress report cannot authorize fire");
if (now < report.ObservedAt) return Reject("observation timestamp is in the future");
if (now - report.ObservedAt > MAX_REPORT_AGE_MS) return Reject("observation older than 60s");
float seconds = (now - report.ObservedAt) / 1000 + flightSeconds;
if (seconds < 0 || seconds > 90)
return Reject("observation age plus flight outside 0..90s");
if (report.TravelMode == "aircraft") return Reject("aircraft target unsupported");
if (report.TravelMode == "mixed travel") return Reject("mixed travel cannot be predicted");
impact = report.Position;
if (!report.HasVelocity || report.Velocity.Length() < 0.5)
return true;
if (report.Velocity.Length() * seconds > 1800)
return Reject("predicted travel exceeds 1800m");
bool vehicle = report.TravelMode == "vehicle" || report.TravelMode == "in vehicle";
if (vehicle)
{
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
if (!world || !world.GetRoadNetworkManager())
return Reject("road network unavailable");
BaseRoad road;
float roadDistance;
world.GetRoadNetworkManager().GetClosestRoad(report.Position, road, roadDistance, skipNavlinks: true);
if (!road) return Reject("no road near reported vehicle position");
if (roadDistance > 20) return Reject("reported vehicle farther than 20m from road");
array<vector> points = {};
road.GetPoints(points);
return FollowRoad(points, report.Position, report.Velocity, seconds, impact);
}
impact += report.Velocity * seconds;
return true;
}
static bool ThreatensFriendly(vector impact, vector position, vector velocity, float flightSeconds)
{
velocity[1] = 0;
vector future = position + velocity * Math.Clamp(flightSeconds, 0, 90);
// Protect the entire predicted path, not only where a soldier is now.
return vector.DistanceXZ(impact, ClosestOnSegment(impact, position, future)) <= 100;
}
}
@@ -0,0 +1,180 @@
enum ERR_MortarSupplyState { FAILED = -1, PENDING, READY }
class ERR_MortarSupplyRules
{
static int CycleGate(bool commandOpen, int confirmedShots, int behaviorShots, int limit, int supplyState)
{
if (!commandOpen) return -1;
if (confirmedShots <= behaviorShots) return 0;
if (confirmedShots < limit) return supplyState;
return 1;
}
}
class ERR_MortarInitialItem : 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); }
void Rejected() { Finish(false); }
}
// One finite allocation, after the ENTIRE original support crew exists. Failed
// or stopped setup retains physical assets and retires only its pending spawn intent.
class ERR_MortarReadiness
{
protected ERR_MortarBattery m_Battery;
protected ref array<IEntity> m_Members = {};
protected ref array<ref ERR_MortarInitialItem> m_Operations = {};
protected bool m_Allocated;
protected bool m_Ready;
protected bool m_Stopped;
protected bool m_Retired;
protected int m_RequestedShells;
void ERR_MortarReadiness(ERR_MortarBattery battery) { m_Battery = battery; }
static ERR_MortarSupplyState Population(int physical, int gunners, int operators, bool complete, bool expired)
{
if (physical > 4 || gunners > 3 || operators > 1) return ERR_MortarSupplyState.FAILED;
if (physical == 4 && gunners == 3 && operators == 1 && complete && !expired) return ERR_MortarSupplyState.READY;
if (expired) return ERR_MortarSupplyState.FAILED;
return ERR_MortarSupplyState.PENDING;
}
static bool AllocationValid(int shells) { return shells >= 0 && shells <= 9; }
int RequestedShells() { return m_RequestedShells; }
bool Ready() { return m_Ready && !m_Stopped; }
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
if (m_Ready || m_Retired || !m_Battery || !m_Battery.Group || !Replication.IsServer()) return;
m_Retired = true;
// As in Core initial guard cleanup: stopping the current request alone does
// not retire the native group's still-authored future expansion intent.
RetireGroup(m_Battery.Group);
}
static void RetireGroup(SCR_AIGroup group)
{
if (!group || !Replication.IsServer()) return;
group.SetSpawnImmediately(false);
group.m_aUnitPrefabSlots = new array<ResourceName>();
if (!GetGame()) return;
ChimeraAIWorld world = ChimeraAIWorld.Cast(GetGame().GetAIWorld());
if (world) world.PurgeSpawnRequestsForGroup(group);
}
protected ERR_MortarSupplyState Fail(string reason)
{
m_Battery.SpawnFailed(reason);
Stop();
return ERR_MortarSupplyState.FAILED;
}
protected bool Allocate()
{
m_Allocated = true; // before native requests; never refill or retry a failure
foreach (IEntity actor : m_Members)
{
if (actor == m_Battery.RadioOperator) continue;
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
int existing = m_Battery.InventoryShells(inventory);
if (!AllocationValid(existing)) return false;
if (ERR_HQRadio.Capability(actor) == 0 && !inventory.TrySpawnPrefabToStorage("{E1A5D4B878AA8980}Prefabs/Items/Equipment/Radios/Radio_R148.et"))
m_Battery.Audit("RADIO EQUIP", "optional crew handset did not fit; dedicated manpack remains required");
for (int i = existing; i < 9; i++)
{
ERR_MortarInitialItem operation = new ERR_MortarInitialItem();
m_Operations.Insert(operation);
m_RequestedShells++;
if (!inventory.TrySpawnPrefabToStorage(ERR_MortarBattery.SHELL, purpose: EStoragePurpose.PURPOSE_DEPOSIT, cb: operation))
{
operation.Rejected();
return false;
}
}
}
return true;
}
ERR_MortarSupplyState Poll(float now)
{
if (m_Stopped || !Replication.IsServer() || !m_Battery || m_Battery.Stopped()) return ERR_MortarSupplyState.FAILED;
if (m_Ready) return ERR_MortarSupplyState.READY;
SCR_AIGroup group = m_Battery.Group;
ERR_GameMode mode = ERR_GameMode.Get();
if (!mode || !group || !m_Battery.Gun || group.GetFaction() != mode.EnemyFaction() || group.GetPlayerCount(true) > 0) return Fail("initial support group/gun/faction changed");
DamageManagerComponent damage = DamageManagerComponent.Cast(m_Battery.Gun.FindComponent(DamageManagerComponent));
if (!damage || damage.IsDestroyed()) return Fail("initial support gun missing damage manager or destroyed");
bool expired = now < m_Battery.SpawnedAt || now - m_Battery.SpawnedAt >= 90000;
array<AIAgent> agents = {}; group.GetAgents(agents);
array<IEntity> physical = {};
int gunners, operators;
IEntity operatorActor;
foreach (AIAgent agent : agents)
{
if (!agent || !agent.GetControlledEntity()) continue;
IEntity actor = agent.GetControlledEntity();
if (physical.Contains(actor)) return Fail("initial support group reports duplicate physical actor");
physical.Insert(actor);
if (!ERR_Actors.CanFight(actor) || SCR_FactionManager.SGetFaction(actor) != mode.EnemyFaction() || !actor.GetPrefabData()) return Fail("initial support actor unavailable or has wrong faction/resource");
ResourceName prefab = actor.GetPrefabData().GetPrefabName();
if (prefab == ERR_MortarBattery.CREW) gunners++;
else if (prefab == ERR_MortarBattery.RADIO_OPERATOR) { operators++; operatorActor = actor; }
else return Fail("initial support actor does not belong to authored crew catalog");
}
ERR_MortarSupplyState population = Population(physical.Count(), gunners, operators, group.IsExpandComplete(), expired);
if (population == ERR_MortarSupplyState.FAILED) return Fail("initial four-person support population incomplete/invalid within 90 seconds");
if (population == ERR_MortarSupplyState.PENDING) return population;
if (m_Allocated)
{
foreach (IEntity original : m_Members)
if (!physical.Contains(original)) return Fail("initial support member replaced after finite allocation");
}
foreach (IEntity member : physical)
{
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(member.FindComponent(InventoryStorageManagerComponent));
if (!inventory) return ERR_MortarSupplyState.PENDING;
if (!m_Allocated && member != operatorActor && m_Battery.InventoryShells(inventory) < 9 && !inventory.CanInsertResource(ERR_MortarBattery.SHELL, EStoragePurpose.PURPOSE_DEPOSIT)) return ERR_MortarSupplyState.PENDING;
}
if (ERR_HQRadio.Capability(operatorActor) != 2) return ERR_MortarSupplyState.PENDING;
// Preserve the existing earliest activation delay, but it is not readiness.
if (now - m_Battery.SpawnedAt < 10000) return ERR_MortarSupplyState.PENDING;
if (!m_Allocated)
{
if (!group.GetGroupUtilityComponent() || !group.GetGroupUtilityComponent().m_VehicleMgr) return ERR_MortarSupplyState.PENDING;
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Battery.Gun.FindComponent(SCR_AIVehicleUsageComponent));
if (!usage || !group.GetGroupUtilityComponent().m_VehicleMgr.TryAddVehicle(usage)) return Fail("native support gun registration failed");
m_Members.Copy(physical); m_Battery.RadioOperator = operatorActor;
if (!Allocate()) return Fail("finite initial support shell allocation rejected; no retry");
return ERR_MortarSupplyState.PENDING; // never accept mutation-frame readiness
}
foreach (ERR_MortarInitialItem operation : m_Operations)
{
if (!operation.Finished) return ERR_MortarSupplyState.PENDING;
if (!operation.Succeeded) return Fail("finite initial support shell operation failed; no retry");
}
foreach (IEntity equipped : m_Members)
{
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(equipped.FindComponent(InventoryStorageManagerComponent));
int shells = m_Battery.InventoryShells(inventory);
if (equipped == operatorActor)
{
if (shells != 0) return Fail("dedicated initial manpack operator unexpectedly carries mortar shells");
}
else if (shells != 9) return ERR_MortarSupplyState.PENDING;
}
if (m_Battery.CountShells() != 27) return ERR_MortarSupplyState.PENDING;
m_Ready = true;
// One physical-ready handoff: HQ already owns/reserves this group, while
// Intel must see its actual late-spawned members after finite stock settles.
mode.RegisterGroup(group);
group.ActivateAI();
foreach (AIAgent readyAgent : agents)
{
if (readyAgent) { readyAgent.SetPermanentLOD(0); readyAgent.ActivateAI(); }
}
m_Battery.Audit("CREW READY", string.Format("four original physical members, one manpack, 27 confirmed HE; new shell requests=%1; allocation permanently closed", m_RequestedShells));
return ERR_MortarSupplyState.READY;
}
}
@@ -0,0 +1,37 @@
// One newer observation in transit for one existing physical firing order.
// The report's observed time survives both radio legs unchanged.
class ERR_MortarRefresh
{
IEntity Gun;
SCR_AIGroup Group;
int OrderId;
int RadioId;
ref ERR_HQReport Report;
ref ERR_HQRoute Route;
void ERR_MortarRefresh(ERR_MortarBattery battery, ERR_HQReport report, ERR_HQRoute route)
{
Gun = battery.Gun;
Group = battery.Group;
OrderId = battery.OrderId;
RadioId = route.MessageId;
Report = report.Snapshot();
Route = route;
}
bool Owns(ERR_MortarBattery battery)
{
return battery && Gun && Group && Gun == battery.Gun && Group == battery.Group && OrderId == battery.OrderId;
}
bool DeliveredFor(ERR_MortarBattery battery, ERR_HQReport report)
{
return !Route && Report && Report == report && Owns(battery);
}
}
class ERR_MortarRefreshRules
{
static bool Newer(ERR_HQReport current, ERR_HQReport incoming, float now)
{
return current && incoming && current.Target && incoming.Target == current.Target && incoming.MessageId > 0 && incoming.MessageId != current.MessageId && incoming.ObservedAt > current.ObservedAt && ERR_MortarPrediction.Fresh(incoming, now);
}
}
@@ -0,0 +1,20 @@
// One finite response per delivered observation at this battery. Cancellation
// does not turn the same radio message into new ammunition authorization.
class ERR_MortarReportLedger
{
protected ref map<int, float> m_Attempts = new map<int, float>();
bool Available(int messageId, float observedAt, float now)
{
if (messageId <= 0 || observedAt < 0 || now < observedAt || now - observedAt > ERR_MortarPrediction.MAX_REPORT_AGE_MS) return false;
for (int i = m_Attempts.Count() - 1; i >= 0; i--)
if (now - m_Attempts.GetElement(i) > ERR_MortarPrediction.MAX_REPORT_AGE_MS) m_Attempts.Remove(m_Attempts.GetKey(i));
return !m_Attempts.Contains(messageId) && m_Attempts.Count() < 128;
}
bool Claim(int messageId, float observedAt, float now)
{
if (!Available(messageId, observedAt, now)) return false;
m_Attempts.Insert(messageId, observedAt);
return true;
}
}
@@ -0,0 +1,16 @@
// One delivered HQ order drives a continuous native loading/firing cycle.
class ERR_MortarSalvo
{
static const int ROUNDS = 8;
static const float RADIUS_METRES = 40;
static const float TIMEOUT_MS = 120000;
static vector Offset(int site, int order, int shot)
{
// Stable throughout one ballistic solve; successive shells cover the disk
// instead of clustering on the same point or changing every iteration.
float radius = RADIUS_METRES * Math.Sqrt((shot + 1.0) / ROUNDS);
float angle = (site * 53 + order * 97 + shot * 137.5) * Math.DEG2RAD;
return Vector(Math.Sin(angle) * radius, 0, Math.Cos(angle) * radius);
}
}
@@ -0,0 +1,108 @@
class ERR_MortarSupplyCallback : ScriptedInventoryOperationCallback
{
ERR_MortarSupplyTransfer Transfer;
protected override void OnComplete() { if (Transfer) Transfer.Acknowledge(true); }
protected override void OnFailed() { if (Transfer) Transfer.Acknowledge(false); }
}
// Exact-item native transaction. Callbacks only record outcomes; the HQ tick
// authorizes progress after checking actual inventory ownership and the order.
class ERR_MortarSupplyTransfer
{
protected ERR_MortarBattery m_Battery;
protected IEntity m_Gun;
protected SCR_AIGroup m_Group;
protected IEntity m_Donor;
protected IEntity m_Gunner;
protected IEntity m_Shell;
protected InventoryStorageManagerComponent m_Source;
protected InventoryStorageManagerComponent m_Destination;
protected BaseInventoryStorageComponent m_Storage;
protected ref ERR_MortarSupplyCallback m_Callback;
protected int m_Order;
protected int m_Acknowledgement;
protected float m_Deadline;
protected bool m_Accepted;
protected bool m_Invalidated;
protected bool m_Resolved;
protected bool m_Ready;
void ERR_MortarSupplyTransfer(ERR_MortarBattery battery, IEntity donor, IEntity gunner, IEntity shell, BaseInventoryStorageComponent storage, float now)
{
m_Battery = battery; m_Gun = battery.Gun; m_Group = battery.Group;
m_Donor = donor; m_Gunner = gunner; m_Shell = shell; m_Storage = storage;
m_Order = battery.OrderId; m_Deadline = now + 8000;
m_Source = InventoryStorageManagerComponent.Cast(donor.FindComponent(InventoryStorageManagerComponent));
m_Destination = InventoryStorageManagerComponent.Cast(gunner.FindComponent(InventoryStorageManagerComponent));
}
static bool Authority(bool valid, bool invalidated, int capturedOrder, int currentOrder)
{
return valid && !invalidated && capturedOrder == currentOrder;
}
static bool Proven(bool callbackComplete, bool sourceContains, bool destinationContains, bool exactSlot)
{
return callbackComplete && !sourceContains && destinationContains && exactSlot;
}
protected bool Contains(InventoryStorageManagerComponent inventory)
{
if (!inventory || !m_Shell) return false;
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
return items.Contains(m_Shell);
}
protected bool Valid(float now)
{
if (!m_Battery || !Authority(m_Battery.SupplyAuthorized(now), m_Invalidated, m_Order, m_Battery.OrderId)) return false;
if (m_Battery.Gun != m_Gun || m_Battery.Group != m_Group || !m_Donor || !m_Gunner || m_Donor == m_Gunner || !m_Shell || !m_Source || !m_Destination || !m_Storage) return false;
if (!ERR_Actors.CanFight(m_Donor) || !ERR_Actors.CanFight(m_Gunner) || ERR_Actors.Group(m_Donor) != m_Group || ERR_Actors.Group(m_Gunner) != m_Group) return false;
Faction faction = m_Group.GetFaction();
if (SCR_FactionManager.SGetFaction(m_Donor) != faction || SCR_FactionManager.SGetFaction(m_Gunner) != faction || vector.Distance(m_Donor.GetOrigin(), m_Gunner.GetOrigin()) > 12) return false;
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Gun.FindComponent(SCR_AIVehicleUsageComponent));
if (!usage || !usage.GetTurretCompartmentSlot()) return false;
IEntity occupant = usage.GetTurretCompartmentSlot().GetOccupant();
return !occupant || occupant == m_Gunner;
}
void Acknowledge(bool success)
{
if (m_Acknowledgement != 0) return;
if (success) m_Acknowledgement = 1;
else m_Acknowledgement = -1;
}
bool Begin(float now)
{
if (m_Callback || !Replication.IsServer() || !Valid(now) || !Contains(m_Source) || Contains(m_Destination)) return false;
m_Callback = new ERR_MortarSupplyCallback(); m_Callback.Transfer = this;
// Do not use callback.GetItem(): the native contract excludes Move operations.
m_Accepted = m_Source.TryMoveItemToStorage(m_Shell, m_Storage, -1, m_Callback);
if (!m_Accepted) { Acknowledge(false); m_Resolved = true; }
return m_Accepted;
}
void Invalidate() { m_Invalidated = true; m_Ready = false; }
bool Unresolved() { return m_Accepted && !m_Resolved; }
int CapturedOrder() { return m_Order; }
ERR_MortarSupplyState Poll(float now)
{
if (!Replication.IsServer()) return ERR_MortarSupplyState.FAILED;
bool valid = Valid(now);
bool sourceContains = Contains(m_Source);
bool destinationContains = Contains(m_Destination);
bool exactSlot;
InventoryItemComponent item;
if (m_Shell) item = InventoryItemComponent.Cast(m_Shell.FindComponent(InventoryItemComponent));
if (item && item.GetParentSlot()) exactSlot = item.GetParentSlot().GetStorage() == m_Storage;
bool proven = Proven(m_Acknowledgement == 1, sourceContains, destinationContains, exactSlot);
if (proven || m_Acknowledgement < 0) m_Resolved = true;
// A successful callback without physical destination proof is ambiguous.
// Keep it reserved through timeout/cancellation; never start a second move.
if (!valid || now >= m_Deadline)
{
m_Invalidated = true; m_Ready = false;
return ERR_MortarSupplyState.FAILED;
}
if (proven)
{
m_Ready = true;
return ERR_MortarSupplyState.READY;
}
if (m_Acknowledgement < 0 || !m_Accepted) return ERR_MortarSupplyState.FAILED;
return ERR_MortarSupplyState.PENDING;
}
}
@@ -0,0 +1,343 @@
// Owned and ticked by HQ. Initial assets are authored once; losses are never replaced.
class ERR_MortarSystem
{
protected ERR_HQ m_HQ;
protected ref array<ref ERR_MortarBattery> m_Batteries = {};
protected ref ERR_SettlementSafety m_Settlements = new ERR_SettlementSafety();
protected bool m_Prepared;
protected bool m_Stopped;
protected float m_NextDecision;
protected float m_NextRefreshDecision;
string SafetyReason;
string SettlementReason;
void ERR_MortarSystem(ERR_HQ hq) { m_HQ = hq; }
static ERR_MortarSystem Get()
{
ERR_HQ hq = ERR_HQ.Get();
if (!hq || !Replication.IsServer()) return null;
return hq.Mortars();
}
static bool NearPlayer(vector position)
{
array<IEntity> players = {};
ERR_Actors.Players(players);
foreach (IEntity player : players)
if (vector.DistanceXZ(player.GetOrigin(), position) <= 1200) return true;
return false;
}
bool Prepare()
{
if (!Replication.IsServer() || m_Prepared || m_Stopped) return false;
m_Prepared = true;
vector position = ERR_Actors.Ground("6000 0 7000");
if (position[1] < 1) return false;
IEntity gun = ERR_Actors.Spawn(ERR_MortarBattery.GUN, position);
SCR_AIGroup group = SCR_AIGroup.Cast(ERR_Actors.Spawn(MortarGroup(), ERR_Actors.Ground(position + "0 0 4")));
if (!gun || !group)
{
ERR_Actors.DeleteSpawned(gun);
ERR_MortarReadiness.RetireGroup(group);
ERR_Actors.DeleteSpawned(group);
return false;
}
if (group.GetFaction() != ERR_GameMode.Get().EnemyFaction())
{
ERR_Actors.DeleteSpawned(gun);
ERR_MortarReadiness.RetireGroup(group);
ERR_Actors.DeleteSpawned(group);
return false;
}
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(gun.FindComponent(FactionAffiliationComponent));
if (affiliation) affiliation.SetAffiliatedFaction(ERR_GameMode.Get().EnemyFaction());
ERR_MortarBattery battery = new ERR_MortarBattery();
battery.Bind(1, group, gun, GetGame().GetWorld().GetWorldTime());
m_Batteries.Insert(battery);
ERR_GameMode mode = ERR_GameMode.Get();
mode.Own(gun);
// HQ owns/reserves the finite asset immediately. Publish its physical
// membership to companion consumers only after readiness has completed.
mode.Own(group);
m_HQ.RegisterGroup(group);
m_HQ.ReserveGroup(group);
battery.Audit("DEPLOY", "authored gun and four-person crew; finite ammunition initialization pending");
return true;
}
static ResourceName MortarGroup() { return "{B84C05941C4C81B8}Prefabs/ERR/MortarCrew.et"; }
void Batteries(out array<ref ERR_MortarBattery> batteries)
{
batteries.Clear();
foreach (ERR_MortarBattery battery : m_Batteries) batteries.Insert(battery);
}
ERR_MortarBattery FindGun(IEntity gun)
{
foreach (ERR_MortarBattery battery : m_Batteries)
if (gun && battery.Gun == gun) return battery;
return null;
}
ERR_MortarBattery FindGroup(SCR_AIGroup group)
{
foreach (ERR_MortarBattery battery : m_Batteries)
if (group && battery.Group == group) return battery;
return null;
}
ERR_HQReport Latest(ERR_MortarBattery battery, float now)
{
// Only the coordinates actually delivered to this battery are available.
// A newer HQ contact cannot update a gun's knowledge without another downlink.
if (!battery || !ERR_MortarPrediction.Fresh(battery.Report, now)) return null;
return battery.Report;
}
string ContactFailure(ERR_MortarBattery battery)
{
if (!battery.Report) return "no delivered target report";
return "delivered target report expired or invalid";
}
// Classify only after SupplyState has observed any accepted inventory move.
// Report expiry must not be mislabeled as physical ammunition depletion.
string SupplyFailure(ERR_MortarBattery battery, float now)
{
if (!Latest(battery, now)) return ContactFailure(battery);
return "next native load lacks confirmed physical gunner ammunition";
}
bool OutsideSettlements(vector impact)
{
bool clear = m_Settlements.OutsideSettlements(impact);
SettlementReason = m_Settlements.SettlementReason;
return clear;
}
bool Safe(ERR_MortarBattery battery, vector impact, float flight)
{
SafetyReason = "clear";
if (!battery.Operational()) { SafetyReason = battery.OperationalReason(); return false; }
if (!OutsideSettlements(impact)) { SafetyReason = SettlementReason; return false; }
if (SCR_TerrainHelper.GetTerrainY(impact) < 0) { SafetyReason = "impact below Everon sea level"; return false; }
Faction faction = battery.Group.GetFaction();
if (!faction) { SafetyReason = "battery faction missing"; return false; }
array<IEntity> friendlies = {};
m_HQ.PhysicalForces(friendlies);
foreach (IEntity entity : friendlies)
{
Faction friendly = SCR_FactionManager.SGetFaction(entity);
if (!friendly || faction.IsFactionEnemy(friendly) || !ERR_Actors.Alive(entity)) continue;
vector velocity;
IEntity moving = CompartmentAccessComponent.GetVehicleIn(entity);
if (!moving) moving = entity;
if (moving.GetPhysics()) velocity = moving.GetPhysics().GetVelocity();
if (ERR_MortarPrediction.ThreatensFriendly(impact, entity.GetOrigin(), velocity, flight))
{
SafetyReason = "friendly physical force can enter the impact area";
return false;
}
}
return true;
}
bool Withhold(ERR_MortarBattery battery, string reason, string detail = "")
{
battery.LastFailure = reason + "; " + detail;
battery.Trace("WITHHELD", reason, detail);
// Finish the activity from the next HQ tick, never from inside its own node.
battery.Rejected = true;
return false;
}
bool AllowShot(ERR_MortarBattery battery)
{
float now = GetGame().GetWorld().GetWorldTime();
if (m_Stopped || !ERR_HQ.MissionActive()) return Withhold(battery, "mission inactive or HQ stopped");
if (!battery.Operational()) return Withhold(battery, battery.OperationalReason());
if (battery.RefreshRejected) return Withhold(battery, "target update failed; order awaiting cancellation");
if (!battery.CommandOpen || battery.OrderShots >= ERR_MortarSalvo.ROUNDS) return Withhold(battery, "salvo complete or order closed");
if (battery.Released) return Withhold(battery, "waiting for committed projectile");
if (battery.AimedShot != battery.OrderShots || battery.AimedAt < 0 || now < battery.AimedAt) return Withhold(battery, "fresh aim solution required");
ERR_HQReport report = Latest(battery, now);
if (!report) return Withhold(battery, ContactFailure(battery));
if (!battery.AimSolution || !battery.AimSolution.Matches(battery, report)) return Withhold(battery, "aim command does not match delivered order");
if (battery.AimValidatedAt < battery.AimedAt || now < battery.AimValidatedAt) return Withhold(battery, "aim validation timestamp invalid");
if (now - battery.AimValidatedAt > 10000)
{
if (!battery.AimSolver) return Withhold(battery, "original native aim solver no longer available");
if (!battery.AimSolver.ERR_RevalidateAim(battery, this, report, now)) return false;
}
vector updated;
if (!ERR_MortarPrediction.Predict(report, now, battery.FlightTime, updated)) return Withhold(battery, ERR_MortarPrediction.FailureReason);
updated += battery.AimOffset;
if (vector.DistanceXZ(updated, battery.Impact) > 60) return Withhold(battery, "predicted target left aimed impact area");
if (!OutsideSettlements(updated)) return Withhold(battery, SettlementReason);
if (!Safe(battery, battery.Impact, battery.FlightTime)) return Withhold(battery, SafetyReason);
return true;
}
protected void AdvanceOrder(ERR_MortarBattery battery, float now)
{
if (battery.RadioOrder)
{
int deliveredState = battery.RadioOrder.Advance(now, m_HQ.Journal());
if (deliveredState == 0) return;
ERR_HQReport delivered = battery.PendingRadioReport;
battery.RadioOrder = null;
battery.PendingRadioReport = null;
if (deliveredState < 0 || !ERR_MortarPrediction.Fresh(delivered, now) || !battery.Operational() || battery.Activity || !battery.CommandOpen)
{
battery.Cancel("radio delivery, report freshness or battery availability failed");
battery.NextOrder = now + 10000;
return;
}
battery.PendingSupplyReport = delivered;
}
if (!battery.PendingSupplyReport) return;
if (!battery.CommandOpen || !ERR_MortarPrediction.Fresh(battery.PendingSupplyReport, now) || !battery.Operational() || battery.Activity)
{
battery.Cancel("delivered order expired or became unavailable while awaiting physical ammunition");
battery.NextOrder = now + 10000;
return;
}
ERR_MortarSupplyState supply = battery.SupplyState(now);
if (supply == ERR_MortarSupplyState.PENDING) return;
if (supply == ERR_MortarSupplyState.FAILED)
{
battery.Cancel("physical gunner ammunition unavailable or transfer failed");
battery.NextOrder = now + 10000;
return;
}
ERR_HQReport ready = battery.PendingSupplyReport;
battery.PendingSupplyReport = null;
battery.Order(ready, now);
}
protected void AdvanceRefresh(ERR_MortarBattery battery, float now)
{
ERR_MortarRefresh update = battery.Refresh;
if (!update) return;
// A stale transaction must not cancel or modify a later order.
if (update.OrderId != battery.OrderId || !battery.CommandOpen)
{
battery.Refresh = null;
return;
}
if (!update.Owns(battery) || !battery.Operational() || !battery.Activity || !ERR_MortarRefreshRules.Newer(battery.Report, update.Report, now))
{
battery.RejectRefresh("target update ownership, observation freshness or activity changed");
return;
}
if (update.Route)
{
int state = update.Route.Advance(now, m_HQ.Journal());
if (state == 0) return;
if (state < 0)
{
battery.RejectRefresh("new target observation failed physical HQ radio delivery");
return;
}
update.Route = null;
}
// The original committed shell owns its report, geometry and airspace until
// native OnWeaponFired confirms it. A delivered update cannot relabel it.
if (battery.Released) return;
if (battery.AimSolution)
{
if (!battery.AimSolver || !battery.AimSolver.ERR_AdoptReport(battery, this, update.Report, now))
{
battery.RejectRefresh("delivered update cannot preserve the original native aim command");
return;
}
}
else
{
// No barrel command exists for the next shell yet. Its normal native aim
// node must solve this report; receiving it is not an aim authorization.
battery.Report = update.Report;
}
battery.Audit("UPDATE RECEIVED", string.Format("same order; update radio=M%1 accepted observation=M%2 observed=%3; shots=%4 original aim time=%5", update.RadioId, update.Report.MessageId, update.Report.ObservedAt, battery.OrderShots, battery.AimedAt));
battery.Refresh = null;
}
protected void QueueRefreshes(float now)
{
if (now < m_NextRefreshDecision) return;
m_NextRefreshDecision = now + 5000;
array<ref ERR_HQReport> reports = {};
m_HQ.SupportContacts(reports, now);
foreach (ERR_MortarBattery battery : m_Batteries)
{
ERR_HQReport newest;
foreach (ERR_HQReport report : reports)
{
if (!battery.CanRefresh(report, now)) continue;
if (!newest || report.ObservedAt > newest.ObservedAt) newest = report;
}
if (!newest) continue;
ERR_HQRoute route = m_HQ.Downlink(battery.Group, "MORTAR UPDATE", newest.Description(now), newest.MessageId);
if (!route)
{
battery.RejectRefresh("HQ has a newer observation but no physical downlink to the active battery");
continue;
}
battery.BeginRefresh(newest, route, now);
}
}
void Tick(float now)
{
if (m_Stopped || !Replication.IsServer()) return;
foreach (ERR_MortarBattery battery : m_Batteries)
{
if ((battery.Rejected || battery.RefreshRejected) && !battery.Released)
{
battery.Cancel("native aim or firing authorization rejected");
battery.NextOrder = now + 10000;
}
battery.Rejected = false;
battery.Update(now);
AdvanceOrder(battery, now);
AdvanceRefresh(battery, now);
}
QueueRefreshes(now);
if (now < m_NextDecision) return;
m_NextDecision = now + 10000;
array<ref ERR_HQReport> reports = {};
m_HQ.SupportContacts(reports, now);
ERR_MortarBattery selected;
ERR_HQReport target;
float distance = float.MAX;
foreach (ERR_MortarBattery battery : m_Batteries)
{
if (!battery.StartupActivated || !battery.Operational() || battery.Activity || battery.RadioOrder || battery.PendingSupplyReport || battery.SupplyPending() || now < battery.NextOrder || battery.CountShells() == 0) continue;
foreach (ERR_HQReport report : reports)
{
if (!battery.CanRequest(report, now)) continue;
float candidate = vector.DistanceXZ(battery.Position, report.Position);
if (candidate < 350 || candidate > 4000 || candidate >= distance || !OutsideSettlements(report.Position)) continue;
selected = battery;
target = report;
distance = candidate;
}
}
if (!selected || !target) return;
ERR_HQRoute route = m_HQ.Downlink(selected.Group, "MORTAR ORDER", target.Description(now), target.MessageId);
if (!route) return;
if (!selected.BeginRequest(target, now)) return;
selected.RadioId = route.MessageId;
selected.RadioOrder = route;
selected.PendingRadioReport = target.Snapshot();
m_NextDecision = now + 20000;
}
void AppendGuns(array<IEntity> entities)
{
foreach (ERR_MortarBattery battery : m_Batteries)
if (battery.Gun && !entities.Contains(battery.Gun)) entities.Insert(battery.Gun);
}
string Status()
{
int ready, shells;
foreach (ERR_MortarBattery battery : m_Batteries)
{
if (battery.StartupActivated && battery.Operational()) ready++;
shells += battery.CountShells();
}
return string.Format("mortars %1/%2; HE shells %3", ready, m_Batteries.Count(), shells);
}
void Stop()
{
m_Stopped = true;
foreach (ERR_MortarBattery battery : m_Batteries) battery.Stop();
}
}
@@ -0,0 +1,366 @@
#ifdef WORKBENCH
// Separate Chat fixture for the ACTUAL dormant AI controller. Core's original
// powered-flight probe is disabled for this outcome and retains its own evidence.
modded class SCR_CharacterControllerComponent
{
override void OnPrepareControls(IEntity owner, ActionManager am, float dt, bool player)
{
ERR_NativeAircraftControlProbe probe = ERR_NativeAircraftControlProbe.Current();
if (probe && !player) probe.PrepareActions(this, owner, am, dt);
super.OnPrepareControls(owner, am, dt, player);
}
}
modded class SCR_HelicopterControllerComponent
{
protected override event void OnPrepareControls()
{
super.OnPrepareControls();
ERR_NativeAircraftControlProbe probe = ERR_NativeAircraftControlProbe.Current();
if (probe) probe.PrepareHelicopter(this);
}
}
// Read-only native fixture observation; no production method or state override.
modded class ERR_AircraftFlight
{
float ERR_NativeControlDeadline() { return m_Deadline; }
}
class ERR_NativeAircraftControlRules
{
static bool Terminal(bool cancellation, int state, int cancellationCount)
{
if (cancellation) return state == ERR_AircraftFlightState.CANCELLED && cancellationCount == 1;
return state == ERR_AircraftFlightState.COMPLETE && cancellationCount == 0;
}
static bool CanCancel(int state, bool contact, float height)
{
return state == ERR_AircraftFlightState.CLIMB && !contact && height >= 5 && height <= 18;
}
static bool CancelProof(bool airborneHooks, bool landingHeld, bool sameDeadline, bool sameToken)
{
return airborneHooks && landingHeld && sameDeadline && sameToken;
}
static bool TranslateProof(bool proven, bool held, float requestedDistance, float displacement, float targetError)
{
return proven && held && requestedDistance >= 1.75 && requestedDistance <= 4.05 && displacement >= 1 && targetError >= 0 && targetError <= 0.75;
}
static bool PhysicalFinish(bool originalOccupancy, bool landed, bool engineOff, float fuelUsed, int wakes, int starts, int controllerStarts, int pilotHooks, int helicopterHooks)
{
if (!originalOccupancy || !landed || !engineOff) return false;
return fuelUsed > 0.05 && wakes == 1 && starts == 1 && controllerStarts == 1 && pilotHooks > 100 && helicopterHooks > 100;
}
static bool CanDispose(bool context, bool noPlayers, bool sameFaction, bool groupUnchanged, bool pilotUnchanged)
{
return context && noPlayers && sameFaction && groupUnchanged && pilotUnchanged;
}
}
class ERR_NativeAircraftControlProbe
{
protected static ERR_NativeAircraftControlProbe s_Current;
protected ERR_GameMode m_Mode;
protected Vehicle m_Aircraft;
protected SCR_AIGroup m_Group;
protected IEntity m_Pilot;
protected PilotCompartmentSlot m_Seat;
protected HelicopterControllerComponent m_Controller;
protected VehicleHelicopterSimulation m_Simulation;
protected FuelManagerComponent m_Fuel;
protected ref ERR_AircraftFlightEnvelope m_Envelope;
protected ref ERR_AircraftFlight m_Flight;
protected float m_Deadline;
protected float m_StageDeadline;
protected float m_Stable = -1;
protected float m_LastTick = -1;
protected float m_NextLog;
protected float m_InitialFuel;
protected float m_ControlDeadline;
protected float m_CancelAt = -1;
protected float m_LandingStable = -1;
protected int m_CancelCount;
protected int m_CancelPilotHooks;
protected int m_CancelHelicopterHooks;
protected bool m_CancelFixture;
protected bool m_TranslationFixture;
protected bool m_TranslationObserved;
protected bool m_TranslationHeld;
protected vector m_TranslationOrigin;
protected vector m_TranslationTarget;
protected float m_TranslationStable = -1;
protected bool m_ContinuedAirborneControl;
protected bool m_LandingHeld;
protected vector m_Pad;
protected int m_Step;
protected int m_Wakes;
protected int m_ControllerStarts;
protected int m_EngineStarts;
protected int m_PilotHooks;
protected int m_HelicopterHooks;
protected bool m_LastEngine;
protected bool m_AllocationRequested;
protected bool m_PilotRequested;
protected bool m_Stopped;
protected bool m_Complete;
static const int ORDER_TOKEN = 771001;
static const ResourceName AIRCRAFT = "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et";
static const ResourceName GROUP = "{000CD338713F2B5A}Prefabs/AI/Groups/Group_Base.et";
static const ResourceName PILOT = "{86E565387A634103}Prefabs/Characters/Campaign/Final/OPFOR/USSR_Army/Regular/Campaign_USSR_Player_MortarCrew.et";
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && (outcome == "aircraft-control" || outcome == "aircraft-cancel" || outcome == "aircraft-translate");
}
static ERR_NativeAircraftControlProbe Current() { return s_Current; }
void ERR_NativeAircraftControlProbe(ERR_GameMode mode)
{
m_Mode = mode;
if (!GetGame() || !GetGame().GetWorld() || !Enabled() || s_Current) { m_Stopped = true; return; }
string outcome; System.GetCLIParam("errSmokeOutcome", outcome); m_CancelFixture = outcome == "aircraft-cancel"; m_TranslationFixture = outcome == "aircraft-translate";
m_Deadline = GetGame().GetWorld().GetWorldTime() + 300000;
s_Current = this;
}
protected bool Check(bool passed, string detail)
{
string outcome = "aircraft-control"; if (m_CancelFixture) outcome = "aircraft-cancel"; else if (m_TranslationFixture) outcome = "aircraft-translate";
if (passed) { Print("ERR SMOKE PASS: " + outcome + " " + detail); return true; }
Print("ERR SMOKE FAIL: " + outcome + " " + detail, LogLevel.ERROR); Stop(); return false;
}
protected bool NoPlayers()
{
if (!GetGame() || !GetGame().GetPlayerManager()) return false;
if (m_Group && m_Group.GetPlayerCount(true) > 0) return false;
array<int> players = {}; GetGame().GetPlayerManager().GetPlayers(players);
foreach (int id : players)
{
IEntity actor = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
if (actor && (actor == m_Pilot || actor == m_Aircraft || (m_Aircraft && CompartmentAccessComponent.GetVehicleIn(actor) == m_Aircraft))) return false;
}
return true;
}
protected bool OriginalOccupancy()
{
if (!m_Pilot || !m_Group || !m_Aircraft || !m_Controller || !m_Seat || !NoPlayers()) return false;
if (m_Group.GetAgentsCount() != 1 || ERR_Actors.Group(m_Pilot) != m_Group || !ERR_Actors.CanFight(m_Pilot)) return false;
return m_Seat.GetOccupant() == m_Pilot && m_Controller.GetPilotCompartmentSlot() == m_Seat && CompartmentAccessComponent.GetVehicleIn(m_Pilot) == m_Aircraft;
}
void PrepareActions(CharacterControllerComponent controller, IEntity owner, ActionManager am, float dt)
{
if (m_Stopped || !m_Flight || owner != m_Pilot) return;
m_Flight.PrepareActions(controller, owner, am, dt); m_PilotHooks++;
}
void PrepareHelicopter(HelicopterControllerComponent controller)
{
if (m_Stopped || !m_Flight || controller != m_Controller) return;
m_Flight.PrepareHelicopter(controller); m_HelicopterHooks++;
}
protected bool FindPad(out vector result)
{
vector anchor = m_Mode.ExtractionVehicle().GetOrigin();
float bestSlope = float.MAX; vector bestCandidate, refinementAnchor;
int localCandidates = ERR_PoweredFlightRules.PAD_CANDIDATES + ERR_PoweredFlightRules.PAD_REFINEMENTS;
for (int index = 0; index < localCandidates + ERR_PoweredFlightRules.AIRPORT_CANDIDATES; index++)
{
if (index == ERR_PoweredFlightRules.PAD_CANDIDATES)
{
refinementAnchor = anchor; if (bestSlope != float.MAX) refinementAnchor = bestCandidate;
}
vector candidate;
if (index < ERR_PoweredFlightRules.PAD_CANDIDATES) candidate = ERR_Actors.Ground(anchor + ERR_PoweredFlightRules.PadOffset(index));
else if (index < localCandidates) candidate = ERR_Actors.Ground(refinementAnchor + ERR_PoweredFlightRules.RefineOffset(index - ERR_PoweredFlightRules.PAD_CANDIDATES));
else candidate = ERR_Actors.Ground("4900.953 0 11787.412" + ERR_PoweredFlightRules.AirportOffset(index - localCandidates));
if (candidate[1] < 2) continue;
float delta;
for (int edge = 0; edge < 4; edge++)
{
float side = edge * Math.PI / 2;
vector point = candidate + Vector(Math.Cos(side) * 14, 0, Math.Sin(side) * 14);
delta = Math.Max(delta, Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(point) - candidate[1]));
}
if (delta < bestSlope) { bestSlope = delta; bestCandidate = candidate; }
if (delta > 0.8) continue;
if (!SCR_WorldTools.TraceCilinderUtil(candidate + "0 2.5 0", 40, 4, TraceFlags.ENTS)) continue;
TraceBox clearance = new TraceBox(); clearance.Start = candidate + "0 3 0"; clearance.End = candidate + "0 60 0";
clearance.Mins = "-40 -1 -40"; clearance.Maxs = "40 1 40"; clearance.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
if (!(GetGame().GetWorld().TraceMove(clearance, null) >= 1)) continue;
PrintFormat("ERR AIRCRAFT FIXTURE PAD: candidate=%1 tested=%2 terrainDelta=%3; no fallback", candidate, index + 1, delta);
result = candidate; return true;
}
return false;
}
protected bool ReadyComponents()
{
m_Controller = HelicopterControllerComponent.Cast(m_Aircraft.FindComponent(HelicopterControllerComponent));
if (!m_Controller) return false;
m_Simulation = m_Controller.GetSimulation();
m_Fuel = FuelManagerComponent.Cast(m_Aircraft.FindComponent(FuelManagerComponent));
if (!m_Simulation || !m_Simulation.IsValid() || !m_Fuel || !m_Fuel.HasFuel()) return false;
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(m_Aircraft.FindComponent(BaseCompartmentManagerComponent));
if (!manager) return false;
array<BaseCompartmentSlot> slots = {}; manager.GetCompartments(slots);
if (slots.Count() > 64) return false;
foreach (BaseCompartmentSlot slot : slots)
{
PilotCompartmentSlot pilotSlot = PilotCompartmentSlot.Cast(slot);
if (!pilotSlot || pilotSlot.GetOwner() != m_Aircraft || pilotSlot.GetVehicle() != m_Aircraft || pilotSlot.GetCompartmentName() != "PilotCompartment") continue;
if (pilotSlot.GetType() != ECompartmentType.PILOT) continue;
if (pilotSlot.GetOccupant() || pilotSlot.IsReserved() || pilotSlot.IsGetInLocked() || !pilotSlot.IsCompartmentAccessible()) continue;
m_Seat = pilotSlot; return true;
}
return false;
}
protected void Diagnostic(float now)
{
if (!m_Aircraft || !m_Controller || !m_Simulation || now < m_NextLog) return;
m_NextLog = now + 2000;
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
vector home = m_Pad; if (m_Envelope) home = m_Envelope.Home();
int state = -1; string reason; if (m_Flight) { state = m_Flight.State(); reason = m_Flight.Reason(); }
PrintFormat("ERR AIRCRAFT FIXTURE: step=%1 state=%2 height=%3 drift=%4 speed=%5 contact=%6 engine=%7 fuelUsed=%8 reason=%9", m_Step, state, transform[3][1] - home[1], vector.DistanceXZ(transform[3], home), m_Simulation.GetVelocity().Length(), m_Simulation.HasAnyGroundContact(), m_Controller.IsEngineOn(), m_InitialFuel - m_Fuel.GetTotalFuel(), reason);
}
int Tick(float now)
{
if (m_Complete) return 1;
if (m_Stopped) return -1;
if (!Enabled() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || now >= m_Deadline) { Check(false, "active exact mission and bounded300-second fixture"); return -1; }
if (!NoPlayers()) { Check(false, "fixture remains unclaimed by any human"); return -1; }
if (m_LastTick >= 0 && (now < m_LastTick || now - m_LastTick > 1500)) { m_Stable = -1; m_LandingStable = -1; m_TranslationStable = -1; }
m_LastTick = now;
if (m_Step == 0)
{
if (!m_Mode.ExtractionVehicle()) return 0;
if (!Check(!m_AllocationRequested && FindPad(m_Pad), "independent qualified candidate found before one finite allocation")) return -1;
m_AllocationRequested = true;
m_Aircraft = Vehicle.Cast(ERR_Actors.Spawn(AIRCRAFT, m_Pad));
m_Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(GROUP, m_Pad + "20 0 0"));
if (!Check(m_Aircraft && m_Group && m_Group.InitFactionKey("USSR") && m_Group.GetFaction() == m_Mode.EnemyFaction(), "one finite native Mi8 and memberless enemy group created")) return -1;
m_Group.SetPermanentLOD(0); m_Group.ActivateAI();
m_Step = 1; m_StageDeadline = now + 30000; return 0;
}
if (!m_Aircraft || !m_Group || !ERR_Actors.Alive(m_Aircraft)) { Check(false, "original airframe and group survive without replacement"); return -1; }
if (m_Step == 1)
{
if (now >= m_StageDeadline) { Check(false, "real components and original pilot initialize within30seconds"); return -1; }
if (!ReadyComponents()) return 0;
if (!Check(!m_PilotRequested && m_Group.GetAgentsCount() == 0, "one original native pilot request only")) return -1;
m_PilotRequested = true; AIGroup originalGroup = m_Group;
m_Pilot = m_Seat.SpawnCharacterInCompartment(PILOT, originalGroup, GROUP);
AIControlComponent control; if (m_Pilot) control = AIControlComponent.Cast(m_Pilot.FindComponent(AIControlComponent));
AIAgent agent; if (control) agent = control.GetControlAIAgent();
if (!Check(m_Pilot && originalGroup == m_Group && agent, "pilot seated by native mechanism in original group")) return -1;
agent.SetPermanentLOD(0); agent.ActivateAI(); m_Step = 2; return 0;
}
Diagnostic(now);
if (m_Step == 2)
{
if (now >= m_StageDeadline) { Check(false, "actual initial seating and native ground settling within30seconds"); return -1; }
if (!OriginalOccupancy()) { m_Stable = -1; return 0; }
Physics physics = m_Aircraft.GetPhysics();
if (!physics || !physics.IsDynamic()) return 0;
if (m_Wakes == 0) { m_Wakes = 1; physics.SetActive(ActiveState.ACTIVE); m_Stable = -1; return 0; }
if (!m_Simulation.HasAnyGroundContact() || !ERR_CasualtyLoadingRules.AircraftReady(m_Aircraft)) { m_Stable = -1; return 0; }
if (m_Stable < 0) m_Stable = now;
if (now - m_Stable < 1000) return 0;
if (!Check(!m_Controller.IsEngineOn(), "post-spawn qualification precedes native engine start")) return -1;
m_Envelope = ERR_AircraftFlightEnvelope.Qualify(m_Mode, m_Aircraft, m_Pilot);
if (!Check(m_Envelope != null, "actual post-spawn column and complete rotor/model footprint issue exact qualification")) return -1;
m_InitialFuel = m_Fuel.GetTotalFuel();
m_Flight = new ERR_AircraftFlight(); m_ControllerStarts++;
m_ControlDeadline = Math.Min(m_Deadline, now + 240000);
bool started;
if (m_TranslationFixture) started = m_Flight.StartTranslation(m_Mode, m_Aircraft, m_Group, m_Pilot, m_Seat, m_Envelope, ORDER_TOKEN, m_ControlDeadline, m_Envelope.Home());
else started = m_Flight.Start(m_Mode, m_Aircraft, m_Group, m_Pilot, m_Seat, m_Envelope, ORDER_TOKEN, m_ControlDeadline);
if (!Check(started, "actual controller accepts one qualified original pilot/aircraft order")) return -1;
m_Step = 3; return 0;
}
if (m_Step == 3)
{
if (!OriginalOccupancy()) { Check(false, "original pilot remains conscious and in its exact seat/group"); return -1; }
bool engine = m_Controller.IsEngineOn(); if (engine && !m_LastEngine) m_EngineStarts++; m_LastEngine = engine;
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
vector home = m_Envelope.Home(); float height = transform[3][1] - home[1];
bool contact = m_Simulation.HasAnyGroundContact();
bool landed = ERR_AircraftFlightRules.Landed(contact, height, m_Simulation.GetVelocity().Length(), m_Simulation.GetAngularVelocity().Length(), transform[1][1]);
if (m_CancelFixture && m_CancelCount == 0 && ERR_NativeAircraftControlRules.CanCancel(m_Flight.State(), contact, height))
{
m_CancelCount = 1; m_CancelAt = now;
m_CancelPilotHooks = m_PilotHooks; m_CancelHelicopterHooks = m_HelicopterHooks;
m_Flight.Cancel("native fixture deliberate airborne cancellation");
PrintFormat("ERR AIRCRAFT CANCEL: height=%1 contact=%2 state=%3 token=%4 deadline=%5 pilotHooks=%6 helicopterHooks=%7", height, contact, m_Flight.State(), m_Flight.OrderToken(), m_Flight.ERR_NativeControlDeadline(), m_PilotHooks, m_HelicopterHooks);
if (!Check(m_Flight.State() == ERR_AircraftFlightState.LANDING && !m_Flight.Finished(), "one actual airborne Cancel enters retained LANDING control")) return -1;
}
if (m_CancelCount == 1)
{
if (m_Flight.ERR_NativeControlDeadline() != m_ControlDeadline || m_Flight.OrderToken() != ORDER_TOKEN) { Check(false, "cancel retains original overall deadline and exact order token"); return -1; }
int state = m_Flight.State();
if (state != ERR_AircraftFlightState.LANDING && state != ERR_AircraftFlightState.SHUTDOWN && state != ERR_AircraftFlightState.CANCELLED) { Check(false, "cancel retains landing or grounded shutdown states only"); return -1; }
if (state == ERR_AircraftFlightState.LANDING && !contact && height > 1 && m_PilotHooks > m_CancelPilotHooks + 10 && m_HelicopterHooks > m_CancelHelicopterHooks + 10) m_ContinuedAirborneControl = true;
if (!landed) m_LandingStable = -1;
else if (m_LandingStable < 0) m_LandingStable = now;
}
if (m_TranslationFixture && m_Flight.State() == ERR_AircraftFlightState.TRANSLATING)
{
if (!m_TranslationObserved)
{
m_TranslationObserved = true; m_TranslationOrigin = m_Flight.TranslationOrigin(); m_TranslationTarget = m_Flight.TranslationTarget();
if (!Check(vector.DistanceXZ(m_TranslationTarget, home) < 0.001, "pre-takeoff translation target remains the original qualified home")) return -1;
}
if (m_Flight.TranslationTarget() != m_TranslationTarget || m_Flight.TranslationOrigin() != m_TranslationOrigin || m_Flight.ERR_NativeControlDeadline() != m_ControlDeadline) { Check(false, "translation never retargets or renews its original deadline"); return -1; }
vector horizontal = m_Simulation.GetVelocity(); horizontal[1] = 0;
float requested = vector.DistanceXZ(m_TranslationOrigin, m_TranslationTarget);
bool held = ERR_AircraftTranslationRules.Held(contact, height, m_Simulation.GetVelocity().Length(), m_Simulation.GetVelocity()[1], transform[1][1], horizontal.Length(), vector.DistanceXZ(transform[3], m_TranslationTarget), vector.DistanceXZ(transform[3], m_TranslationOrigin), requested);
if (!held) m_TranslationStable = -1;
else if (m_TranslationStable < 0) m_TranslationStable = now;
if (m_TranslationStable >= 0 && now - m_TranslationStable >= 5000) m_TranslationHeld = true;
}
m_Flight.Tick();
if (m_CancelCount == 1 && m_Flight.State() == ERR_AircraftFlightState.SHUTDOWN)
{
if (!Check(landed && m_LandingStable >= 0 && now - m_LandingStable >= 2000, "cancel reaches shutdown only after two observed stable grounded seconds")) return -1;
m_LandingHeld = true;
}
if (!m_Flight.Finished()) return 0;
PrintFormat("ERR AIRCRAFT FIXTURE RESULT: state=%1 token=%2 engineStarts=%3 controllerStarts=%4 wakes=%5 pilotHooks=%6 helicopterHooks=%7 reason=%8", m_Flight.State(), m_Flight.OrderToken(), m_EngineStarts, m_ControllerStarts, m_Wakes, m_PilotHooks, m_HelicopterHooks, m_Flight.Reason());
bool complete = ERR_NativeAircraftControlRules.Terminal(m_CancelFixture, m_Flight.State(), m_CancelCount);
complete = complete && ERR_NativeAircraftControlRules.PhysicalFinish(OriginalOccupancy(), landed, !m_Controller.IsEngineOn(), m_InitialFuel - m_Fuel.GetTotalFuel(), m_Wakes, m_EngineStarts, m_ControllerStarts, m_PilotHooks, m_HelicopterHooks);
if (m_CancelFixture)
{
bool proof = ERR_NativeAircraftControlRules.CancelProof(m_ContinuedAirborneControl, m_LandingHeld, m_Flight.ERR_NativeControlDeadline() == m_ControlDeadline, m_Flight.OrderToken() == ORDER_TOKEN);
PrintFormat("ERR AIRCRAFT CANCEL RESULT: count=%1 elapsed=%2 airborneHooks=%3 landingHeld=%4 deadline=%5 originalDeadline=%6", m_CancelCount, now - m_CancelAt, m_ContinuedAirborneControl, m_LandingHeld, m_Flight.ERR_NativeControlDeadline(), m_ControlDeadline);
complete = complete && proof;
}
if (m_TranslationFixture)
{
float requestedDistance = vector.DistanceXZ(m_TranslationOrigin, m_TranslationTarget);
float displacement = vector.DistanceXZ(transform[3], m_TranslationOrigin);
float targetError = vector.DistanceXZ(transform[3], m_TranslationTarget);
bool translated = ERR_NativeAircraftControlRules.TranslateProof(m_Flight.TranslationProven(), m_TranslationObserved && m_TranslationHeld, requestedDistance, displacement, targetError);
PrintFormat("ERR AIRCRAFT TRANSLATION RESULT: proven=%1 held=%2 requested=%3 displacement=%4 targetError=%5 updates=%6", m_Flight.TranslationProven(), m_TranslationHeld, requestedDistance, displacement, targetError, m_Flight.TranslationUpdates());
complete = complete && translated;
}
if (!Check(complete && m_Flight.OrderToken() == ORDER_TOKEN, "actual control order completes on original physical grounded aircraft with engine off, finite fuel use and exact pilot")) return -1;
m_Complete = true; Stop(); return 1;
}
return 0;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
if (s_Current == this) s_Current = null;
if (m_Flight && !m_Flight.Finished() && m_CancelCount == 0) m_Flight.Cancel("native fixture stopped");
// End-of-test disposal is not successful flight or a production cancellation.
// These assets are fixture-owned only, never registered with mission cleanup.
// Release the controller before deleting disposable unclaimed physical assets.
m_Flight = null;
bool context = GetGame() && GetGame().GetWorld() && GetGame().GetPlayerManager() && m_Mode && m_Mode == ERR_GameMode.Get();
bool faction = context && (!m_Group || (!m_PilotRequested && m_Group.GetAgentsCount() == 0 && !m_Group.GetFaction()) || m_Group.GetFaction() == m_Mode.EnemyFaction()) && (!m_Aircraft || SCR_FactionManager.SGetFaction(m_Aircraft) == m_Mode.EnemyFaction());
bool groupUnchanged = context && (!m_Group || m_Group.GetAgentsCount() == 0 || (m_Group.GetAgentsCount() == 1 && m_Pilot && ERR_Actors.Group(m_Pilot) == m_Group));
bool pilotUnchanged = context && (!m_Pilot || (m_Group && ERR_Actors.Group(m_Pilot) == m_Group && !ERR_Actors.Player(m_Pilot)));
if (ERR_NativeAircraftControlRules.CanDispose(context, NoPlayers(), faction, groupUnchanged, pilotUnchanged))
{
ERR_Actors.DeleteSpawned(m_Group); ERR_Actors.DeleteSpawned(m_Pilot); ERR_Actors.DeleteSpawned(m_Aircraft);
}
else Print("ERR AIRCRAFT FIXTURE CLEANUP: ownership changed; captured entities preserved");
m_Envelope = null;
}
void ~ERR_NativeAircraftControlProbe() { Stop(); }
}
#endif
@@ -0,0 +1,213 @@
#ifdef WORKBENCH
class ERR_NativeCourierMoveCallback : ScriptedInventoryOperationCallback
{
int Result;
protected override void OnComplete() { if (Result == 0) Result = 1; }
protected override void OnFailed() { if (Result == 0) Result = -1; }
}
modded class ERR_HQ
{
override bool QueueSupplyStatus(ERR_MortarBattery battery, float now)
{
bool queued = super.QueueSupplyStatus(battery, now);
ERR_NativeMortarCourierProbe probe = ERR_NativeMortarCourierProbe.Current();
if (queued && probe)
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == battery.Group && unit.PendingSupplyStatus) probe.Uplink(unit.Uplink, unit.PendingSupplyStatus);
return queued;
}
}
class ERR_NativeMortarCourierProbe
{
protected static ERR_NativeMortarCourierProbe s_Current;
protected ERR_GameMode m_Mode;
protected ERR_MortarLogistics m_Service;
protected ERR_MortarBattery m_Battery;
protected ref array<IEntity> m_Original = {};
protected ref array<IEntity> m_Crew = {};
protected ref array<IEntity> m_Depot = {};
protected ref array<IEntity> m_Forces = {};
protected ref array<IEntity> m_Boxes = {};
protected ref array<BaseInventoryStorageComponent> m_Stores = {};
protected ref ERR_NativeCourierMoveCallback m_Callback;
protected IEntity m_Moving;
protected IEntity m_Source;
protected BaseInventoryStorageComponent m_Destination;
protected ref ERR_HQRoute m_Uplink;
protected ref ERR_HQRoute m_Downlink;
protected ref ERR_MortarSupplyStatus m_Status;
protected float m_ObservedAt;
protected int m_Message;
protected int m_Index;
protected int m_Stage;
protected float m_Deadline;
protected float m_MoveDeadline;
protected bool m_Complete;
protected bool m_Stopped;
protected vector m_Departure;
protected IEntity m_Bearer;
protected AIWaypointCycle m_OriginalPatrolCycle;
protected ref array<AIWaypoint> m_OriginalPatrolMoves = {};
static const int STORAGE_CANDIDATES = 64;
static const int STORAGE_COUNT = 5;
// The compact 8x8 grid keeps 3m between centers; blocked points are skipped.
static vector StorageOffset(int index)
{
if (index < 0 || index >= STORAGE_CANDIDATES) return vector.Zero;
int column = Math.Mod(index, 8);
int row = index / 8;
return Vector(20 + column * 3, 0, 15 + row * 3);
}
static const ResourceName BOX = "{EBBC859CCD94D96D}Prefabs/Props/EmptyStorages/USSR_AmmoBox_empty.et";
static ERR_NativeMortarCourierProbe Current() { return s_Current; }
static bool Enabled()
{
string smoke, outcome;
return Replication.IsServer() && System.GetCLIParam("errSmoke", smoke) && smoke == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "mortar-courier";
}
void ERR_NativeMortarCourierProbe(ERR_GameMode mode) { m_Mode = mode; if (Enabled()) s_Current = this; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: mortar courier " + detail); return true; }
Print("ERR SMOKE FAIL: mortar courier " + detail, LogLevel.ERROR); Stop(); return false;
}
protected bool Has(IEntity owner, IEntity shell)
{
if (!owner || !shell) return false;
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(owner.FindComponent(InventoryStorageManagerComponent));
if (!inventory) return false;
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items); return items.Contains(shell);
}
void Uplink(ERR_HQRoute route, ERR_MortarSupplyStatus report)
{
if (m_Stopped || !report || !m_Battery || report.Group != m_Battery.Group || m_Status) return;
m_Uplink = route; m_Status = report; m_ObservedAt = report.ObservedAt; m_Message = report.MessageId;
}
protected bool OriginalStored()
{
foreach (IEntity shell : m_Original)
{
if (!shell) return false;
int owners;
foreach (BaseInventoryStorageComponent storage : m_Stores) if (storage && storage.Contains(shell)) owners++;
foreach (IEntity actor : m_Crew) if (Has(actor, shell)) owners++;
if (owners != 1) return false;
}
return true;
}
protected bool PreservedForces(ERR_HQ hq)
{
array<IEntity> forces = {}; hq.PhysicalForces(forces);
if (forces.Count() != m_Forces.Count()) return false;
foreach (IEntity original : m_Forces) if (!original || !forces.Contains(original)) return false;
return true;
}
int Tick(float now)
{
if (m_Complete) return 1;
if (m_Stopped || !Enabled()) return -1;
if (m_Deadline == 0) m_Deadline = now + 360000;
if (now > m_Deadline) { Check(false, "bounded native courier deadline"); return -1; }
ERR_HQ hq = ERR_HQ.Get(); if (!hq) return 0;
m_Service = hq.Logistics(); if (!m_Service) return 0;
m_Battery = m_Service.Battery();
if (m_Service.Stopped()) { Check(false, "production finite stock setup remains available"); return -1; }
if (!m_Battery || !m_Battery.StartupActivated || m_Service.State() == 0) return 0;
// No fire-controller hold, synthetic contact or routing bypass: this fixture
// requires the actual local prisoner to remain inside the gun's minimum range.
PlayerController controller = GetGame().GetPlayerController();
IEntity player; if (controller) player = controller.GetControlledEntity();
if (!player || !m_Mode.Players().CurrentActor(player) || vector.DistanceXZ(player.GetOrigin(), m_Battery.Gun.GetOrigin()) >= 300) { Check(false, "actual local prisoner remains below artillery minimum engagement range"); return -1; }
if (m_Stage == 0)
{
m_Service.InitialStock(m_Depot); hq.PhysicalForces(m_Forces);
ERR_ScenarioComponent scenario = ERR_ScenarioComponent.Cast(m_Mode.FindComponent(ERR_ScenarioComponent));
ERR_SiteRuntime site = scenario.Sites().Find("everon-supply-2");
if (!Check(site && site.Group == m_Service.Group() && site.PatrolCycle, "same original western cache patrol")) return -1;
m_OriginalPatrolCycle = site.PatrolCycle; m_OriginalPatrolMoves.Copy(site.PatrolWaypoints);
array<AIAgent> agents = {}; m_Battery.Group.GetAgents(agents);
foreach (AIAgent agent : agents)
{
if (!agent || !agent.GetControlledEntity()) continue;
IEntity actor = agent.GetControlledEntity(); m_Crew.Insert(actor);
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
foreach (IEntity item : items) if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_MortarBattery.SHELL && !m_Original.Contains(item)) m_Original.Insert(item);
}
if (!Check(m_Original.Count() == 27 && m_Depot.Count() == 12 && m_Battery.TotalShots == 0, "27 original crew rounds and 12 distinct finite depot rounds")) return -1;
foreach (IEntity depotShell : m_Depot) if (!Check(!m_Original.Contains(depotShell) && Has(m_Service.Cache(), depotShell), "depot round is a distinct original cache item")) return -1;
int tested, lowGround, obstructed;
for (int i = 0; i < STORAGE_CANDIDATES && m_Stores.Count() < STORAGE_COUNT; i++)
{
tested++;
vector position = ERR_Actors.Ground(m_Battery.Gun.GetOrigin() + StorageOffset(i));
if (position[1] <= 1) { lowGround++; continue; }
if (!SCR_WorldTools.TraceCilinderUtil(position + "0 1 0", 1.3, 2, TraceFlags.ENTS | TraceFlags.WORLD)) { obstructed++; continue; }
IEntity box = ERR_Actors.Spawn(BOX, position);
if (!Check(box != null, "fixture-only empty box created; no new ammunition")) return -1;
m_Mode.Own(box); m_Boxes.Insert(box);
BaseInventoryStorageComponent storage = BaseInventoryStorageComponent.Cast(box.FindComponent(BaseInventoryStorageComponent));
if (!Check(storage != null, "fixture box has real native storage")) return -1;
m_Stores.Insert(storage);
}
Print(string.Format("ERR COURIER STORAGE: tested=%1/64 ready=%2/5 lowGround=%3 obstructed=%4", tested, m_Stores.Count(), lowGround, obstructed));
if (!Check(m_Boxes.Count() == STORAGE_COUNT && m_Stores.Count() == STORAGE_COUNT, "five owned empty native stores ready before moving any original shell")) return -1;
Print("ERR COURIER FIXTURE: original 27 shells will be physically sequestered in five fixture boxes, not fired/destroyed; no report insertion, teleport, AI hold or replacement ammunition");
m_Stage = 1;
}
if (m_Stage == 1)
{
if (m_Callback)
{
if (m_Callback.Result < 0 || now >= m_MoveDeadline || !m_Moving) { Check(false, "native depletion move neither failed nor timed out/item lost"); return -1; }
InventoryItemComponent item = InventoryItemComponent.Cast(m_Moving.FindComponent(InventoryItemComponent));
if (m_Callback.Result == 0 || !item || !item.GetParentSlot() || item.GetParentSlot().GetStorage() != m_Destination || Has(m_Source, m_Moving) || !m_Destination.Contains(m_Moving)) return 0;
m_Callback = null; m_Moving = null; m_Source = null; m_Destination = null; m_Index++;
}
if (m_Index < m_Original.Count())
{
m_Moving = m_Original[m_Index];
foreach (IEntity owner : m_Crew) if (Has(owner, m_Moving)) m_Source = owner;
if (!Check(m_Source != null, "original shell still belongs to original crew before depletion")) return -1;
foreach (BaseInventoryStorageComponent candidate : m_Stores) if (candidate.PerformVolumeValidation(m_Moving)) { m_Destination = candidate; break; }
if (!Check(m_Destination != null, "five native boxes have capacity for original shells")) return -1;
InventoryStorageManagerComponent source = InventoryStorageManagerComponent.Cast(m_Source.FindComponent(InventoryStorageManagerComponent));
m_Callback = new ERR_NativeCourierMoveCallback(); m_MoveDeadline = now + 8000;
if (!Check(source.TryMoveItemToStorage(m_Moving, m_Destination, -1, m_Callback), "native depletion move accepted, awaiting exact ownership proof")) return -1;
return 0;
}
if (!Check(OriginalStored() && m_Battery.CountShells() == 0, "all 27 original shells remain physical while real battery inventory is exhausted")) return -1;
m_Stage = 2;
}
if (!OriginalStored() || !PreservedForces(hq)) { Check(false, "all original shells and all original physical forces preserved"); return -1; }
if (!m_Downlink && m_Service.ProbeOrder()) m_Downlink = m_Service.ProbeOrder();
if (!m_Bearer && m_Service.Courier()) { m_Bearer = m_Service.Courier(); m_Departure = m_Bearer.GetOrigin(); }
if (m_Service.Delivered() < 1 || m_Service.CompletedTrips() < 1 || m_Service.State() != 1) return 0;
AIWaypoint current = m_Service.Group().GetCurrentWaypoint();
if (!current || (current != m_OriginalPatrolCycle && !m_OriginalPatrolMoves.Contains(current))) return 0;
if (!Check(m_Service.CompletedTrips() == 1 && hq.LogisticsToken(m_Service.Group()) == 0, "complete physical trip released HQ reservation and restored original native patrol")) return -1;
if (!Check(m_Status && m_Service.ProbeSourceMessage() == m_Message && m_Status.MessageId == m_Message && m_Status.ObservedAt == m_ObservedAt && ERR_MortarLogisticsRules.Fresh(m_ObservedAt, m_Service.ProbeLastDelivery()), "same immutable originally delivered supply status authorizes delivery")) return -1;
if (!Check(m_Uplink && !m_Uplink.Failed && !m_Uplink.Sending && m_Uplink.Cursor >= m_Uplink.Nodes.Count(), "actual low-ammo radio uplink reached HQ")) return -1;
if (!Check(m_Downlink && !m_Downlink.Failed && !m_Downlink.Sending && m_Downlink.Cursor == m_Downlink.Nodes.Count() - 1, "actual courier downlink completed before item delivery")) return -1;
if (!Check(m_Bearer && ERR_Actors.Group(m_Bearer) == m_Service.Group() && vector.DistanceXZ(m_Departure, m_Bearer.GetOrigin()) > 100, "same original bearer physically walked more than 100m")) return -1;
bool found; foreach (IEntity deliveredShell : m_Depot) foreach (IEntity recipient : m_Crew) if (Has(recipient, deliveredShell)) found = true;
if (!Check(found && m_Battery.CountShells() > 0 && m_Battery.TotalShots == 0, "previously empty battery now owns an exact original depot shell; no synthetic rounds or shots")) return -1;
foreach (IEntity retained : m_Depot)
{
int owners;
if (Has(m_Service.Cache(), retained)) owners++;
if (Has(m_Bearer, retained)) owners++;
foreach (IEntity crew : m_Crew) if (Has(crew, retained)) owners++;
if (!Check(retained && owners == 1, "each of 12 initial depot shells has exactly one physical owner")) return -1;
}
m_Complete = true; Stop(); return 1;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true; if (s_Current == this) s_Current = null;
// Boxes with borrowed originals remain owned by normal mission cleanup.
// No manual restoration/deletion can race the production/native moves.
}
}
#endif
@@ -0,0 +1,334 @@
#ifdef WORKBENCH
// Explicit synthetic observation only. Delivery, HQ scheduling and native fire
// are unchanged; this helper never writes directly into the HQ contact list.
modded class ERR_HQ
{
int ERR_QueueMortarFixtureReport(SCR_AIGroup source, IEntity target, vector position, float now)
{
if (!ERR_NativeMortarFiringProbe.Enabled() || !target || !m_Contacts.IsEmpty()) return 0;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != source || !unit.ReservedForSupport || !unit.Radio || unit.Uplink || unit.PendingReport) continue;
ERR_HQRoute route = m_Network.Route(unit.Radio);
if (!route || route.Nodes.IsEmpty()) return 0;
ERR_HQReport report = new ERR_HQReport();
report.MessageId = m_Journal.Message(); report.Target = target; report.Source = source;
report.Position = position; report.ObservedAt = now; report.DetailsObservedAt = now;
report.HasVelocity = true; report.Velocity = vector.Zero;
report.TravelMode = "on foot"; report.SeenEscapists = 1; report.SeenWeapons = "fixture observation";
route.MessageId = report.MessageId; route.Payload = "EXPLICIT SYNTHETIC OBSERVATION: " + report.Description(now);
unit.PendingReport = report; unit.Uplink = route;
m_Journal.Write("FIXTURE REPORT", route.Payload, report.MessageId);
return report.MessageId;
}
return 0;
}
void ERR_RemoveMortarFixtureReport(IEntity target)
{
if (!target) return;
for (int i = m_Contacts.Count() - 1; i >= 0; i--)
if (m_Contacts[i].Target == target) m_Contacts.Remove(i);
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.PendingReport && unit.PendingReport.Target == target) { unit.PendingReport = null; unit.Uplink = null; }
if ((unit.PendingOrder && unit.PendingOrder.Target == target) || (unit.Assignment && unit.Assignment.Target == target)) unit.Release();
}
}
}
// Production sets Released immediately before reserving shell airspace, then
// invokes native inventory transfer. Observe that existing commit boundary so
// even a synchronous native projectile has a prior recorded authorization.
modded class ERR_MortarBattery
{
override void ReserveShellAirspace(float now)
{
super.ReserveShellAirspace(now);
ERR_NativeMortarFiringProbe probe = ERR_NativeMortarFiringProbe.Active();
if (probe) probe.NativeCommit(this, now);
}
override void Cancel(string reason = "cancel requested")
{
ERR_NativeMortarFiringProbe probe = ERR_NativeMortarFiringProbe.Active();
if (probe) probe.NativeClosing(this, reason, LastFailure);
super.Cancel(reason);
}
}
class ERR_NativeMortarEvidenceRules
{
static bool AimFresh(float commandedAt, float validatedAt, float now)
{
return commandedAt >= 0 && now >= commandedAt && validatedAt >= commandedAt && now >= validatedAt && now - validatedAt <= 10000;
}
static bool CommitFresh(float observed, float expectedObserved, float committed)
{
return observed == expectedObserved && observed >= 0 && committed >= observed && committed - observed <= ERR_MortarPrediction.MAX_REPORT_AGE_MS;
}
static bool EventBound(bool pending, int order, int currentOrder, int shot, int currentShot, float committed, float emitted)
{
// The fixture has its own overall deadline. A committed shell does not gain
// a second authorization expiry while native physical transfer completes.
return pending && order == currentOrder && shot == currentShot && emitted >= committed;
}
static bool ExpiredClosure(float observed, float closed, string failure)
{
return observed >= 0 && closed > observed + ERR_MortarPrediction.MAX_REPORT_AGE_MS && (failure.Contains("delivered target report expired") || failure.Contains("observation older than 60s"));
}
}
// Observe only the actual native event. Node SUCCESS, accepted inventory moves
// and committed shell loading are never counted as physical projectiles.
modded class SCR_MortarMuzzleComponent
{
override void OnWeaponFired(IEntity effectEntity, BaseMuzzleComponent muzzle, IEntity projectileEntity)
{
ERR_NativeMortarFiringProbe probe = ERR_NativeMortarFiringProbe.Active();
if (probe) probe.NativeProjectile(GetOwner(), projectileEntity);
super.OnWeaponFired(effectEntity, muzzle, projectileEntity);
}
}
class ERR_NativeMortarFiringProbe
{
protected static ERR_NativeMortarFiringProbe s_Active;
protected ERR_GameMode m_Mode;
protected ERR_MortarBattery m_Battery;
protected IEntity m_Gun;
protected SCR_AIGroup m_Group;
protected IEntity m_Target;
protected vector m_Impact;
protected ref array<IEntity> m_Crew = {};
protected ref array<IEntity> m_InitialShells = {};
protected ref array<IEntity> m_Projectiles = {};
protected int m_Source;
protected int m_Order;
protected int m_Step;
protected int m_Events;
protected int m_StoppedShells;
protected float m_Deadline;
protected float m_NextDiagnostic;
protected float m_StopUntil;
protected float m_ObservedAt;
protected bool m_WaitForExpiry;
protected int m_FinalEvents;
protected bool m_CommitPending;
protected float m_CommittedAt;
protected int m_CommitOrder;
protected int m_CommitShot;
protected int m_Commits;
protected bool m_ClosureSeen;
protected bool m_ClosureExpired;
protected bool m_ClosureComplete;
protected bool m_Unsafe;
protected bool m_BadProjectile;
protected bool m_SawUplink;
protected bool m_SawDownlink;
protected bool m_SawActivity;
protected bool m_Stopped;
protected string m_BadReason;
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && (outcome == "mortar-fire" || outcome == "mortar-expiry");
}
static ERR_NativeMortarFiringProbe Active() { return s_Active; }
void ERR_NativeMortarFiringProbe(ERR_GameMode mode)
{
m_Mode = mode;
string outcome; System.GetCLIParam("errSmokeOutcome", outcome); m_WaitForExpiry = outcome == "mortar-expiry";
m_Deadline = GetGame().GetWorld().GetWorldTime() + 180000;
if (Enabled() && !s_Active) s_Active = this;
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: mortar firing " + detail); return true; }
Print("ERR SMOKE FAIL: mortar firing " + detail, LogLevel.ERROR); Stop(); return false;
}
protected void Shells(out array<IEntity> shells)
{
shells.Clear();
foreach (IEntity actor : m_Crew)
{
if (!actor) continue;
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
foreach (IEntity item : items)
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_MortarBattery.SHELL && !shells.Contains(item)) shells.Insert(item);
}
}
protected bool CheckNoOccupants(IEntity entity)
{
if (ChimeraCharacter.Cast(entity) || Vehicle.Cast(entity)) { m_Unsafe = true; return false; }
return true;
}
protected bool EmptyImpact(vector position)
{
m_Unsafe = false;
GetGame().GetWorld().QueryEntitiesByAABB(position - "200 5000 200", position + "200 5000 200", CheckNoOccupants, null, EQueryEntitiesFlags.ALL);
return !m_Unsafe;
}
protected bool SelectImpact(ERR_MortarSystem system)
{
// Bounded candidate survey: no clearance assumption from coordinates alone.
array<vector> offsets = {"-800 0 -600", "-600 0 -800", "0 0 -1000", "600 0 -800", "-1000 0 0", "800 0 -600", "-800 0 600", "0 0 1000"};
foreach (vector offset : offsets)
{
vector candidate = ERR_Actors.Ground(m_Battery.Position + offset);
if (candidate[1] < 1 || !EmptyImpact(candidate) || !system.Safe(m_Battery, candidate, 75)) continue;
bool clear = true;
for (int shot = 0; shot < ERR_MortarSalvo.ROUNDS; shot++)
{
vector spread = ERR_Actors.Ground(candidate + ERR_MortarSalvo.Offset(m_Battery.SiteId, m_Battery.OrderId + 1, shot));
if (!system.Safe(m_Battery, spread, 75)) { clear = false; break; }
}
if (!clear) continue;
m_Impact = candidate;
PrintFormat("ERR MORTAR FIXTURE IMPACT: position=%1 batteryDistance=%2; no characters/vehicles in 400m square; production settlement/friendly exclusions passed", candidate, vector.DistanceXZ(candidate, m_Battery.Position));
return true;
}
return false;
}
protected void Diagnose(float now)
{
if (now < m_NextDiagnostic) return;
m_NextDiagnostic = now + 5000;
if (!m_Battery) { Print("ERR MORTAR FIXTURE: waiting for one authored battery"); return; }
PrintFormat("ERR MORTAR FIXTURE: step=%1 startup=%2 source=M%3 observedEvents=%4 ready=%5 failure=%6", m_Step, m_Battery.StartupActivated, m_Source, m_Events, m_Battery.Operational(), m_Battery.SpawnFailure);
m_Battery.SiteStatus(now);
}
void NativeCommit(ERR_MortarBattery battery, float now)
{
if (m_Stopped || battery != m_Battery) return;
if ((m_Step != 1 && m_Step != 3) || m_CommitPending || !battery.CommandOpen || !battery.Released || battery.SourceId != m_Source || battery.OrderId != m_Order || !battery.Report || battery.Report.MessageId != m_Source || !ERR_NativeMortarEvidenceRules.CommitFresh(battery.Report.ObservedAt, m_ObservedAt, now) || !ERR_NativeMortarEvidenceRules.AimFresh(battery.AimedAt, battery.AimValidatedAt, now) || battery.FlightTime <= 0)
{ m_BadProjectile = true; m_BadReason = "physical commit lacks immutable fresh report/current order/native aim, or duplicates pending commit"; return; }
m_CommitPending = true; m_CommittedAt = now; m_CommitOrder = battery.OrderId; m_CommitShot = battery.OrderShots; m_Commits++;
PrintFormat("ERR MORTAR FIXTURE COMMIT: count=%1 order=%2 shot=%3 source=M%4 observedAge=%5; recorded before native inventory transfer", m_Commits, m_CommitOrder, m_CommitShot, m_Source, now - m_ObservedAt);
}
void NativeClosing(ERR_MortarBattery battery, string reason, string failure)
{
if (m_Stopped || !m_WaitForExpiry || battery != m_Battery || !battery.CommandOpen || battery.SourceId != m_Source || m_Source <= 0) return;
float now = GetGame().GetWorld().GetWorldTime();
m_ClosureSeen = true;
m_ClosureComplete = battery.OrderShots == ERR_MortarSalvo.ROUNDS && m_Events == ERR_MortarSalvo.ROUNDS;
m_ClosureExpired = ERR_NativeMortarEvidenceRules.ExpiredClosure(m_ObservedAt, now, failure);
PrintFormat("ERR MORTAR FIXTURE CLOSURE: order=%1 observedAge=%2 reason=%3 lastFailure=%4 complete=%5 expiredWithheld=%6", battery.OrderId, now - m_ObservedAt, reason, failure, m_ClosureComplete, m_ClosureExpired);
if (battery.Stopped() || battery.OrderId != m_Order || (!m_ClosureExpired && !m_ClosureComplete))
{ m_BadProjectile = true; m_BadReason = "expiry evidence interrupted by Stop or unrelated/incomplete order closure"; }
}
void NativeProjectile(IEntity gun, IEntity projectile)
{
if (m_Stopped || !m_Battery || gun != m_Gun) return;
if (!projectile) { m_BadProjectile = true; m_BadReason = "native fired event has no physical projectile"; return; }
if (m_Projectiles.Contains(projectile)) { m_BadProjectile = true; m_BadReason = "same physical projectile observed twice"; return; }
m_Projectiles.Insert(projectile); m_Events++;
float now = GetGame().GetWorld().GetWorldTime();
if ((m_Step != 1 && m_Step != 3) || !m_Battery.Released || m_Battery.SourceId != m_Source || !ERR_NativeMortarEvidenceRules.EventBound(m_CommitPending, m_CommitOrder, m_Battery.OrderId, m_CommitShot, m_Battery.OrderShots, m_CommittedAt, now))
{ m_BadProjectile = true; m_BadReason = "projectile lacks the recorded fresh physical commit for its current order/shot"; }
m_CommitPending = false;
PrintFormat("ERR MORTAR FIXTURE PROJECTILE: event=%1 entity=%2 origin=%3 order=%4 source=M%5 aimAge=%6 flight=%7; impact is not measured", m_Events, projectile, projectile.GetOrigin(), m_Battery.OrderId, m_Battery.SourceId, now - m_Battery.AimedAt, m_Battery.FlightTime);
}
int Tick(float now)
{
if (m_Stopped) return -1;
if (!Enabled() || s_Active != this || !m_Mode || !m_Mode.Active()) { Check(false, "explicit active fixture context retained"); return -1; }
ERR_HQ hq = ERR_HQ.Get();
ERR_MortarSystem system;
if (hq) system = hq.Mortars();
if (!system) { Check(false, "real HQ mortar service available"); return -1; }
if (!m_Battery)
{
array<IEntity> guns = {}; system.AppendGuns(guns);
if (guns.Count() == 1) { m_Gun = guns[0]; m_Battery = system.FindGun(m_Gun); }
}
Diagnose(now);
if (now >= m_Deadline) { Check(false, "bounded deadline; inspect readiness/route/activity/aim diagnostics"); return -1; }
if (m_BadProjectile) { Check(false, m_BadReason); return -1; }
if (!m_Battery) return 0;
if (!m_Battery.SpawnFailure.IsEmpty()) { Check(false, "battery startup failed: " + m_Battery.SpawnFailure); return -1; }
if (m_Step == 0)
{
if (!m_Battery.StartupActivated || !m_Battery.Operational() || !hq.MortarConnected(m_Battery.Group)) return 0;
m_Group = m_Battery.Group;
if (!Check(m_Group && m_Group.IsExpandComplete() && m_Group.GetAgentsCount() == 4 && m_Battery.TotalShots == 0 && m_Battery.OrderId == 0 && !m_Battery.CommandOpen && !m_Battery.Activity && !m_Battery.RadioOrder, "untasked original gun and complete four-person crew start without shots")) return -1;
array<AIAgent> agents = {}; m_Group.GetAgents(agents);
foreach (AIAgent agent : agents)
{
IEntity actor; if (agent) actor = agent.GetControlledEntity();
if (!Check(ERR_Actors.CanFight(actor) && !m_Crew.Contains(actor), "initial physical crew member is conscious and unique")) return -1;
m_Crew.Insert(actor);
}
if (!Check(m_Battery.RadioOperator && ERR_HQRadio.Capability(m_Battery.RadioOperator) == 2, "dedicated actual manpack supplies the radio route")) return -1;
Shells(m_InitialShells);
if (!Check(m_InitialShells.Count() == 27 && m_Battery.CountShells() == 27, "exactly 27 distinct physical initial HE shells")) return -1;
if (!Check(SelectImpact(system), "bounded candidate survey finds an unoccupied production-safe impact area")) return -1;
m_Target = ERR_Actors.Spawn(ERR_Resources.IntelFolder(), m_Impact);
if (!Check(m_Target != null, "fixture-owned inert report identity exists; no enemy perception is claimed")) return -1;
m_Source = hq.ERR_QueueMortarFixtureReport(m_Group, m_Target, m_Impact, now);
if (!Check(m_Source > 0, "synthetic observation enters actual existing radio uplink once")) return -1;
m_ObservedAt = now; m_SawUplink = true; m_Deadline = now + 90000; m_Step = 1; return 0;
}
if (m_WaitForExpiry && m_Battery.Stopped()) { Check(false, "battery must remain running throughout natural expiry observation"); return -1; }
if (m_Battery.Report && (m_Battery.Report.MessageId != m_Source || m_Battery.Report.ObservedAt != m_ObservedAt)) { Check(false, "delivered report identity and original observation timestamp remain immutable"); return -1; }
if (m_Battery.Gun != m_Gun || m_Battery.Group != m_Group) { Check(false, "original gun and crew identity retained"); return -1; }
if (!EmptyImpact(m_Impact)) { Check(false, "impact exclusion stays unoccupied before native firing/cancellation"); return -1; }
array<IEntity> remaining = {}; Shells(remaining);
foreach (IEntity item : remaining)
if (!m_InitialShells.Contains(item)) { Check(false, "no replacement HE shell appears after initialization"); return -1; }
if (m_Step == 1)
{
if (m_Battery.RadioOrder && m_Battery.SourceId == m_Source) { m_SawDownlink = true; m_Order = m_Battery.OrderId; }
if (m_Battery.Activity && m_Battery.Report && m_Battery.Report.MessageId == m_Source) m_SawActivity = true;
if (m_Battery.OrderId > 1 || (m_Battery.SourceId != 0 && m_Battery.SourceId != m_Source)) { Check(false, "one unchanged fixture source creates only one HQ order"); return -1; }
if (m_Events == 0) return 0;
if (!Check(m_Events == 1 && m_Battery.TotalShots == 1 && m_Battery.OrderShots == 1 && !m_Battery.Released && m_SawUplink && m_SawDownlink && m_SawActivity && m_Order == 1, "one physical projectile follows real uplink/downlink, native activity and fresh aim")) return -1;
if (!Check(remaining.Count() == 26 && m_Battery.CountShells() == 26, "one fired projectile consumes one of 27 original inventory shells")) return -1;
if (m_WaitForExpiry)
{
m_Step = 3; m_Deadline = m_ObservedAt + ERR_MortarPrediction.MAX_REPORT_AGE_MS + 50000;
return 0;
}
m_FinalEvents = m_Events;
// Cancel on this later fixture tick, never reentrantly inside the firing node.
hq.ERR_RemoveMortarFixtureReport(m_Target);
system.Stop(); system.Stop();
if (!Check(!m_Battery.CommandOpen && !m_Battery.Activity && !m_Battery.RadioOrder, "explicit support Stop closes native order and route idempotently")) return -1;
m_StoppedShells = remaining.Count(); m_StopUntil = now + 12000; m_Deadline = m_StopUntil + 2000; m_Step = 2; return 0;
}
if (m_Step == 3)
{
if (m_Battery.OrderId != m_Order || m_Battery.SourceId != m_Source) { Check(false, "unchanged delivered observation received a second order"); return -1; }
if (m_Events > ERR_MortarSalvo.ROUNDS || m_Battery.TotalShots > ERR_MortarSalvo.ROUNDS) { Check(false, "single observation cannot exceed finite salvo"); return -1; }
if (now < m_ObservedAt + ERR_MortarPrediction.MAX_REPORT_AGE_MS + 25000) return 0;
if (!Check(m_ClosureSeen && (m_ClosureExpired || m_ClosureComplete) && !m_CommitPending && m_Commits == m_Events && m_Events >= 1 && m_Battery.TotalShots == m_Events && m_Battery.OrderShots == m_Events && remaining.Count() == 27 - m_Events && !m_Battery.CommandOpen && !m_Battery.Activity && !m_Battery.RadioOrder && !m_Battery.Report, "explicit expired-withheld closure or complete finite salvo, with only fresh commits and observed physical shells consumed")) return -1;
m_FinalEvents = m_Events;
// Keep HQ running and the expired contact present throughout this next wait.
m_StoppedShells = remaining.Count(); m_StopUntil = now + 12000; m_Deadline = m_StopUntil + 2000; m_Step = 4;
return 0;
}
if (now < m_StopUntil) return 0;
if (!Check(m_Battery.TotalShots == m_FinalEvents && m_Events == m_FinalEvents && m_Battery.OrderId == m_Order && remaining.Count() == m_StoppedShells && !m_Battery.CommandOpen && !m_Battery.Activity && !m_Battery.RadioOrder, "closed order issues no duplicate order, projectile or replenishment over 12 seconds")) return -1;
array<AIAgent> finalCrew = {}; m_Group.GetAgents(finalCrew);
if (!Check(finalCrew.Count() == 4 && m_Battery.Gun == m_Gun, "original group and gun retain four crew")) return -1;
foreach (AIAgent finalAgent : finalCrew)
if (!Check(finalAgent && m_Crew.Contains(finalAgent.GetControlledEntity()) && ERR_Actors.CanFight(finalAgent.GetControlledEntity()), "final conscious crew entity belongs to original physical roster")) return -1;
if (m_WaitForExpiry) { hq.ERR_RemoveMortarFixtureReport(m_Target); system.Stop(); system.Stop(); }
Stop(); return 1;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
ERR_HQ hq = ERR_HQ.Get();
if (hq)
{
hq.ERR_RemoveMortarFixtureReport(m_Target);
if (m_Battery && m_Battery.SourceId == m_Source && m_Source > 0) m_Battery.Cancel("mortar firing fixture cleanup");
}
ERR_Actors.DeleteSpawned(m_Target); m_Target = null;
if (s_Active == this) s_Active = null;
}
void ~ERR_NativeMortarFiringProbe() { Stop(); }
}
#endif
@@ -0,0 +1,476 @@
#ifdef WORKBENCH
// Explicit synthetic observation only. Delivery, HQ scheduling and native fire
// are unchanged; this helper never writes directly into the HQ contact list.
modded class ERR_HQ
{
int ERR_QueueMortarRefreshReport(SCR_AIGroup source, IEntity radio, IEntity target, vector position, out float observedAt)
{
observedAt = -1;
if (!ERR_NativeMortarSalvoProbe.RefreshEnabled() || !target || !radio || ERR_HQRadio.Capability(radio) != 2) return 0;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != source || !unit.ReservedForSupport || unit.Radio != radio || unit.Uplink || unit.PendingReport || unit.PendingSupplyStatus) continue;
ERR_HQRoute route = m_Network.Route(radio);
if (!route || route.Nodes.IsEmpty()) return 0;
observedAt = GetGame().GetWorld().GetWorldTime();
ERR_HQReport report = new ERR_HQReport();
report.MessageId = m_Journal.Message(); report.Target = target; report.Source = source;
report.Position = position; report.ObservedAt = observedAt; report.DetailsObservedAt = observedAt;
report.HasVelocity = true; report.Velocity = vector.Zero;
report.TravelMode = "on foot"; report.SeenEscapists = 1; report.SeenWeapons = "fixture observation";
route.MessageId = report.MessageId; route.Payload = "NEW EXPLICIT SYNTHETIC OBSERVATION: " + report.Description(observedAt);
unit.PendingReport = report; unit.Uplink = route;
m_Journal.Write("FIXTURE REPORT", route.Payload, report.MessageId);
return report.MessageId;
}
return 0;
}
int ERR_QueueMortarSalvoReport(SCR_AIGroup source, IEntity target, vector position, float now)
{
if (!ERR_NativeMortarSalvoProbe.Enabled() || !target || !m_Contacts.IsEmpty()) return 0;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != source || !unit.ReservedForSupport || !unit.Radio || unit.Uplink || unit.PendingReport) continue;
ERR_HQRoute route = m_Network.Route(unit.Radio);
if (!route || route.Nodes.IsEmpty()) return 0;
ERR_HQReport report = new ERR_HQReport();
report.MessageId = m_Journal.Message(); report.Target = target; report.Source = source;
report.Position = position; report.ObservedAt = now; report.DetailsObservedAt = now;
report.HasVelocity = true; report.Velocity = vector.Zero;
report.TravelMode = "on foot"; report.SeenEscapists = 1; report.SeenWeapons = "fixture observation";
route.MessageId = report.MessageId; route.Payload = "EXPLICIT SYNTHETIC OBSERVATION: " + report.Description(now);
unit.PendingReport = report; unit.Uplink = route;
m_Journal.Write("FIXTURE REPORT", route.Payload, report.MessageId);
return report.MessageId;
}
return 0;
}
void ERR_RemoveMortarSalvoReport(IEntity target)
{
if (!target) return;
for (int i = m_Contacts.Count() - 1; i >= 0; i--)
if (m_Contacts[i].Target == target) m_Contacts.Remove(i);
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.PendingReport && unit.PendingReport.Target == target) { unit.PendingReport = null; unit.Uplink = null; }
if ((unit.PendingOrder && unit.PendingOrder.Target == target) || (unit.Assignment && unit.Assignment.Target == target)) unit.Release();
}
}
}
// Production sets Released immediately before reserving shell airspace, then
// invokes native inventory transfer. Observe that existing commit boundary so
// even a synchronous native projectile has a prior recorded authorization.
modded class ERR_MortarBattery
{
override void Audit(string kind, string detail)
{
super.Audit(kind, detail);
ERR_NativeMortarSalvoProbe probe = ERR_NativeMortarSalvoProbe.Active();
if (probe) probe.NativeAudit(this, kind);
}
override void ReserveShellAirspace(float now)
{
super.ReserveShellAirspace(now);
ERR_NativeMortarSalvoProbe probe = ERR_NativeMortarSalvoProbe.Active();
if (probe) probe.NativeCommit(this, now);
}
override void Cancel(string reason = "cancel requested")
{
ERR_NativeMortarSalvoProbe probe = ERR_NativeMortarSalvoProbe.Active();
if (probe) probe.NativeClosing(this, reason, LastFailure);
super.Cancel(reason);
}
}
// Exact completion is separate from native activity success or accepted loading.
class ERR_NativeMortarSalvoRules
{
static bool Complete(int events, int commits, int total, int orderShots, int shells, bool pending)
{
return events == ERR_MortarSalvo.ROUNDS && commits == events && total == events && orderShots == events && shells == 27 - events && !pending;
}
static bool QueueUpdate(bool accepted, bool pending, int shots, float observedAt, float orderedAt, float now)
{
return accepted && !pending && shots < ERR_MortarSalvo.ROUNDS / 2 && now >= observedAt + 20000 && orderedAt >= 0 && now >= orderedAt && now - orderedAt < 90000;
}
}
class ERR_NativeMortarRefreshRecord
{
int MessageId;
float ObservedAt;
int DownlinkId;
bool Accepted;
ref ERR_HQReport Delivered;
bool Matches(ERR_HQReport report, IEntity target, SCR_AIGroup group, vector position)
{
if (!report || report.MessageId != MessageId || report.ObservedAt != ObservedAt || report.Target != target || report.Source != group) return false;
return vector.Distance(report.Position, position) < 0.01 && report.HasVelocity && report.Velocity.Length() < 0.001 && !report.Distress;
}
}
// Observe only the actual native event. Node SUCCESS, accepted inventory moves
// and committed shell loading are never counted as physical projectiles.
modded class SCR_MortarMuzzleComponent
{
override void OnWeaponFired(IEntity effectEntity, BaseMuzzleComponent muzzle, IEntity projectileEntity)
{
ERR_NativeMortarSalvoProbe probe = ERR_NativeMortarSalvoProbe.Active();
if (probe) probe.NativeProjectile(GetOwner(), projectileEntity);
super.OnWeaponFired(effectEntity, muzzle, projectileEntity);
}
}
class ERR_NativeMortarSalvoProbe
{
protected static ERR_NativeMortarSalvoProbe s_Active;
protected ERR_GameMode m_Mode;
protected ERR_MortarBattery m_Battery;
protected IEntity m_Gun;
protected SCR_AIGroup m_Group;
protected IEntity m_Target;
protected vector m_Impact;
protected ref array<IEntity> m_Crew = {};
protected ref array<IEntity> m_InitialShells = {};
protected ref array<IEntity> m_Projectiles = {};
protected int m_Source;
protected int m_Order;
protected int m_Step;
protected int m_Events;
protected int m_StoppedShells;
protected float m_Deadline;
protected float m_NextDiagnostic;
protected float m_StopUntil;
protected float m_ObservedAt;
protected ref ERR_HQReport m_Delivered;
protected bool m_RefreshCase;
protected IEntity m_OriginalRTO;
protected SCR_AIStaticArtilleryActivity m_OriginalActivity;
protected ref array<ref ERR_NativeMortarRefreshRecord> m_Observations = {};
protected int m_UpdatesAccepted;
protected int m_UpdatedCommits;
protected int m_CommitSource;
protected float m_CommitObservedAt;
protected bool m_CommitPending;
protected float m_CommittedAt;
protected int m_CommitOrder;
protected int m_CommitShot;
protected int m_Commits;
protected bool m_ClosureSeen;
protected bool m_ClosureComplete;
protected bool m_Unsafe;
protected bool m_BadProjectile;
protected bool m_SawUplink;
protected bool m_SawDownlink;
protected bool m_SawActivity;
protected bool m_Stopped;
protected string m_BadReason;
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && (outcome == "mortar-salvo" || outcome == "mortar-refresh");
}
static bool RefreshEnabled()
{
string outcome;
return Enabled() && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "mortar-refresh";
}
static ERR_NativeMortarSalvoProbe Active() { return s_Active; }
void ERR_NativeMortarSalvoProbe(ERR_GameMode mode)
{
m_Mode = mode;
m_RefreshCase = RefreshEnabled();
m_Deadline = GetGame().GetWorld().GetWorldTime() + 180000;
if (Enabled() && !s_Active) s_Active = this;
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: mortar salvo " + detail); return true; }
Print("ERR SMOKE FAIL: mortar salvo " + detail, LogLevel.ERROR); Stop(); return false;
}
protected void Shells(out array<IEntity> shells)
{
shells.Clear();
foreach (IEntity actor : m_Crew)
{
if (!actor) continue;
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
foreach (IEntity item : items)
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_MortarBattery.SHELL && !shells.Contains(item)) shells.Insert(item);
}
}
protected bool CheckNoOccupants(IEntity entity)
{
if (ChimeraCharacter.Cast(entity) || Vehicle.Cast(entity)) { m_Unsafe = true; return false; }
return true;
}
protected bool EmptyImpact(vector position)
{
m_Unsafe = false;
GetGame().GetWorld().QueryEntitiesByAABB(position - "200 5000 200", position + "200 5000 200", CheckNoOccupants, null, EQueryEntitiesFlags.ALL);
return !m_Unsafe;
}
protected bool SelectImpact(ERR_MortarSystem system)
{
// Bounded candidate survey: no clearance assumption from coordinates alone.
array<vector> offsets = {"-800 0 -600", "-600 0 -800", "0 0 -1000", "600 0 -800", "-1000 0 0", "800 0 -600", "-800 0 600", "0 0 1000"};
foreach (vector offset : offsets)
{
vector candidate = ERR_Actors.Ground(m_Battery.Position + offset);
if (candidate[1] < 1 || !EmptyImpact(candidate) || !system.Safe(m_Battery, candidate, 75)) continue;
bool clear = true;
for (int shot = 0; shot < ERR_MortarSalvo.ROUNDS; shot++)
{
vector spread = ERR_Actors.Ground(candidate + ERR_MortarSalvo.Offset(m_Battery.SiteId, m_Battery.OrderId + 1, shot));
if (!system.Safe(m_Battery, spread, 75)) { clear = false; break; }
}
if (!clear) continue;
m_Impact = candidate;
PrintFormat("ERR MORTAR SALVO IMPACT: position=%1 batteryDistance=%2; no characters/vehicles in 400m square; production settlement/friendly exclusions passed", candidate, vector.DistanceXZ(candidate, m_Battery.Position));
return true;
}
return false;
}
protected void Diagnose(float now)
{
if (now < m_NextDiagnostic) return;
m_NextDiagnostic = now + 5000;
if (!m_Battery) { Print("ERR MORTAR SALVO: waiting for one authored battery"); return; }
PrintFormat("ERR MORTAR SALVO: step=%1 startup=%2 source=M%3 observedEvents=%4 ready=%5 failure=%6", m_Step, m_Battery.StartupActivated, m_Source, m_Events, m_Battery.Operational(), m_Battery.SpawnFailure);
m_Battery.SiteStatus(now);
}
protected ERR_NativeMortarRefreshRecord FindObservation(int messageId)
{
foreach (ERR_NativeMortarRefreshRecord observation : m_Observations)
if (observation.MessageId == messageId) return observation;
return null;
}
protected bool AddObservation(int messageId, float observedAt)
{
if (messageId <= 0 || observedAt < 0 || FindObservation(messageId) || m_Observations.Count() >= 8) return false;
if (!m_Observations.IsEmpty() && observedAt <= m_Observations[m_Observations.Count() - 1].ObservedAt) return false;
ERR_NativeMortarRefreshRecord record = new ERR_NativeMortarRefreshRecord();
record.MessageId = messageId; record.ObservedAt = observedAt;
m_Observations.Insert(record);
PrintFormat("ERR MORTAR REFRESH OBSERVATION: source=M%1 actualObservedAt=%2 originalRTO=%3 synthetic stationary inert target; awaiting actual uplink and HQ downlink", messageId, observedAt, m_OriginalRTO);
return true;
}
protected bool ReportKnown(ERR_HQReport report, bool accepted = true)
{
if (!report) return false;
if (!m_RefreshCase) return report.MessageId == m_Source && report.ObservedAt == m_ObservedAt;
ERR_NativeMortarRefreshRecord record = FindObservation(report.MessageId);
return record && (!accepted || record.Accepted) && record.Matches(report, m_Target, m_Group, m_Impact);
}
protected bool AllAccepted()
{
if (!m_RefreshCase) return true;
if (m_Observations.Count() < 2 || m_UpdatesAccepted != m_Observations.Count() - 1 || m_UpdatedCommits < 1) return false;
foreach (ERR_NativeMortarRefreshRecord record : m_Observations)
if (!record.Accepted || !record.Delivered || record.DownlinkId <= 0) return false;
return true;
}
protected bool NoReportReplay(float now)
{
if (!m_RefreshCase) return m_Delivered && !m_Battery.CanRequest(m_Delivered, now);
if (!AllAccepted()) return false;
foreach (ERR_NativeMortarRefreshRecord record : m_Observations)
if (m_Battery.CanRequest(record.Delivered, now)) return false;
return true;
}
// Observational only: never stop/cancel inside production's current call.
void NativeAudit(ERR_MortarBattery battery, string kind)
{
if (!m_RefreshCase || m_Stopped || battery != m_Battery || m_Source <= 0) return;
if (kind == "AIM")
{
if (m_OriginalActivity || !battery.Activity || !m_SawDownlink || battery.SourceId != m_Source || battery.OrderId != m_Order || !ReportKnown(battery.Report, false))
{ m_BadProjectile = true; m_BadReason = "initial native activity does not match recorded report/downlink or was recreated"; return; }
m_OriginalActivity = battery.Activity; m_SawActivity = true;
ERR_NativeMortarRefreshRecord initial = FindObservation(m_Source);
initial.Accepted = true; initial.Delivered = battery.Report.Snapshot();
m_Delivered = initial.Delivered;
return;
}
if (kind != "UPDATE QUEUED" && kind != "UPDATE RECEIVED") return;
ERR_MortarRefresh update = battery.Refresh;
if (!update || !update.Owns(battery) || battery.OrderId != m_Order || battery.Activity != m_OriginalActivity || !ReportKnown(update.Report, false))
{ m_BadProjectile = true; m_BadReason = "update lost recorded observation or original gun/group/order/activity"; return; }
ERR_NativeMortarRefreshRecord record = FindObservation(update.Report.MessageId);
if (kind == "UPDATE QUEUED")
{
if (record.DownlinkId != 0 || record.Accepted || !update.Route || !update.Route.Downlink || update.Route.Nodes.IsEmpty() || update.Route.MessageId != update.RadioId)
{ m_BadProjectile = true; m_BadReason = "refresh downlink was missing, duplicated or already accepted"; return; }
record.DownlinkId = update.RadioId;
return;
}
if (record.Accepted || record.DownlinkId != update.RadioId || update.Route || battery.Released || battery.Report != update.Report || !ReportKnown(battery.Report, false))
{ m_BadProjectile = true; m_BadReason = "refresh acceptance lacks its completed route or overwrites committed shell"; return; }
record.Accepted = true; record.Delivered = battery.Report.Snapshot(); m_UpdatesAccepted++;
m_Delivered = record.Delivered;
PrintFormat("ERR MORTAR REFRESH ACCEPTED: source=M%1 observedAt=%2 downlink=M%3 sameOrder=%4 originalActivity=%5", record.MessageId, record.ObservedAt, record.DownlinkId, battery.OrderId, battery.Activity == m_OriginalActivity);
}
void NativeCommit(ERR_MortarBattery battery, float now)
{
if (m_Stopped || battery != m_Battery) return;
if (m_RefreshCase && (!m_OriginalActivity || battery.Activity != m_OriginalActivity))
{ m_BadProjectile = true; m_BadReason = "refreshed commit requires the original unchanged native activity"; return; }
if (m_Commits >= ERR_MortarSalvo.ROUNDS || battery.OrderShots != m_Commits)
{ m_BadProjectile = true; m_BadReason = "commit count or physical shot sequence exceeds configured finite salvo"; return; }
if (m_Step != 1 || m_CommitPending || !battery.CommandOpen || !battery.Released || battery.SourceId != m_Source || battery.OrderId != m_Order || !ReportKnown(battery.Report) || !ERR_NativeMortarEvidenceRules.CommitFresh(battery.Report.ObservedAt, battery.Report.ObservedAt, now) || !ERR_NativeMortarEvidenceRules.AimFresh(battery.AimedAt, battery.AimValidatedAt, now) || battery.FlightTime <= 0)
{ m_BadProjectile = true; m_BadReason = "physical commit lacks immutable fresh report/current order/native aim, or duplicates pending commit"; return; }
m_CommitPending = true; m_CommittedAt = now; m_CommitOrder = battery.OrderId; m_CommitShot = battery.OrderShots; m_Commits++;
m_CommitSource = battery.Report.MessageId; m_CommitObservedAt = battery.Report.ObservedAt;
if (m_RefreshCase && m_CommitSource != m_Source) m_UpdatedCommits++;
PrintFormat("ERR MORTAR SALVO COMMIT: count=%1 order=%2 shot=%3 acceptedSource=M%4 observedAge=%5 commandAge=%6 validationAge=%7; recorded before native inventory transfer", m_Commits, m_CommitOrder, m_CommitShot, m_CommitSource, now - m_CommitObservedAt, now - battery.AimedAt, now - battery.AimValidatedAt);
}
void NativeClosing(ERR_MortarBattery battery, string reason, string failure)
{
if (m_Stopped || battery != m_Battery || !battery.CommandOpen || battery.SourceId != m_Source || m_Source <= 0) return;
m_ClosureSeen = true;
m_ClosureComplete = battery.OrderShots == ERR_MortarSalvo.ROUNDS && m_Events == ERR_MortarSalvo.ROUNDS && !m_CommitPending && m_Commits == m_Events;
PrintFormat("ERR MORTAR SALVO CLOSURE: order=%1 reason=%2 lastFailure=%3 events=%4 commits=%5 complete=%6", battery.OrderId, reason, failure, m_Events, m_Commits, m_ClosureComplete);
if (battery.Stopped() || battery.OrderId != m_Order || !m_ClosureComplete || !AllAccepted() || reason != "native artillery activity COMPLETED (shot count is separate)")
{ m_BadProjectile = true; m_BadReason = "full salvo requires natural native completion; partial expiry, rejection, timeout or Stop is not success"; }
if (m_RefreshCase && (battery.Activity != m_OriginalActivity || battery.Refresh || battery.RefreshRejected))
{ m_BadProjectile = true; m_BadReason = "natural completion cannot leave a refresh pending or replace original activity"; }
}
void NativeProjectile(IEntity gun, IEntity projectile)
{
if (m_Stopped || !m_Battery || gun != m_Gun) return;
if (!projectile) { m_BadProjectile = true; m_BadReason = "native fired event has no physical projectile"; return; }
if (m_Projectiles.Contains(projectile)) { m_BadProjectile = true; m_BadReason = "same physical projectile observed twice"; return; }
m_Projectiles.Insert(projectile); m_Events++;
float now = GetGame().GetWorld().GetWorldTime();
if (m_Step != 1 || !m_Battery.Released || m_Battery.SourceId != m_Source || !ERR_NativeMortarEvidenceRules.EventBound(m_CommitPending, m_CommitOrder, m_Battery.OrderId, m_CommitShot, m_Battery.OrderShots, m_CommittedAt, now))
{ m_BadProjectile = true; m_BadReason = "projectile lacks the recorded fresh physical commit for its current order/shot"; }
if (!m_Battery.Report || m_Battery.Report.MessageId != m_CommitSource || m_Battery.Report.ObservedAt != m_CommitObservedAt)
{ m_BadProjectile = true; m_BadReason = "committed shell was relabelled with another report before native emission"; }
m_CommitPending = false;
PrintFormat("ERR MORTAR SALVO PROJECTILE: event=%1 entity=%2 origin=%3 order=%4 acceptedSource=M%5 aimAge=%6 flight=%7; impact is not measured", m_Events, projectile, projectile.GetOrigin(), m_Battery.OrderId, m_CommitSource, now - m_Battery.AimedAt, m_Battery.FlightTime);
}
int Tick(float now)
{
if (m_Stopped) return -1;
if (!Enabled() || s_Active != this || !m_Mode || !m_Mode.Active()) { Check(false, "explicit active fixture context retained"); return -1; }
ERR_HQ hq = ERR_HQ.Get();
ERR_MortarSystem system;
if (hq) system = hq.Mortars();
if (!system) { Check(false, "real HQ mortar service available"); return -1; }
if (!m_Battery)
{
array<IEntity> guns = {}; system.AppendGuns(guns);
if (guns.Count() == 1) { m_Gun = guns[0]; m_Battery = system.FindGun(m_Gun); if (m_Battery) m_Group = m_Battery.Group; }
}
Diagnose(now);
if (now >= m_Deadline) { Check(false, "bounded deadline; inspect readiness/route/activity/aim diagnostics"); return -1; }
if (m_BadProjectile) { Check(false, m_BadReason); return -1; }
if (!m_Battery) return 0;
if (!m_Battery.SpawnFailure.IsEmpty()) { Check(false, "battery startup failed: " + m_Battery.SpawnFailure); return -1; }
if (m_Step == 0)
{
if (!m_Battery.StartupActivated || !m_Battery.Operational() || !hq.MortarConnected(m_Battery.Group)) return 0;
m_Group = m_Battery.Group;
if (!Check(m_Group && m_Group.IsExpandComplete() && m_Group.GetAgentsCount() == 4 && m_Battery.TotalShots == 0 && m_Battery.OrderId == 0 && !m_Battery.CommandOpen && !m_Battery.Activity && !m_Battery.RadioOrder, "untasked original gun and complete four-person crew start without shots")) return -1;
array<AIAgent> agents = {}; m_Group.GetAgents(agents);
foreach (AIAgent agent : agents)
{
IEntity actor; if (agent) actor = agent.GetControlledEntity();
if (!Check(ERR_Actors.CanFight(actor) && !m_Crew.Contains(actor), "initial physical crew member is conscious and unique")) return -1;
m_Crew.Insert(actor);
}
if (!Check(m_Battery.RadioOperator && ERR_HQRadio.Capability(m_Battery.RadioOperator) == 2, "dedicated actual manpack supplies the radio route")) return -1;
m_OriginalRTO = m_Battery.RadioOperator;
Shells(m_InitialShells);
if (!Check(m_InitialShells.Count() == 27 && m_Battery.CountShells() == 27, "exactly 27 distinct physical initial HE shells")) return -1;
if (!Check(SelectImpact(system), "bounded candidate survey finds an unoccupied production-safe impact area")) return -1;
m_Target = ERR_Actors.Spawn(ERR_Resources.IntelFolder(), m_Impact);
if (!Check(m_Target != null, "fixture-owned inert report identity exists; no enemy perception is claimed")) return -1;
if (m_RefreshCase) m_Source = hq.ERR_QueueMortarRefreshReport(m_Group, m_OriginalRTO, m_Target, m_Impact, m_ObservedAt);
else { m_Source = hq.ERR_QueueMortarSalvoReport(m_Group, m_Target, m_Impact, now); m_ObservedAt = now; }
if (!Check(m_Source > 0, "synthetic observation enters actual existing radio uplink once")) return -1;
if (m_RefreshCase && !Check(AddObservation(m_Source, m_ObservedAt), "first actual-clock observation recorded without replacement")) return -1;
m_SawUplink = true; m_Deadline = now + ERR_MortarSalvo.TIMEOUT_MS + 35000; m_Step = 1; return 0;
}
if (m_Battery.Stopped()) { Check(false, "battery must remain running throughout full salvo and replay observation"); return -1; }
if (m_Battery.Report && !ReportKnown(m_Battery.Report)) { Check(false, "delivered report identity and original observation timestamp remain immutable and accepted"); return -1; }
if (m_Battery.Gun != m_Gun || m_Battery.Group != m_Group) { Check(false, "original gun and crew identity retained"); return -1; }
if (m_RefreshCase && (m_Battery.RadioOperator != m_OriginalRTO || (m_OriginalActivity && m_Battery.Activity && m_Battery.Activity != m_OriginalActivity)))
{ Check(false, "refreshes preserve original RTO and native activity"); return -1; }
if (!EmptyImpact(m_Impact)) { Check(false, "impact exclusion stays unoccupied before native firing/cancellation"); return -1; }
array<IEntity> remaining = {}; Shells(remaining);
foreach (IEntity item : remaining)
if (!m_InitialShells.Contains(item)) { Check(false, "no replacement HE shell appears after initialization"); return -1; }
if (m_Battery.OrderId > 1 || (m_Battery.SourceId != 0 && m_Battery.SourceId != m_Source)) { Check(false, "one unchanged fixture source creates only one HQ order"); return -1; }
if (m_Events > ERR_MortarSalvo.ROUNDS || m_Battery.TotalShots > ERR_MortarSalvo.ROUNDS) { Check(false, "physical event count never exceeds configured salvo"); return -1; }
if (m_Step == 1)
{
if (m_Battery.RadioOrder && m_Battery.SourceId == m_Source)
{
m_SawDownlink = true; m_Order = m_Battery.OrderId;
if (m_RefreshCase)
{
ERR_NativeMortarRefreshRecord first = FindObservation(m_Source);
if (!first || first.DownlinkId == 0)
{
if (!Check(first && m_Battery.RadioOrder.Downlink && !m_Battery.RadioOrder.Nodes.IsEmpty(), "initial order traverses actual nonempty HQ downlink")) return -1;
first.DownlinkId = m_Battery.RadioOrder.MessageId;
}
}
}
if (m_Battery.Activity && m_Battery.Report && m_Battery.Report.MessageId == m_Source)
{
m_SawActivity = true;
if (!m_Delivered) m_Delivered = m_Battery.Report.Snapshot();
}
if (m_RefreshCase && m_Battery.CommandOpen && m_Battery.Activity && !m_Observations.IsEmpty())
{
ERR_NativeMortarRefreshRecord latest = m_Observations[m_Observations.Count() - 1];
if (ERR_NativeMortarSalvoRules.QueueUpdate(latest.Accepted, m_Battery.Refresh != null, m_Battery.OrderShots, latest.ObservedAt, m_Battery.OrderedAt, now))
{
float observed;
int messageId = hq.ERR_QueueMortarRefreshReport(m_Group, m_OriginalRTO, m_Target, m_Impact, observed);
if (!Check(messageId > 0 && AddObservation(messageId, observed), "next distinct actual-clock synthetic observation queued only after previous acceptance")) return -1;
}
}
if (m_Battery.CommandOpen || m_Battery.Activity) return 0;
if (!m_ClosureSeen) return 0;
if (!Check(m_ClosureComplete && m_SawUplink && m_SawDownlink && m_SawActivity && m_Order == 1, "one real uplink/downlink and native activity naturally complete the entire configured salvo")) return -1;
if (m_RefreshCase && !Check(AllAccepted(), "every queued update was accepted under the same order before closure and at least one updated report authorized a commit")) return -1;
if (!Check(ERR_NativeMortarSalvoRules.Complete(m_Events, m_Commits, m_Battery.TotalShots, m_Battery.OrderShots, remaining.Count(), m_CommitPending) && m_Battery.CountShells() == remaining.Count(), "exact configured native projectiles equal fresh commits and missing original inventory shells")) return -1;
if (!Check(NoReportReplay(now), "all delivered messages are ineligible for a second request after completion")) return -1;
if (!Check(!m_Battery.RadioOrder && !m_Battery.Report && !m_Battery.PendingRadioReport && !m_Battery.PendingSupplyReport && !m_Battery.SupplyPending() && !m_Battery.Released && !m_Battery.Refresh, "natural completion clears command, routes, loading and projectile reservation")) return -1;
// Keep the unchanged HQ contact, battery and scheduler alive. This exceeds
// the normal 20-second post-dispatch decision interval without forging time.
m_StoppedShells = remaining.Count(); m_StopUntil = now + 25000; m_Deadline = m_StopUntil + 2000; m_Step = 2;
return 0;
}
if (m_Battery.CommandOpen || m_Battery.Activity || m_Battery.RadioOrder || m_Battery.Report || m_Battery.PendingRadioReport || m_Battery.PendingSupplyReport || m_Battery.SupplyPending() || m_Battery.Released)
{ Check(false, "completed observation must not reopen command, activity, route or physical loading"); return -1; }
if (!ERR_NativeMortarSalvoRules.Complete(m_Events, m_Commits, m_Battery.TotalShots, m_Battery.OrderShots, remaining.Count(), m_CommitPending) || m_Battery.OrderId != m_Order || remaining.Count() != m_StoppedShells)
{ Check(false, "closed order cannot add shots, orders or replacement ammunition during replay window"); return -1; }
if (now < m_StopUntil) return 0;
if (!Check(NoReportReplay(now), "all claimed contacts produce no second request, projectile or replenishment over 25 seconds")) return -1;
array<AIAgent> finalCrew = {}; m_Group.GetAgents(finalCrew);
if (!Check(finalCrew.Count() == 4 && m_Battery.Gun == m_Gun, "original group and gun retain four crew")) return -1;
foreach (AIAgent finalAgent : finalCrew)
if (!Check(finalAgent && m_Crew.Contains(finalAgent.GetControlledEntity()) && ERR_Actors.CanFight(finalAgent.GetControlledEntity()), "final conscious crew entity belongs to original physical roster")) return -1;
Stop(); return 1;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
ERR_HQ hq = ERR_HQ.Get();
if (hq) hq.ERR_RemoveMortarSalvoReport(m_Target);
if (m_Battery && m_Battery.Gun == m_Gun && m_Battery.Group == m_Group) m_Battery.Stop();
ERR_Actors.DeleteSpawned(m_Target); m_Target = null;
if (s_Active == this) s_Active = null;
}
void ~ERR_NativeMortarSalvoProbe() { Stop(); }
}
#endif
@@ -0,0 +1,383 @@
#ifdef WORKBENCH
// Mechanism-only fixture: radio delivery and automatic artillery scheduling are
// explicitly bypassed. All ammunition and inventory operations remain physical.
modded class ERR_MortarSystem
{
protected ERR_NativeMortarTransferProbe m_ERRTransferFixture;
bool ERR_HoldForTransfer(ERR_NativeMortarTransferProbe owner)
{
if (!ERR_NativeMortarTransferProbe.Enabled() || m_Stopped || m_ERRTransferFixture || !owner) return false;
m_ERRTransferFixture = owner; return true;
}
void ERR_ReleaseTransferHold(ERR_NativeMortarTransferProbe owner)
{
if (m_ERRTransferFixture == owner) m_ERRTransferFixture = null;
}
override void Tick(float now)
{
if (m_ERRTransferFixture) return;
super.Tick(now);
}
ERR_MortarBattery ERR_TransferBattery()
{
if (m_Batteries.Count() != 1) return null;
return m_Batteries[0];
}
}
modded class ERR_MortarBattery
{
ERR_MortarSupplyTransfer ERR_TransferForProbe() { return m_Transfer; }
int ERR_TransferAttempts() { return m_AttemptedShells.Count(); }
}
modded class ERR_MortarSupplyTransfer
{
int ERR_CallbackState() { return m_Acknowledgement; }
IEntity ERR_TransferredShell() { return m_Shell; }
IEntity ERR_TransferRecipient() { return m_Gunner; }
bool ERR_RequestAccepted() { return m_Accepted; }
}
class ERR_NativeMortarMoveCallback : ScriptedInventoryOperationCallback
{
int Result;
protected override void OnComplete() { if (Result == 0) Result = 1; }
protected override void OnFailed() { if (Result == 0) Result = -1; }
}
class ERR_NativeMortarShellOwner
{
IEntity Shell;
IEntity OriginalOwner;
}
class ERR_NativeMortarTransferProbe
{
protected ERR_GameMode m_Mode;
protected ERR_MortarSystem m_System;
protected ERR_MortarBattery m_Battery;
protected IEntity m_Gunner;
protected BaseCompartmentSlot m_Seat;
protected ref array<IEntity> m_Crew = {};
protected ref array<IEntity> m_Boxes = {};
protected ref array<BaseInventoryStorageComponent> m_Stores = {};
protected ref array<ref ERR_NativeMortarShellOwner> m_Original = {};
protected ref ERR_NativeMortarMoveCallback m_MoveCallback;
protected IEntity m_MoveShell;
protected BaseInventoryStorageComponent m_MoveDestination;
protected float m_MoveDeadline;
protected ref ERR_MortarSupplyTransfer m_ObservedTransfer;
protected IEntity m_FirstShell;
protected IEntity m_SecondShell;
protected int m_AttemptsAtStart;
protected int m_ShotsAtStart;
protected int m_CallbackAtCancel;
protected int m_CancelOrder;
protected int m_Step;
protected float m_Deadline;
protected float m_StepDeadline;
protected float m_Wait;
protected bool m_Held;
protected bool m_Stopped;
protected bool m_Complete;
static const ResourceName STORE = "{EBBC859CCD94D96D}Prefabs/Props/EmptyStorages/USSR_AmmoBox_empty.et";
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "mortar-transfer";
}
void ERR_NativeMortarTransferProbe(ERR_GameMode mode)
{
m_Mode = mode; m_Deadline = GetGame().GetWorld().GetWorldTime() + 180000;
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: mortar transfer " + detail); return true; }
Print("ERR SMOKE FAIL: mortar transfer " + detail, LogLevel.ERROR); Stop(); return false;
}
protected bool IsShell(IEntity item)
{
return item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_MortarBattery.SHELL;
}
protected InventoryStorageManagerComponent Inventory(IEntity actor)
{
if (!actor) return null;
return InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
}
protected bool Has(IEntity actor, IEntity item)
{
array<IEntity> items = {}; ERR_Inventory.Items(Inventory(actor), items);
return item && items.Contains(item);
}
protected IEntity CrewOwner(IEntity item)
{
foreach (IEntity actor : m_Crew) if (Has(actor, item)) return actor;
return null;
}
protected IEntity OriginalOwner(IEntity item)
{
foreach (ERR_NativeMortarShellOwner entry : m_Original) if (entry.Shell == item) return entry.OriginalOwner;
return null;
}
protected bool InStore(IEntity item)
{
foreach (BaseInventoryStorageComponent storage : m_Stores) if (storage && storage.Contains(item)) return true;
return false;
}
static bool Conservation(int originals, int actual, bool allOriginal, bool singleOwner)
{
return originals == 27 && actual == originals && allOriginal && singleOwner;
}
protected bool Conserved()
{
array<IEntity> actual = {};
foreach (IEntity actor : m_Crew)
{
array<IEntity> items = {}; ERR_Inventory.Items(Inventory(actor), items);
foreach (IEntity item : items) if (IsShell(item) && !actual.Contains(item)) actual.Insert(item);
}
foreach (BaseInventoryStorageComponent storage : m_Stores)
{
if (!storage) return false;
array<IEntity> items = {}; storage.GetAll(items);
foreach (IEntity item : items) if (IsShell(item) && !actual.Contains(item)) actual.Insert(item);
}
bool allOriginal = true;
bool singleOwner = true;
foreach (ERR_NativeMortarShellOwner entry : m_Original)
{
if (!entry.Shell || !actual.Contains(entry.Shell)) allOriginal = false;
int owners;
foreach (IEntity actor : m_Crew) if (Has(actor, entry.Shell)) owners++;
foreach (BaseInventoryStorageComponent storage : m_Stores) if (storage && storage.Contains(entry.Shell)) owners++;
if (owners != 1) singleOwner = false;
}
return Conservation(m_Original.Count(), actual.Count(), allOriginal, singleOwner);
}
protected IEntity FirstGunnerShell()
{
array<IEntity> items = {}; ERR_Inventory.Items(Inventory(m_Gunner), items);
foreach (IEntity item : items) if (IsShell(item)) return item;
return null;
}
protected bool BeginMove(IEntity shell, BaseInventoryStorageComponent destination, float now)
{
if (!shell || !destination || m_MoveCallback) return false;
IEntity current = CrewOwner(shell);
// A vanilla empty box has storage but no inventory manager. The original
// recipient manager performs its explicit pickup from the external storage.
if (!current) current = OriginalOwner(shell);
InventoryStorageManagerComponent manager = Inventory(current);
if (!manager) return false;
m_MoveShell = shell; m_MoveDestination = destination;
m_MoveCallback = new ERR_NativeMortarMoveCallback(); m_MoveDeadline = now + 8000;
if (!manager.TryMoveItemToStorage(shell, destination, -1, m_MoveCallback)) return false;
return true;
}
protected int MoveResult(float now)
{
if (!m_MoveCallback) return 1;
if (m_MoveCallback.Result < 0 || now >= m_MoveDeadline) return -1;
if (m_MoveCallback.Result == 0 || !m_MoveShell || !m_MoveDestination || !m_MoveDestination.Contains(m_MoveShell)) return 0;
InventoryItemComponent item = InventoryItemComponent.Cast(m_MoveShell.FindComponent(InventoryItemComponent));
if (!item || !item.GetParentSlot() || item.GetParentSlot().GetStorage() != m_MoveDestination) return 0;
m_MoveCallback = null; m_MoveShell = null; m_MoveDestination = null;
return 1;
}
protected bool Stash(IEntity shell, float now)
{
foreach (BaseInventoryStorageComponent storage : m_Stores)
if (storage && storage.GetEstimatedCountFitForItem(shell) > 0) return BeginMove(shell, storage, now);
return false;
}
protected void StageOrder(float now)
{
ERR_HQReport report = new ERR_HQReport();
report.Target = GetGame().GetPlayerController().GetControlledEntity();
report.Source = m_Battery.Group;
report.Position = m_Battery.Gun.GetOrigin() + "700 0 0";
report.ObservedAt = now; report.DetailsObservedAt = now;
report.TravelMode = "on foot"; report.MessageId = 900001 + m_Battery.OrderId;
m_Battery.BeginRequest(report, now);
m_Battery.PendingSupplyReport = report.Snapshot();
PrintFormat("ERR MORTAR TRANSFER FIXTURE: synthetic supply order=%1; radio bypassed; scheduler held; no artillery activity authorized", m_Battery.OrderId);
}
protected bool NoFire()
{
return m_Battery && m_Battery.TotalShots == m_ShotsAtStart && !m_Battery.Activity && !m_Battery.RadioOrder && !m_Battery.Released;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
if (m_Battery) m_Battery.Cancel("native transfer fixture stopped");
if (m_System)
{
m_System.Stop(); // do not resume automatic fire while test items may be stored
if (m_Held) m_System.ERR_ReleaseTransferHold(this);
}
m_Held = false;
// Never delete a box containing borrowed shells or an outstanding operation.
// Failure preserves it for diagnosis/normal round-owned cleanup.
if (!m_MoveCallback)
{
foreach (IEntity box : m_Boxes)
{
if (!box) continue;
BaseInventoryStorageComponent storage = BaseInventoryStorageComponent.Cast(box.FindComponent(BaseInventoryStorageComponent));
array<IEntity> items = {}; if (storage) storage.GetAll(items);
if (storage && items.IsEmpty()) ERR_Actors.DeleteSpawned(box);
}
}
}
int Tick(float now)
{
if (m_Complete) return 1;
if (m_Stopped) return -1;
if (!Enabled() || now > m_Deadline) { Check(false, "enabled server fixture and bounded 180-second deadline"); return -1; }
if (now < m_Wait) return 0;
if (m_MoveCallback)
{
int moved = MoveResult(now);
if (moved < 0) { Check(false, "fixture inventory move completed with exact destination before deadline; borrowed shells retained"); return -1; }
if (moved == 0) return 0;
if (!Check(Conserved(), "all original 27 shell identities conserved after fixture move")) return -1;
}
if (m_Step == 0)
{
m_System = ERR_MortarSystem.Get();
if (!m_System) return 0;
m_Battery = m_System.ERR_TransferBattery();
if (!m_Battery || !m_Battery.StartupActivated) return 0;
if (!Check(m_Battery.Operational() && m_Battery.CountShells() == 27 && !m_Battery.Activity && !m_Battery.RadioOrder && !m_Battery.CommandOpen, "actual initialized idle battery has finite 27 shells")) return -1;
if (!Check(m_System.ERR_HoldForTransfer(this), "opt-in mechanism fixture holds automatic mortar scheduler")) return -1;
m_Held = true; m_AttemptsAtStart = m_Battery.ERR_TransferAttempts(); m_ShotsAtStart = m_Battery.TotalShots;
array<AIAgent> agents = {}; m_Battery.Group.GetAgents(agents);
foreach (AIAgent agent : agents)
{
if (!agent || !ERR_Actors.CanFight(agent.GetControlledEntity())) continue;
IEntity actor = agent.GetControlledEntity(); if (m_Crew.Contains(actor)) continue;
m_Crew.Insert(actor);
array<IEntity> items = {}; ERR_Inventory.Items(Inventory(actor), items);
foreach (IEntity item : items)
{
if (!IsShell(item)) continue;
ERR_NativeMortarShellOwner entry = new ERR_NativeMortarShellOwner(); entry.Shell = item; entry.OriginalOwner = actor; m_Original.Insert(entry);
}
if (!m_Gunner && actor != m_Battery.RadioOperator) m_Gunner = actor;
}
if (!Check(m_Crew.Count() == 4 && m_Original.Count() == 27 && m_Gunner != null, "four real crew and original shell-owner identities captured")) return -1;
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Battery.Gun.FindComponent(SCR_AIVehicleUsageComponent));
if (usage) m_Seat = usage.GetTurretCompartmentSlot();
if (m_Seat && m_Seat.GetOccupant())
{
IEntity occupant = m_Seat.GetOccupant();
if (!Check(m_Crew.Contains(occupant) && occupant != m_Battery.RadioOperator && ERR_Actors.CanFight(occupant), "existing native seat occupant is original fighting gunner")) return -1;
m_Gunner = occupant;
}
else
{
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_Gunner.FindComponent(CompartmentAccessComponent));
if (!Check(m_Seat && access && access.GetInVehicle(m_Battery.Gun, m_Seat, true, -1, ECloseDoorAfterActions.INVALID, false), "native seating request selects actual original crew gunner")) return -1;
}
if (!Check(m_Battery.InventoryShells(Inventory(m_Gunner)) == 9, "selected actual gunner owns its original nine physical shells")) return -1;
for (int i = 0; i < 2; i++)
{
IEntity box = ERR_Actors.Spawn(STORE, ERR_Actors.Ground(m_Battery.Gun.GetOrigin() + Vector(3 + i * 2, 0, 3)));
if (!Check(box != null, "fixture-owned empty vanilla storage created")) return -1;
m_Boxes.Insert(box); m_Mode.Own(box);
BaseInventoryStorageComponent storage = BaseInventoryStorageComponent.Cast(box.FindComponent(BaseInventoryStorageComponent));
if (!Check(storage != null, "real vanilla storage component available without attaching it to gunner inventory")) return -1;
m_Stores.Insert(storage);
}
m_Step = 1; m_StepDeadline = now + 8000; m_Wait = now + 500; return 0;
}
if (!NoFire()) { Check(false, "no artillery activity, committed load or physical projectile during inventory mechanism test"); return -1; }
if (m_Step == 1)
{
if (m_Seat.GetOccupant() != m_Gunner)
{
if (now >= m_StepDeadline) { Check(false, "actual original gunner occupies native mortar seat"); return -1; }
return 0;
}
if (!Check(Conserved(), "27 original shells have exactly one observed owner each")) return -1;
m_Step = 2;
}
if (m_Step == 2 || m_Step == 5)
{
IEntity shell = FirstGunnerShell();
if (shell)
{
if (!Check(Stash(shell, now), "one existing gunner shell move to vanilla store accepted; no shell minted")) return -1;
return 0;
}
if (!Check(m_Battery.InventoryShells(Inventory(m_Gunner)) == 0 && Conserved(), "actual gunner empty while all 27 original shells remain in crew or fixture storage")) return -1;
StageOrder(now);
ERR_MortarSupplyState started = m_Battery.SupplyState(now);
m_ObservedTransfer = m_Battery.ERR_TransferForProbe();
if (!Check(started == ERR_MortarSupplyState.PENDING && m_ObservedTransfer && m_ObservedTransfer.ERR_RequestAccepted() && m_ObservedTransfer.ERR_TransferRecipient() == m_Gunner && m_ObservedTransfer.CapturedOrder() == m_Battery.OrderId, "production exact-shell request accepted once with captured current order and real gunner")) return -1;
if (m_Step == 2)
{
m_FirstShell = m_ObservedTransfer.ERR_TransferredShell();
m_Step = 3;
}
else
{
m_SecondShell = m_ObservedTransfer.ERR_TransferredShell(); m_CallbackAtCancel = m_ObservedTransfer.ERR_CallbackState(); m_CancelOrder = m_Battery.OrderId;
m_Battery.Cancel("native fixture cancels authorization immediately after accepted inventory request");
if (!Check(m_SecondShell && m_SecondShell != m_FirstShell && !m_Battery.CommandOpen && m_Battery.SupplyState(now) == ERR_MortarSupplyState.FAILED, "canceled current order cannot report supply READY or issue another request")) return -1;
PrintFormat("ERR MORTAR TRANSFER CANCEL: callbackBeforeCancel=%1 order=%2; accepted native move is not retracted", m_CallbackAtCancel, m_CancelOrder);
m_Step = 6;
}
m_StepDeadline = now + 8000; m_Wait = now + 250; return 0;
}
if (m_Step == 3)
{
ERR_MortarSupplyState state = m_Battery.SupplyState(now);
if (state == ERR_MortarSupplyState.FAILED || now >= m_StepDeadline) { Check(false, "production transfer completes within its bounded deadline"); return -1; }
if (state == ERR_MortarSupplyState.PENDING) return 0;
if (!Check(m_ObservedTransfer.ERR_CallbackState() == 1 && Has(m_Gunner, m_FirstShell) && !Has(OriginalOwner(m_FirstShell), m_FirstShell) && Conserved() && m_Battery.ERR_TransferAttempts() == m_AttemptsAtStart + 1, "successful native callback and exact donor-to-gunner ownership, one attempt, original 27 conserved")) return -1;
if (!Check(m_Battery.SupplyState(now) == ERR_MortarSupplyState.READY && m_Battery.ERR_TransferAttempts() == m_AttemptsAtStart + 1, "repeated supply check reuses actual inventory without another move")) return -1;
m_Battery.Cancel("native success subcase finished; no artillery activity requested");
m_Step = 5; return 0;
}
if (m_Step == 6)
{
if (m_Battery.SupplyState(now) != ERR_MortarSupplyState.FAILED || m_Battery.OrderId != m_CancelOrder || m_Battery.ERR_TransferAttempts() != m_AttemptsAtStart + 2) { Check(false, "canceled order/generation remains failed with exactly two total move attempts"); return -1; }
if (m_ObservedTransfer.ERR_CallbackState() == 0 || m_ObservedTransfer.Unresolved())
{
if (now >= m_StepDeadline) { Check(false, "accepted canceled operation reached definite native outcome; no ambiguous retry"); return -1; }
return 0;
}
if (!Check(m_ObservedTransfer.ERR_CallbackState() == 1 && Has(m_Gunner, m_SecondShell) && Conserved(), "canceled accepted native move has definite physical outcome with exactly two attempts and original 27 conserved")) return -1;
if (m_CallbackAtCancel == 0) Print("ERR SMOKE PASS: mortar transfer actual native completion observed after authorization cancellation");
else Print("ERR MORTAR TRANSFER LIMIT: native callback completed synchronously before cancellation; late callback timing not exercised");
m_Step = 7; return 0;
}
if (m_Step == 7)
{
// Return foreign shells first, leaving room for each original nine-round kit.
IEntity misplaced;
foreach (ERR_NativeMortarShellOwner entry : m_Original)
if (Has(m_Gunner, entry.Shell) && entry.OriginalOwner != m_Gunner) { misplaced = entry.Shell; break; }
if (!misplaced)
foreach (ERR_NativeMortarShellOwner entry : m_Original) if (InStore(entry.Shell)) { misplaced = entry.Shell; break; }
if (misplaced)
{
InventoryStorageManagerComponent recipient = Inventory(OriginalOwner(misplaced));
BaseInventoryStorageComponent destination;
if (recipient) destination = recipient.FindStorageForItem(misplaced, EStoragePurpose.PURPOSE_DEPOSIT);
if (!Check(destination && BeginMove(misplaced, destination, now), "one borrowed shell return to its original crew owner accepted")) return -1;
return 0;
}
foreach (ERR_NativeMortarShellOwner entry : m_Original)
if (!Check(Has(entry.OriginalOwner, entry.Shell), "original shell identity returned to original crew inventory")) return -1;
if (!Check(Conserved() && m_Battery.CountShells() == 27 && NoFire(), "all original ammunition restored; no minted/deleted shell or shot")) return -1;
foreach (BaseInventoryStorageComponent storage : m_Stores)
{
array<IEntity> items = {}; storage.GetAll(items);
if (!Check(items.IsEmpty(), "borrowed-ammunition fixture storage empty before deletion")) return -1;
}
Stop(); m_Complete = true; return 1;
}
return 0;
}
}
#endif
@@ -0,0 +1,87 @@
#ifdef WORKBENCH
// Test access only: trigger the real delivered-order method on an existing
// registered squad. Radio transit is intentionally not simulated by this test.
modded class ERR_HQ
{
bool ERR_TestPatrolSearch(SCR_AIGroup group, out AIWaypoint search)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group) continue;
if (unit.Assignment || unit.PendingOrder || unit.Waypoint || !unit.Eligible(m_Mode)) return false;
ERR_HQReport order = new ERR_HQReport();
order.Position = group.GetOrigin() + "40 0 0";
order.ObservedAt = GetGame().GetWorld().GetWorldTime();
order.MessageId = m_Journal.Message();
unit.PendingOrder = order;
DeliverOrder(unit, order.ObservedAt);
search = unit.Waypoint;
return search && unit.Assignment == order;
}
return false;
}
bool ERR_TestPatrolRelease(SCR_AIGroup group, AIWaypoint search)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group || unit.Waypoint != search) continue;
unit.Release();
return !unit.Assignment && !unit.PendingOrder && !unit.Waypoint;
}
return false;
}
}
class ERR_NativePatrolHQProbe
{
protected ERR_GameMode m_Mode;
protected ERR_SiteRuntime m_Site;
protected AIWaypoint m_Search;
protected ref array<AIWaypoint> m_Baseline = {};
protected int m_Step;
protected float m_Wait;
void ERR_NativePatrolHQProbe(ERR_GameMode mode) { m_Mode = mode; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: patrol HQ " + detail); return true; }
Print("ERR SMOKE FAIL: patrol HQ " + detail, LogLevel.ERROR); return false;
}
int Tick(float now)
{
if (now < m_Wait) return 0;
ERR_HQ hq = ERR_HQ.Get();
if (!Check(hq != null, "real HQ available")) return -1;
if (m_Step == 0)
{
m_Site = m_Mode.Sites().Find("everon-outpost-1");
if (!Check(m_Site && m_Site.Group && m_Site.PatrolCycle, "existing outpost route available")) return -1;
m_Site.Group.GetWaypoints(m_Baseline);
if (!Check(!m_Baseline.IsEmpty() && hq.ERR_TestPatrolSearch(m_Site.Group, m_Search), "real delivered-order method assigns existing squad")) return -1;
m_Step = 1; m_Wait = now + 750; return 0;
}
if (m_Step == 1)
{
array<AIWaypoint> queue = {};
m_Site.Group.GetWaypoints(queue);
if (!Check(m_Search && !queue.IsEmpty() && queue[0] == m_Search && m_Site.Group.GetCurrentWaypoint() == m_Search, "Search preempts native patrol")) return -1;
foreach (AIWaypoint waypoint : m_Baseline)
if (!Check(queue.Contains(waypoint), "preempted baseline route remains queued")) return -1;
if (!Check(hq.ERR_TestPatrolRelease(m_Site.Group, m_Search), "real HQ release removes only its assignment")) return -1;
m_Step = 2; m_Wait = now + 750; return 0;
}
if (m_Step == 2)
{
AIWaypoint current = m_Site.Group.GetCurrentWaypoint();
if (!Check(current && (current == m_Site.PatrolCycle || m_Site.PatrolWaypoints.Contains(current)), "native patrol resumes after Search release")) return -1;
m_Step = 3; return 1;
}
return 1;
}
void Stop()
{
ERR_HQ hq = ERR_HQ.Get();
if (hq && m_Site && m_Search) hq.ERR_TestPatrolRelease(m_Site.Group, m_Search);
}
void ~ERR_NativePatrolHQProbe() { Stop(); }
}
#endif
@@ -0,0 +1,63 @@
// Vanilla named settlements supply exclusion areas; no original mission location system.
// Shared town/village exclusion for mortar impacts, roadblocks and mine sites.
// Fixed distance from mapped settlement centres, independent of mission
// activation radii. Scattered buildings and props are not settlements.
class ERR_SettlementSafety
{
static const float RADIUS = 200;
string SettlementReason;
protected vector m_SafeImpact;
protected bool m_SettlementNear;
protected vector m_LastSettlementImpact;
protected float m_LastSettlementCheck = -1;
protected bool m_LastSettlementClear;
bool OutsideSettlements(vector impact)
{
float now = GetGame().GetWorld().GetWorldTime();
if (m_LastSettlementCheck >= 0 && now >= m_LastSettlementCheck && now - m_LastSettlementCheck < 1000 && vector.DistanceXZ(impact, m_LastSettlementImpact) == 0)
return m_LastSettlementClear;
m_LastSettlementCheck = now;
m_LastSettlementImpact = impact;
m_LastSettlementClear = false;
m_SafeImpact = impact;
m_SettlementNear = false;
SettlementReason = "clear";
// Also cover named native map settlements, independent of whether the
// mission placed a garrison marker there. Generic building entities also
// include deer stands, bridge pillars, rubble and electrical cabinets.
// Broad vertical bounds keep the 200m distance horizontal on hills.
GetGame().GetWorld().QueryEntitiesByAABB(impact - Vector(RADIUS, 5000, RADIUS), impact + Vector(RADIUS, 5000, RADIUS), CheckSettlement, FilterSettlement, EQueryEntitiesFlags.ALL);
m_LastSettlementClear = !m_SettlementNear;
return m_LastSettlementClear;
}
protected bool FilterSettlement(IEntity entity)
{
return entity && entity.FindComponent(MapDescriptorComponent);
}
protected bool CheckSettlement(IEntity entity)
{
float radius;
MapDescriptorComponent descriptor = MapDescriptorComponent.Cast(entity.FindComponent(MapDescriptorComponent));
if (descriptor)
{
int type = descriptor.GetBaseType();
if (type == EMapDescriptorType.MDT_NAME_CITY)
radius = RADIUS;
else if (type == EMapDescriptorType.MDT_NAME_TOWN)
radius = RADIUS;
else if (type == EMapDescriptorType.MDT_NAME_VILLAGE || type == EMapDescriptorType.MDT_NAME_SETTLEMENT)
radius = RADIUS;
}
if (radius > 0 && vector.DistanceXZ(m_SafeImpact, entity.GetOrigin()) <= radius)
{
m_SettlementNear = true;
SettlementReason = string.Format("named map settlement entity=%1 position=%2 distance=%3m exclusion=%4m", entity, entity.GetOrigin(), vector.DistanceXZ(m_SafeImpact, entity.GetOrigin()), radius);
return false;
}
return true;
}
}
@@ -0,0 +1,9 @@
modded class ERR_ScenarioComponent
{
protected override bool PrepareAdditionalForces()
{
if (!super.PrepareAdditionalForces()) return false;
ERR_HQ hq = ERR_HQ.Get();
return hq && hq.PrepareSupport();
}
}
@@ -0,0 +1,9 @@
GameProject {
ID "ERR_Ai"
GUID "5192138CB9D5B911"
TITLE "Escapists Rereintroduced - Ai"
Dependencies {
"58D0FB3206B6F859"
"2528466DE220D91C"
}
}
@@ -0,0 +1,11 @@
SCR_MissionHeader {
World "{B6C516F86302C679}Worlds/ERR_Everon.ent"
SystemsConfig "{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf"
m_sName "Rereintroduced: Everon (development)"
m_sAuthor "Rereintroduced contributors"
m_sDescription "Independent escape prototype. Development scenario; runtime validation in progress."
m_sGameMode "Escape"
m_iPlayerCount 12
m_eSaveTypes 0
m_bShowInScenarioMenu 1
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{C1E20667DCE324ED}Missions/ERR_Everon.conf"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,36 @@
// Companion-specific commands extend Core's authenticated transport.
modded class ERR_AdminService
{
protected override string Setting(string section, string option)
{
if (section == "hq")
{
if (option == "interval") return "reportIntervalSeconds";
if (option == "squads") return "supportSquadLimit";
if (option == "radius") return "responseRadiusMetres";
}
if (section == "intel")
{
if (option == "carrying") return "aiIntelChancePercent";
}
return super.Setting(section, option);
}
protected override bool Extension(ERR_GameMode mode, string arguments, array<string> replies)
{
if (arguments != "status" && arguments != "hq show" && arguments != "hq log") return super.Extension(mode, arguments, replies);
ERR_HQ hq = ERR_HQ.Cast(mode.Command());
if (!hq) { replies.Insert("HQ unavailable"); return true; }
if (arguments == "hq show" || arguments == "status") replies.Insert(hq.Status());
else
{
int cursor;
hq.Journal().Read(Math.Max(0, hq.Journal().Sequence() - 8), replies, cursor);
if (replies.IsEmpty()) replies.Insert("HQ journal is empty.");
}
return true;
}
protected override string Help()
{
return super.Help() + " | hq show|log | hq interval|squads|radius show|set N | intel carrying show|set N";
}
}
@@ -0,0 +1,288 @@
#ifdef WORKBENCH
// Read-only evidence hooks. Every production method delegates unchanged.
modded class ERR_HQ
{
void ERR_ResponseUnits(out array<ERR_HQUnit> units)
{
units.Clear();
if (!ERR_NativeHQResponseProof.Enabled()) return;
foreach (ERR_HQUnit unit : m_Units) units.Insert(unit);
}
protected override void Observe(ERR_HQUnit unit, float now)
{
ERR_HQReport previous = unit.PendingReport;
super.Observe(unit, now);
ERR_NativeHQResponseProof proof = ERR_NativeHQResponseProof.Active();
if (proof && unit.PendingReport && unit.PendingReport != previous) proof.Observed(this, unit, now);
}
protected override void Accept(ERR_HQReport report, float now)
{
super.Accept(report, now);
ERR_NativeHQResponseProof proof = ERR_NativeHQResponseProof.Active();
if (!proof || !report) return;
foreach (ERR_HQReport accepted : m_Contacts)
if (accepted.MessageId == report.MessageId) { proof.Accepted(this, accepted, now); break; }
}
protected override void DeliverOrder(ERR_HQUnit unit, float now)
{
ERR_HQReport order = unit.PendingOrder;
ERR_HQRoute route = unit.Downlink;
super.DeliverOrder(unit, now);
ERR_NativeHQResponseProof proof = ERR_NativeHQResponseProof.Active();
if (proof && order && route && unit.Assignment == order && unit.Waypoint) proof.Delivered(this, unit, route, now);
}
}
modded class ERR_HQJournal
{
override void Write(string kind, string message, int messageId = 0)
{
super.Write(kind, message, messageId);
ERR_NativeHQResponseProof proof = ERR_NativeHQResponseProof.Active();
if (proof) proof.JournalEvent(this, kind, message, messageId);
}
}
modded class SCR_AIGroupUtilityComponent
{
IEntity ERR_ResponseVisualObserver(ERR_HQReport report, float now)
{
foreach (SCR_AIInfoComponent info : m_aInfoComponents)
{
if (!info || !info.m_Perception) continue;
AIAgent agent = AIAgent.Cast(info.GetOwner());
if (!agent || !ERR_Actors.CanFight(agent.GetControlledEntity())) continue;
array<BaseTarget> targets = {}; info.m_Perception.GetTargetsList(targets, ETargetCategory.ENEMY);
foreach (BaseTarget target : targets)
{
ERR_HQVisualContact sample = ERR_HQObservation.Capture(target, now);
if (sample && sample.Entity == report.Target && sample.Escapist && Math.AbsFloat(sample.ObservedAt - report.ObservedAt) < 1 && vector.Distance(sample.Position, report.Position) < 0.1) return agent.GetControlledEntity();
}
}
return null;
}
}
class ERR_NativeResponseUnit
{
ERR_HQUnit Unit;
SCR_AIGroup Group;
IEntity Radio;
ref array<IEntity> Actors = {};
}
class ERR_NativeResponseJournal
{
bool Report;
bool Transmit;
bool HQReceive;
bool HQTransmit;
bool UnitReceive;
bool Delivered;
}
class ERR_NativeResponseLineage
{
ref ERR_HQReport Report;
ERR_HQUnit Source;
IEntity Observer;
IEntity SourceRadio;
ref ERR_HQRoute Uplink;
bool Accepted;
float AcceptedAt;
ERR_HQUnit Recipient;
IEntity RecipientRadio;
int DeliveryId;
AIWaypoint Waypoint;
IEntity MovingActor;
vector DeliveredOrigin;
float DeliveredAt;
}
class ERR_NativeHQResponseProof
{
protected static ERR_NativeHQResponseProof s_Active;
protected ERR_GameMode m_Mode;
protected ERR_HQ m_HQ;
protected IEntity m_Player;
protected SCR_PlayerController m_Controller;
protected ERR_PlayerLife m_Life;
protected ERR_HQUnit m_Source;
protected DamageManagerComponent m_Damage;
protected bool m_DamageWasEnabled;
protected bool m_Protected;
protected bool m_Started;
protected bool m_Stopped;
protected bool m_Failed;
protected string m_Failure;
protected float m_Deadline;
protected float m_NextDiagnostic;
protected ref array<ref ERR_NativeResponseUnit> m_Units = {};
protected ref array<IEntity> m_Forces = {};
protected ref array<ref ERR_NativeResponseLineage> m_Reports = {};
protected ref map<int, ref ERR_NativeResponseJournal> m_Journal = new map<int, ref ERR_NativeResponseJournal>();
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "hq-response";
}
static ERR_NativeHQResponseProof Active() { return s_Active; }
void ERR_NativeHQResponseProof(ERR_GameMode mode, IEntity player)
{
m_Mode = mode; m_Player = player; m_HQ = ERR_HQ.Get(); m_Deadline = GetGame().GetWorld().GetWorldTime() + 140000;
if (Enabled() && !s_Active) s_Active = this;
}
protected bool Fail(string reason) { m_Failed = true; m_Failure = reason; return false; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: HQ response " + detail); return true; }
Print("ERR SMOKE FAIL: HQ response " + detail, LogLevel.ERROR); Stop(); return false;
}
protected ERR_NativeResponseUnit Original(ERR_HQUnit unit)
{
foreach (ERR_NativeResponseUnit original : m_Units) if (original.Unit == unit && original.Group == unit.Group) return original;
return null;
}
protected ERR_NativeResponseLineage Report(int id)
{
foreach (ERR_NativeResponseLineage report : m_Reports) if (report.Report.MessageId == id) return report;
return null;
}
protected bool Canonical()
{
return m_Player && m_Controller && m_Life && m_Mode.Players().CurrentActor(m_Player) == m_Life && m_Controller == GetGame().GetPlayerController() && m_Controller.GetControlledEntity() == m_Player && m_Life.Actor == m_Player && m_Life.Connected && m_Life.UsedLife;
}
protected bool UnchangedForces()
{
array<ERR_HQUnit> current = {}; m_HQ.ERR_ResponseUnits(current);
if (current.Count() != m_Units.Count()) return false;
foreach (ERR_HQUnit unit : current)
{
ERR_NativeResponseUnit original = Original(unit);
if (!original) return false;
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
if (agents.Count() != original.Actors.Count()) return false;
foreach (AIAgent agent : agents) if (!agent || !original.Actors.Contains(agent.GetControlledEntity())) return false;
}
array<IEntity> forces = {}; m_HQ.PhysicalForces(forces);
if (forces.Count() != m_Forces.Count()) return false;
foreach (IEntity entity : forces) if (!m_Forces.Contains(entity)) return false;
return true;
}
protected bool Arrange()
{
array<ERR_HQUnit> units = {}; m_HQ.ERR_ResponseUnits(units);
foreach (ERR_HQUnit unit : units)
{
if (!unit.Group || !unit.Utility || !unit.Radio || ERR_HQRadio.Capability(unit.Radio) != 2) return false;
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
if (agents.IsEmpty() || !unit.Group.IsExpandComplete()) return false;
ERR_NativeResponseUnit original = new ERR_NativeResponseUnit(); original.Unit = unit; original.Group = unit.Group; original.Radio = unit.Radio;
foreach (AIAgent agent : agents)
{
if (!agent || !ERR_Actors.CanFight(agent.GetControlledEntity())) return false;
original.Actors.Insert(agent.GetControlledEntity());
}
m_Units.Insert(original);
if (!m_Source && !unit.ReservedForSupport) m_Source = unit;
}
if (!m_Source || m_Units.Count() < 2) return false;
m_HQ.PhysicalForces(m_Forces);
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController()); m_Life = m_Mode.Players().CurrentActor(m_Player);
if (!Canonical()) return false;
m_Damage = DamageManagerComponent.Cast(m_Player.FindComponent(DamageManagerComponent));
if (!m_Damage) return false;
m_DamageWasEnabled = m_Damage.IsDamageHandlingEnabled(); m_Damage.EnableDamageHandling(false); m_Protected = true;
// Preserve the established fixture arrangement: move only the real prisoner.
// Native perception must discover him; neither targets nor reports are seeded.
vector position = ERR_Actors.Ground(m_Source.Group.GetOrigin() + "0 0 20");
vector transform[4]; m_Player.GetWorldTransform(transform); transform[3] = position;
ChimeraCharacter.Cast(m_Player).Teleport(transform);
m_Started = true;
PrintFormat("ERR HQ RESPONSE ARRANGEMENT: canonical prisoner moved to=%1 near original source group=%2; temporary player damage protection=%3; no AI/report/radio cadence changes", position, m_Source.Id, m_Protected);
return true;
}
void JournalEvent(ERR_HQJournal journal, string kind, string message, int messageId)
{
if (!m_Started || m_Stopped || !m_HQ || journal != m_HQ.Journal() || messageId <= 0) return;
ERR_NativeResponseJournal entry = m_Journal.Get(messageId);
if (!entry)
{
if (m_Journal.Count() >= 128) { Fail("bounded journal evidence map exhausted"); return; }
entry = new ERR_NativeResponseJournal(); m_Journal.Insert(messageId, entry);
}
if (kind == "REPORT") entry.Report = true;
if (kind == "TRANSMIT") { entry.Transmit = true; if (message.StartsWith("HQ -> ")) entry.HQTransmit = true; }
if (kind == "RECEIVE") { if (message.StartsWith("HQ received from ")) entry.HQReceive = true; else if (message.StartsWith("unit ")) entry.UnitReceive = true; }
if (kind == "ORDER DELIVERED") entry.Delivered = true;
}
void Observed(ERR_HQ hq, ERR_HQUnit unit, float now)
{
if (!m_Started || m_Stopped || hq != m_HQ || unit != m_Source || unit.PendingReport.Target != m_Player) return;
ERR_HQReport report = unit.PendingReport;
ERR_NativeResponseUnit original = Original(unit);
IEntity observer = unit.Utility.ERR_ResponseVisualObserver(report, now);
if (!Canonical() || !original || !original.Actors.Contains(observer) || !unit.Uplink || unit.Uplink.Nodes.IsEmpty() || unit.Uplink.Nodes[0].Carrier != unit.Radio || unit.Uplink.MessageId != report.MessageId || unit.Radio != original.Radio || !original.Actors.Contains(unit.Radio) || report.Source != original.Group || report.ObservedAt > now || Report(report.MessageId)) { Fail("real observed report lacks canonical original observer/radio/source lineage"); return; }
if (m_Reports.Count() >= 32) { Fail("bounded report evidence exhausted"); return; }
ERR_NativeResponseLineage proof = new ERR_NativeResponseLineage(); proof.Report = report.Snapshot(); proof.Source = unit; proof.Observer = observer; proof.SourceRadio = unit.Radio; proof.Uplink = unit.Uplink; m_Reports.Insert(proof);
PrintFormat("ERR HQ RESPONSE SIGHTING: source=%1 report=M%2 observer=%3 radio=%4 target=%5 observedAt=%6; actual native visual sample", unit.Id, report.MessageId, observer, unit.Radio, m_Player, report.ObservedAt);
}
void Accepted(ERR_HQ hq, ERR_HQReport report, float now)
{
if (!m_Started || m_Stopped || hq != m_HQ) return;
ERR_NativeResponseLineage proof = Report(report.MessageId); if (!proof) return;
if (report.Target != m_Player || report.Source != proof.Report.Source || report.ObservedAt != proof.Report.ObservedAt || vector.Distance(report.Position, proof.Report.Position) > 0.01 || !proof.Uplink || proof.Uplink.Failed || proof.Uplink.Sending || proof.Uplink.Cursor < proof.Uplink.Nodes.Count()) { Fail("accepted report lacks completed actual uplink or immutable observed time"); return; }
proof.Accepted = true; proof.AcceptedAt = now;
}
void Delivered(ERR_HQ hq, ERR_HQUnit unit, ERR_HQRoute route, float now)
{
if (!m_Started || m_Stopped || hq != m_HQ || !unit.Assignment) return;
ERR_NativeResponseLineage proof = Report(unit.Assignment.MessageId); if (!proof || proof.Recipient) return;
ERR_NativeResponseUnit original = Original(unit);
if (!proof.Accepted || !original || unit == proof.Source || !unit.Assignment || !unit.Waypoint) { Fail("delivered response lacks accepted original recipient/order"); return; }
if (unit.Assignment.Target != m_Player || unit.Assignment.Source != proof.Report.Source || unit.Assignment.ObservedAt != proof.Report.ObservedAt || vector.Distance(unit.Assignment.Position, proof.Report.Position) > 0.01) { Fail("delivered response changed immutable report"); return; }
if (unit.Radio != original.Radio || !original.Actors.Contains(unit.Radio) || !route.Downlink || route.Nodes.IsEmpty()) { Fail("delivered response lacks original radio/downlink"); return; }
ERR_HQRadioNode recipientNode = route.Nodes[route.Nodes.Count() - 1];
if (recipientNode.Carrier != unit.Radio || route.Failed || route.Sending || route.Cursor != route.Nodes.Count() - 1) { Fail("delivered response lacks completed actual recipient downlink"); return; }
proof.Recipient = unit; proof.RecipientRadio = unit.Radio; proof.DeliveryId = route.MessageId; proof.Waypoint = unit.Waypoint; proof.DeliveredAt = now;
foreach (IEntity actor : original.Actors) if (ERR_Actors.CanFight(actor)) { proof.MovingActor = actor; break; }
if (proof.MovingActor) proof.DeliveredOrigin = proof.MovingActor.GetOrigin();
PrintFormat("ERR HQ RESPONSE DELIVERED: report=M%1 downlink=M%2 source=%3 recipient=%4 radio=%5 waypoint=%6 observedAt=%7", proof.Report.MessageId, route.MessageId, proof.Source.Id, unit.Id, unit.Radio, unit.Waypoint, unit.Assignment.ObservedAt);
}
int Tick(float now)
{
if (m_Stopped) return -1;
if (!Enabled() || s_Active != this || !m_Mode || !m_Mode.Active() || !m_HQ || now > m_Deadline || m_Failed) { Check(false, "bounded active evidence context: " + m_Failure); return -1; }
if (!m_Started)
{
if (!m_HQ.ERR_ProbeSquadsReady()) return 0;
array<IEntity> guns = {}; m_HQ.Mortars().AppendGuns(guns);
foreach (IEntity gun : guns) { ERR_MortarBattery battery = m_HQ.Mortars().FindGun(gun); if (!battery || !battery.StartupActivated || !battery.Operational() || !m_HQ.MortarConnected(battery.Group)) return 0; }
if (!Check(Arrange(), "original physical force, RTOs and canonical prisoner captured before arranged sighting")) return -1;
return 0;
}
if (!Canonical() || !UnchangedForces()) { Check(false, "canonical player and all original physical group/actor identities retained without added force"); return -1; }
if (now >= m_NextDiagnostic)
{
PrintFormat("ERR HQ RESPONSE PROOF: observed source reports=%1; %2", m_Reports.Count(), m_HQ.ERR_ProbePerception()); m_NextDiagnostic = now + 10000;
}
foreach (ERR_NativeResponseLineage proof : m_Reports)
{
if (!proof.Recipient || !proof.MovingActor) continue;
if (proof.Recipient.Waypoint != proof.Waypoint || !proof.Recipient.Assignment || proof.Recipient.Assignment.MessageId != proof.Report.MessageId) continue;
ERR_NativeResponseJournal uplink = m_Journal.Get(proof.Report.MessageId);
ERR_NativeResponseJournal downlink = m_Journal.Get(proof.DeliveryId);
if (!uplink || !downlink || !uplink.Report || !uplink.Transmit || !uplink.HQReceive || !downlink.HQTransmit || !downlink.UnitReceive || !downlink.Delivered) continue;
if (proof.Recipient.Group.GetCurrentWaypoint() != proof.Waypoint || vector.DistanceXZ(proof.MovingActor.GetOrigin(), proof.DeliveredOrigin) < 3) continue;
if (vector.DistanceXZ(proof.MovingActor.GetOrigin(), proof.Report.Position) >= vector.DistanceXZ(proof.DeliveredOrigin, proof.Report.Position) - 1) continue;
if (!Check(proof.SourceRadio && proof.RecipientRadio && m_HQ.ERR_ProbeUniqueAssignments(), "actual sighting/report/uplink/HQ receipt/downlink journal lineage reaches distinct original infantry with bounded unique waypoints")) return -1;
if (!Check(UnchangedForces(), "original recipient physically moves toward its native Search waypoint with no replacement or additional force")) return -1;
Stop(); return 1;
}
return 0;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
if (m_Protected && m_Damage) m_Damage.EnableDamageHandling(m_DamageWasEnabled);
m_Protected = false;
if (s_Active == this) s_Active = null;
}
void ~ERR_NativeHQResponseProof() { Stop(); }
}
#endif
@@ -0,0 +1,10 @@
#ifdef WORKBENCH
modded class ERR_MortarSystem
{
ERR_MortarBattery ERR_ProbeBattery()
{
if (m_Batteries.Count() != 1) return null;
return m_Batteries[0];
}
}
#endif
@@ -0,0 +1,114 @@
// Opt-in engine fixture: physical radios and production delivery routes only.
#ifdef WORKBENCH
modded class ERR_HQ
{
vector ERR_ProbeResponseOrigin()
{
foreach (ERR_HQUnit unit : m_Units)
if (!unit.ReservedForSupport && unit.Group) return unit.Group.GetOrigin() + "0 0 20";
return vector.Zero;
}
bool ERR_ProbeDeliveredResponse()
{
foreach (ERR_HQUnit unit : m_Units)
if (unit.Assignment && unit.Waypoint && unit.Group != unit.Assignment.Source) return true;
return false;
}
bool ERR_ProbeUniqueAssignments()
{
array<AIWaypoint> waypoints = {};
foreach (ERR_HQUnit unit : m_Units)
{
if (!unit.Waypoint) continue;
if (!unit.Assignment || waypoints.Contains(unit.Waypoint)) return false;
waypoints.Insert(unit.Waypoint);
}
return waypoints.Count() > 0 && Assigned() <= m_Mode.Settings().Get("supportSquadLimit");
}
string ERR_ProbePerception()
{
int targets, sightings, radios;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Radio) radios++;
if (!unit.Utility) continue;
array<ref ERR_HQVisualContact> samples = {};
ERR_HQObservation.Gather(unit.Utility, GetGame().GetWorld().GetWorldTime(), samples);
sightings += samples.Count();
if (unit.Utility.m_Perception) targets += unit.Utility.m_Perception.m_aTargets.Count();
}
return string.Format("%1; radios=%2 targets=%3 fresh visual samples=%4", Status(), radios, targets, sightings);
}
ERR_HQRoute ERR_ProbeRoute(IEntity carrier = null, bool downlink = false)
{
if (carrier) return m_Network.Route(carrier, downlink);
foreach (ERR_HQUnit unit : m_Units)
if (unit.Radio) return m_Network.Route(unit.Radio, downlink);
return null;
}
bool ERR_ProbeSquadsReady()
{
int infantry;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.ReservedForSupport) continue;
infantry++;
if (!unit.Group || ERR_HQRadio.Capability(unit.Radio) != 2) return false;
array<AIAgent> agents = {};
unit.Group.GetAgents(agents);
if (agents.Count() != 6) return false;
}
return infantry == 6;
}
}
class ERR_NativeRadioProbe
{
protected ref ERR_HQRoute m_Route;
protected IEntity m_Carrier;
protected int m_Step;
int Tick(ERR_HQ hq, float now)
{
if (!hq) return -1;
if (m_Step == 0)
{
if (!hq.ERR_ProbeSquadsReady()) return 0;
Print("ERR SMOKE PASS: six physical six-person squads each have a manpack operator");
m_Route = hq.ERR_ProbeRoute();
if (!m_Route || m_Route.Nodes.IsEmpty()) return -1;
m_Carrier = m_Route.Nodes[0].Carrier;
m_Route.MessageId = hq.Journal().Message();
m_Step = 1;
}
int result = m_Route.Advance(now, hq.Journal());
if (result == 0) return 0;
if (m_Step == 3)
{
if (result >= 0) return -1;
Print("ERR SMOKE PASS: removing receiving manpack interrupts an in-flight HQ order");
return 1;
}
if (result < 0) return -1;
if (m_Step == 1) Print("ERR SMOKE PASS: physical manpack uplink delivers to HQ");
if (m_Step == 2) Print("ERR SMOKE PASS: HQ downlink delivers to a physical receiver");
m_Route = hq.ERR_ProbeRoute(m_Carrier, true);
if (!m_Route || m_Route.Nodes.IsEmpty()) return -1;
m_Route.MessageId = hq.Journal().Message();
m_Step++;
if (m_Step == 3)
{
// Start HQ transmission, then remove the actual receiving equipment.
if (m_Route.Advance(now, hq.Journal()) != 0) return -1;
IEntity receiver = m_Route.Nodes[0].Carrier;
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(receiver.FindComponent(InventoryStorageManagerComponent));
if (!inventory) return -1;
array<IEntity> items = {};
inventory.GetItems(items);
foreach (IEntity item : items)
if (item && item.FindComponent(BaseRadioComponent)) SCR_EntityHelper.DeleteEntityAndChildren(item);
}
return 0;
}
}
#endif
@@ -0,0 +1,579 @@
// Explicit, local Workbench diagnostic only. Never compiled into game/server builds.
#ifdef WORKBENCH
modded class ERR_GameMode
{
protected bool m_ERRTestPatrolHQ;
protected ref ERR_NativePatrolProbe m_ERRHQPatrolMovement;
protected ref ERR_NativePatrolHQProbe m_ERRHQPatrolOrder;
protected int m_ERRSmokeStep;
protected float m_ERRSmokeDeadline;
protected float m_ERRSmokeWait;
protected IEntity m_ERRSmokePlayer;
protected BaseDoorComponent m_ERRGate;
protected ERR_LocationComponent m_ERRSmokeRadio;
protected bool m_ERRSmokeDone;
protected bool m_ERRTestLostHelicopter;
protected bool m_ERRTestPlayerDeath;
protected bool m_ERRTestRadioNetwork;
protected bool m_ERRTestHQResponse;
protected bool m_ERRTestMortar;
protected bool m_ERRTestMortarFire;
protected bool m_ERRTestMapOwnership;
protected bool m_ERRTestAircraftControl;
protected bool m_ERRTestAircraftCancel;
protected bool m_ERRTestAircraftTranslate;
protected ref ERR_NativeAircraftControlProbe m_ERRAircraftControl;
protected ref ERRM_NativeMapOwnershipProbe m_ERRMapOwnership;
protected bool m_ERRTestMortarSalvo;
protected ref ERR_NativeMortarSalvoProbe m_ERRMortarSalvo;
protected bool m_ERRTestMortarCourier;
protected ref ERR_NativeMortarCourierProbe m_ERRMortarCourier;
protected bool m_ERRTestMortarTransfer;
protected ref ERR_NativeMortarTransferProbe m_ERRMortarTransfer;
protected ref ERR_NativeMortarFiringProbe m_ERRMortarFire;
protected float m_ERRNextProbeLog;
protected ref ERR_NativeRadioProbe m_ERRRadioProbe;
protected ref ERR_NativeHQResponseProof m_ERRHQResponseProof;
protected ref array<IEntity> m_ERRCacheFolders = {};
protected override void EOnInit(IEntity owner)
{
super.EOnInit(owner);
string enabled;
if (SCR_Global.IsEditMode() || !Replication.IsServer() || !System.GetCLIParam("errSmoke", enabled) || enabled != "1") return;
string outcome;
m_ERRTestLostHelicopter = System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "lost-helicopter";
m_ERRTestRadioNetwork = outcome == "radio-network";
m_ERRTestHQResponse = outcome == "hq-response";
m_ERRTestMortar = outcome == "mortar-inventory";
m_ERRTestMapOwnership = outcome == "map-ownership";
m_ERRTestAircraftCancel = outcome == "aircraft-cancel";
m_ERRTestAircraftTranslate = outcome == "aircraft-translate";
m_ERRTestAircraftControl = outcome == "aircraft-control" || m_ERRTestAircraftCancel || m_ERRTestAircraftTranslate;
m_ERRTestMortarFire = outcome == "mortar-fire" || outcome == "mortar-expiry";
m_ERRTestMortarCourier = outcome == "mortar-courier";
m_ERRTestMortarSalvo = outcome == "mortar-salvo" || outcome == "mortar-refresh";
m_ERRTestMortarTransfer = outcome == "mortar-transfer";
m_ERRTestPatrolHQ = outcome == "patrol-hq";
m_ERRTestPlayerDeath = outcome == "player-death" || m_ERRTestRadioNetwork || m_ERRTestHQResponse || m_ERRTestMortar;
m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 150000;
if (m_ERRTestAircraftControl) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
if (m_ERRTestMortarCourier) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 450000;
if (m_ERRTestMortarTransfer) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
if (m_ERRTestMortarSalvo) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
if (m_ERRTestMortarFire) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
if (m_ERRTestPatrolHQ) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 420000;
OnStop().Insert(ERR_StopHQPatrol);
OnPhaseChanged().Insert(ERR_SmokePhase);
GetGame().GetCallqueue().CallLater(ERR_SmokeTick, 500, true);
Print("ERR SMOKE: scripted integration test enabled; gameplay acceptance remains separate");
}
protected bool ERR_Check(bool condition, string name)
{
if (condition) { Print("ERR SMOKE PASS: " + name); return true; }
ERR_StopHQPatrol();
Print("ERR SMOKE FAIL: " + name, LogLevel.ERROR);
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
return false;
}
protected void ERR_StopHQPatrol()
{
if (m_ERRMapOwnership) m_ERRMapOwnership.Stop();
if (m_ERRAircraftControl) m_ERRAircraftControl.Stop();
if (m_ERRHQResponseProof) m_ERRHQResponseProof.Stop();
if (m_ERRMortarSalvo) m_ERRMortarSalvo.Stop();
if (m_ERRMortarCourier) m_ERRMortarCourier.Stop();
if (m_ERRMortarTransfer) m_ERRMortarTransfer.Stop();
if (m_ERRMortarFire) m_ERRMortarFire.Stop();
if (m_ERRHQPatrolMovement) m_ERRHQPatrolMovement.Stop();
if (m_ERRHQPatrolOrder) m_ERRHQPatrolOrder.Stop();
}
protected void ERR_MovePlayer(vector position)
{
vector transform[4];
m_ERRSmokePlayer.GetWorldTransform(transform);
transform[3] = ERR_Actors.Ground(position);
ChimeraCharacter.Cast(m_ERRSmokePlayer).Teleport(transform);
}
protected ERR_UnlockPrisonAction ERR_FindLock(IEntity entity)
{
BaseActionsManagerComponent manager = BaseActionsManagerComponent.Cast(entity.FindComponent(BaseActionsManagerComponent));
if (manager)
{
array<BaseUserAction> actions = {};
manager.GetActionsList(actions);
foreach (BaseUserAction action : actions)
{
ERR_UnlockPrisonAction lock = ERR_UnlockPrisonAction.Cast(action);
if (lock) return lock;
}
}
IEntity child = entity.GetChildren();
while (child)
{
ERR_UnlockPrisonAction lock = ERR_FindLock(child);
if (lock) return lock;
child = child.GetSibling();
}
return null;
}
protected void ERR_SmokePhase(ERR_Phase previous, ERR_Phase current)
{
if (m_ERRSmokeDone || !ERR_MissionRules.Terminal(current)) return;
if (m_ERRTestAircraftControl)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 221, "actual aircraft controller completed before explicit fixture reset")) return;
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
ERR_StopHQPatrol();
if (m_ERRTestAircraftCancel)
Print("ERR SMOKE COMPLETE: actual airborne aircraft cancellation retains inputs through settled contact landing and engine-off; isolated mechanism, not HQ dispatch");
else if (m_ERRTestAircraftTranslate)
Print("ERR SMOKE COMPLETE: actual aircraft controller immutable target translation, observed target hold and contact landing; bounded mechanism, not route support or HQ dispatch");
else Print("ERR SMOKE COMPLETE: actual aircraft controller and post-spawn qualification, powered hover and contact landing; isolated physical mechanism, not HQ dispatch");
return;
}
if (m_ERRTestMapOwnership)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 201, "native map ownership probe cleaned up before explicit reset")) return;
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
ERR_StopHQPatrol();
Print("ERR SMOKE COMPLETE: native replacement map/config/root ownership and spare deletion; camera authorization observed, render flag not measured");
return;
}
if (m_ERRTestMortarSalvo)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 191, "native configured salvo and unchanged-report replay observation complete before reset")) return;
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
ERR_StopHQPatrol();
Print("ERR SMOKE COMPLETE: native full configured mortar salvo, exact original ammunition and unchanged-report replay refusal; fixture reset requested");
return;
}
if (m_ERRTestMortarCourier)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 181, "native finite courier reached an exhausted battery before explicit reset")) return;
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
ERR_StopHQPatrol();
Print("ERR SMOKE COMPLETE: native finite courier, real supply uplink/downlink, original patrol walking and exact physical shell delivery; original 27 shells sequestered in fixture boxes; reset requested");
return;
}
if (m_ERRTestMortarTransfer)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 161, "native exact shell transfers and original-owner restoration complete before explicit reset")) return;
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
ERR_StopHQPatrol();
Print("ERR SMOKE COMPLETE: native exact mortar inventory transfer, accepted-operation cancellation and original-owner restoration; radio bypassed; fixture reset requested");
return;
}
if (m_ERRTestMortarFire)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 151, "native mortar first shot and stop complete before explicit reset")) return;
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
ERR_StopHQPatrol();
Print("ERR SMOKE COMPLETE: native HQ radio mortar projectile and configured cancellation/expiry; fixture reset requested");
return;
}
if (m_ERRTestPatrolHQ)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 133, "HQ patrol preemption and physical resumption before explicit reset")) return;
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
Print("ERR SMOKE COMPLETE: native HQ patrol preemption and physical resumption");
return;
}
if (m_ERRTestPlayerDeath)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 91, "death exhausts round life and reaches defeat after spectator grace")) return;
}
else if (m_ERRTestLostHelicopter)
{
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 8, "lost extraction helicopter causes defeat")) return;
}
else if (!ERR_Check(current == ERR_Phase.VICTORY && m_ERRSmokeStep == 7, "scripted escape reaches victory after physical vehicle occupancy")) return;
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
PrintFormat("ERR SMOKE COMPLETE: terminal=%1 lostHelicopterCase=%2 playerDeathCase=%3", current, m_ERRTestLostHelicopter, m_ERRTestPlayerDeath);
GetGame().GetCallqueue().CallLater(ERR_EndScreenshot, 4000);
}
protected bool ERR_CheckRoster()
{
ERR_PlayerRoster roster = new ERR_PlayerRoster();
UUID first = "00e77100-0000-0000-0000-000000000101";
UUID second = "00e77100-0000-0000-0000-000000000202";
if (!ERR_Check(roster.Bind(101, first) && roster.CanEnter(101, ERR_Phase.PRISON), "new identity may enter prison")) return false;
roster.Spawned(101, m_ERRSmokePlayer);
if (!ERR_Check(!roster.CanEnter(101, ERR_Phase.PRISON), "spawn consumes identity life")) return false;
roster.Disconnect(101);
if (!ERR_Check(roster.HasSurvivor() && !roster.Get(101), "disconnect retains living actor and clears connection")) return false;
if (!ERR_Check(roster.Bind(202, first) && !roster.CanEnter(202, ERR_Phase.PRISON), "reconnected identity retains spent life")) return false;
if (!ERR_Check(!roster.Bind(303, first), "duplicate active identity rejected")) return false;
if (!ERR_Check(roster.Bind(101, second) && roster.CanEnter(101, ERR_Phase.RADIO_DEFENCE) && !roster.CanEnter(101, ERR_Phase.EXTRACTION), "recycled connection receives independent life and extraction admission policy")) return false;
return true;
}
protected bool ERR_CheckCompanionCommands()
{
array<string> replies = {};
array<string> commands = {"hq interval show", "hq squads show", "hq radius show", "intel carrying show", "intel reveal show"};
array<string> keys = {"reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent"};
for (int i = 0; i < commands.Count(); i++)
{
Admin().Execute(this, true, commands[i], replies);
if (!ERR_Check(replies.Count() == 1 && replies[0] == keys[i] + "=" + Settings().Get(keys[i]).ToString(), "companion command retained: " + commands[i])) return false;
}
Admin().Execute(this, false, "hq log", replies);
if (!ERR_Check(replies.Count() == 1 && replies[0] == "Administrator access required.", "companion journal is behind Core admin authorization")) return false;
Admin().Execute(this, true, "hq show", replies);
ERR_HQ hq = ERR_HQ.Cast(Command());
if (!ERR_Check(hq && replies.Count() == 1 && replies[0] == hq.Status(), "companion HQ status uses the shared command service")) return false;
return true;
}
protected void ERR_PrisonScreenshot() { System.MakeScreenshot("$logs:err-prison"); }
protected void ERR_EndScreenshot() { System.MakeScreenshot("$logs:err-result"); }
protected void ERR_SmokeTick()
{
float now = GetWorld().GetWorldTime();
if (m_ERRSmokeDone) return;
if (now > m_ERRSmokeDeadline) { ERR_Check(false, "timeout at step " + m_ERRSmokeStep.ToString()); return; }
if (now < m_ERRSmokeWait) return;
if (m_ERRSmokeStep == 220)
{
int result = m_ERRAircraftControl.Tick(now);
if (result < 0) { ERR_Check(false, "native actual aircraft controller fixture"); return; }
if (result == 1) { m_ERRSmokeStep = 221; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRSmokeStep == 200)
{
int result = m_ERRMapOwnership.Tick(now);
if (result < 0) { ERR_Check(false, "native map ownership fixture"); return; }
if (result == 1) { m_ERRSmokeStep = 201; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRSmokeStep == 190)
{
int result = m_ERRMortarSalvo.Tick(now);
if (result < 0) { ERR_Check(false, "native complete mortar salvo fixture; inspect source diagnostics"); return; }
if (result == 1) { m_ERRSmokeStep = 191; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRSmokeStep == 180)
{
int result = m_ERRMortarCourier.Tick(now);
if (result < 0) { ERR_Check(false, "native finite courier fixture"); return; }
if (result == 1) { m_ERRSmokeStep = 181; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRSmokeStep == 160)
{
int result = m_ERRMortarTransfer.Tick(now);
if (result < 0) { ERR_Check(false, "native mortar exact-shell transfer fixture"); return; }
if (result == 1) { m_ERRSmokeStep = 161; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRSmokeStep == 150)
{
int result = m_ERRMortarFire.Tick(now);
if (result < 0) { ERR_Check(false, "native mortar firing probe failed; see diagnostics"); return; }
if (result == 1) { m_ERRSmokeStep = 151; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRSmokeStep == 130 || m_ERRSmokeStep == 132)
{
int result = m_ERRHQPatrolMovement.Tick(now);
if (result < 0) { ERR_StopHQPatrol(); GetGame().GetCallqueue().Remove(ERR_SmokeTick); return; }
if (result == 1)
{
if (m_ERRSmokeStep == 130) { m_ERRHQPatrolOrder = new ERR_NativePatrolHQProbe(this); m_ERRSmokeStep = 131; }
else { m_ERRSmokeStep = 133; Transition(ERR_Phase.DEFEAT); }
}
return;
}
if (m_ERRSmokeStep == 131)
{
int result = m_ERRHQPatrolOrder.Tick(now);
if (result < 0) { ERR_StopHQPatrol(); GetGame().GetCallqueue().Remove(ERR_SmokeTick); return; }
if (result == 1) { m_ERRHQPatrolMovement = new ERR_NativePatrolProbe(this); m_ERRSmokeStep = 132; }
return;
}
if (m_ERRSmokeStep == 0)
{
array<IEntity> players = {};
ERR_Actors.Players(players, true);
if (players.Count() != 1 || Phase() != ERR_Phase.PRISON) return;
m_ERRSmokePlayer = players[0];
if (!ERR_CheckRoster() || !ERR_NativeContractsProbe.Run(this, m_ERRSmokePlayer) || !ERR_CheckCompanionCommands()) return;
if (m_ERRTestAircraftControl)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRAircraftControl = new ERR_NativeAircraftControlProbe(this);
m_ERRSmokeStep = 220;
return;
}
if (m_ERRTestPatrolHQ)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRHQPatrolMovement = new ERR_NativePatrolProbe(this);
m_ERRSmokeStep = 130;
return;
}
if (m_ERRTestMapOwnership)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRMapOwnership = new ERRM_NativeMapOwnershipProbe();
m_ERRSmokeStep = 200;
return;
}
if (m_ERRTestMortarSalvo)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRMortarSalvo = new ERR_NativeMortarSalvoProbe(this);
m_ERRSmokeStep = 190;
return;
}
if (m_ERRTestMortarCourier)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRMortarCourier = new ERR_NativeMortarCourierProbe(this);
m_ERRSmokeStep = 180;
return;
}
if (m_ERRTestMortarTransfer)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRMortarTransfer = new ERR_NativeMortarTransferProbe(this);
m_ERRSmokeStep = 160;
return;
}
if (m_ERRTestMortarFire)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRMortarFire = new ERR_NativeMortarFiringProbe(this);
m_ERRSmokeStep = 150;
return;
}
if (m_ERRTestMortar)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRSmokeStep = 120;
return;
}
if (m_ERRTestHQResponse)
{
m_ERRHQResponseProof = new ERR_NativeHQResponseProof(this, m_ERRSmokePlayer);
m_ERRSmokeStep = 110;
return;
}
if (m_ERRTestRadioNetwork)
{
m_ERRRadioProbe = new ERR_NativeRadioProbe();
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRSmokeStep = 100;
return;
}
if (m_ERRTestPlayerDeath)
{
m_ERRSmokeStep = 9;
SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent)).Kill(Instigator.CreateInstigator(m_ERRSmokePlayer));
m_ERRSmokeWait = now + 4000;
return;
}
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
if (!ERR_Check(vector.DistanceXZ(m_ERRSmokePlayer.GetOrigin(), PrisonPosition()) < 5, "initial prisoner position")) return;
foreach (ERR_LocationComponent location : m_Locations)
if (location.Kind() == ERR_LocationKind.RADIO) m_ERRSmokeRadio = location;
if (!ERR_Check(m_ERRSmokeRadio && ERR_Actors.Alive(ExtractionVehicle()), "radio and extraction vehicle exist")) return;
ERR_UnlockPrisonAction lock;
foreach (IEntity tracked : m_OwnedEntities)
{
if (tracked) lock = ERR_FindLock(tracked);
if (lock) break;
}
if (!ERR_Check(lock != null, "authored gate exposes lock action")) return;
ERR_MovePlayer(lock.GetOwner().GetOrigin() + "1 0 1");
vector gateLow, gateHigh;
lock.GetOwner().GetWorldBounds(gateLow, gateHigh);
PrintFormat("ERR GEOMETRY: gate low=%1 high=%2", gateLow, gateHigh);
IEntity wall = lock.GetOwner().GetParent().GetChildren();
while (wall)
{
vector wallLow, wallHigh;
wall.GetWorldBounds(wallLow, wallHigh);
PrintFormat("ERR GEOMETRY: child origin=%1 low=%2 high=%3", wall.GetOrigin(), wallLow, wallHigh);
wall = wall.GetSibling();
}
GetGame().GetCallqueue().CallLater(ERR_PrisonScreenshot, 2000);
if (!ERR_Check(lock.CanBePerformedScript(m_ERRSmokePlayer), "lock permits prisoner")) return;
m_ERRGate = BaseDoorComponent.Cast(lock.GetOwner().FindComponent(BaseDoorComponent));
lock.PerformAction(lock.GetOwner(), m_ERRSmokePlayer);
if (!ERR_Check(Phase() == ERR_Phase.RADIO_SEARCH, "lock action advances mission")) return;
array<IEntity> cacheStock = {};
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(Sites().Find("everon-supply-1").Container.FindComponent(InventoryStorageManagerComponent)), cacheStock);
foreach (IEntity tracked : cacheStock)
{
if (tracked && tracked.GetPrefabData() && tracked.GetPrefabData().GetPrefabName() == ERR_Resources.ObjectiveDocument()) m_ERRCacheFolders.Insert(tracked);
}
if (!ERR_Check(m_ERRCacheFolders.Count() == 2, "two guaranteed physical cache documents")) return;
if (!ERR_Check(m_Settings.Set("aiIntelChancePercent", 0), "test does not rely on random intel")) return;
if (!ERR_Check(m_Settings.Set("radioDefenceSeconds", 30), "isolated test profile defence duration")) return;
m_ERRSmokeStep = 1;
}
else if (m_ERRSmokeStep == 1 || m_ERRSmokeStep == 2)
{
if (m_ERRSmokeStep == 2 && !ERR_Check(m_ERRGate && m_ERRGate.IsOpen() && m_ERRGate.CanCharacterPass(0.6), "unlocked gate physically permits passage")) return;
ERR_MovePlayer(Sites().Find("everon-supply-1").Container.GetOrigin() + "0 0 2");
SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_InventoryStorageManagerComponent));
InventoryStorageManagerComponent source = InventoryStorageManagerComponent.Cast(Sites().Find("everon-supply-1").Container.FindComponent(InventoryStorageManagerComponent));
BaseInventoryStorageComponent destination = inventory.FindStorageForItem(m_ERRCacheFolders[m_ERRSmokeStep - 1]);
if (!ERR_Check(destination && source.TryMoveItemToStorage(m_ERRCacheFolders[m_ERRSmokeStep - 1], destination), "authored cache document recovered into inventory")) return;
m_ERRSmokeStep++;
m_ERRSmokeWait = now + 2500;
}
else if (m_ERRSmokeStep == 3)
{
if (!ERR_Check(m_ERRSmokeRadio.Knowledge() == 2, "carried intel identifies radio through intel service")) return;
if (!ERR_Check(!TuneRadio(m_ERRSmokePlayer, m_ERRSmokeRadio), "radio rejects distant player")) return;
ERR_MovePlayer(m_ERRSmokeRadio.Position() + "2 0 0");
m_ERRSmokeStep = 30;
m_ERRSmokeWait = now + 1000;
}
else if (m_ERRSmokeStep == 30)
{
PrintFormat("ERR SMOKE: radio=%1 player=%2", m_ERRSmokeRadio.Position(), m_ERRSmokePlayer.GetOrigin());
if (!ERR_Check(TuneRadio(m_ERRSmokePlayer, m_ERRSmokeRadio), "identified radio starts defence")) return;
m_ERRSmokeStep = 4;
m_ERRSmokeWait = now + 3000;
}
else if (m_ERRSmokeStep == 4)
{
if (!ERR_Check(DefenceOccupied(), "occupied defence timer running")) return;
ERR_MovePlayer(m_ERRSmokeRadio.Position() + "90 0 0");
m_ERRSmokeStep = 5;
m_ERRSmokeWait = now + 2500;
}
else if (m_ERRSmokeStep == 5)
{
if (!ERR_Check(!DefenceOccupied() && Phase() == ERR_Phase.RADIO_DEFENCE, "leaving area resets defence")) return;
ERR_MovePlayer(m_ERRSmokeRadio.Position() + "2 0 0");
m_ERRSmokeStep = 6;
m_ERRSmokeWait = now + 33000;
}
else if (m_ERRSmokeStep == 6)
{
if (!ERR_Check(Phase() == ERR_Phase.EXTRACTION, "continuous occupied defence completes")) return;
if (m_ERRTestLostHelicopter)
{
m_ERRSmokeStep = 8;
ERR_Actors.DeleteSpawned(ExtractionVehicle());
return;
}
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_ERRSmokePlayer.FindComponent(CompartmentAccessComponent));
BaseCompartmentSlot slot = access.FindFreeCompartment(ExtractionVehicle(), ECompartmentType.PILOT);
if (!ERR_Check(slot && access.GetInVehicle(ExtractionVehicle(), slot, true, -1, ECloseDoorAfterActions.INVALID, false), "boarding extraction vehicle")) return;
m_ERRSmokeStep = 7;
m_ERRSmokeWait = now + 3000;
}
else if (m_ERRSmokeStep == 120)
{
ERR_MortarBattery battery = ERR_MortarSystem.Get().ERR_ProbeBattery();
if (!battery || !battery.StartupActivated) return;
if (!ERR_Check(battery.Operational() && battery.Group.GetAgentsCount() == 4, "authored physical mortar and four-person crew initialize")) return;
if (!ERR_Check(ERR_HQRadio.Capability(battery.RadioOperator) == 2, "mortar has a dedicated physical manpack operator")) return;
if (!ERR_Check(battery.CountShells() == 27, "mortar starts with exactly 27 physical HE shells")) return;
array<AIAgent> agents = {};
battery.Group.GetAgents(agents);
array<IEntity> intelCrew = {};
foreach (AIAgent intelAgent : agents)
{
IEntity intelActor;
if (intelAgent) intelActor = intelAgent.GetControlledEntity();
if (!ERR_Check(intelActor && !intelCrew.Contains(intelActor), "mortar Intel observation uses distinct actual crew actors")) return;
if (!ERR_Check(m_Intel && m_Intel.ERR_ProbeWasRolled(intelActor), "ready mortar crew actor reached its one optional Intel carrying roll")) return;
intelCrew.Insert(intelActor);
}
if (!ERR_Check(intelCrew.Count() == 4, "all four original ready physical crew reached Intel without forcing documents or changing carrying chance")) return;
foreach (AIAgent agent : agents)
{
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(agent.GetControlledEntity().FindComponent(InventoryStorageManagerComponent));
array<IEntity> items = {};
ERR_Inventory.Items(inventory, items);
foreach (IEntity item : items)
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_MortarBattery.SHELL) SCR_EntityHelper.DeleteEntityAndChildren(item);
}
m_ERRSmokeStep = 121;
m_ERRSmokeWait = now + 15000;
}
else if (m_ERRSmokeStep == 121)
{
ERR_MortarBattery battery = ERR_MortarSystem.Get().ERR_ProbeBattery();
if (!ERR_Check(battery.CountShells() == 0 && battery.TotalShots == 0, "depleted mortar inventory stays empty without fabricated ammunition")) return;
ERR_Actors.DeleteSpawned(battery.Gun);
ERR_Actors.DeleteSpawned(battery.Group);
m_ERRSmokeStep = 122;
m_ERRSmokeWait = now + 12000;
}
else if (m_ERRSmokeStep == 122)
{
ERR_MortarBattery battery = ERR_MortarSystem.Get().ERR_ProbeBattery();
if (!ERR_Check(battery && !battery.Gun && !battery.Group && !battery.Operational(), "lost mortar and crew remain lost without replacement spawning")) return;
m_ERRSmokeStep = 9;
SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent));
damage.EnableDamageHandling(true);
damage.Kill(Instigator.CreateInstigator(m_ERRSmokePlayer));
m_ERRSmokeWait = now + 4000;
}
else if (m_ERRSmokeStep == 110)
{
int result = m_ERRHQResponseProof.Tick(now);
if (result < 0) { ERR_Check(false, "native HQ response evidence failed; inspect report lineage diagnostics"); return; }
if (result == 0) return;
m_ERRSmokeStep = 9;
SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent));
damage.EnableDamageHandling(true);
damage.Kill(Instigator.CreateInstigator(m_ERRSmokePlayer));
m_ERRSmokeWait = now + 4000;
}
else if (m_ERRSmokeStep == 100)
{
int result = m_ERRRadioProbe.Tick(ERR_HQ.Cast(Command()), now);
if (result == 0) return;
if (!ERR_Check(result == 1, "native radio delivery and interruption sequence")) return;
m_ERRSmokeStep = 9;
SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent));
damage.EnableDamageHandling(true);
damage.Kill(Instigator.CreateInstigator(m_ERRSmokePlayer));
m_ERRSmokeWait = now + 4000;
}
else if (m_ERRSmokeStep == 9)
{
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
if (!ERR_Check(!ERR_Actors.Alive(m_ERRSmokePlayer), "native damage kills prisoner")) return;
if (!ERR_Check(controller && controller.ERR_ObserverState() == ERR_ObserverReason.SPENT_LIFE, "death selects spectator without another life")) return;
if (!ERR_Check(controller.ERR_HasObserverCamera() && SCR_ManualCamera.Cast(GetGame().GetCameraManager().CurrentCamera()), "spectator camera activates after native death")) return;
m_ERRSmokeStep = 91;
}
else if (m_ERRSmokeStep == 7)
{
if (!ERR_Check(CompartmentAccessComponent.GetVehicleIn(m_ERRSmokePlayer) == ExtractionVehicle(), "physical extraction occupancy")) return;
// Controlled transport tests the boundary detector, not piloting skill.
ExtractionVehicle().SetOrigin(m_EscapeCenter + Vector(m_EscapeRadius + 100, 300, 0));
m_ERRSmokeWait = now + 5000;
}
}
void ~ERR_GameMode()
{
if (GetGame())
{
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
GetGame().GetCallqueue().Remove(ERR_PrisonScreenshot);
GetGame().GetCallqueue().Remove(ERR_EndScreenshot);
}
}
}
#endif
@@ -0,0 +1,16 @@
#ifndef WORKBENCH
modded class ArmaReforgerScripted
{
override protected ResourceName ERR_DiagnosticHeader(string project)
{
if (project == "chat") return "{C1E20667DCE324ED}Missions/ERR_Everon.conf";
return super.ERR_DiagnosticHeader(project);
}
override protected string ERR_DiagnosticAddon(string project)
{
if (project == "chat") return "FA804F411BDDF2C6";
return super.ERR_DiagnosticAddon(project);
}
}
#endif
@@ -0,0 +1,51 @@
[WorkbenchPluginAttribute(name: "Validate Rereintroduced scenario", wbModules: {"WorldEditor"})]
class ERR_ValidateWorldPlugin : WorldEditorPlugin
{
protected bool m_Obstructed;
protected bool CheckClearance(IEntity entity)
{
if (entity && entity.GetVObject()) m_Obstructed = true;
return !m_Obstructed;
}
override void Run() { Validate(); }
override void RunCommandline() { Validate(); }
protected void Validate()
{
WorldEditor editor = Workbench.GetModule(WorldEditor);
if (!editor) { Print("ERR VALIDATION: World Editor missing", LogLevel.ERROR); return; }
WorldEditorAPI api = editor.GetApi();
if (!api) { Print("ERR VALIDATION: world API missing", LogLevel.ERROR); return; }
PrintFormat("ERR VALIDATION: prison terrain=%1, radio terrain=%2, extraction terrain=%3", api.GetTerrainSurfaceY(6090,7160), api.GetTerrainSurfaceY(6500,7200), api.GetTerrainSurfaceY(7000,7500));
for (int dx = -6; dx <= 6; dx += 3)
for (int dz = -6; dz <= 6; dz += 3)
PrintFormat("ERR TERRAIN: x=%1 z=%2 y=%3", 6090 + dx, 7160 + dz, api.GetTerrainSurfaceY(6090 + dx, 7160 + dz));
float bestSlope = 1000;
vector bestPosition;
for (int x = 5800; x <= 6200; x += 10)
{
for (int z = 6800; z <= 7200; z += 10)
{
float low = 10000;
float high = -10000;
for (int sx = -9; sx <= 9; sx += 3)
for (int sz = -15; sz <= 9; sz += 3)
{
float height = api.GetTerrainSurfaceY(x + sx, z + sz);
low = Math.Min(low, height);
high = Math.Max(high, height);
}
if (low > 5 && high - low < bestSlope)
{
m_Obstructed = false;
api.GetWorld().QueryEntitiesByAABB(Vector(x - 10, low + 0.1, z - 16), Vector(x + 10, high + 5, z + 10), CheckClearance);
if (m_Obstructed) continue;
bestSlope = high - low;
bestPosition = Vector(x, api.GetTerrainSurfaceY(x, z), z);
}
}
}
PrintFormat("ERR SITE CANDIDATE: position=%1 elevationRange=%2", bestPosition, bestSlope);
string play;
if (System.GetCLIParam("errPlay", play) && play == "1") editor.SwitchToGameMode();
}
}
@@ -0,0 +1,12 @@
GameProject {
ID "ERR_Chat"
GUID "FA804F411BDDF2C6"
TITLE "Escapists Rereintroduced - Chat"
Dependencies {
"58D0FB3206B6F859"
"2528466DE220D91C"
"5192138CB9D5B911"
"D31039C7C53E348A"
"9AAEE9041D79809E"
}
}
@@ -0,0 +1,136 @@
ERR_ScenarioDefinition {
MinimumRadioSources 2
Sites {
ERR_SiteDefinition {
Id "everon-prison-1"
Label "Prison"
Kind 0
Prefab "{BE6B8D56349038A6}Prefabs/ERR/Prison.et"
Position 6090 0 7160
Guarded 1
GuardOffset 35 0 0
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
ContainerOffset -4 0 0
Stock {
ERR_SiteStock { Prefab "{C0F7DD85A86B2900}Prefabs/Weapons/Handguns/PM/Handgun_PM.et" Count 5 }
ERR_SiteStock { Prefab "{8B853CDD11BA916E}Prefabs/Weapons/Magazines/Magazine_9x18_PM_8rnd_Ball.et" Count 8 }
ERR_SiteStock { Prefab "{13772C903CB5E4F7}Prefabs/Items/Equipment/Maps/Map_Paper_01/PaperMap_01_folded.et" Count 1 }
}
}
ERR_SiteDefinition {
Id "everon-supply-1"
Label "Southern supply cache"
Kind 4
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
Position 6150 0 7050
Guarded 1
GuardOffset 18 0 0
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
ContainerOffset 0 0 0
Stock {
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 8 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 16 }
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 12 }
ERR_SiteStock { Prefab "{554159A6740C3F3E}Prefabs/ERR/ObjectiveDocument.et" Count 2 TargetId "everon-radio-1" }
}
RevealOnEscape 1
}
ERR_SiteDefinition {
Id "everon-radio-1"
Label "Radio station"
DiscoverRadius 8
Kind 1
Prefab "{F7E7E3D6ACF0C860}Prefabs/ERR/Radio.et"
Position 6500 0 7200
Guarded 1
GuardOffset 30 0 0
}
ERR_SiteDefinition {
Id "everon-extraction-1"
Label "Initial helicopter pickup site"
Kind 2
Prefab "{033503110AC34EAC}Prefabs/ERR/Extraction.et"
Position 7000 0 7500
Guarded 1
GuardOffset 30 0 0
}
ERR_SiteDefinition {
Id "everon-supply-2"
Label "Western supply cache"
Kind 4
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
Position 5960 0 7220
Guarded 1
GuardOffset 16 0 0
Patrol {
ERR_SitePatrolPoint { Offset 18 0 0 }
ERR_SitePatrolPoint { Offset 18 0 24 }
ERR_SitePatrolPoint { Offset -6 0 24 }
ERR_SitePatrolPoint { Offset -6 0 0 }
}
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
DiscoverRadius 25
RevealOnEscape 1
Transport ERR_SiteTransport {
Prefab "{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et"
Offset 0 0 -8
Heading 180
ClearanceRadius 4
MaximumTerrainRange 1
}
Stock {
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 4 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 8 }
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 6 }
ERR_SiteStock { Prefab "{554159A6740C3F3E}Prefabs/ERR/ObjectiveDocument.et" Count 2 TargetId "everon-radio-1" }
}
}
ERR_SiteDefinition {
Id "everon-medical-1"
Label "Medical stash"
Kind 4
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
Position 6340 0 7400
Guarded 0
GuardOffset 16 0 0
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
DiscoverRadius 25
RevealOnEscape 0
Stock {
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 12 }
ERR_SiteStock { Prefab "{527D7C5D2E476BDC}Prefabs/Items/Medicine/SalineBag_01/SalineBag_USSR_01.et" Count 6 }
ERR_SiteStock { Prefab "{0D9A5DCF89AE7AA9}Prefabs/Items/Medicine/MorphineInjection_01/MorphineInjection_01.et" Count 6 }
ERR_SiteStock { Prefab "{13772C903CB5E4F7}Prefabs/Items/Equipment/Maps/Map_Paper_01/PaperMap_01_folded.et" Count 3 }
}
}
ERR_SiteDefinition {
Id "everon-outpost-1"
Label "Eastern guard outpost"
Kind 3
Prefab "{7C3D16C17C24D351}Prefabs/ERR/SupplySite.et"
Position 6740 0 7300
Guarded 1
GuardOffset 16 0 0
Patrol {
ERR_SitePatrolPoint { Offset 18 0 0 }
ERR_SitePatrolPoint { Offset 18 0 24 }
ERR_SitePatrolPoint { Offset -6 0 24 }
ERR_SitePatrolPoint { Offset -6 0 0 }
}
Container "{40986810D0B1F4AE}Prefabs/ERR/SupplyCache.et"
DiscoverRadius 35
RevealOnEscape 0
Stock {
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 2 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 10 }
ERR_SiteStock { Prefab "{243948B23D90BECB}Prefabs/Items/Equipment/Binoculars/Binoculars_B8/Binoculars_B8.et" Count 2 }
ERR_SiteStock { Prefab "{C3F1FA1E2EC2B345}Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et" Count 4 }
}
Props {
ERR_SiteProp { Prefab "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et" Offset 0 0 -4 }
ERR_SiteProp { Prefab "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et" Offset -4 0 0 Angles 90 0 0 }
ERR_SiteProp { Prefab "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et" Offset 4 0 0 Angles 90 0 0 }
}
}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{BC310DC0CD27F523}Configs/ERR/EveronSites.conf"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,24 @@
SCR_EntityCatalogMultiList {
m_eEntityCatalogType VEHICLE
m_aMultiLists {
SCR_EntityCatalogMultiListEntry {
m_sIdentifier "Mission transport"
m_aEntities {
SCR_EntityCatalogEntry {
m_sEntityPrefab "{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et"
m_aEntityDataList {
SCR_EntityCatalogEditorData { m_eValidEditorModes 33 }
SCR_EntityCatalogSpawnerData { m_eSlotTypes 24 }
}
}
SCR_EntityCatalogEntry {
m_sEntityPrefab "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et"
m_aEntityDataList {
SCR_EntityCatalogEditorData { m_eValidEditorModes 33 }
SCR_EntityCatalogSpawnerData { m_eSlotTypes VEHICLE_LARGE }
}
}
}
}
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf"
Configurations {
ResourceClass PC {}
ResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,277 @@
SCR_Faction : "{E7B7EA4823DD81F0}Configs/Factions/BLUFOR.conf" {
FactionKey "US"
UIInfo SCR_FactionUIInfo "{528C961236B7DCD3}" {
Name "#AR-Faction_US"
Icon "{EB7F65DBC9392557}UI/Textures/Editor/EditableEntities/Factions/EditableEntity_Faction_USA.edds"
m_sNameUpper "#AR-Faction_US-UC"
}
"Identity of soldiers" FactionIdentity "{570D128F4022EF5A}" : "{B600946E9ACC17C2}Configs/Identities/FactionIdentity_US.conf" {
}
"Radio encryption key" "chickenNuggets"
m_sFactionFlag "{46FB2FB2C2125A3C}UI/Textures/Flags/US-flag-base-3it.edds"
m_FactionFlagMaterial "{610803CC3F3D6649}Assets/Props/Fabric/Flags/Data/Flag_1_2_USA.emat"
m_FactionLabel FACTION_US
m_CallsignInfo SCR_FactionCallsignInfo "{5DA0F2A6677ADA9E}" : "{CB7824D572DD0D5C}Configs/Callsigns/Callsigns_US.conf" {
}
m_iFactionRadioFrequency 48000
m_aEntityCatalogs {
SCR_EntityCatalogMultiList "{5C68A0EE815FA292}" : "{B56E8B4D4B195559}Configs/EntityCatalog/US/Characters_EntityCatalog_US.conf" {
}
SCR_EntityCatalogMultiList "{5C9E82F29B327997}" : "{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf" {
}
SCR_EntityCatalogMultiList "{5C68A0EE8B97CA50}" : "{4E70E7C4C2C17D4A}Configs/EntityCatalog/US/Groups_EntityCatalog_US.conf" {
}
SCR_EntityCatalog "{5C68A0EEB7DB3E84}" : "{0E4B62F186F97C43}Configs/EntityCatalog/US/WeaponTripod_EntityCatalog_US.conf" {
}
SCR_EntityCatalogMultiList "{5C68A0EEB3F124F9}" : "{5F7EC52FC40A03E2}Configs/EntityCatalog/US/InventoryItems_EntityCatalog_US.conf" {
}
SCR_EntityCatalog "{5C9EDDD37394AD40}" : "{0BF36EFF9FFAF92B}Configs/EntityCatalog/US/SupplyContainerItems_EntityCatalog_US.conf" {
}
}
m_aFlagNames +{
}
m_aBaseCallsigns {
SCR_MilitaryBaseCallsign "{5C90D92F493E764B}" {
m_sCallsign "#AR-Campaign_PointAlabama"
m_sCallsignShort "#AR-Comm_Variable_Base_Alabama_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Alabama_US_UC"
m_iSignalIndex 0
}
SCR_MilitaryBaseCallsign "{5C90D92F49F79967}" {
m_sCallsign "#AR-Campaign_PointAlaska"
m_sCallsignShort "#AR-Comm_Variable_Base_Alaska_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Alaska_US_UC"
m_iSignalIndex 1
}
SCR_MilitaryBaseCallsign "{5C90D92F482DA59B}" {
m_sCallsign "#AR-Campaign_PointArizona"
m_sCallsignShort "#AR-Comm_Variable_Base_Arizona_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Arizona_US_UC"
m_iSignalIndex 2
}
SCR_MilitaryBaseCallsign "{5C90D92F483330D2}" {
m_sCallsign "#AR-Campaign_PointArkansas"
m_sCallsignShort "#AR-Comm_Variable_Base_Arkansas_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Arkansas_US_UC"
m_iSignalIndex 3
}
SCR_MilitaryBaseCallsign "{5C90D92F48170DA8}" {
m_sCallsign "#AR-Campaign_PointCalifornia"
m_sCallsignShort "#AR-Comm_Variable_Base_California_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_California_US_UC"
m_iSignalIndex 4
}
SCR_MilitaryBaseCallsign "{5C90D92F487834E8}" {
m_sCallsign "#AR-Campaign_PointColorado"
m_sCallsignShort "#AR-Comm_Variable_Base_Colorado_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Colorado_US_UC"
m_iSignalIndex 5
}
SCR_MilitaryBaseCallsign "{5C90D92F48410A86}" {
m_sCallsign "#AR-Campaign_PointConnecticut"
m_sCallsignShort "#AR-Comm_Variable_Base_Connecticut_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Connecticut_US_UC"
m_iSignalIndex 6
}
SCR_MilitaryBaseCallsign "{5C90D92F48A4E31F}" {
m_sCallsign "#AR-Campaign_PointDelaware"
m_sCallsignShort "#AR-Comm_Variable_Base_Delaware_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Delaware_US_UC"
m_iSignalIndex 7
}
SCR_MilitaryBaseCallsign "{5C90D92F488C6B57}" {
m_sCallsign "#AR-Campaign_PointFlorida"
m_sCallsignShort "#AR-Comm_Variable_Base_Florida_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Florida_US_UC"
m_iSignalIndex 8
}
SCR_MilitaryBaseCallsign "{5C90D92F4896F764}" {
m_sCallsign "#AR-Campaign_PointGeorgia"
m_sCallsignShort "#AR-Comm_Variable_Base_Georgia_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Georgia_US_UC"
m_iSignalIndex 9
}
SCR_MilitaryBaseCallsign "{5C90D92F48FBD90E}" {
m_sCallsign "#AR-Campaign_PointHawaii"
m_sCallsignShort "#AR-Comm_Variable_Base_Hawaii_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Hawaii_US_UC"
m_iSignalIndex 10
}
SCR_MilitaryBaseCallsign "{5C90D92F48DFF562}" {
m_sCallsign "#AR-Campaign_PointIdaho"
m_sCallsignShort "#AR-Comm_Variable_Base_Idaho_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Idaho_US_UC"
m_iSignalIndex 11
}
SCR_MilitaryBaseCallsign "{5C90D92F7724C851}" {
m_sCallsign "#AR-Campaign_PointIllinois"
m_sCallsignShort "#AR-Comm_Variable_Base_Illinois_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Illinois_US_UC"
m_iSignalIndex 12
}
SCR_MilitaryBaseCallsign "{5C90D92F77097F3F}" {
m_sCallsign "#AR-Campaign_PointIndiana"
m_sCallsignShort "#AR-Comm_Variable_Base_Indiana_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Indiana_US_UC"
m_iSignalIndex 13
}
SCR_MilitaryBaseCallsign "{5C90D92F77102B55}" {
m_sCallsign "#AR-Campaign_PointIowa"
m_sCallsignShort "#AR-Comm_Variable_Base_Iowa_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Iowa_US_UC"
m_iSignalIndex 14
}
SCR_MilitaryBaseCallsign "{5C90D92F777ABD04}" {
m_sCallsign "#AR-Campaign_PointKansas"
m_sCallsignShort "#AR-Comm_Variable_Base_Kansas_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Kansas_US_UC"
m_iSignalIndex 15
}
SCR_MilitaryBaseCallsign "{5C90D92F775EBBC1}" {
m_sCallsign "#AR-Campaign_PointKentucky"
m_sCallsignShort "#AR-Comm_Variable_Base_Kentucky_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Kentucky_US_UC"
m_iSignalIndex 16
}
SCR_MilitaryBaseCallsign "{5C90D92F77A28EB6}" {
m_sCallsign "#AR-Campaign_PointLouisiana"
m_sCallsignShort "#AR-Comm_Variable_Base_Louisiana_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Louisiana_US_UC"
m_iSignalIndex 17
}
SCR_MilitaryBaseCallsign "{5C90D92F77871C23}" {
m_sCallsign "#AR-Campaign_PointMaine"
m_sCallsignShort "#AR-Comm_Variable_Base_Maine_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Maine_US_UC"
m_iSignalIndex 18
}
SCR_MilitaryBaseCallsign "{5C90D92F77EB96A0}" {
m_sCallsign "#AR-Campaign_PointMaryland"
m_sCallsignShort "#AR-Comm_Variable_Base_Maryland_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Maryland_US_UC"
m_iSignalIndex 19
}
SCR_MilitaryBaseCallsign "{5C90D92F77CC2518}" {
m_sCallsign "#AR-Campaign_PointMassachusetts"
m_sCallsignShort "#AR-Comm_Variable_Base_Massachusetts_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Massachusetts_US_UC"
m_iSignalIndex 20
}
SCR_MilitaryBaseCallsign "{5C90D92F77D3715B}" {
m_sCallsign "#AR-Campaign_PointMichigan"
m_sCallsignShort "#AR-Comm_Variable_Base_Michigan_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Michigan_US_UC"
m_iSignalIndex 21
}
SCR_MilitaryBaseCallsign "{5C90D92F7635D418}" {
m_sCallsign "#AR-Campaign_PointMinnesota"
m_sCallsignShort "#AR-Comm_Variable_Base_Minnesota_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Minnesota_US_UC"
m_iSignalIndex 22
}
SCR_MilitaryBaseCallsign "{5C90D92F761C3FF2}" {
m_sCallsign "#AR-Campaign_PointMississippi"
m_sCallsignShort "#AR-Comm_Variable_Base_Mississippi_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Mississippi_US_UC"
m_iSignalIndex 23
}
SCR_MilitaryBaseCallsign "{5C90D92F7660CFAA}" {
m_sCallsign "#AR-Campaign_PointMissouri"
m_sCallsignShort "#AR-Comm_Variable_Base_Missouri_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Missouri_US_UC"
m_iSignalIndex 24
}
SCR_MilitaryBaseCallsign "{5C90D92F764A6E6B}" {
m_sCallsign "#AR-Campaign_PointMontana"
m_sCallsignShort "#AR-Comm_Variable_Base_Montana_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Montana_US_UC"
m_iSignalIndex 25
}
SCR_MilitaryBaseCallsign "{5C90D92F76AD4D35}" {
m_sCallsign "#AR-Campaign_PointNebraska"
m_sCallsignShort "#AR-Comm_Variable_Base_Nebraska_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Nebraska_US_UC"
m_iSignalIndex 26
}
SCR_MilitaryBaseCallsign "{5C90D92F76B1EF3A}" {
m_sCallsign "#AR-Campaign_PointNevada"
m_sCallsignShort "#AR-Comm_Variable_Base_Nevada_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Nevada_US_UC"
m_iSignalIndex 27
}
SCR_MilitaryBaseCallsign "{5C90D92F7695FF2F}" {
m_sCallsign "#AR-Campaign_PointNewYork"
m_sCallsignShort "#AR-Comm_Variable_Base_NewYork_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_NewYork_US_UC"
m_iSignalIndex 28
}
SCR_MilitaryBaseCallsign "{5C90D92F76FF94B1}" {
m_sCallsign "#AR-Campaign_PointOhio"
m_sCallsignShort "#AR-Comm_Variable_Base_Ohio_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Ohio_US_UC"
m_iSignalIndex 29
}
SCR_MilitaryBaseCallsign "{5C90D92F76C68EED}" {
m_sCallsign "#AR-Campaign_PointOklahoma"
m_sCallsignShort "#AR-Comm_Variable_Base_Oklahoma_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Oklahoma_US_UC"
m_iSignalIndex 30
}
SCR_MilitaryBaseCallsign "{5C90D92F752A9F29}" {
m_sCallsign "#AR-Campaign_PointOregon"
m_sCallsignShort "#AR-Comm_Variable_Base_Oregon_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Oregon_US_UC"
m_iSignalIndex 31
}
SCR_MilitaryBaseCallsign "{5C90D92F750E6FB9}" {
m_sCallsign "#AR-Campaign_PointPennsylvania"
m_sCallsignShort "#AR-Comm_Variable_Base_Pennsylvania_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Pennsylvania_US_UC"
m_iSignalIndex 32
}
SCR_MilitaryBaseCallsign "{5C90D92F7517240F}" {
m_sCallsign "#AR-Campaign_PointTexas"
m_sCallsignShort "#AR-Comm_Variable_Base_Texas_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Texas_US_UC"
m_iSignalIndex 33
}
SCR_MilitaryBaseCallsign "{5C90D92F7574D867}" {
m_sCallsign "#AR-Campaign_PointUtah"
m_sCallsignShort "#AR-Comm_Variable_Base_Utah_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Utah_US_UC"
m_iSignalIndex 34
}
SCR_MilitaryBaseCallsign "{5C90D92F755C4871}" {
m_sCallsign "#AR-Campaign_PointVermont"
m_sCallsignShort "#AR-Comm_Variable_Base_Vermont_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Vermont_US_UC"
m_iSignalIndex 35
}
SCR_MilitaryBaseCallsign "{5C90D92F75A0502B}" {
m_sCallsign "#AR-Campaign_PointVirginia"
m_sCallsignShort "#AR-Comm_Variable_Base_Virginia_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Virginia_US_UC"
m_iSignalIndex 36
}
SCR_MilitaryBaseCallsign "{5C90D92F758ABFD3}" {
m_sCallsign "#AR-Campaign_PointWashington"
m_sCallsignShort "#AR-Comm_Variable_Base_Washington_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Washington_US_UC"
m_iSignalIndex 37
}
SCR_MilitaryBaseCallsign "{5C90D92F75EF8263}" {
m_sCallsign "#AR-Campaign_PointWisconsin"
m_sCallsignShort "#AR-Comm_Variable_Base_Wisconsin_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Wisconsin_US_UC"
m_iSignalIndex 38
}
SCR_MilitaryBaseCallsign "{5C90D92F75F0F224}" {
m_sCallsign "#AR-Campaign_PointWyoming"
m_sCallsignShort "#AR-Comm_Variable_Base_Wyoming_US"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Wyoming_US_UC"
m_iSignalIndex 39
}
}
m_FactionHomeTerritoryConfig SCR_FactionHomeTerritoryConfig "{5AEE94D54FB58C33}" : "{B548A5B264939A98}Configs/Identities/Territories/FactionHomeTerritory_US.conf" {
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{5C67A1DDE0044806}Configs/ERR/Factions/US.conf"
Configurations {
ResourceClass PC {}
ResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,276 @@
SCR_Faction : "{B122E3B10BB3404B}Configs/Factions/OPFOR.conf" {
FactionKey "USSR"
UIInfo SCR_FactionUIInfo "{528C96127F8B6B08}" {
Name "#AR-Faction_USSR"
Icon "{40B12B0DF911B856}UI/Textures/Editor/EditableEntities/Factions/EditableEntity_Faction_USSR.edds"
m_sNameUpper "#AR-Faction_USSR-UC"
}
"Identity of soldiers" FactionIdentity "{570CFDC6685B6B25}" : "{FBBCD73926989580}Configs/Identities/FactionIdentity_USSR.conf" {
}
"Radio encryption key" "coldBorscht"
m_sFactionFlag "{50824EB9F2C215AB}UI/Textures/Flags/USSR-flag-base-3it.edds"
m_FactionFlagMaterial "{B47B52D9DA68905D}Assets/Props/Fabric/Flags/Data/Flag_1_2_SovietArmy.emat"
m_FactionLabel FACTION_USSR
m_CallsignInfo SCR_FactionCallsignInfo "{5DA0F2A67DFB8809}" : "{958043CE1648EC43}Configs/Callsigns/Callsigns_USSR.conf" {
}
m_iFactionRadioFrequency 42000
m_aEntityCatalogs {
SCR_EntityCatalogMultiList "{5C68A0E9BD5E5DD9}" : "{3B679F1799D55F6F}Configs/EntityCatalog/USSR/Characters_EntityCatalog_USSR.conf" {
}
SCR_EntityCatalogMultiList "{5C9E82F285C09E24}" : "{E4785D3AF1D043C2}Configs/ERR/Factions/MissionVehicles.conf" {
}
SCR_EntityCatalogMultiList "{5C68A0E9B9AA3C2D}" : "{37D5B75AAC3E72EA}Configs/EntityCatalog/USSR/Groups_EntityCatalog_USSR.conf" {
}
SCR_EntityCatalog "{5C68A0E9A51460AB}" : "{DDF17C8C140BD1F2}Configs/EntityCatalog/USSR/WeaponTripod_EntityCatalog_USSR.conf" {
}
SCR_EntityCatalogMultiList "{5C68A0E9AFE1E631}" : "{C53421647C3D0D2E}Configs/EntityCatalog/USSR/InventoryItems_EntityCatalog_USSR.conf" {
}
SCR_EntityCatalog "{5C9EDDD378BA3298}" : "{B4CA6761AC935808}Configs/EntityCatalog/USSR/SupplyContainerItems_EntityCatalog_USSR.conf" {
}
}
m_iIdentityVoiceSignal 100
m_aBaseCallsigns {
SCR_MilitaryBaseCallsign "{5C9108F95F3FB520}" {
m_sCallsign "#AR-Campaign_PointArena"
m_sCallsignShort "#AR-Comm_Variable_Base_Arena_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Arena_RU_UC"
m_iSignalIndex 0
}
SCR_MilitaryBaseCallsign "{5C9108F95F0034DB}" {
m_sCallsign "#AR-Campaign_PointAvrora"
m_sCallsignShort "#AR-Comm_Variable_Base_Avrora_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Avrora_RU_UC"
m_iSignalIndex 1
}
SCR_MilitaryBaseCallsign "{5C9108F95F6514E7}" {
m_sCallsign "#AR-Campaign_PointBekas"
m_sCallsignShort "#AR-Comm_Variable_Base_Bekas_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Bekas_RU_UC"
m_iSignalIndex 2
}
SCR_MilitaryBaseCallsign "{5C9108F95F4EE8BF}" {
m_sCallsign "#AR-Campaign_PointBizon"
m_sCallsignShort "#AR-Comm_Variable_Base_Bizon_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Bizon_RU_UC"
m_iSignalIndex 3
}
SCR_MilitaryBaseCallsign "{5C9108F95F5021F9}" {
m_sCallsign "#AR-Campaign_PointBolero"
m_sCallsignShort "#AR-Comm_Variable_Base_Bolero_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Bolero_RU_UC"
m_iSignalIndex 4
}
SCR_MilitaryBaseCallsign "{5C9108F95FBA4C89}" {
m_sCallsign "#AR-Campaign_PointCafftan"
m_sCallsignShort "#AR-Comm_Variable_Base_Cafftan_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Cafftan_RU_UC"
m_iSignalIndex 5
}
SCR_MilitaryBaseCallsign "{5C9108F95F9C00A9}" {
m_sCallsign "#AR-Campaign_PointKaban"
m_sCallsignShort "#AR-Comm_Variable_Base_Kaban_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Kaban_RU_UC"
m_iSignalIndex 6
}
SCR_MilitaryBaseCallsign "{5C9108F95FE17E10}" {
m_sCallsign "#AR-Campaign_PointConsul"
m_sCallsignShort "#AR-Comm_Variable_Base_Consul_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Consul_RU_UC"
m_iSignalIndex 7
}
SCR_MilitaryBaseCallsign "{5C9108F95FCBA475}" {
m_sCallsign "#AR-Campaign_PointDimok"
m_sCallsignShort "#AR-Comm_Variable_Base_Dimok_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Dimok_RU_UC"
m_iSignalIndex 8
}
SCR_MilitaryBaseCallsign "{5C9108F95E2B2963}" {
m_sCallsign "#AR-Campaign_PointEffir"
m_sCallsignShort "#AR-Comm_Variable_Base_Effir_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Effir_RU_UC"
m_iSignalIndex 9
}
SCR_MilitaryBaseCallsign "{5C9108F95E3040F5}" {
m_sCallsign "#AR-Campaign_PointFakir"
m_sCallsignShort "#AR-Comm_Variable_Base_Fakir_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Fakir_RU_UC"
m_iSignalIndex 10
}
SCR_MilitaryBaseCallsign "{5C9108F95E17C67D}" {
m_sCallsign "#AR-Campaign_PointGips"
m_sCallsignShort "#AR-Comm_Variable_Base_Gips_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Gips_RU_UC"
m_iSignalIndex 11
}
SCR_MilitaryBaseCallsign "{5C9108F95E797F8A}" {
m_sCallsign "#AR-Campaign_PointGussar"
m_sCallsignShort "#AR-Comm_Variable_Base_Gussar_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Gussar_RU_UC"
m_iSignalIndex 12
}
SCR_MilitaryBaseCallsign "{5C9108F95E431F83}" {
m_sCallsign "#AR-Campaign_PointIgla"
m_sCallsignShort "#AR-Comm_Variable_Base_Igla_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Igla_RU_UC"
m_iSignalIndex 13
}
SCR_MilitaryBaseCallsign "{5C9108F95EA7F87E}" {
m_sCallsign "#AR-Campaign_PointKhek"
m_sCallsignShort "#AR-Comm_Variable_Base_Khek_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Khek_RU_UC"
m_iSignalIndex 14
}
SCR_MilitaryBaseCallsign "{5C9108F95E88C6B9}" {
m_sCallsign "#AR-Campaign_PointLednik"
m_sCallsignShort "#AR-Comm_Variable_Base_Lednik_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Lednik_RU_UC"
m_iSignalIndex 15
}
SCR_MilitaryBaseCallsign "{5C9108F95EEDE322}" {
m_sCallsign "#AR-Campaign_PointMatros"
m_sCallsignShort "#AR-Comm_Variable_Base_Matros_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Matros_RU_UC"
m_iSignalIndex 16
}
SCR_MilitaryBaseCallsign "{5C9108F95EF6244A}" {
m_sCallsign "#AR-Campaign_PointMuskat"
m_sCallsignShort "#AR-Comm_Variable_Base_Muskat_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Muskat_RU_UC"
m_iSignalIndex 17
}
SCR_MilitaryBaseCallsign "{5C9108F95EDAF04A}" {
m_sCallsign "#AR-Campaign_PointNagan"
m_sCallsignShort "#AR-Comm_Variable_Base_Nagan_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Nagan_RU_UC"
m_iSignalIndex 18
}
SCR_MilitaryBaseCallsign "{5C9108F95D3CCF27}" {
m_sCallsign "#AR-Campaign_PointNitka"
m_sCallsignShort "#AR-Comm_Variable_Base_Nitka_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Nitka_RU_UC"
m_iSignalIndex 19
}
SCR_MilitaryBaseCallsign "{5C9108F95D076483}" {
m_sCallsign "#AR-Campaign_PointOmega"
m_sCallsignShort "#AR-Comm_Variable_Base_Omega_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Omega_RU_UC"
m_iSignalIndex 20
}
SCR_MilitaryBaseCallsign "{5C9108F95D68D0F3}" {
m_sCallsign "#AR-Campaign_PointPlazma"
m_sCallsignShort "#AR-Comm_Variable_Base_Plazma_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Plazma_RU_UC"
m_iSignalIndex 21
}
SCR_MilitaryBaseCallsign "{5C9108F95D4C0F8E}" {
m_sCallsign "#AR-Campaign_PointPolyn"
m_sCallsignShort "#AR-Comm_Variable_Base_Polyn_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Polyn_RU_UC"
m_iSignalIndex 22
}
SCR_MilitaryBaseCallsign "{5C9108F95D56EEEE}" {
m_sCallsign "#AR-Campaign_PointProlog"
m_sCallsignShort "#AR-Comm_Variable_Base_Prolog_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Prolog_RU_UC"
m_iSignalIndex 23
}
SCR_MilitaryBaseCallsign "{5C9108F95DBA92EE}" {
m_sCallsign "#AR-Campaign_PointRabkor"
m_sCallsignShort "#AR-Comm_Variable_Base_Rabkor_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Rabkor_RU_UC"
m_iSignalIndex 24
}
SCR_MilitaryBaseCallsign "{5C9108F95C69680D}" {
m_sCallsign "#AR-Campaign_PointRadon"
m_sCallsignShort "#AR-Comm_Variable_Base_Radon_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Radon_RU_UC"
m_iSignalIndex 25
}
SCR_MilitaryBaseCallsign "{5C9108F95C4FD930}" {
m_sCallsign "#AR-Campaign_PointSadko"
m_sCallsignShort "#AR-Comm_Variable_Base_Sadko_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Sadko_RU_UC"
m_iSignalIndex 26
}
SCR_MilitaryBaseCallsign "{5C9108F95C5255FD}" {
m_sCallsign "#AR-Campaign_PointSekret"
m_sCallsignShort "#AR-Comm_Variable_Base_Sekret_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Sekret_RU_UC"
m_iSignalIndex 27
}
SCR_MilitaryBaseCallsign "{5C9108F95CB621AE}" {
m_sCallsign "#AR-Campaign_PointShtanga"
m_sCallsignShort "#AR-Comm_Variable_Base_Shtanga_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Shtanga_RU_UC"
m_iSignalIndex 28
}
SCR_MilitaryBaseCallsign "{5C9108F95C9545BC}" {
m_sCallsign "#AR-Campaign_PointShuka"
m_sCallsignShort "#AR-Comm_Variable_Base_Shuka_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Shuka_RU_UC"
m_iSignalIndex 29
}
SCR_MilitaryBaseCallsign "{5C9108F95CFE26C3}" {
m_sCallsign "#AR-Campaign_PointTabor"
m_sCallsignShort "#AR-Comm_Variable_Base_Tabor_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Tabor_RU_UC"
m_iSignalIndex 30
}
SCR_MilitaryBaseCallsign "{5C9108F95CDD263C}" {
m_sCallsign "#AR-Campaign_PointTenor"
m_sCallsignShort "#AR-Comm_Variable_Base_Tenor_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Tenor_RU_UC"
m_iSignalIndex 31
}
SCR_MilitaryBaseCallsign "{5C9108F95B27128C}" {
m_sCallsign "#AR-Campaign_PointTsikson"
m_sCallsignShort "#AR-Comm_Variable_Base_Tsikson_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Tsikson_RU_UC"
m_iSignalIndex 32
}
SCR_MilitaryBaseCallsign "{5C9108F95B08D83D}" {
m_sCallsign "#AR-Campaign_PointUlan"
m_sCallsignShort "#AR-Comm_Variable_Base_Ulan_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Ulan_RU_UC"
m_iSignalIndex 33
}
SCR_MilitaryBaseCallsign "{5C9108F95B6D3FC2}" {
m_sCallsign "#AR-Campaign_PointVermout"
m_sCallsignShort "#AR-Comm_Variable_Base_Vermout_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Vermout_RU_UC"
m_iSignalIndex 34
}
SCR_MilitaryBaseCallsign "{5C9108F95B712949}" {
m_sCallsign "#AR-Campaign_PointViympel"
m_sCallsignShort "#AR-Comm_Variable_Base_Viympel_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Viympel_RU_UC"
m_iSignalIndex 35
}
SCR_MilitaryBaseCallsign "{5C9108F95B57D185}" {
m_sCallsign "#AR-Campaign_PointYantar"
m_sCallsignShort "#AR-Comm_Variable_Base_Yantar_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Yantar_RU_UC"
m_iSignalIndex 36
}
SCR_MilitaryBaseCallsign "{5C9108F95BB17114}" {
m_sCallsign "#AR-Campaign_PointYolka"
m_sCallsignShort "#AR-Comm_Variable_Base_Yolka_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Yolka_RU_UC"
m_iSignalIndex 37
}
SCR_MilitaryBaseCallsign "{5C9108F95B97C054}" {
m_sCallsign "#AR-Campaign_PointZheton"
m_sCallsignShort "#AR-Comm_Variable_Base_Zheton_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Zheton_RU_UC"
m_iSignalIndex 38
}
SCR_MilitaryBaseCallsign "{5C9108F95BFC33E5}" {
m_sCallsign "#AR-Campaign_PointZurna"
m_sCallsignShort "#AR-Comm_Variable_Base_Zurna_RU"
m_sCallsignUpperCase "#AR-Comm_Variable_Base_Zurna_RU_UC"
m_iSignalIndex 39
}
}
m_FactionHomeTerritoryConfig SCR_FactionHomeTerritoryConfig "{5AEE94D5517C30BD}" : "{31FC78AF00523A99}Configs/Identities/Territories/FactionHomeTerritory_USSR.conf" {
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf"
Configurations {
ResourceClass PC {}
ResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,21 @@
SCR_GameOverScreenConfig : "{651583FFFB7FC765}Configs/GameOverScreen/BaseGameOverScreensConfig.conf" {
m_aGameOverScreens {
ERR_EscapeGameOverScreenInfo "{6B193DC8DDC63507}" {
m_GameOverScreenId VICTORY
m_GameOverContentLayout "{C4507D07F3C54831}UI/layouts/HUD/GameOver/EndScreen/EndScreenContent_NoImage.layout"
m_OptionalParams SCR_BaseGameOverScreenInfoOptional "{4984A5C1BFEFC04E}" {
m_sTitle "Escape complete"
m_sAudioOneShot "SOUND_ONVICTORY"
}
}
ERR_FailureGameOverScreenInfo "{E66F29A7A8AD7438}" {
m_GameOverScreenId LOOSE
m_GameOverContentLayout "{C4507D07F3C54831}UI/layouts/HUD/GameOver/EndScreen/EndScreenContent_NoImage.layout"
m_OptionalParams SCR_BaseGameOverScreenInfoOptional "{3DBF0BDE715945FA}" {
m_sTitle "Escape failed"
m_sDebriefing "No surviving prisoners can complete the escape, or the extraction helicopter has been lost."
m_sAudioOneShot "SOUND_ONDEFEAT"
}
}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{F6633922A25F9441}Configs/ERR/GameOverScreens.conf"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,7 @@
ERR_FactionCatalog {
PlayerFactionKey "US"
EnemyFactionKey "USSR"
PrisonerPrefab "{3FF693715C9F8928}Prefabs/ERR/Prisoner.et"
GuardGroupPrefab "{D5A4D0D3AE1A3E26}Prefabs/ERR/Patrol.et"
ExtractionPrefab "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et"
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{BE50B97B8E2B36E6}Configs/ERR/VanillaFactions.conf"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,11 @@
SCR_MissionHeader {
World "{B6C516F86302C679}Worlds/ERR_Everon.ent"
SystemsConfig "{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf"
m_sName "Rereintroduced: Everon Base (development)"
m_sAuthor "Rereintroduced contributors"
m_sDescription "Independent base escape mission using vanilla Reforger. Companion addons optional. Development scenario."
m_sGameMode "Escape"
m_iPlayerCount 12
m_eSaveTypes 0
m_bShowInScenarioMenu 1
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{AD62322D5213C440}Missions/ERR_Everon_Base.conf"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,6 @@
GenericEntity {
components {
ERR_LocationComponent "{C17CA14398A1CD7E}" { m_Kind 2 }
RplComponent "{0BF5BAAA5FBFEECE}" { Streamable Disabled }
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{033503110AC34EAC}Prefabs/ERR/Extraction.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,10 @@
SCR_FactionManager : "{A242612865F2A48E}Prefabs/MP/Managers/Factions/FactionManager_Base.et" {
Factions {
SCR_Faction : "{5C67A1DDE0044806}Configs/ERR/Factions/US.conf" {
m_sFactionBackground "{F8AC305446D5D480}UI/Textures/DeployMenu/Loadout-BG/Loadout_US_BG.imageset"
}
SCR_Faction : "{D7A5CFA62A36480D}Configs/ERR/Factions/USSR.conf" {
m_sFactionBackground "{21533F2089005056}UI/Textures/DeployMenu/Loadout-BG/Loadout_BG_USSR.imageset"
}
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{5414041A15DC4B54}Prefabs/ERR/FactionManager.et"
Configurations {
EntityTemplateResourceClass PC {}
EntityTemplateResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,13 @@
GenericEntity {
components {
MeshObject "{A557F6DC012E5101}" {
Object "{268DBDABB3C94AD7}Assets/Props/Military/Sandbags/Sandbag_01_short.xob"
Materials {
MaterialAssignClass "{A557F6DC012E5102}" { SourceMaterial "Sandbags_01_Short" AssignedMaterial "{31529E064AA7C917}Assets/Props/Military/Sandbags/Data/Sandbags_01_short_Burlap.emat" }
MaterialAssignClass "{A557F6DC012E5103}" { SourceMaterial "Sandbags_01_MLOD" AssignedMaterial "{2169E8EB73E1C127}Assets/Props/Military/Sandbags/Data/Sandbags_01_Burlap_MLOD.emat" }
}
}
RigidBody "{A557F6DC012E5104}" { ModelGeometry 1 Static 1 }
RplComponent "{A557F6DC012E5105}" {}
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et"
Configurations {
ResourceClass PC {}
ResourceClass HEADLESS : PC {}
}
}
@@ -0,0 +1,14 @@
ERR_GameMode : "{0F307326459A1395}Prefabs/MP/Modes/GameMode_Base.et" {
components {
SCR_GameOverScreenManagerComponent "{56DEC646298AD219}" {
m_GameOverScreenConfig SCR_GameOverScreenConfig "{56DEC6462FFDD82D}" : "{F6633922A25F9441}Configs/ERR/GameOverScreens.conf" {}
}
ERR_ScenarioComponent "{B78E910E88DE30D2}" {
m_Definition "{BC310DC0CD27F523}Configs/ERR/EveronSites.conf"
}
SCR_RespawnSystemComponent "{56B2B4793051E7C9}" {
m_SpawnLogic ERR_SpawnLogic "{79AD722B2EFD595F}" {}
}
}
m_bUseSpawnPreload 0
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{978E045E53EF36C8}Prefabs/ERR/GameMode.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,5 @@
GenericEntity : "{6D56FED1E55A8F84}Prefabs/Items/Misc/IntelligenceFolder_E_01/IntelligenceFolder_E_01.et" {
components {
ERR_DocumentComponent "{E4598D0BE2C251A3}" {}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{554159A6740C3F3E}Prefabs/ERR/ObjectiveDocument.et"
Configurations {
ResourceClass PC {
}
ResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,10 @@
SCR_AIGroup : "{E552DABF3636C2AD}Prefabs/Groups/OPFOR/Group_USSR_RifleSquad.et" {
m_aUnitPrefabSlots {
"{5436629450D8387A}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_SL.et"
"{612F43A4D5AE765F}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_RTO.et"
"{23ADBBC31B6A3DC6}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_AR.et"
"{1C78331E156A3D65}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_AT.et"
"{333DA6244C7DA34C}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_SR.et"
"{BF643BE4ADBDFDD3}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_LAT.et"
}
}
@@ -0,0 +1,7 @@
MetaFileClass {
Name "{D5A4D0D3AE1A3E26}Prefabs/ERR/Patrol.et"
Configurations {
EntityTemplateResourceClass PC {}
EntityTemplateResourceClass HEADLESS {}
}
}
@@ -0,0 +1,100 @@
GenericEntity {
components {
ERR_LocationComponent "{3B60CBEB3C04333C}" { m_Kind 0 }
Hierarchy "{30ADADE77D1EC3B8}" {}
RplComponent "{7FA5168C6FBD0C22}" {}
}
{
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "2879356FFFBE36E0"
coords -6 0 6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "C99EF9AFCEBA863D"
coords -3 0 6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "91048277F04ACA61"
coords 0 0 6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "8F2200A922FB0A6E"
coords 3 0 6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "3C442429BBCF5D2F"
coords -6 0 -6
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "9E855CC802C592CA"
coords 6 0 -6
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "778DAC42C9208F67"
coords -6 0 -3
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "7E350EB9053673C4"
coords 6 0 -3
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "AD17884D5EA37D37"
coords -6 0 0
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "1A157E23E1A995D8"
coords 6 0 0
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "3A78A6F3ACB7ACB1"
coords -6 0 3
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "275B472B565FFF6C"
coords 6 0 3
angles 0 -90 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "1B20E11B3FC7B59A"
coords -6 0 -6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "2735B52246DD6213"
coords -3 0 -6
angles 0 0 0
}
GenericEntity : "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et" {
ID "28101A0E33470B38"
coords 3 0 -6
angles 0 0 0
}
GenericEntity : "{C3FDC4DBCD58C2F5}Prefabs/ERR/PrisonDoor.et" {
ID "54D8F96ECC743335"
scale 1.5
coords 0 -0.1 -6
angles 0 0 0
}
DecalEntity : "{4AD6EB80A8B1FA3A}Prefabs/Decals/Dirt/Decal_Dirt_Patch_03.et" {
ID "FCFBA935CC13E016"
components {
Hierarchy "{EF4B704D646D1759}" {}
}
coords 0 0 0
angles -90 0 0
scale 10
FarPlane 1.5
}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{BE6B8D56349038A6}Prefabs/ERR/Prison.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,19 @@
GenericEntity : "{34B08027DBDE322A}Prefabs/Structures/BuildingParts/Doors/Door_MetalGate_01_Short_LEFT_EXT_ST_B.et" {
components {
DoorComponent "{221C667A9F7D4B69}" {
DoorAction ERR_PrisonDoorAction "{50888D3646A61640}" {
ParentContextList { "SCR_DoorUserAction" }
}
}
ActionsManagerComponent "{F32B667C3C823800}" {
ActionContexts { UserActionContext "{50888D37F529F720}" { ContextName "SCR_DoorUserAction" Radius 2 } }
additionalActions {
ERR_UnlockPrisonAction "{CA7918FEA177BFB8}" {
ParentContextList { "SCR_DoorUserAction" }
UIInfo SCR_ActionUIInfo "{F734C81E11AA6BEF}" { Name "Pick prison lock" }
Duration 4
}
}
}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{C3FDC4DBCD58C2F5}Prefabs/ERR/PrisonDoor.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}
@@ -0,0 +1,8 @@
GenericEntity {
components {
Hierarchy "{4F20A90EDBD625A4}" {}
MeshObject "{4F20A90EDBD625A1}" { Object "{0B96D139FEBCA006}Assets/Structures/Walls/Concrete/ConcreteWall_01_3m.xob" }
RigidBody "{4F20A90EDBD625A2}" { ModelGeometry 1 Static 1 }
RplComponent "{4F20A90EDBD625A3}" {}
}
}
@@ -0,0 +1,9 @@
MetaFileClass {
Name "{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et"
Configurations {
EntityTemplateResourceClass PC {
}
EntityTemplateResourceClass HEADLESS : PC {
}
}
}

Some files were not shown because too many files have changed in this diff Show More