193 lines
13 KiB
C
193 lines
13 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;
|
|
if (m_State == ERR_AircraftFlightState.TRANSLATING) demand = ERR_AircraftRouteRules.Demand(target, transform[3], m_Simulation.GetVelocity());
|
|
else 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();
|
|
}
|
|
}
|
|
}
|