Files
EscapistsReintroduced/addons/ai/Scripts/Game/ERR/ERR_NativeAircraftControlProbe.c
T

369 lines
22 KiB
C

#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;
// Observation only: the production bridge owns the sole control dispatch.
m_PilotHooks++;
}
void PrepareHelicopter(HelicopterControllerComponent controller)
{
if (m_Stopped || !m_Flight || controller != m_Controller) return;
// Observation only: super-chain order cannot cause a second control write.
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