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

191 lines
12 KiB
C

// Experimental immutable 18..20m direct leg. No HQ selection or global dispatch.
// Inheritance deliberately shares the proven controller's exclusive airframe
// registry, exact native ownership/health, signed-input startup and release paths.
class ERR_AircraftRouteFlight : ERR_AircraftFlight
{
protected ref ERR_AircraftRouteEnvelope m_Route;
protected vector m_LandingPoint;
protected bool m_RouteArrived;
bool Arrived() { return m_RouteArrived; }
vector Destination() { vector destination = "0 0 0"; if (m_Route) destination = m_Route.Destination(); return destination; }
vector LandingPoint() { return m_LandingPoint; }
bool StartRoute(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftRouteEnvelope route, int token, float deadline)
{
if (m_StartAttempted || m_Route || !route || !ERR_AircraftRouteRules.Length(vector.DistanceXZ(route.Home(), route.Destination()))) return false;
m_Route = route; m_TranslationRequested = true; m_TranslationTarget = route.Destination();
vector home = route.Home(); m_TranslationTarget[1] = home[1] + 8;
m_LandingPoint = route.Home();
return super.Start(mode, aircraft, group, pilot, seat, route, token, deadline);
}
// A derived controller cannot be started through the point-only public APIs.
override bool Start(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftFlightEnvelope qualification, int token, float deadline) { return false; }
override bool StartTranslation(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftFlightEnvelope qualification, int token, float deadline, vector target) { return false; }
protected bool RouteContains(vector position, float upright)
{
if (!m_Route || !m_Route.Contains(position, upright)) return false;
// Until the command actually enters translation, retain the original pad cap.
if (m_State < ERR_AircraftFlightState.LANDING && !m_TranslationStarted)
return vector.DistanceXZ(position, m_Home) < 8;
return true;
}
protected override bool Grounded()
{
if (!super.Grounded()) return false;
if (m_State == ERR_AircraftFlightState.LANDING || m_State == ERR_AircraftFlightState.SHUTDOWN)
return vector.DistanceXZ(m_Aircraft.GetOrigin(), m_LandingPoint) <= 0.75;
return true;
}
protected override bool Landing(float now)
{
if (!Owns() || !m_Route || !m_Route.Contains(m_Aircraft.GetOrigin(), 1)) { Fail("route landing ownership or corridor unavailable"); return false; }
// Normal destination is immutable. Cancellation freezes the actual landing
// centre once inside the fully qualified corridor, never rebases the route.
if (m_Cancelled || !m_RouteArrived) m_LandingPoint = m_Aircraft.GetOrigin();
else m_LandingPoint = m_Route.Destination();
m_LandingPoint[1] = m_Home[1];
return super.Landing(now);
}
protected override bool BeginTranslation(float now, vector position)
{
if (!m_Route || !m_TranslationRequested || m_TranslationStarted || m_State != ERR_AircraftFlightState.CLIMB) { Cancel("route stage ownership refused"); return false; }
SignalsManagerComponent signals = m_Controller.GetSignalsManager();
if (!signals || signals.FindSignal("MainRotorCyclicForward") < 0 || signals.FindSignal("MainRotorCyclicAside") < 0 || !m_Controller.GetAutohoverEnabled()) { Cancel("native route cyclic signals unavailable"); return false; }
m_TranslationOrigin = position; m_TranslationDistance = vector.DistanceXZ(position, m_Route.Destination());
m_TranslationStarted = true; m_State = ERR_AircraftFlightState.TRANSLATING;
m_Stable = -1; m_StageDeadline = Math.Min(m_Deadline, now + 160000);
PrintFormat("ERR AIRCRAFT ROUTE BEGIN: token=%1 home=%2 destination=%3 actualOrigin=%4 distance=%5 deadline=%6", m_Token, m_Home, m_Route.Destination(), position, m_TranslationDistance, m_StageDeadline);
return true;
}
protected override void CheckTranslation(float now, vector position, float height, float speed, float upright)
{
if (!m_Route || !m_TranslationStarted || !m_Controller.GetAutohoverEnabled()) { Cancel("route ownership or autohover lost"); return; }
vector velocity = m_Simulation.GetVelocity(); vector horizontal = velocity; horizontal[1] = 0;
bool held = m_TranslationUpdates >= 3 && ERR_AircraftRouteRules.Held(m_Simulation.HasAnyGroundContact(), height, speed, velocity[1], upright, horizontal.Length(), vector.DistanceXZ(position, m_Route.Destination()));
if (!held) { m_Stable = -1; return; }
if (m_Stable < 0) m_Stable = now;
if (now - m_Stable < 5000) return;
m_RouteArrived = true; m_TranslationProven = true;
PrintFormat("ERR AIRCRAFT ROUTE HELD: token=%1 targetError=%2 displacement=%3 updates=%4", m_Token, vector.DistanceXZ(position, m_Route.Destination()), vector.DistanceXZ(position, m_TranslationOrigin), m_TranslationUpdates);
Landing(now);
}
protected override 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 (!RouteContains(transform[3], transform[1][1])) { Fail("control callback exceeded qualified flight envelope"); return false; }
return true;
}
override 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 || m_State == ERR_AircraftFlightState.LANDING)
{
if (!m_Route) { Fail("immutable route unavailable"); return; }
vector target = m_Route.Destination(); if (m_State == ERR_AircraftFlightState.LANDING) target = m_LandingPoint;
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
vector demand = ERR_AircraftTranslationRules.Demand(target, 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);
if (m_State == ERR_AircraftFlightState.TRANSLATING) m_TranslationUpdates++;
}
if (m_State == ERR_AircraftFlightState.LANDING && Grounded()) { collective = -1; forward = 0; aside = 0; }
}
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();
}
override 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 (!RouteContains(transform[3], 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();
}
}
}