|
|
|
@@ -5,8 +5,9 @@ modded class SCR_CharacterControllerComponent
|
|
|
|
|
{
|
|
|
|
|
override void OnPrepareControls(IEntity owner, ActionManager am, float dt, bool player)
|
|
|
|
|
{
|
|
|
|
|
super.OnPrepareControls(owner, am, dt, player);
|
|
|
|
|
ERR_NativePoweredFlightProbe probe = ERR_NativePoweredFlightProbe.Current();
|
|
|
|
|
if (probe && !player) probe.PreparePilotActions(this, owner, am, dt);
|
|
|
|
|
super.OnPrepareControls(owner, am, dt, player);
|
|
|
|
|
if (probe && !player) probe.ObservePilotPreparation(this, owner);
|
|
|
|
|
}
|
|
|
|
|
protected override void OnApplyControls(IEntity owner, float timeSlice)
|
|
|
|
@@ -78,11 +79,24 @@ class ERR_PoweredFlightRules
|
|
|
|
|
}
|
|
|
|
|
static float NativeCollective(float signedDemand)
|
|
|
|
|
{
|
|
|
|
|
// The native apply stage clamps negative collective to zero (100422).
|
|
|
|
|
// Keep the feedback controller's signed state; encode its final demand in
|
|
|
|
|
// the nonnegative native collective domain, unlike signed cyclic/torque.
|
|
|
|
|
// Preserve the legacy Apply experiment unchanged. Later observations showed
|
|
|
|
|
// overwritten positive inputs too, so this is not a proven native domain.
|
|
|
|
|
return (ClampInput(signedDemand) + 1) * 0.5;
|
|
|
|
|
}
|
|
|
|
|
static float CollectiveCommand(float signedDemand, bool actions, int stage)
|
|
|
|
|
{
|
|
|
|
|
// Preserve pre-engine/startup zero and the old Apply comparison branch.
|
|
|
|
|
if (!actions) return NativeCollective(signedDemand);
|
|
|
|
|
if (stage == 9) return 0.05;
|
|
|
|
|
if (stage == 10) return -0.05;
|
|
|
|
|
if (stage == 7) return -1; // keep minimum pitch while the stopped engine's rotors coast
|
|
|
|
|
if (stage >= 4 && stage <= 6) return ClampInput(signedDemand);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
static bool NegativeCollectiveObserved(float command, float signal)
|
|
|
|
|
{
|
|
|
|
|
return command >= -0.06 && command <= -0.04 && Math.AbsFloat(signal - command) <= 0.01;
|
|
|
|
|
}
|
|
|
|
|
static bool CollectiveObserved(float command, float signal)
|
|
|
|
|
{
|
|
|
|
|
return command >= 0.04 && command <= 0.06 && Math.AbsFloat(signal - command) <= 0.01;
|
|
|
|
@@ -134,6 +148,9 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
protected int m_PilotPreparations;
|
|
|
|
|
protected int m_PositiveGateUpdates;
|
|
|
|
|
protected float m_PositiveSignalSince = -1;
|
|
|
|
|
protected int m_NegativeGateUpdates;
|
|
|
|
|
protected float m_NegativeSignalSince = -1;
|
|
|
|
|
protected bool m_SignedCollectiveProven;
|
|
|
|
|
protected int m_HelicopterUpdates;
|
|
|
|
|
protected bool m_PilotSpawnRequested;
|
|
|
|
|
protected AIAgent m_PilotAgent;
|
|
|
|
@@ -147,6 +164,14 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
protected bool m_Stopped;
|
|
|
|
|
protected bool m_Complete;
|
|
|
|
|
protected bool m_ReadbackOK = true;
|
|
|
|
|
protected bool m_ActionInputs;
|
|
|
|
|
protected bool m_Started;
|
|
|
|
|
protected bool m_AutohoverMode;
|
|
|
|
|
protected bool m_AutohoverSaved;
|
|
|
|
|
protected bool m_OriginalAutohover;
|
|
|
|
|
protected HelicopterControllerComponent m_AutohoverController;
|
|
|
|
|
protected ActionManager m_OwnedActions;
|
|
|
|
|
protected int m_ActionContextSamples;
|
|
|
|
|
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";
|
|
|
|
@@ -158,10 +183,48 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
static ERR_NativePoweredFlightProbe Current() { return s_Current; }
|
|
|
|
|
void ERR_NativePoweredFlightProbe(ERR_GameMode mode)
|
|
|
|
|
{
|
|
|
|
|
string actionInputs;
|
|
|
|
|
m_ActionInputs = System.GetCLIParam("errFlightActionInputs", actionInputs) && actionInputs == "1";
|
|
|
|
|
m_Mode = mode; m_Deadline = GetGame().GetWorld().GetWorldTime() + 240000;
|
|
|
|
|
if (Enabled() && !s_Current) s_Current = this;
|
|
|
|
|
else m_Stopped = true;
|
|
|
|
|
}
|
|
|
|
|
// Fixture configuration only: caller must select this before the first Tick.
|
|
|
|
|
bool SetAutohoverMode(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
if (m_Started || m_Stopped || m_Complete || m_Bound || m_Aircraft) return false;
|
|
|
|
|
m_AutohoverMode = enabled;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
protected bool BeginAutohover()
|
|
|
|
|
{
|
|
|
|
|
if (!m_AutohoverMode) return true;
|
|
|
|
|
if (m_AutohoverSaved || !m_Bound || !PoweredAuthority() || !m_Controller || m_Controller.GetOwner() != m_Aircraft) return false;
|
|
|
|
|
m_AutohoverController = m_Controller;
|
|
|
|
|
m_OriginalAutohover = m_Controller.GetAutohoverEnabled();
|
|
|
|
|
m_AutohoverSaved = true;
|
|
|
|
|
m_Controller.SetAutohoverEnabled(true);
|
|
|
|
|
return m_Controller.GetAutohoverEnabled();
|
|
|
|
|
}
|
|
|
|
|
protected bool AutohoverReady()
|
|
|
|
|
{
|
|
|
|
|
if (!m_AutohoverMode) return true;
|
|
|
|
|
// Native autohover also rewrites collective. Prove direct input consumption
|
|
|
|
|
// first, then enable its controller only for the bounded flight phase.
|
|
|
|
|
if (!m_AutohoverSaved && (m_Stage == 2 || m_Stage == 3 || m_Stage == 9 || m_Stage == 10)) return true;
|
|
|
|
|
return m_AutohoverSaved && m_AutohoverController && m_AutohoverController == m_Controller && m_Controller.GetOwner() == m_Aircraft && m_Controller.GetAutohoverEnabled();
|
|
|
|
|
}
|
|
|
|
|
protected void RestoreAutohover(bool ownedSeat)
|
|
|
|
|
{
|
|
|
|
|
if (ownedSeat && m_AutohoverSaved && m_AutohoverController && m_AutohoverController == m_Controller && m_Controller.GetOwner() == m_Aircraft)
|
|
|
|
|
{
|
|
|
|
|
m_Controller.SetAutohoverEnabled(m_OriginalAutohover);
|
|
|
|
|
PrintFormat("ERR POWERED AUTOHOVER RESTORE: original=%1 observed=%2", m_OriginalAutohover, m_Controller.GetAutohoverEnabled());
|
|
|
|
|
if (m_Controller.GetAutohoverEnabled() != m_OriginalAutohover) Print("ERR SMOKE FAIL: powered-flight original owned autohover state restored", LogLevel.ERROR);
|
|
|
|
|
}
|
|
|
|
|
m_AutohoverSaved = false;
|
|
|
|
|
m_AutohoverController = null;
|
|
|
|
|
}
|
|
|
|
|
protected bool Check(bool condition, string detail)
|
|
|
|
|
{
|
|
|
|
|
if (condition) { Print("ERR SMOKE PASS: powered-flight " + detail); return true; }
|
|
|
|
@@ -200,22 +263,24 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
// Override only this owned fixture controller, never global/user settings.
|
|
|
|
|
controller.SetCollectiveMode(CollectiveMode.Direct); m_HelicopterUpdates++;
|
|
|
|
|
}
|
|
|
|
|
bool UsesActionInputs() { return m_ActionInputs; }
|
|
|
|
|
void ObservePilotPreparation(CharacterControllerComponent controller, IEntity owner)
|
|
|
|
|
{
|
|
|
|
|
if (!m_Bound || controller != m_Character || owner != m_Pilot || !PoweredAuthority()) return;
|
|
|
|
|
m_PilotPreparations++;
|
|
|
|
|
}
|
|
|
|
|
// Native prepare inputs are overwritten before application (101342).
|
|
|
|
|
// Integrate and write once here, after vanilla OnApplyControls, never in both.
|
|
|
|
|
void ApplyPilot(CharacterControllerComponent controller, IEntity owner, float dt)
|
|
|
|
|
// Both selectable input paths share one computation. Only the selected
|
|
|
|
|
// path integrates per callback; neither path invokes the other writer.
|
|
|
|
|
protected bool ComputePilotDemand(CharacterControllerComponent controller, IEntity owner, float dt, out float collective, out vector axes)
|
|
|
|
|
{
|
|
|
|
|
if (!m_Bound || controller != m_Character || owner != m_Pilot || !PoweredAuthority() || !m_Input || !m_Simulation || dt <= 0 || dt > 0.2) return;
|
|
|
|
|
if (!m_Bound || controller != m_Character || owner != m_Pilot || !PoweredAuthority() || !AutohoverReady() || !m_Input || !m_Simulation || dt <= 0 || dt > 0.2) return false;
|
|
|
|
|
if (m_ActionInputs && m_Stage >= 4 && m_Stage <= 6 && !m_SignedCollectiveProven) return false;
|
|
|
|
|
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
|
|
|
|
|
vector velocity = m_Simulation.GetVelocity();
|
|
|
|
|
vector angular = m_Simulation.GetAngularVelocityLocal();
|
|
|
|
|
float height = transform[3][1] - m_Home[1];
|
|
|
|
|
float forward, aside, torque;
|
|
|
|
|
float collective = -1;
|
|
|
|
|
collective = -1;
|
|
|
|
|
if (m_Stage == 9) collective = -0.9; // native0.05, bounded signal-consumption proof
|
|
|
|
|
if (m_Stage >= 4 && m_Stage <= 6 && m_Controller.IsEngineOn())
|
|
|
|
|
{
|
|
|
|
@@ -224,18 +289,73 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
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);
|
|
|
|
|
vector offset = m_Home - transform[3]; offset[1] = 0;
|
|
|
|
|
vector acceleration = offset * 0.08 - velocity * 0.3; acceleration[1] = 0;
|
|
|
|
|
// Native105226 already brakes before8m refusal; increase only velocity damping.
|
|
|
|
|
vector acceleration = offset * 0.08 - velocity * 0.6; acceleration[1] = 0;
|
|
|
|
|
// Small local cyclic corrections counter drift/tilt through native inputs.
|
|
|
|
|
// Sign/gain and native Direct-mode response remain experimental evidence.
|
|
|
|
|
forward = ERR_PoweredFlightRules.ClampInput((vector.Dot(acceleration, transform[2]) / 9.81 + transform[2][1]) * 0.8 - angular[0] * 0.3, 0.15);
|
|
|
|
|
aside = ERR_PoweredFlightRules.ClampInput((vector.Dot(acceleration, transform[0]) / 9.81 + transform[0][1]) * 0.8 + angular[2] * 0.3, 0.15);
|
|
|
|
|
float headingError = m_Heading - m_Aircraft.GetAngles()[0];
|
|
|
|
|
forward = ERR_PoweredFlightRules.ClampInput((vector.Dot(acceleration, transform[2]) / 9.81 + transform[2][1]) * 0.8 - angular[0] * 1.2, 0.15);
|
|
|
|
|
aside = ERR_PoweredFlightRules.ClampInput((vector.Dot(acceleration, transform[0]) / 9.81 + transform[0][1]) * 0.8 + angular[2] * 1.2, 0.15);
|
|
|
|
|
// Native autohover also adjusts collective; observe its physical response.
|
|
|
|
|
// Give its lateral controller zero custom cyclic in this experiment.
|
|
|
|
|
// Preserve collective/yaw and bounds; never combine two cyclic controllers.
|
|
|
|
|
if (m_AutohoverMode) { forward = 0; aside = 0; }
|
|
|
|
|
float headingError = m_Heading - m_Aircraft.GetYawPitchRoll()[0];
|
|
|
|
|
while (headingError > 180) headingError -= 360;
|
|
|
|
|
while (headingError < -180) headingError += 360;
|
|
|
|
|
torque = ERR_PoweredFlightRules.ClampInput(headingError * 0.01 - angular[1] * 0.3, 0.3);
|
|
|
|
|
if (m_Stage == 6 && ERR_PoweredFlightRules.Landed(m_Simulation.HasAnyGroundContact(), height, velocity.Length(), angular.Length(), transform[1][1])) collective = -1;
|
|
|
|
|
}
|
|
|
|
|
collective = ERR_PoweredFlightRules.NativeCollective(collective);
|
|
|
|
|
collective = ERR_PoweredFlightRules.CollectiveCommand(collective, m_ActionInputs, m_Stage);
|
|
|
|
|
axes = Vector(forward, aside, torque);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
protected bool WriteActionPair(ActionManager am, string positive, string negative, float demand)
|
|
|
|
|
{
|
|
|
|
|
float a = Math.Clamp(demand, 0, 1);
|
|
|
|
|
float b = Math.Clamp(-demand, 0, 1);
|
|
|
|
|
am.SetActionValue(positive, a); am.SetActionValue(negative, b);
|
|
|
|
|
return Math.AbsFloat(am.GetActionValue(positive) - a) < 0.001 && Math.AbsFloat(am.GetActionValue(negative) - b) < 0.001;
|
|
|
|
|
}
|
|
|
|
|
protected bool OwnActionManager(ActionManager am)
|
|
|
|
|
{
|
|
|
|
|
// Read the global manager only to reject shared ownership. Never write it.
|
|
|
|
|
return am && m_PilotAgent && am == m_PilotAgent.GetActionManager() && am != GetGame().GetInputManager();
|
|
|
|
|
}
|
|
|
|
|
void PreparePilotActions(CharacterControllerComponent controller, IEntity owner, ActionManager am, float dt)
|
|
|
|
|
{
|
|
|
|
|
if (!m_ActionInputs || !m_Bound || controller != m_Character || owner != m_Pilot || !PoweredAuthority()) return;
|
|
|
|
|
if (!OwnActionManager(am)) { m_ReadbackOK = false; return; }
|
|
|
|
|
m_OwnedActions = am;
|
|
|
|
|
// Same short activation pattern used by vanilla game.c; this manager belongs
|
|
|
|
|
// solely to the fixture AI, never to global hardware or the human player.
|
|
|
|
|
// Native104256 reports false from ActivateContext while already active.
|
|
|
|
|
// Treat observed active state as authority, not an undocumented return meaning.
|
|
|
|
|
bool alreadyActive = am.IsContextActive("HelicopterContext");
|
|
|
|
|
bool activated;
|
|
|
|
|
if (!alreadyActive) activated = am.ActivateContext("HelicopterContext", 1);
|
|
|
|
|
bool contextActive = am.IsContextActive("HelicopterContext");
|
|
|
|
|
if (m_ActionContextSamples < 4)
|
|
|
|
|
{
|
|
|
|
|
m_ActionContextSamples++;
|
|
|
|
|
PrintFormat("ERR POWERED ACTION CONTEXT: sample=%1 activated=%2 active=%3 dt=%4 input=%5 simulation=%6", m_ActionContextSamples, activated, contextActive, dt, m_Input != null, m_Simulation != null);
|
|
|
|
|
}
|
|
|
|
|
if (!contextActive) { m_ReadbackOK = false; return; }
|
|
|
|
|
float collective; vector axes;
|
|
|
|
|
if (!ComputePilotDemand(controller, owner, dt, collective, axes)) return;
|
|
|
|
|
bool a = WriteActionPair(am, "HelicopterCollectiveIncrease", "HelicopterCollectiveDecrease", collective);
|
|
|
|
|
bool b = WriteActionPair(am, "HelicopterCyclicForward", "HelicopterCyclicBack", axes[0]);
|
|
|
|
|
bool c = WriteActionPair(am, "HelicopterCyclicRight", "HelicopterCyclicLeft", axes[1]);
|
|
|
|
|
bool d = WriteActionPair(am, "HelicopterAntiTorqueRight", "HelicopterAntiTorqueLeft", axes[2]);
|
|
|
|
|
m_ReadbackOK = m_ReadbackOK && a && b && c && d;
|
|
|
|
|
m_ControlUpdates++; m_LastControl = GetGame().GetWorld().GetWorldTime();
|
|
|
|
|
}
|
|
|
|
|
void ApplyPilot(CharacterControllerComponent controller, IEntity owner, float dt)
|
|
|
|
|
{
|
|
|
|
|
// The experimental actor-action path and old context path never both write.
|
|
|
|
|
if (m_ActionInputs) return;
|
|
|
|
|
float collective; vector axes;
|
|
|
|
|
if (!ComputePilotDemand(controller, owner, dt, collective, axes)) return;
|
|
|
|
|
float forward = axes[0]; float aside = axes[1]; float torque = axes[2];
|
|
|
|
|
m_Input.SetVehicleThrottle(1);
|
|
|
|
|
m_Input.SetVehicleCollective(collective);
|
|
|
|
|
m_Input.SetVehicleCyclicForward(forward);
|
|
|
|
@@ -294,8 +414,20 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
{
|
|
|
|
|
if (!m_Simulation || !m_Aircraft || now < m_NextLog) return;
|
|
|
|
|
m_NextLog = now + 2000;
|
|
|
|
|
if (m_Stage == 4 && now < m_StageDeadline - 57000) m_NextLog = now + 250;
|
|
|
|
|
if (m_ActionInputs)
|
|
|
|
|
{
|
|
|
|
|
bool ownedActions = OwnActionManager(m_OwnedActions);
|
|
|
|
|
bool contextActive = m_OwnedActions && m_OwnedActions.IsContextActive("HelicopterContext");
|
|
|
|
|
PrintFormat("ERR POWERED ACTION MANAGER: selected=1 exactOwnedNonGlobal=%1 contextActive=%2 virtualWrites=%3", ownedActions, contextActive, m_ControlUpdates);
|
|
|
|
|
}
|
|
|
|
|
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
|
|
|
|
|
PrintFormat("ERR POWERED AUTOHOVER: selected=%1 saved=%2 enabled=%3 exactController=%4", m_AutohoverMode, m_AutohoverSaved, m_Controller.GetAutohoverEnabled(), m_AutohoverController == m_Controller);
|
|
|
|
|
PrintFormat("ERR POWERED FLIGHT: stage=%1 height=%2 drift=%3 speed=%4 vertical=%5 up=%6 groundContact=%7 fuel=%8 engine=%9", m_Stage, transform[3][1] - m_Home[1], vector.DistanceXZ(transform[3], m_Home), m_Simulation.GetVelocity().Length(), m_Simulation.GetVelocity()[1], transform[1][1], m_Simulation.HasAnyGroundContact(), m_Fuel.GetTotalFuel(), m_Controller.IsEngineOn());
|
|
|
|
|
vector rotorAxis; m_Simulation.RotorGetAxis(0, rotorAxis);
|
|
|
|
|
PrintFormat("ERR POWERED HEADING: desiredYaw=%1 actualYawPitchRoll=%2", m_Heading, m_Aircraft.GetYawPitchRoll());
|
|
|
|
|
PrintFormat("ERR POWERED ROTOR AXIS: local=%1 acceleration=%2", rotorAxis, m_Simulation.GetAcceleration());
|
|
|
|
|
PrintFormat("ERR POWERED MOTION: offset=%1 velocity=%2 angularLocal=%3 forward=%4 right=%5", m_Home - transform[3], m_Simulation.GetVelocity(), m_Simulation.GetAngularVelocityLocal(), transform[2], transform[0]);
|
|
|
|
|
PrintFormat("ERR POWERED INPUT: pilotHooks=%1 vehicleHooks=%2 readback=%3 collective=%4 cyclicF=%5 cyclicR=%6 antiTorque=%7 rotorRPM=%8 targetRPM=%9", m_ControlUpdates, m_HelicopterUpdates, m_ReadbackOK, m_Input.GetVehicleCollective(), m_Input.GetVehicleCyclicForward(), m_Input.GetVehicleCyclicAside(), m_Input.GetVehicleAntiTorque(), m_Simulation.RotorGetRPM(0), m_Simulation.RotorGetRPMTarget(0));
|
|
|
|
|
}
|
|
|
|
|
protected bool PollNativeFlightComponents(float now)
|
|
|
|
@@ -378,14 +510,24 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
if (m_Stopped) return;
|
|
|
|
|
// Input cleanup is confined to the same unclaimed test pilot/aircraft.
|
|
|
|
|
bool ours = m_Bound && OwnPilot() && !PlayerPresent();
|
|
|
|
|
bool ownSeat = ours && m_Seat && m_Seat.GetOccupant() == m_Pilot && m_Controller && m_Controller.GetPilotCompartmentSlot() == m_Seat && CompartmentAccessComponent.GetVehicleIn(m_Pilot) == m_Aircraft;
|
|
|
|
|
RestoreAutohover(ownSeat);
|
|
|
|
|
m_Stopped = true; m_Bound = false;
|
|
|
|
|
if (s_Current == this) s_Current = null;
|
|
|
|
|
if (ours && m_Input)
|
|
|
|
|
if (ours && m_ActionInputs && OwnActionManager(m_OwnedActions))
|
|
|
|
|
{
|
|
|
|
|
WriteActionPair(m_OwnedActions, "HelicopterCollectiveIncrease", "HelicopterCollectiveDecrease", 0);
|
|
|
|
|
WriteActionPair(m_OwnedActions, "HelicopterCyclicForward", "HelicopterCyclicBack", 0);
|
|
|
|
|
WriteActionPair(m_OwnedActions, "HelicopterCyclicRight", "HelicopterCyclicLeft", 0);
|
|
|
|
|
WriteActionPair(m_OwnedActions, "HelicopterAntiTorqueRight", "HelicopterAntiTorqueLeft", 0);
|
|
|
|
|
}
|
|
|
|
|
if (ours && !m_ActionInputs && m_Input)
|
|
|
|
|
{
|
|
|
|
|
m_Input.SetVehicleCollective(0); m_Input.SetVehicleThrottle(0);
|
|
|
|
|
m_Input.SetVehicleCyclicForward(0); m_Input.SetVehicleCyclicAside(0); m_Input.SetVehicleAntiTorque(0);
|
|
|
|
|
if (m_Controller && m_ModeSaved) m_Controller.SetCollectiveMode(m_OriginalMode);
|
|
|
|
|
}
|
|
|
|
|
if (ours && m_Controller && m_ModeSaved) m_Controller.SetCollectiveMode(m_OriginalMode);
|
|
|
|
|
m_OwnedActions = null;
|
|
|
|
|
if (m_PilotLODSaved && m_PilotAgent && OwnPilot() && !PlayerPresent()) m_PilotAgent.SetPermanentLOD(m_OriginalPilotLOD);
|
|
|
|
|
if (m_Group && m_Group.GetPlayerCount(true) == 0) m_Group.SetPermanentLOD(-1);
|
|
|
|
|
// Keep real airborne/failed test actors for diagnosis and mode-owned cleanup.
|
|
|
|
@@ -395,8 +537,9 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
{
|
|
|
|
|
if (m_Complete) return 1;
|
|
|
|
|
if (m_Stopped) return -1;
|
|
|
|
|
m_Started = true;
|
|
|
|
|
if (!Enabled() || !m_Mode || now >= m_Deadline || ERR_MissionRules.Terminal(m_Mode.Phase())) { Check(false, "server fixture active within 240-second bound"); return -1; }
|
|
|
|
|
if (m_PreviousTick >= 0 && (now < m_PreviousTick || now - m_PreviousTick > 1500)) { m_Stable = -1; m_PositiveSignalSince = -1; }
|
|
|
|
|
if (m_PreviousTick >= 0 && (now < m_PreviousTick || now - m_PreviousTick > 1500)) { m_Stable = -1; m_PositiveSignalSince = -1; m_NegativeSignalSince = -1; }
|
|
|
|
|
m_PreviousTick = now;
|
|
|
|
|
if (m_Stage == 0)
|
|
|
|
|
{
|
|
|
|
@@ -448,13 +591,13 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
m_Character = CharacterControllerComponent.Cast(m_Pilot.FindComponent(CharacterControllerComponent));
|
|
|
|
|
if (m_Character) m_Input = m_Character.GetInputContext();
|
|
|
|
|
if (!Check(m_Input && m_Group.GetAgentsCount() == 1 && PoweredAuthority(), "one living native AI pilot physically occupies its own fueled helicopter")) return -1;
|
|
|
|
|
m_Home = m_Aircraft.GetOrigin(); m_Heading = m_Aircraft.GetAngles()[0];
|
|
|
|
|
m_Home = m_Aircraft.GetOrigin(); m_Heading = m_Aircraft.GetYawPitchRoll()[0];
|
|
|
|
|
m_OriginalMode = m_Controller.GetCollectiveMode(); m_ModeSaved = true;
|
|
|
|
|
m_InitialFuel = m_Fuel.GetTotalFuel(); m_Bound = true;
|
|
|
|
|
m_Stage = 2; m_StageDeadline = now + 5000; return 0;
|
|
|
|
|
}
|
|
|
|
|
Diagnostic(now);
|
|
|
|
|
if (!PoweredAuthority() || !m_ReadbackOK) { Check(false, "current owned conscious pilot, fuel, rotors and exact input readback remain valid"); return -1; }
|
|
|
|
|
if (!PoweredAuthority() || !m_ReadbackOK || !AutohoverReady()) { Check(false, "current owned conscious pilot, fuel, rotors, selected autohover and exact input readback remain valid"); return -1; }
|
|
|
|
|
if (m_Stage == 2)
|
|
|
|
|
{
|
|
|
|
|
if (now >= m_StageDeadline) { Check(false, "native AI input and helicopter preparation hooks execute before engine start"); return -1; }
|
|
|
|
@@ -487,7 +630,7 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
m_PositiveGateUpdates = m_ControlUpdates; m_PositiveSignalSince = -1;
|
|
|
|
|
m_Stage = 9; m_StageDeadline = now + 3000; return 0;
|
|
|
|
|
}
|
|
|
|
|
if (((m_Stage >= 4 && m_Stage <= 6) || m_Stage == 9) && !m_Controller.IsEngineOn()) { Check(false, "powered flight retains real running engine"); return -1; }
|
|
|
|
|
if (((m_Stage >= 4 && m_Stage <= 6) || m_Stage == 9 || m_Stage == 10) && !m_Controller.IsEngineOn()) { Check(false, "powered flight retains real running engine"); return -1; }
|
|
|
|
|
if (m_Stage == 9)
|
|
|
|
|
{
|
|
|
|
|
if (now >= m_StageDeadline) { Check(false, "positive native collective reaches actual rotor signal before climb within three seconds"); return -1; }
|
|
|
|
@@ -502,6 +645,31 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
if (m_PositiveSignalSince < 0) m_PositiveSignalSince = now;
|
|
|
|
|
if (now - m_PositiveSignalSince < 500) return 0;
|
|
|
|
|
if (!Check(true, "positive collective persists in existing native rotor signal across actual ticks before climb")) return -1;
|
|
|
|
|
if (m_ActionInputs)
|
|
|
|
|
{
|
|
|
|
|
m_NegativeGateUpdates = m_ControlUpdates; m_NegativeSignalSince = -1;
|
|
|
|
|
m_Stage = 10; m_StageDeadline = now + 3000; return 0;
|
|
|
|
|
}
|
|
|
|
|
if (!Check(BeginAutohover(), "selected native autohover enabled after direct collective consumption proof; exact owned controller readback")) return -1;
|
|
|
|
|
m_Collective = -0.8; m_Stage = 4; m_StageDeadline = now + 65000; m_Stable = -1; return 0;
|
|
|
|
|
}
|
|
|
|
|
if (m_Stage == 10)
|
|
|
|
|
{
|
|
|
|
|
if (now >= m_StageDeadline) { Check(false, "negative actor collective reaches actual rotor signal before climb within three seconds"); return -1; }
|
|
|
|
|
if (!Check(m_ActionInputs && m_Controller.GetCollectiveMode() == CollectiveMode.Direct && !m_Controller.GetAutohoverEnabled(), "negative consumption proof uses owned actor actions in Direct mode with autohover off")) return -1;
|
|
|
|
|
SignalsManagerComponent negativeSignals = m_Controller.GetSignalsManager();
|
|
|
|
|
int negativeIndex = -1;
|
|
|
|
|
if (negativeSignals) negativeIndex = negativeSignals.FindSignal("MainRotorCollective");
|
|
|
|
|
if (!Check(negativeIndex >= 0, "existing native main rotor collective signal available for negative proof")) return -1;
|
|
|
|
|
float negativeObserved = negativeSignals.GetSignalValue(negativeIndex);
|
|
|
|
|
bool negativeMatched = m_ControlUpdates >= m_NegativeGateUpdates + 3 && ERR_PoweredFlightRules.NegativeCollectiveObserved(m_Input.GetVehicleCollective(), negativeObserved);
|
|
|
|
|
PrintFormat("ERR POWERED NEGATIVE COLLECTIVE GATE: command=%1 rotorSignal=%2 actionUpdates=%3 matched=%4", m_Input.GetVehicleCollective(), negativeObserved, m_ControlUpdates - m_NegativeGateUpdates, negativeMatched);
|
|
|
|
|
if (!negativeMatched) { m_NegativeSignalSince = -1; return 0; }
|
|
|
|
|
if (m_NegativeSignalSince < 0) m_NegativeSignalSince = now;
|
|
|
|
|
if (now - m_NegativeSignalSince < 500) return 0;
|
|
|
|
|
if (!Check(true, "negative actor collective persists in existing native rotor signal across actual ticks before signed flight demand")) return -1;
|
|
|
|
|
m_SignedCollectiveProven = true;
|
|
|
|
|
if (!Check(BeginAutohover(), "selected native autohover enabled only after both direct collective consumption proofs")) return -1;
|
|
|
|
|
m_Collective = -0.8; m_Stage = 4; m_StageDeadline = now + 65000; m_Stable = -1; return 0;
|
|
|
|
|
}
|
|
|
|
|
if (m_Stage == 4 || m_Stage == 5)
|
|
|
|
@@ -532,6 +700,7 @@ class ERR_NativePoweredFlightProbe
|
|
|
|
|
{
|
|
|
|
|
if (now >= m_StageDeadline) { Check(false, "normal engine shutdown completed on actual ground"); return -1; }
|
|
|
|
|
if (m_Controller.IsEngineOn()) return 0;
|
|
|
|
|
PrintFormat("ERR POWERED SHUTDOWN: height=%1 speed=%2 angular=%3 up=%4 contact=%5 authority=%6 collective=%7 rotorRPM=%8", height, speed, m_Simulation.GetAngularVelocity().Length(), transform[1][1], m_Simulation.HasAnyGroundContact(), PoweredAuthority(), m_Input.GetVehicleCollective(), m_Simulation.RotorGetRPM(0));
|
|
|
|
|
if (!Check(ERR_PoweredFlightRules.Landed(m_Simulation.HasAnyGroundContact(), height, speed, m_Simulation.GetAngularVelocity().Length(), transform[1][1]) && PoweredAuthority(), "native engine off after contact landing; no transform/velocity/health/fuel correction")) return -1;
|
|
|
|
|
Stop(); m_Complete = true; return 1;
|
|
|
|
|
}
|
|
|
|
|