Add typed field-flight prototype and preserve native qualification failures
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// Experimental single immutable100m field only. No route tiling or fallback.
|
||||
// Inherits the original exact-instance, single-use, two-second claim contract.
|
||||
class ERR_AircraftFieldEnvelope : ERR_AircraftFlightEnvelope
|
||||
{
|
||||
static const int MAX_TERRAIN_NODES = 4096;
|
||||
protected vector m_Destination;
|
||||
vector Destination() { return m_Destination; }
|
||||
bool Contains(vector position, float upright) { return ERR_AircraftFieldRules.Contains(m_Home, m_Destination, position, upright); }
|
||||
static bool FinitePoint(vector point)
|
||||
{
|
||||
for (int axis = 0; axis < 3; axis++)
|
||||
if (!(Math.AbsFloat(point[axis]) <= 1000000)) return false;
|
||||
return true;
|
||||
}
|
||||
static int TerrainColumns(float distance)
|
||||
{
|
||||
if (!ERR_AircraftFieldRules.Length(distance)) return 0;
|
||||
return Math.Ceil((distance + 80) / 2);
|
||||
}
|
||||
static bool TerrainBudget(int columns, int rows)
|
||||
{
|
||||
if (columns < 1 || rows < 1 || columns >= MAX_TERRAIN_NODES || rows >= MAX_TERRAIN_NODES) return false;
|
||||
return (columns + 1) * (rows + 1) <= MAX_TERRAIN_NODES;
|
||||
}
|
||||
static bool Survey(vector start, vector end, array<IEntity> excluded = null)
|
||||
{
|
||||
if (!GetGame() || !GetGame().GetWorld() || !FinitePoint(start) || !FinitePoint(end)) return false;
|
||||
float distance = vector.DistanceXZ(start, end);
|
||||
int columns = TerrainColumns(distance), rows = 40;
|
||||
if (!TerrainBudget(columns, rows) || !(start[1] >= 2) || !(Math.AbsFloat(end[1] - start[1]) <= 0.3)) return false;
|
||||
vector along = end - start; along[1] = 0; along = along * (1 / distance);
|
||||
vector across = Vector(-along[2], 0, along[0]);
|
||||
float span = distance + 80;
|
||||
// Route-aligned grid includes40m beyond both ends and each side, enclosing
|
||||
// every allowed centre (8m cap) plus the measured complete footprint/margin.
|
||||
// <=2m along either grid axis, <=4096 total; all nodes must pass before traces.
|
||||
for (int x = 0; x <= columns; x++)
|
||||
for (int z = 0; z <= rows; z++)
|
||||
{
|
||||
vector sample = start + along * (-40 + span * x / columns) + across * (-40 + 80 * z / rows);
|
||||
if (!(Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(sample) - start[1]) <= 0.3)) return false;
|
||||
}
|
||||
vector lower, upper;
|
||||
for (int axis = 0; axis < 3; axis++)
|
||||
{
|
||||
lower[axis] = Math.Min(start[axis], end[axis]);
|
||||
upper[axis] = Math.Max(start[axis], end[axis]);
|
||||
}
|
||||
vector centre = (lower + upper) * 0.5; centre[1] = start[1];
|
||||
vector half = (upper - lower) * 0.5;
|
||||
half[0] = half[0] + 40; half[2] = half[2] + 40;
|
||||
// AABB encloses the entire finite segment expanded40m, including endpoint
|
||||
// caps. A failed whole-volume trace never becomes a partly qualified leg.
|
||||
TraceBox corridor = new TraceBox();
|
||||
corridor.Start = centre + "0 3 0"; corridor.End = centre + "0 60 0";
|
||||
corridor.Mins = Vector(-half[0], -1, -half[2]); corridor.Maxs = Vector(half[0], 1, half[2]);
|
||||
corridor.Flags = TraceFlags.ENTS | TraceFlags.WORLD; corridor.ExcludeArray = excluded;
|
||||
if (!(GetGame().GetWorld().TraceMove(corridor, null) >= 1)) return false;
|
||||
TraceBox low = new TraceBox(); low.Start = centre + "0 0.5 0"; low.End = centre + "0 3 0";
|
||||
low.Mins = Vector(-half[0], -0.2, -half[2]); low.Maxs = Vector(half[0], 0.2, half[2]);
|
||||
low.Flags = TraceFlags.ENTS; low.ExcludeArray = excluded;
|
||||
return GetGame().GetWorld().TraceMove(low, null) >= 1;
|
||||
}
|
||||
static ERR_AircraftFieldEnvelope QualifyField(ERR_GameMode mode, Vehicle aircraft, IEntity pilot, vector destination)
|
||||
{
|
||||
if (!GetGame() || !GetGame().GetWorld() || !FinitePoint(destination)) return null;
|
||||
BaseWorld world = GetGame().GetWorld();
|
||||
float began = world.GetWorldTime();
|
||||
// Reuse complete native model/rotor measurement, exact vanilla airframe,
|
||||
// original occupied pilot seat and grounded qualification. Never synthesize it.
|
||||
ERR_AircraftFlightEnvelope point = ERR_AircraftFlightEnvelope.Qualify(mode, aircraft, pilot);
|
||||
if (!point) return null;
|
||||
vector start = ERR_Actors.Ground(point.Home());
|
||||
vector end = ERR_Actors.Ground(destination);
|
||||
if (!FinitePoint(start) || !FinitePoint(end) || !ERR_AircraftFieldRules.Length(vector.DistanceXZ(start, end)) || !(end[1] >= 2) || !(Math.AbsFloat(end[1] - start[1]) <= 0.3)) return null;
|
||||
array<IEntity> excluded = {};
|
||||
if (!Hierarchy(aircraft, excluded) || !Hierarchy(pilot, excluded)) return null;
|
||||
if (!ClearColumn(aircraft, pilot, end) || !Survey(start, end, excluded)) return null;
|
||||
if (!GetGame() || GetGame().GetWorld() != world || !mode || mode != ERR_GameMode.Get() || !mode.Active()) return null;
|
||||
float now = world.GetWorldTime();
|
||||
if (!(now >= began && now - began <= 2000) || !aircraft || !pilot || aircraft.GetWorld() != world || pilot.GetWorld() != world || vector.Distance(aircraft.GetOrigin(), point.Home()) > 0.1) return null;
|
||||
ERR_AircraftFieldEnvelope record = new ERR_AircraftFieldEnvelope();
|
||||
record.m_Mode = mode; record.m_Aircraft = aircraft; record.m_Pilot = pilot;
|
||||
record.m_Home = point.Home(); record.m_Radius = point.Radius();
|
||||
record.m_Destination = end; record.m_Destination[1] = record.m_Home[1];
|
||||
// Survey work consumes the original admission lifetime; it cannot renew it.
|
||||
record.m_QualifiedAt = began;
|
||||
return record;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// Unqualified 100m field-route prototype. No allocation, HQ dispatch or service
|
||||
// integration. The shared base registry/bridge remains the sole input owner.
|
||||
class ERR_AircraftFieldFlight : ERR_AircraftFlight
|
||||
{
|
||||
protected ref ERR_AircraftFieldEnvelope m_Field;
|
||||
protected int m_FieldPhase; // 0 startup, 1 cruise, 2 braking, 3 slow settling
|
||||
protected vector m_FieldReference;
|
||||
protected vector m_FieldIntegral;
|
||||
protected float m_FieldBrakeSpeed;
|
||||
protected vector m_FieldLandingPoint;
|
||||
protected bool m_FieldStopFrozen;
|
||||
protected bool m_FieldArrived;
|
||||
protected float m_FieldLowSince = -1;
|
||||
int FieldPhase() { return m_FieldPhase; }
|
||||
bool Arrived() { return m_FieldArrived; }
|
||||
vector LandingPoint() { return m_FieldLandingPoint; }
|
||||
vector ReferenceVelocity() { return m_FieldReference; }
|
||||
vector IntegralDemand() { return m_FieldIntegral; }
|
||||
vector Destination() { vector result = "0 0 0"; if (m_Field) result = m_Field.Destination(); return result; }
|
||||
// Candidate native fuel quantities, not a demonstrated range/reserve model.
|
||||
protected bool FuelAdmission(Vehicle aircraft)
|
||||
{
|
||||
if (!aircraft) return false;
|
||||
FuelManagerComponent fuel = FuelManagerComponent.Cast(aircraft.FindComponent(FuelManagerComponent));
|
||||
return fuel && fuel.HasFuel() && fuel.GetTotalFuel() >= 50;
|
||||
}
|
||||
bool StartField(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftFieldEnvelope field, int token, float deadline)
|
||||
{
|
||||
if (m_StartAttempted || m_Field || !field || !ERR_AircraftFieldRules.Length(vector.DistanceXZ(field.Home(), field.Destination()))) return false;
|
||||
if (!ERR_AircraftFieldRules.LeaseAdmitted(Now(), deadline) || !FuelAdmission(aircraft)) return false;
|
||||
m_Field = field; m_TranslationRequested = true;
|
||||
vector home = field.Home();
|
||||
m_TranslationTarget = field.Destination(); m_TranslationTarget[1] = home[1] + 8;
|
||||
m_FieldLandingPoint = field.Home();
|
||||
return super.Start(mode, aircraft, group, pilot, seat, field, token, deadline);
|
||||
}
|
||||
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 FieldContains(vector position, float upright)
|
||||
{
|
||||
if (!m_Field || !m_Field.Contains(position, upright)) return false;
|
||||
if (m_FieldPhase == 0) 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_FieldLandingPoint) <= 0.75;
|
||||
return true;
|
||||
}
|
||||
// A normal arrival and an airborne cancellation both brake before descent.
|
||||
// Once selected, the landing centre and braking deadline cannot be renewed.
|
||||
protected bool BeginFieldBrake(float now)
|
||||
{
|
||||
if (m_FieldStopFrozen) return true;
|
||||
if (!Owns() || !m_Field || !Healthy()) { Fail("field braking ownership or capability unavailable"); return false; }
|
||||
vector point = m_Field.Destination();
|
||||
if (m_Cancelled)
|
||||
{
|
||||
point = m_Aircraft.GetOrigin();
|
||||
vector velocity = m_Simulation.GetVelocity(); velocity[1] = 0;
|
||||
float speed = velocity.Length();
|
||||
if (speed >= 0.25) point = point + velocity * (ERR_AircraftFieldRules.BrakingReserve(speed) / speed);
|
||||
}
|
||||
point[1] = m_Home[1];
|
||||
if (!m_Field.Contains(point, 1)) { Fail("field stopping centre outside original qualified corridor"); return false; }
|
||||
// Cancel can arrive while airborne during the signed collective checks,
|
||||
// before shared startup enables autohover. Match the base landing handshake.
|
||||
m_Controller.SetAutohoverEnabled(true);
|
||||
if (!m_Controller.GetAutohoverEnabled()) { Fail("native field braking autohover enable refused"); return false; }
|
||||
m_FieldLandingPoint = point; m_FieldStopFrozen = true;
|
||||
// Begin reducing the reference at entry, before the modeled reserve boundary.
|
||||
// This one frozen ceiling cannot increase on another cancellation or tick.
|
||||
m_FieldBrakeSpeed = m_FieldReference.Length() * 0.9;
|
||||
m_FieldPhase = 2; m_State = ERR_AircraftFlightState.TRANSLATING;
|
||||
m_FieldLowSince = -1; m_Stable = -1;
|
||||
// Includes braking, two low-speed seconds and five seconds of final hold.
|
||||
m_StageDeadline = Math.Min(m_Deadline, now + 45000);
|
||||
PrintFormat("ERR AIRCRAFT FIELD BRAKE: token=%1 cancelled=%2 centre=%3 deadline=%4 originalDeadline=%5", m_Token, m_Cancelled, point, m_StageDeadline, m_Deadline);
|
||||
return true;
|
||||
}
|
||||
protected override bool Landing(float now)
|
||||
{
|
||||
if (!Owns() || !m_Field) { Relinquish("field landing lost original ownership"); return false; }
|
||||
if (m_Cancelled)
|
||||
{
|
||||
if (m_FieldPhase != 3) return BeginFieldBrake(now);
|
||||
if (m_Stable < 0 || now - m_Stable < 5000) return true; // retain the current settle/hold budget
|
||||
}
|
||||
if (!m_FieldStopFrozen || m_FieldPhase != 3 || m_Stable < 0 || now - m_Stable < 5000) { Fail("field descent requires observed braking and destination hold"); return false; }
|
||||
if (!super.Landing(now)) return false;
|
||||
m_StageDeadline = Math.Min(m_Deadline, now + 25000);
|
||||
return true;
|
||||
}
|
||||
protected override bool BeginTranslation(float now, vector position)
|
||||
{
|
||||
if (!m_Field || !m_TranslationRequested || m_TranslationStarted || m_State != ERR_AircraftFlightState.CLIMB) { Cancel("field 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 field cyclic signals unavailable"); return false; }
|
||||
m_TranslationOrigin = position; m_TranslationDistance = vector.DistanceXZ(position, m_Field.Destination());
|
||||
m_TranslationStarted = true; m_State = ERR_AircraftFlightState.TRANSLATING; m_FieldPhase = 1;
|
||||
m_FieldReference = "0 0 0"; m_FieldIntegral = "0 0 0"; m_Stable = -1;
|
||||
m_StageDeadline = Math.Min(m_Deadline, now + 160000);
|
||||
PrintFormat("ERR AIRCRAFT FIELD BEGIN: token=%1 home=%2 destination=%3 actualOrigin=%4 deadline=%5", m_Token, m_Home, m_Field.Destination(), position, m_Deadline);
|
||||
return true;
|
||||
}
|
||||
protected override bool ControlLease()
|
||||
{
|
||||
if (!Lease()) return false;
|
||||
if (!Owns()) { Relinquish("field callback lost original ownership"); return false; }
|
||||
float now = Now();
|
||||
if (now < 0 || now >= m_Deadline || now >= m_StageDeadline) { Fail("bounded field stage expired"); return false; }
|
||||
if (!Healthy() || !m_Readback) { Fail("field capability or native input readback lost"); return false; }
|
||||
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
|
||||
if (!FieldContains(transform[3], transform[1][1]) || !ERR_AircraftFieldRules.SpeedAllowed(m_Simulation.GetVelocity())) { Fail("qualified field position, attitude or speed exceeded"); return false; }
|
||||
return true;
|
||||
}
|
||||
protected vector FieldDemand(vector position, vector velocity, float dt)
|
||||
{
|
||||
vector target = m_Field.Destination(); if (m_FieldStopFrozen) target = m_FieldLandingPoint;
|
||||
if (m_FieldPhase == 3 || m_State == ERR_AircraftFlightState.LANDING) return ERR_AircraftTranslationRules.Demand(target, position, velocity);
|
||||
vector direction = target - position; direction[1] = 0;
|
||||
float remaining = direction.Length(); float targetSpeed = 2;
|
||||
if (m_FieldPhase == 2) targetSpeed = ERR_AircraftFieldRules.ApproachSpeed(remaining, Math.Min(m_FieldReference.Length(), m_FieldBrakeSpeed));
|
||||
vector desired;
|
||||
if (remaining > 0.001) desired = direction * (targetSpeed / remaining);
|
||||
m_FieldReference = ERR_AircraftFieldRules.RampReference(m_FieldReference, desired, dt);
|
||||
vector nextIntegral;
|
||||
vector demand = ERR_AircraftFieldRules.VelocityDemand(m_FieldReference, velocity, m_FieldIntegral, dt, nextIntegral);
|
||||
m_FieldIntegral = nextIntegral;
|
||||
return demand;
|
||||
}
|
||||
override void PrepareActions(CharacterControllerComponent controller, IEntity owner, ActionManager actions, float dt)
|
||||
{
|
||||
if (m_State != ERR_AircraftFlightState.TRANSLATING && m_State != ERR_AircraftFlightState.LANDING) { super.PrepareActions(controller, owner, actions, dt); return; }
|
||||
if (!Lease() || controller != m_Character || owner != m_Pilot || actions != m_Actions || !ControlLease()) return;
|
||||
if (!m_Readback || !ERR_AircraftFieldRules.ValidStep(dt) || dt > 0.2) return;
|
||||
if (!actions.IsContextActive("HelicopterContext")) actions.ActivateContext("HelicopterContext", 1);
|
||||
if (!actions.IsContextActive("HelicopterContext")) { m_Readback = false; return; }
|
||||
if (!m_Controller.GetAutohoverEnabled()) { Fail("native field autohover lost"); return; }
|
||||
vector position = m_Aircraft.GetOrigin(); vector velocity = m_Simulation.GetVelocity();
|
||||
float height = position[1] - m_Home[1];
|
||||
float error = ERR_AircraftFlightRules.Vertical(height, m_State == ERR_AircraftFlightState.LANDING) - velocity[1];
|
||||
m_Collective = Math.Clamp(m_Collective + error * dt * 0.12, -0.95, 0.8);
|
||||
float 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;
|
||||
vector angular = m_Simulation.GetAngularVelocityLocal();
|
||||
float torque = Math.Clamp(headingError * 0.01 - angular[1] * 0.3, -0.3, 0.3);
|
||||
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
|
||||
vector demand = FieldDemand(transform[3], m_Simulation.GetVelocity(), dt);
|
||||
float limit = 0.08; if (m_FieldPhase == 3 || m_State == ERR_AircraftFlightState.LANDING) limit = 0.03;
|
||||
float forward = Math.Clamp(vector.Dot(demand, transform[2]), -limit, limit);
|
||||
float aside = Math.Clamp(vector.Dot(demand, transform[0]), -limit, limit);
|
||||
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();
|
||||
if (m_State == ERR_AircraftFlightState.TRANSLATING) m_TranslationUpdates++;
|
||||
}
|
||||
protected override void CheckTranslation(float now, vector position, float height, float speed, float upright)
|
||||
{
|
||||
if (!m_Field || !m_Controller.GetAutohoverEnabled()) { Fail("field route or autohover unavailable"); return; }
|
||||
vector velocity = m_Simulation.GetVelocity(); float horizontal = ERR_AircraftFieldRules.HorizontalSpeed(velocity);
|
||||
if (m_FieldPhase == 1)
|
||||
{
|
||||
if (ERR_AircraftFieldRules.ShouldBrake(vector.DistanceXZ(position, m_Field.Destination()), horizontal)) BeginFieldBrake(now);
|
||||
return;
|
||||
}
|
||||
if (m_FieldPhase == 2)
|
||||
{
|
||||
if (!(horizontal < 0.25)) m_FieldLowSince = -1;
|
||||
else if (m_FieldLowSince < 0) m_FieldLowSince = now;
|
||||
if (!ERR_AircraftFieldRules.LowSpeedHeld(horizontal, m_FieldLowSince, now)) return;
|
||||
m_FieldPhase = 3; m_FieldReference = "0 0 0"; m_FieldIntegral = "0 0 0"; m_Stable = -1;
|
||||
// No new deadline: slow positioning/hold belongs to the same braking budget.
|
||||
}
|
||||
bool held = m_FieldPhase == 3 && !m_Simulation.HasAnyGroundContact() && ERR_AircraftFlightRules.Hover(height, speed, velocity[1], upright) && horizontal < 0.25 && vector.DistanceXZ(position, m_FieldLandingPoint) <= 0.75;
|
||||
if (!held) { m_Stable = -1; return; }
|
||||
if (m_Stable < 0) m_Stable = now;
|
||||
if (now - m_Stable < 5000) return;
|
||||
if (!m_Cancelled) { m_FieldArrived = true; m_TranslationProven = true; }
|
||||
Landing(now);
|
||||
}
|
||||
override void Tick()
|
||||
{
|
||||
if (!ControlLease()) return;
|
||||
float now = Now();
|
||||
// The owner must continue Tick even with no players. Do not transition an
|
||||
// inherited startup callback into a field phase halfway through native input.
|
||||
// 86.5s soft threshold reserves the 85s phase budget plus the maximum
|
||||
// accepted 1.5s observation gap. Hard stage/original deadlines never move.
|
||||
bool reserveAbort = ERR_AircraftFieldRules.AbortDue(now + 1500, m_Deadline);
|
||||
bool fuelAbort = !m_Fuel || !(m_Fuel.GetTotalFuel() > 20);
|
||||
if (!m_Cancelled && !m_FieldArrived && m_State != ERR_AircraftFlightState.LANDING && m_State != ERR_AircraftFlightState.SHUTDOWN && (reserveAbort || fuelAbort))
|
||||
{
|
||||
string reason = "field remaining lease reserved for braking and landing";
|
||||
if (fuelAbort) reason = "field candidate low-fuel reserve reached";
|
||||
Cancel(reason);
|
||||
if (Finished()) return;
|
||||
}
|
||||
// The stock-independent shared startup includes native signed collective
|
||||
// consumption, engine/fuel proof and five-second hover without duplication.
|
||||
if (m_State != ERR_AircraftFlightState.TRANSLATING && m_State != ERR_AircraftFlightState.LANDING && m_State != ERR_AircraftFlightState.SHUTDOWN) { super.Tick(); return; }
|
||||
if (m_LastTick >= 0 && (now < m_LastTick || now - m_LastTick > 1500)) { m_Stable = -1; m_FieldLowSince = -1; }
|
||||
m_LastTick = now;
|
||||
if (m_LastControl < 0 || now - m_LastControl > 1000) { Fail("field pilot callback stopped"); return; }
|
||||
if (m_State != ERR_AircraftFlightState.SHUTDOWN && !m_Controller.IsEngineOn()) { Fail("field running engine lost"); return; }
|
||||
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
|
||||
if (m_State == ERR_AircraftFlightState.TRANSLATING) { CheckTranslation(now, transform[3], transform[3][1] - m_Home[1], m_Simulation.GetVelocity().Length(), transform[1][1]); return; }
|
||||
if (m_State == ERR_AircraftFlightState.LANDING)
|
||||
{
|
||||
if (!m_Controller.GetAutohoverEnabled()) { Fail("field landing autohover lost"); return; }
|
||||
if (!Grounded()) m_Stable = -1;
|
||||
else if (m_Stable < 0) m_Stable = now;
|
||||
if (m_Stable >= 0 && now - m_Stable >= 2000) Shutdown(now);
|
||||
return;
|
||||
}
|
||||
if (m_State == ERR_AircraftFlightState.SHUTDOWN && !m_Controller.IsEngineOn())
|
||||
{
|
||||
if (!Grounded()) { Fail("field engine-off physical ground check failed"); return; }
|
||||
if (m_Cancelled) m_State = ERR_AircraftFlightState.CANCELLED;
|
||||
else m_State = ERR_AircraftFlightState.COMPLETE;
|
||||
Release(true); RecordState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Candidate 100m field experiment only. BrakingReserve is an admission
|
||||
// hypothesis, not measured stopping performance or an emergency-landing promise.
|
||||
class ERR_AircraftFieldRules
|
||||
{
|
||||
static bool Length(float distance) { return distance >= 99.95 && distance <= 100.05; }
|
||||
static bool FiniteVector(vector value)
|
||||
{
|
||||
return Math.AbsFloat(value[0]) <= 1000000 && Math.AbsFloat(value[1]) <= 1000000 && Math.AbsFloat(value[2]) <= 1000000;
|
||||
}
|
||||
static float HorizontalSpeed(vector velocity)
|
||||
{
|
||||
velocity[1] = 0;
|
||||
return velocity.Length();
|
||||
}
|
||||
static bool SpeedAllowed(vector velocity) { return FiniteVector(velocity) && HorizontalSpeed(velocity) <= 2.5; }
|
||||
static bool ValidStep(float dt) { return dt > 0 && dt <= 0.5; }
|
||||
static float RampSpeed(float current, float target, float dt)
|
||||
{
|
||||
if (!(current >= 0 && current <= 2)) return 0;
|
||||
if (!(target >= 0 && target <= 2) || !ValidStep(dt)) return current;
|
||||
return current + Math.Clamp(target - current, -0.2 * dt, 0.2 * dt);
|
||||
}
|
||||
static vector CapHorizontal(vector value, float limit)
|
||||
{
|
||||
value[1] = 0;
|
||||
float magnitude = value.Length();
|
||||
if (magnitude > limit) value = value * (limit / magnitude);
|
||||
return value;
|
||||
}
|
||||
// Slew the complete horizontal vector: changing bearing cannot bypass the
|
||||
// acceleration velocityReference cap through an instantaneous sign/direction change.
|
||||
static vector RampReference(vector current, vector desired, float dt)
|
||||
{
|
||||
if (!FiniteVector(current)) return "0 0 0";
|
||||
current = CapHorizontal(current, 2);
|
||||
if (!FiniteVector(desired) || !ValidStep(dt)) return current;
|
||||
desired = CapHorizontal(desired, 2);
|
||||
return CapHorizontal(current + CapHorizontal(desired - current, 0.2 * dt), 2);
|
||||
}
|
||||
// Remaining target distance includes permitted takeoff/corridor drift; its
|
||||
// 120m arithmetic bound grants no authority beyond Contains/Length.
|
||||
// Invert the entire candidate stopping reserve (including1s latency and3m
|
||||
// margin), not just its deceleration term. Caller also freezes a0.9 entry-speed
|
||||
// cap to begin reducing the velocityReference immediately, then slews the vector.
|
||||
// The assumed0.15m/s² physical deceleration still requires native measurement.
|
||||
static float ApproachSpeed(float remaining, float current)
|
||||
{
|
||||
if (!(remaining >= 0 && remaining <= 120) || !(current >= 0 && current <= 2)) return 0;
|
||||
float reservedSpeed = (Math.Sqrt(0.09 + 1.2 * Math.Max(0, remaining - 3)) - 0.3) * 0.5;
|
||||
return Math.Min(current, Math.Max(0, reservedSpeed));
|
||||
}
|
||||
// Integral units are (m/s)*seconds; nextIntegral is caller-owned state.
|
||||
// Horizontal demand is world-space; the exact-owner controller converts it
|
||||
// to the native airframe basis. The caller resets state at phase transitions.
|
||||
static vector VelocityDemand(vector velocityReference, vector velocity, vector integral, float dt, out vector nextIntegral)
|
||||
{
|
||||
nextIntegral = "0 0 0";
|
||||
if (!FiniteVector(velocityReference) || !FiniteVector(velocity) || !FiniteVector(integral) || !ValidStep(dt)) return "0 0 0";
|
||||
velocityReference = CapHorizontal(velocityReference, 2);
|
||||
velocity[1] = 0;
|
||||
integral = CapHorizontal(integral, 4);
|
||||
vector error = velocityReference - velocity;
|
||||
vector candidate = CapHorizontal(integral + error * dt, 4);
|
||||
vector proposed = error * 0.10 + candidate * 0.02;
|
||||
// Freeze only integration which pushes an already saturated vector outward.
|
||||
// Opposing error can unwind stored demand while the output remains capped.
|
||||
nextIntegral = candidate;
|
||||
if (proposed.Length() > 0.08 && vector.Dot(proposed, error) > 0) nextIntegral = integral;
|
||||
return CapHorizontal(error * 0.10 + nextIntegral * 0.02, 0.08);
|
||||
}
|
||||
static float BrakingReserve(float speed)
|
||||
{
|
||||
if (!(speed >= 0 && speed <= 2.5)) return 1000000;
|
||||
return speed * speed / 0.30 + speed + 3;
|
||||
}
|
||||
static bool ShouldBrake(float remaining, float speed)
|
||||
{
|
||||
if (!(remaining >= 0 && remaining <= 120) || !(speed >= 0 && speed <= 2.5)) return true;
|
||||
return remaining <= Math.Max(25, BrakingReserve(speed));
|
||||
}
|
||||
// Admit only the full experimental lease, with <=1s dispatch allowance.
|
||||
// Never recompute a deadline from these checks or renew the original lease.
|
||||
static bool LeaseAdmitted(float now, float deadline)
|
||||
{
|
||||
return now >= 0 && deadline - now >= 239000 && deadline - now <= 240000;
|
||||
}
|
||||
static bool AbortDue(float now, float deadline)
|
||||
{
|
||||
if (!(now >= 0 && deadline > now && deadline - now <= 240000)) return true;
|
||||
return deadline - now <= 85000;
|
||||
}
|
||||
// 'since' is the caller's continuous low-speed observation start, in ms.
|
||||
// The caller clears it whenever actual horizontal speed is >=0.25m/s.
|
||||
static bool LowSpeedHeld(float speed, float since, float now)
|
||||
{
|
||||
return speed >= 0 && speed < 0.25 && since >= 0 && now >= since && now - since >= 2000;
|
||||
}
|
||||
static vector Nearest(vector start, vector end, vector point)
|
||||
{
|
||||
vector leg = end - start; leg[1] = 0;
|
||||
vector offset = point - start; offset[1] = 0;
|
||||
float squared = vector.Dot(leg, leg);
|
||||
if (!(squared > 0)) return start;
|
||||
return start + leg * Math.Clamp(vector.Dot(offset, leg) / squared, 0, 1);
|
||||
}
|
||||
static bool Contains(vector start, vector end, vector point, float upright)
|
||||
{
|
||||
if (!FiniteVector(start) || !FiniteVector(end) || !FiniteVector(point) || !Length(vector.DistanceXZ(start, end))) return false;
|
||||
float height = point[1] - start[1];
|
||||
return height >= -3 && height <= 18 && upright >= 0.85 && upright <= 1.01 && vector.DistanceXZ(point, Nearest(start, end, point)) < 8;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ class ERR_AircraftServiceEntry
|
||||
SCR_AIGroup Group;
|
||||
IEntity Pilot;
|
||||
PilotCompartmentSlot Seat;
|
||||
ref ERR_AircraftRouteFlight Flight;
|
||||
ref ERR_AircraftFlight Flight;
|
||||
int Token;
|
||||
float Deadline;
|
||||
vector Destination;
|
||||
@@ -89,18 +89,45 @@ class ERR_AircraftService
|
||||
if (!Context() || m_Stopping || !entry || entry.Pending || entry.Retired || s_Retainers.Get(aircraft) != this) return false;
|
||||
return PhysicalReady(entry);
|
||||
}
|
||||
bool StartRoute(Vehicle aircraft, ERR_AircraftRouteEnvelope qualification, int token, float deadline)
|
||||
// Both explicit kinds share one attempt history and physical reservation.
|
||||
protected ERR_AircraftServiceEntry BeginAttempt(Vehicle aircraft, int token, float deadline)
|
||||
{
|
||||
if (!Context() || m_Stopping || !qualification || token <= 0 || m_UsedTokens.Contains(token) || m_UsedTokens.Count() >= MAX_ORDERS || !Ready(aircraft)) return false;
|
||||
if (!Context() || m_Stopping || token <= 0 || m_UsedTokens.Contains(token) || m_UsedTokens.Count() >= MAX_ORDERS || !Ready(aircraft)) return null;
|
||||
float now = m_World.GetWorldTime();
|
||||
if (!ERR_AircraftFlightRules.Deadline(now, deadline)) return false;
|
||||
if (!ERR_AircraftFlightRules.Deadline(now, deadline)) return null;
|
||||
ERR_AircraftServiceEntry entry = Find(aircraft);
|
||||
// Commit the attempt before calling the native-backed controller. No retry of
|
||||
// this token, including a partially initialized or conflicting Start refusal.
|
||||
m_UsedTokens.Insert(token); entry.Token = token; entry.Deadline = deadline;
|
||||
entry.Destination = qualification.Destination(); entry.CancelRequested = false;
|
||||
entry.Flight = new ERR_AircraftRouteFlight(); entry.Pending = true;
|
||||
bool started = entry.Flight.StartRoute(m_Mode, entry.Aircraft, entry.Group, entry.Pilot, entry.Seat, qualification, token, deadline);
|
||||
entry.CancelRequested = false;
|
||||
return entry;
|
||||
}
|
||||
bool StartRoute(Vehicle aircraft, ERR_AircraftRouteEnvelope qualification, int token, float deadline)
|
||||
{
|
||||
if (!qualification) return false;
|
||||
ERR_AircraftServiceEntry entry = BeginAttempt(aircraft, token, deadline);
|
||||
if (!entry) return false;
|
||||
entry.Destination = qualification.Destination();
|
||||
ERR_AircraftRouteFlight flight = new ERR_AircraftRouteFlight();
|
||||
entry.Flight = flight; entry.Pending = true;
|
||||
bool started = flight.StartRoute(m_Mode, entry.Aircraft, entry.Group, entry.Pilot, entry.Seat, qualification, token, deadline);
|
||||
return RecordStart(entry, started);
|
||||
}
|
||||
// This explicit prototype entry point accepts only a field certificate and
|
||||
// constructs its known controller. No arbitrary caller-owned flight is adopted.
|
||||
bool StartField(Vehicle aircraft, ERR_AircraftFieldEnvelope qualification, int token, float deadline)
|
||||
{
|
||||
if (!qualification) return false;
|
||||
ERR_AircraftServiceEntry entry = BeginAttempt(aircraft, token, deadline);
|
||||
if (!entry) return false;
|
||||
entry.Destination = qualification.Destination();
|
||||
ERR_AircraftFieldFlight flight = new ERR_AircraftFieldFlight();
|
||||
entry.Flight = flight; entry.Pending = true;
|
||||
bool started = flight.StartField(m_Mode, entry.Aircraft, entry.Group, entry.Pilot, entry.Seat, qualification, token, deadline);
|
||||
return RecordStart(entry, started);
|
||||
}
|
||||
protected bool RecordStart(ERR_AircraftServiceEntry entry, bool started)
|
||||
{
|
||||
if (!started)
|
||||
{
|
||||
entry.Retired = true; entry.Reason = "flight start refused; no automatic retry or replacement";
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
#ifdef WORKBENCH
|
||||
// Separate opt-in experimental100m field fixture; original aircraft outcomes unchanged.
|
||||
// Full terrain/clearance refusal is retained; no operational dispatch is installed.
|
||||
modded class ERR_AircraftFieldFlight
|
||||
{
|
||||
float ERR_NativeFieldStageDeadline() { return m_StageDeadline; }
|
||||
int ERR_NativeFieldActionUpdates() { return m_ActionUpdates; }
|
||||
int ERR_NativeFieldHelicopterUpdates() { return m_HelicopterUpdates; }
|
||||
bool ERR_NativeFieldFreshReadback(float now) { return m_Readback && m_LastControl >= 0 && now >= m_LastControl && now - m_LastControl <= 1000; }
|
||||
vector ERR_NativeFieldInputDemand()
|
||||
{
|
||||
vector result;
|
||||
if (m_Actions) result = Vector(m_Actions.GetActionValue("HelicopterCyclicRight") - m_Actions.GetActionValue("HelicopterCyclicLeft"), 0, m_Actions.GetActionValue("HelicopterCyclicForward") - m_Actions.GetActionValue("HelicopterCyclicBack"));
|
||||
return result;
|
||||
}
|
||||
protected override bool BeginFieldBrake(float now)
|
||||
{
|
||||
bool alreadyFrozen = m_FieldStopFrozen;
|
||||
bool result = super.BeginFieldBrake(now);
|
||||
ERR_NativeAircraftFieldProbe probe = ERR_NativeAircraftFieldProbe.Current();
|
||||
if (result && !alreadyFrozen && probe && m_Aircraft && m_Simulation) probe.BrakeBegan(this, now, m_Aircraft.GetOrigin(), m_Simulation.GetVelocity());
|
||||
return result;
|
||||
}
|
||||
protected override void CheckTranslation(float now, vector position, float height, float speed, float upright)
|
||||
{
|
||||
int oldPhase = m_FieldPhase;
|
||||
super.CheckTranslation(now, position, height, speed, upright);
|
||||
ERR_NativeAircraftFieldProbe probe = ERR_NativeAircraftFieldProbe.Current();
|
||||
if (oldPhase == 2 && m_FieldPhase == 3 && probe && m_Aircraft && m_Simulation) probe.BrakeStopped(this, now, m_Aircraft.GetOrigin(), m_Simulation.GetVelocity());
|
||||
}
|
||||
|
||||
}
|
||||
// Read-only fixture access to the service's exact retained controller.
|
||||
modded class ERR_AircraftService
|
||||
{
|
||||
ERR_AircraftFieldFlight ERR_NativeOriginalFieldFlight(Vehicle aircraft)
|
||||
{
|
||||
ERR_AircraftFieldFlight result;
|
||||
ERR_AircraftServiceEntry entry = Find(aircraft);
|
||||
if (entry) result = ERR_AircraftFieldFlight.Cast(entry.Flight);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
modded class SCR_CharacterControllerComponent
|
||||
{
|
||||
override void OnPrepareControls(IEntity owner, ActionManager am, float dt, bool player)
|
||||
{
|
||||
ERR_NativeAircraftFieldProbe probe = ERR_NativeAircraftFieldProbe.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_NativeAircraftFieldProbe probe = ERR_NativeAircraftFieldProbe.Current();
|
||||
if (probe) probe.PrepareHelicopter(this);
|
||||
}
|
||||
}
|
||||
class ERR_NativeAircraftFieldRules
|
||||
{
|
||||
static bool Cruise(float alongSpeed, float horizontal, bool contact, int phase)
|
||||
{
|
||||
return phase == 1 && !contact && alongSpeed >= 1 && horizontal >= 1 && horizontal <= 2.5;
|
||||
}
|
||||
static bool Braking(float reserve, float sampledTravel, float displacement, float lowSince, float now, float horizontal)
|
||||
{
|
||||
return reserve >= 3 && reserve <= 26.334 && sampledTravel >= 0 && sampledTravel <= reserve && displacement >= 0 && displacement <= reserve && horizontal >= 0 && horizontal < 0.25 && lowSince >= 0 && now - lowSince >= 2000;
|
||||
}
|
||||
static bool Terminal(bool cancellation, int state, int stops, bool arrived, bool cruiseHeld, bool endpointHeld, bool braking, float displacement, float targetError)
|
||||
{
|
||||
if (!braking || !(targetError >= 0 && targetError <= 0.75)) return false;
|
||||
if (cancellation) return state == ERR_AircraftFlightState.CANCELLED && stops == 1 && !arrived;
|
||||
return state == ERR_AircraftFlightState.COMPLETE && stops == 0 && arrived && cruiseHeld && endpointHeld && displacement >= 99.25 && displacement <= 100.8;
|
||||
}
|
||||
static bool PhysicalFinish(bool originalOccupancy, bool landed, bool engineOff, float fuelUsed, int wakes, int starts, int controllerStarts, int pilotHooks, int helicopterHooks)
|
||||
{
|
||||
return originalOccupancy && landed && engineOff && fuelUsed > 0.05 && fuelUsed < 1000000 && 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_NativeAircraftFieldProbe
|
||||
{
|
||||
protected static ERR_NativeAircraftFieldProbe 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_AircraftFieldEnvelope m_Envelope;
|
||||
protected ref ERR_AircraftFieldFlight m_Flight;
|
||||
protected ref ERR_AircraftService m_Service;
|
||||
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 int m_CancelCount;
|
||||
protected bool m_CancelFixture;
|
||||
protected float m_CancelAt = -1;
|
||||
protected float m_LandingStable = -1;
|
||||
protected int m_CancelActionUpdates;
|
||||
protected int m_CancelHelicopterUpdates;
|
||||
protected bool m_ContinuedAirborneControl;
|
||||
protected bool m_LandingHeld;
|
||||
protected vector m_CancelLandingPoint;
|
||||
protected bool m_TranslationHeld;
|
||||
protected float m_TranslationStable = -1;
|
||||
protected vector m_Pad;
|
||||
protected vector m_Destination;
|
||||
protected float m_CruiseSince = -1;
|
||||
protected bool m_CruiseHeld;
|
||||
protected bool m_BrakeObserved;
|
||||
protected bool m_BrakeStopped;
|
||||
protected bool m_BrakePassed;
|
||||
protected string m_ObservationFailure;
|
||||
protected vector m_BrakeOrigin;
|
||||
protected vector m_BrakeLandingPoint;
|
||||
protected vector m_BrakePrevious;
|
||||
protected float m_BrakeReserve;
|
||||
protected float m_BrakeSampledTravel;
|
||||
protected float m_BrakeLowSince = -1;
|
||||
protected float m_BrakeAt;
|
||||
protected float m_BrakeStageDeadline;
|
||||
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 = 771101;
|
||||
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-field";
|
||||
}
|
||||
static ERR_NativeAircraftFieldProbe Current() { return s_Current; }
|
||||
void ERR_NativeAircraftFieldProbe(ERR_GameMode mode)
|
||||
{
|
||||
m_Mode = mode;
|
||||
if (!GetGame() || !GetGame().GetWorld() || !Enabled() || s_Current) { m_Stopped = true; return; }
|
||||
string cancellation; m_CancelFixture = System.GetCLIParam("errSmokeFieldServiceCancel", cancellation) && cancellation == "1";
|
||||
m_Deadline = GetGame().GetWorld().GetWorldTime() + 300000;
|
||||
s_Current = this;
|
||||
}
|
||||
protected bool Check(bool passed, string detail)
|
||||
{
|
||||
string outcome = "aircraft-field";
|
||||
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)
|
||||
{
|
||||
// This exact airport start was observed in the twenty-metre fixture. The
|
||||
// longer corridor still requires its own complete survey; there is no search.
|
||||
vector candidate = ERR_Actors.Ground("4850.953 0 11787.412");
|
||||
vector destination = ERR_Actors.Ground(candidate + "100 0 0");
|
||||
if (!ERR_AircraftFieldEnvelope.Survey(candidate, destination)) return false;
|
||||
PrintFormat("ERR AIRCRAFT FIELD FIXTURE CORRIDOR: singleStart=%1 destination=%2; fixed100m complete survey passed", candidate, destination);
|
||||
result = candidate; m_Destination = destination; return true;
|
||||
}
|
||||
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 bool RetainedIdentity()
|
||||
{
|
||||
return m_Service && m_Flight && m_Service.ERR_NativeOriginalFieldFlight(m_Aircraft) == m_Flight && m_Service.RegisteredCount() == 1 && m_Service.AttemptedOrders() == 1 && m_Service.OrderToken(m_Aircraft) == ORDER_TOKEN && m_Service.OrderDeadline(m_Aircraft) == m_ControlDeadline;
|
||||
}
|
||||
protected bool StartRetainedField()
|
||||
{
|
||||
if (m_Service || m_Flight) return Check(false, "one retained aircraft service only");
|
||||
m_Service = new ERR_AircraftService(m_Mode);
|
||||
if (!Check(m_Service.Register(m_Aircraft, m_Group, m_Pilot, m_Seat) && m_Service.RegisteredCount() == 1 && m_Service.Ready(m_Aircraft), "retention service registers one exact physical aircraft, original pilot/group/seat and current readiness")) return false;
|
||||
m_ControllerStarts++;
|
||||
bool started = m_Service.StartField(m_Aircraft, m_Envelope, ORDER_TOKEN, m_ControlDeadline);
|
||||
m_Flight = m_Service.ERR_NativeOriginalFieldFlight(m_Aircraft);
|
||||
return Check(started && RetainedIdentity() && m_Service.Pending(m_Aircraft), "actual controller accepts one qualified original pilot/aircraft order through retention service");
|
||||
}
|
||||
protected bool StopRetainedField(float now)
|
||||
{
|
||||
if (m_CancelCount != 0 || !RetainedIdentity() || m_Service.Stopping()) return Check(false, "one explicit retained field service stop only");
|
||||
m_CancelCount = 1; m_CancelAt = now;
|
||||
m_CancelActionUpdates = m_Flight.ERR_NativeFieldActionUpdates();
|
||||
m_CancelHelicopterUpdates = m_Flight.ERR_NativeFieldHelicopterUpdates();
|
||||
m_Service.Stop(); m_CancelLandingPoint = m_Flight.LandingPoint();
|
||||
return Check(RetainedIdentity() && m_Service.Stopping() && m_Service.Pending(m_Aircraft) && !m_Service.Drained() && !m_Service.Ready(m_Aircraft) && m_Flight.State() == ERR_AircraftFlightState.TRANSLATING && m_Flight.FieldPhase() == 2 && m_Flight.ERR_NativeControlDeadline() == m_ControlDeadline && m_Envelope.Contains(m_CancelLandingPoint, 1), "service Stop retains airborne braking, original controller and frozen qualified point under same lease");
|
||||
}
|
||||
protected void Diagnostic(float now)
|
||||
{
|
||||
if (!m_Flight || !m_Aircraft || !m_Simulation || !m_Envelope || now < m_NextLog) return;
|
||||
m_NextLog = now + 1000;
|
||||
vector position = m_Aircraft.GetOrigin(); vector velocity = m_Simulation.GetVelocity();
|
||||
PrintFormat("ERR AIRCRAFT FIELD MOTION: now=%1 state=%2 phase=%3 position=%4 velocity=%5 refVelocity=%6 integral=%7 observedLocalInput=%8 fuel=%9", now, m_Flight.State(), m_Flight.FieldPhase(), position, velocity, m_Flight.ReferenceVelocity(), m_Flight.IntegralDemand(), m_Flight.ERR_NativeFieldInputDemand(), m_Fuel.GetTotalFuel());
|
||||
PrintFormat("ERR AIRCRAFT FIELD LEASE: token=%1 originalDeadline=%2 stageDeadline=%3 actions=%4 helicopter=%5 contact=%6 engine=%7 targetError=%8", m_Flight.OrderToken(), m_ControlDeadline, m_Flight.ERR_NativeFieldStageDeadline(), m_Flight.ERR_NativeFieldActionUpdates(), m_Flight.ERR_NativeFieldHelicopterUpdates(), m_Simulation.HasAnyGroundContact(), m_Controller.IsEngineOn(), vector.DistanceXZ(position, m_Flight.LandingPoint()));
|
||||
}
|
||||
void BrakeBegan(ERR_AircraftFieldFlight flight, float now, vector position, vector velocity)
|
||||
{
|
||||
if (m_Stopped || flight != m_Flight) return;
|
||||
if (m_BrakeObserved) { m_ObservationFailure = "production brake event occurs exactly once"; return; }
|
||||
m_BrakeObserved = true; m_BrakeAt = now;
|
||||
m_BrakeOrigin = position; m_BrakePrevious = position;
|
||||
m_BrakeReserve = ERR_AircraftFieldRules.BrakingReserve(ERR_AircraftFieldRules.HorizontalSpeed(velocity));
|
||||
m_BrakeStageDeadline = flight.ERR_NativeFieldStageDeadline(); m_BrakeLandingPoint = flight.LandingPoint();
|
||||
PrintFormat("ERR AIRCRAFT FIELD BRAKE OBSERVED: now=%1 position=%2 velocity=%3 declaredHypothesisReserve=%4 stageDeadline=%5 originalDeadline=%6", now, position, velocity, m_BrakeReserve, m_BrakeStageDeadline, m_ControlDeadline);
|
||||
}
|
||||
void BrakeStopped(ERR_AircraftFieldFlight flight, float now, vector position, vector velocity)
|
||||
{
|
||||
if (m_Stopped || flight != m_Flight) return;
|
||||
if (!m_BrakeObserved || m_BrakeStopped) { m_ObservationFailure = "one actual low-speed braking transition after original brake event"; return; }
|
||||
m_BrakeStopped = true;
|
||||
m_BrakeSampledTravel += vector.DistanceXZ(position, m_BrakePrevious);
|
||||
m_BrakePrevious = position;
|
||||
float displacement = vector.DistanceXZ(position, m_BrakeOrigin);
|
||||
m_BrakePassed = ERR_NativeAircraftFieldRules.Braking(m_BrakeReserve, m_BrakeSampledTravel, displacement, m_BrakeLowSince, now, ERR_AircraftFieldRules.HorizontalSpeed(velocity));
|
||||
PrintFormat("ERR AIRCRAFT FIELD STOP OBSERVED: now=%1 position=%2 sampledTravel=%3 displacement=%4 declaredReserve=%5 lowSince=%6 passed=%7", now, position, m_BrakeSampledTravel, displacement, m_BrakeReserve, m_BrakeLowSince, m_BrakePassed);
|
||||
if (!m_BrakePassed) m_ObservationFailure = "observed braking exceeded declared reserve or lacked independent two-second low-speed observations";
|
||||
}
|
||||
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_TranslationStable = -1; m_LandingStable = -1; m_CruiseSince = -1; m_BrakeLowSince = -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_AircraftFieldEnvelope.QualifyField(m_Mode, m_Aircraft, m_Pilot, m_Destination);
|
||||
if (!Check(m_Envelope != null, "actual full corridor, both pads and complete native rotor/model footprint issue exact route qualification")) return -1;
|
||||
m_InitialFuel = m_Fuel.GetTotalFuel();
|
||||
m_ControlDeadline = Math.Min(m_Deadline, now + 240000);
|
||||
if (!StartRetainedField()) return -1;
|
||||
m_Step = 3; return 0;
|
||||
}
|
||||
if (m_Step == 3)
|
||||
{
|
||||
if (!RetainedIdentity() || !OriginalOccupancy()) { Check(false, "service and exact original airframe/pilot/group/seat retained"); return -1; }
|
||||
if (m_Flight.ERR_NativeControlDeadline() != m_ControlDeadline || m_Flight.Destination() != m_Envelope.Destination()) { Check(false, "original endpoint/token/deadline never change"); return -1; }
|
||||
if (m_BrakeObserved)
|
||||
{
|
||||
if (m_Flight.LandingPoint() != m_BrakeLandingPoint) { Check(false, "first production braking point remains immutable"); return -1; }
|
||||
if (m_Flight.State() == ERR_AircraftFlightState.TRANSLATING && m_Flight.ERR_NativeFieldStageDeadline() != m_BrakeStageDeadline) { Check(false, "braking and settling never renew their stage deadline"); return -1; }
|
||||
}
|
||||
bool engine = m_Controller.IsEngineOn(); if (engine && !m_LastEngine) m_EngineStarts++; m_LastEngine = engine;
|
||||
vector transform[4]; m_Aircraft.GetWorldTransform(transform);
|
||||
vector position = transform[3]; vector velocity = m_Simulation.GetVelocity();
|
||||
vector home = m_Envelope.Home(); vector leg = m_Envelope.Destination() - home; leg[1] = 0;
|
||||
float alongSpeed = vector.Dot(velocity, leg * (1 / leg.Length()));
|
||||
float horizontal = ERR_AircraftFieldRules.HorizontalSpeed(velocity);
|
||||
bool contact = m_Simulation.HasAnyGroundContact(); float height = position[1] - home[1];
|
||||
bool cruise = ERR_NativeAircraftFieldRules.Cruise(alongSpeed, horizontal, contact, m_Flight.FieldPhase());
|
||||
if (!cruise) m_CruiseSince = -1;
|
||||
else if (m_CruiseSince < 0) m_CruiseSince = now;
|
||||
if (m_CruiseSince >= 0 && now - m_CruiseSince >= 10000) m_CruiseHeld = true;
|
||||
if (m_CancelFixture && m_CancelCount == 0 && m_CruiseSince >= 0 && now - m_CruiseSince >= 1000)
|
||||
if (!StopRetainedField(now)) return -1;
|
||||
if (m_BrakeObserved && !m_BrakeStopped)
|
||||
{
|
||||
m_BrakeSampledTravel += vector.DistanceXZ(position, m_BrakePrevious); m_BrakePrevious = position;
|
||||
if (!(horizontal < 0.25)) m_BrakeLowSince = -1;
|
||||
else if (m_BrakeLowSince < 0) m_BrakeLowSince = now;
|
||||
}
|
||||
bool held = !contact && ERR_AircraftFlightRules.Hover(height, velocity.Length(), velocity[1], transform[1][1]) && horizontal < 0.25 && vector.DistanceXZ(position, m_Envelope.Destination()) <= 0.75;
|
||||
if (!held) m_TranslationStable = -1;
|
||||
else if (m_TranslationStable < 0) m_TranslationStable = now;
|
||||
if (m_TranslationStable >= 0 && now - m_TranslationStable >= 5000) m_TranslationHeld = true;
|
||||
bool landed = ERR_AircraftFlightRules.Landed(contact, height, velocity.Length(), m_Simulation.GetAngularVelocity().Length(), transform[1][1]) && vector.DistanceXZ(position, m_Flight.LandingPoint()) <= 0.75;
|
||||
if (!landed) m_LandingStable = -1;
|
||||
else if (m_LandingStable < 0) m_LandingStable = now;
|
||||
if (m_CancelCount == 1)
|
||||
{
|
||||
if (!m_Service.Stopping() || m_Service.Ready(m_Aircraft) || m_Flight.LandingPoint() != m_CancelLandingPoint || !m_Envelope.Contains(m_CancelLandingPoint, 1)) { Check(false, "cancelled order retains frozen qualified point and no new admission"); return -1; }
|
||||
if (!m_Flight.Finished() && (!m_Service.Pending(m_Aircraft) || m_Service.Drained())) { Check(false, "nonterminal cancellation stays pending"); return -1; }
|
||||
if (!contact && height > 1 && m_Flight.ERR_NativeFieldActionUpdates() > m_CancelActionUpdates + 10 && m_Flight.ERR_NativeFieldHelicopterUpdates() > m_CancelHelicopterUpdates + 10 && m_Flight.ERR_NativeFieldFreshReadback(now)) m_ContinuedAirborneControl = true;
|
||||
}
|
||||
m_Service.Tick();
|
||||
if (!m_ObservationFailure.IsEmpty()) { Check(false, m_ObservationFailure); return -1; } // outside production callback stack
|
||||
if (m_Flight.State() == ERR_AircraftFlightState.LANDING && !m_BrakePassed) { Check(false, "no descent before qualified observed braking"); return -1; }
|
||||
if (m_Flight.State() == ERR_AircraftFlightState.SHUTDOWN)
|
||||
{
|
||||
if (!m_LandingHeld && !Check(landed && m_LandingStable >= 0 && now - m_LandingStable >= 2000, "shutdown follows two independently observed stable grounded seconds")) return -1;
|
||||
m_LandingHeld = true;
|
||||
}
|
||||
if (!m_Flight.Finished()) return 0;
|
||||
float targetError = vector.DistanceXZ(position, m_Flight.LandingPoint());
|
||||
float displacement = vector.DistanceXZ(position, home);
|
||||
bool complete = ERR_NativeAircraftFieldRules.Terminal(m_CancelFixture, m_Flight.State(), m_CancelCount, m_Flight.Arrived(), m_CruiseHeld, m_TranslationHeld, m_BrakePassed, displacement, targetError);
|
||||
complete = complete && m_LandingHeld && ERR_NativeAircraftFieldRules.PhysicalFinish(OriginalOccupancy(), landed, !m_Controller.IsEngineOn(), m_InitialFuel - m_Fuel.GetTotalFuel(), m_Wakes, m_EngineStarts, m_ControllerStarts, m_PilotHooks, m_HelicopterHooks);
|
||||
if (m_CancelFixture) complete = complete && m_ContinuedAirborneControl && m_Service.Stopping();
|
||||
PrintFormat("ERR AIRCRAFT FIELD RESULT: state=%1 cancellation=%2 displacement=%3 targetError=%4 cruise10s=%5 endpoint5s=%6 braking=%7 grounded2s=%8 fuelUsed=%9", m_Flight.State(), m_CancelFixture, displacement, targetError, m_CruiseHeld, m_TranslationHeld, m_BrakePassed, m_LandingHeld, m_InitialFuel - m_Fuel.GetTotalFuel());
|
||||
if (!Check(complete, "original physical aircraft completes exact field outcome, measured motion/braking, grounded engine-off and finite fuel use")) return -1;
|
||||
if (!Check(RetainedIdentity() && m_Service.Drained() && !m_Service.Pending(m_Aircraft) && !m_Service.Retired(m_Aircraft), "one original retained flight drains only after actual terminal observation")) 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_Service && !m_Service.Stopping()) m_Service.Stop();
|
||||
// End-of-test disposal is not successful flight or a production cancellation.
|
||||
// These assets are fixture-owned only, never registered with mission cleanup.
|
||||
// Retain service/controller through guarded disposal; then observe ownership
|
||||
// loss before releasing their references. No direct second Cancel or Tick.
|
||||
|
||||
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_NativeAircraftFieldRules.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 FIELD FIXTURE CLEANUP: ownership changed; captured entities preserved");
|
||||
if (m_Service) m_Service.Tick();
|
||||
m_Flight = null;
|
||||
m_Service = null;
|
||||
m_Envelope = null;
|
||||
}
|
||||
void ~ERR_NativeAircraftFieldProbe() { Stop(); }
|
||||
}
|
||||
#endif
|
||||
@@ -13,9 +13,10 @@ modded class ERR_AircraftService
|
||||
{
|
||||
ERR_AircraftRouteFlight ERR_NativeOriginalFlight(Vehicle aircraft)
|
||||
{
|
||||
ERR_AircraftRouteFlight flight = null;
|
||||
ERR_AircraftServiceEntry entry = Find(aircraft);
|
||||
if (entry) return entry.Flight;
|
||||
return null;
|
||||
if (entry) flight = ERR_AircraftRouteFlight.Cast(entry.Flight);
|
||||
return flight;
|
||||
}
|
||||
}
|
||||
modded class SCR_CharacterControllerComponent
|
||||
|
||||
@@ -19,6 +19,8 @@ modded class ERR_GameMode
|
||||
protected bool m_ERRTestMortar;
|
||||
protected bool m_ERRTestMortarFire;
|
||||
protected bool m_ERRTestMapOwnership;
|
||||
protected bool m_ERRTestAircraftField;
|
||||
protected ref ERR_NativeAircraftFieldProbe m_ERRAircraftField;
|
||||
protected bool m_ERRTestAircraftRoute;
|
||||
protected ref ERR_NativeAircraftRouteProbe m_ERRAircraftRoute;
|
||||
protected bool m_ERRTestAircraftControl;
|
||||
@@ -49,6 +51,7 @@ modded class ERR_GameMode
|
||||
m_ERRTestHQResponse = outcome == "hq-response";
|
||||
m_ERRTestMortar = outcome == "mortar-inventory";
|
||||
m_ERRTestMapOwnership = outcome == "map-ownership";
|
||||
m_ERRTestAircraftField = outcome == "aircraft-field";
|
||||
m_ERRTestAircraftRoute = outcome == "aircraft-route";
|
||||
m_ERRTestAircraftCancel = outcome == "aircraft-cancel";
|
||||
m_ERRTestAircraftTranslate = outcome == "aircraft-translate";
|
||||
@@ -60,7 +63,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_ERRTestAircraftRoute) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
|
||||
if (m_ERRTestAircraftControl || m_ERRTestAircraftRoute || m_ERRTestAircraftField) 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;
|
||||
@@ -86,6 +89,7 @@ modded class ERR_GameMode
|
||||
if (m_ERRMapOwnership) m_ERRMapOwnership.Stop();
|
||||
if (m_ERRAircraftControl) m_ERRAircraftControl.Stop();
|
||||
if (m_ERRAircraftRoute) m_ERRAircraftRoute.Stop();
|
||||
if (m_ERRAircraftField) m_ERRAircraftField.Stop();
|
||||
if (m_ERRHQResponseProof) m_ERRHQResponseProof.Stop();
|
||||
if (m_ERRMortarSalvo) m_ERRMortarSalvo.Stop();
|
||||
if (m_ERRMortarCourier) m_ERRMortarCourier.Stop();
|
||||
@@ -126,6 +130,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_ERRTestAircraftField)
|
||||
{
|
||||
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 241, "actual aircraft field fixture completed before explicit reset")) return;
|
||||
m_ERRSmokeDone = true;
|
||||
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
|
||||
ERR_StopHQPatrol();
|
||||
string fieldCancel;
|
||||
string fieldResult = "actual immutable100m field route, measured cruise/braking, destination hold and contact landing; bounded prototype, not HQ support";
|
||||
if (System.GetCLIParam("errSmokeFieldServiceCancel", fieldCancel) && fieldCancel == "1")
|
||||
fieldResult = "actual moving field service Stop retains control through braking, frozen-point contact landing and engine shutdown; no destination arrival or HQ support claim";
|
||||
Print("ERR SMOKE COMPLETE: " + fieldResult);
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestAircraftRoute)
|
||||
{
|
||||
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 231, "actual aircraft route completed before explicit fixture reset")) return;
|
||||
@@ -259,6 +276,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 == 240)
|
||||
{
|
||||
int result = m_ERRAircraftField.Tick(now);
|
||||
if (result < 0) { ERR_Check(false, "native actual aircraft field fixture"); return; }
|
||||
if (result == 1) { m_ERRSmokeStep = 241; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRSmokeStep == 230)
|
||||
{
|
||||
int result = m_ERRAircraftRoute.Tick(now);
|
||||
@@ -333,6 +357,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_ERRTestAircraftField)
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRAircraftField = new ERR_NativeAircraftFieldProbe(this);
|
||||
m_ERRSmokeStep = 240;
|
||||
return;
|
||||
}
|
||||
if (m_ERRTestAircraftRoute)
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
|
||||
@@ -37,6 +37,8 @@ class ERR_OrdinaryGateObserver
|
||||
protected int m_Previous;
|
||||
protected int m_Index;
|
||||
protected bool m_Stopped;
|
||||
protected bool m_HeldAction;
|
||||
protected int m_ObservedPhase = -1;
|
||||
|
||||
protected static bool Enabled(string nonce)
|
||||
{
|
||||
@@ -64,6 +66,8 @@ class ERR_OrdinaryGateObserver
|
||||
s_Request.m_Nonce = nonce; s_Request.m_Start = started; s_Request.m_Previous = started;
|
||||
if (mode && mode.Players()) s_Request.m_Life = mode.Players().CurrentActor(actor);
|
||||
if (s_Request.m_Life) s_Request.m_Account = s_Request.m_Life.AccountId;
|
||||
if (!ERR_OrdinaryInputObserverRules.HeldScope(nonce, s_Request.m_HeldAction))
|
||||
{ s_Request.Cancel(index, now, "held-option-binding-lost"); return; }
|
||||
if (!s_Request.BindGate()) { s_Request.Cancel(index, now, "original-prison-lock-unavailable"); return; }
|
||||
}
|
||||
s_Request.Observe(mode, world, controller, actor, nonce, started, index, now);
|
||||
@@ -95,7 +99,7 @@ class ERR_OrdinaryGateObserver
|
||||
protected bool Context()
|
||||
{
|
||||
if (!GetGame() || !m_Mode || !m_World || !m_Controller || !m_Actor || !m_Life || !m_Gate || !m_Manager || !m_Lock || !m_Context || !m_Door || !m_Handler) return false;
|
||||
if (m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || (m_Mode.Phase() != ERR_Phase.PRISON && m_Mode.Phase() != ERR_Phase.RADIO_SEARCH)) return false;
|
||||
if (m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return false;
|
||||
if (GetGame().GetWorld() != m_World || m_Mode.GetWorld() != m_World || m_Actor.GetWorld() != m_World || m_Gate.GetWorld() != m_World) return false;
|
||||
if (GetGame().GetPlayerController() != m_Controller || m_Controller.GetControlledEntity() != m_Actor || m_Handler.GetControlledEntity() != m_Actor) return false;
|
||||
PlayerManager players = GetGame().GetPlayerManager();
|
||||
@@ -106,6 +110,11 @@ class ERR_OrdinaryGateObserver
|
||||
if (m_Gate.FindComponent(BaseActionsManagerComponent) != m_Manager || m_Gate.FindComponent(BaseDoorComponent) != m_Door || m_Controller.FindComponent(SCR_InteractionHandlerComponent) != m_Handler) return false;
|
||||
if (m_Manager.GetContext("SCR_DoorUserAction") != m_Context || m_Manager.FindAction(m_Lock.GetActionID()) != m_Lock || m_Lock.GetOwner() != m_Gate || m_Lock.GetActionDuration() != 4) return false;
|
||||
if (!GetGame().GetInputManager() || !GetGame().GetMenuManager() || GetGame().GetMenuManager().GetTopMenu()) return false;
|
||||
bool held;
|
||||
if (!ERR_OrdinaryInputObserverRules.HeldScope(m_Nonce, held) || held != m_HeldAction) return false;
|
||||
int phase = m_Mode.Phase();
|
||||
if (!ERR_OrdinaryInputObserverRules.PhaseAllowed(phase, m_ObservedPhase, held, true)) return false;
|
||||
m_ObservedPhase = phase;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,26 @@ class ERR_OrdinaryInputObserverRules
|
||||
for (int i = 0; i < nonce.Length(); i++) if (!allowed.Contains(nonce[i])) return false;
|
||||
return true;
|
||||
}
|
||||
// A held-action observer is only a same-run phase extension, never input.
|
||||
static bool HeldScope(string nonce, out bool held)
|
||||
{
|
||||
held = false;
|
||||
string requested, input, look, gate;
|
||||
if (!System.GetCLIParam("errOrdinaryHeldAction", requested)) return true;
|
||||
if (System.IsConsoleApp() || !Replication.IsServer() || RplSession.Mode() != RplMode.None || requested != nonce || !Nonce(nonce)
|
||||
|| !System.GetCLIParam("errOrdinaryInputObserve", input) || input != nonce
|
||||
|| !System.GetCLIParam("errOrdinaryLookObserve", look) || look != nonce
|
||||
|| !System.GetCLIParam("errOrdinaryGateObserve", gate) || gate != nonce) return false;
|
||||
held = true;
|
||||
return true;
|
||||
}
|
||||
static bool PhaseAllowed(int phase, int previous, bool held, bool legacyGate = false)
|
||||
{
|
||||
if (previous < 0) return phase == ERR_Phase.PRISON;
|
||||
if (!held) return phase == ERR_Phase.PRISON || (legacyGate && phase == ERR_Phase.RADIO_SEARCH);
|
||||
if (previous == ERR_Phase.PRISON) return phase == ERR_Phase.PRISON || phase == ERR_Phase.RADIO_SEARCH;
|
||||
return previous == ERR_Phase.RADIO_SEARCH && phase == ERR_Phase.RADIO_SEARCH;
|
||||
}
|
||||
static bool Within(int start, int previous, int now, int samples)
|
||||
{
|
||||
return now >= start && now >= previous && now - start < 45000 && samples < 450;
|
||||
@@ -46,6 +66,8 @@ class ERR_OrdinaryInputObserver : Managed
|
||||
protected UUID m_Account;
|
||||
protected int m_PlayerId;
|
||||
protected string m_Nonce;
|
||||
protected bool m_HeldAction;
|
||||
protected int m_ObservedPhase = -1;
|
||||
protected bool m_Screenshots;
|
||||
protected bool m_Finished;
|
||||
protected bool m_Ready;
|
||||
@@ -88,6 +110,10 @@ class ERR_OrdinaryInputObserver : Managed
|
||||
request.m_Character = CharacterControllerComponent.Cast(actor.FindComponent(CharacterControllerComponent));
|
||||
request.m_Life = life; request.m_Account = life.AccountId;
|
||||
request.m_PlayerId = controller.GetPlayerId(); request.m_Nonce = nonce;
|
||||
if (!ERR_OrdinaryInputObserverRules.HeldScope(nonce, request.m_HeldAction)) return;
|
||||
// Once a held run has an admitted life, invalid initial context is terminal.
|
||||
// A later PRISON phase or replacement admission cannot restart its evidence.
|
||||
if (request.m_HeldAction) s_Attempted = true;
|
||||
if (!request.Valid()) return;
|
||||
string screenshots;
|
||||
request.m_Screenshots = System.GetCLIParam("errOrdinaryInputScreenshots", screenshots) && screenshots == "1";
|
||||
@@ -102,14 +128,21 @@ class ERR_OrdinaryInputObserver : Managed
|
||||
{
|
||||
string nonce;
|
||||
if (!Enabled(nonce) || nonce != m_Nonce || !GetGame() || !m_Mode || m_Mode != ERR_GameMode.Get()) return false;
|
||||
if (!m_World || GetGame().GetWorld() != m_World || m_Mode.GetWorld() != m_World || !m_Mode.Active() || m_Mode.Phase() != ERR_Phase.PRISON) return false;
|
||||
if (!m_World || GetGame().GetWorld() != m_World || m_Mode.GetWorld() != m_World || !m_Mode.Active()) 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.CanFight(m_Actor) || m_Actor.GetWorld() != m_World || !m_Mode.Players()) return false;
|
||||
if (!m_Character || m_Actor.FindComponent(CharacterControllerComponent) != m_Character || m_Character.IsUnconscious()) return false;
|
||||
ERR_PlayerLife current = m_Mode.Players().CurrentActor(m_Actor);
|
||||
return current && current == m_Life && current.AccountId == m_Account;
|
||||
if (!current || current != m_Life || current.AccountId != m_Account) return false;
|
||||
bool held;
|
||||
if (!ERR_OrdinaryInputObserverRules.HeldScope(m_Nonce, held) || held != m_HeldAction) return false;
|
||||
if (held && (CompartmentAccessComponent.GetVehicleIn(m_Actor) || !GetGame().GetMenuManager() || GetGame().GetMenuManager().GetTopMenu())) return false;
|
||||
int phase = m_Mode.Phase();
|
||||
if (!ERR_OrdinaryInputObserverRules.PhaseAllowed(phase, m_ObservedPhase, held)) return false;
|
||||
m_ObservedPhase = phase;
|
||||
return true;
|
||||
}
|
||||
protected void Record(string detail)
|
||||
{
|
||||
|
||||
@@ -17,6 +17,8 @@ class ERR_OrdinaryLookObserver
|
||||
protected int m_Previous;
|
||||
protected int m_Index;
|
||||
protected bool m_Stopped;
|
||||
protected bool m_HeldAction;
|
||||
protected int m_ObservedPhase = -1;
|
||||
|
||||
protected static bool Enabled(string nonce)
|
||||
{
|
||||
@@ -45,6 +47,8 @@ class ERR_OrdinaryLookObserver
|
||||
if (controller) s_Request.m_Camera = controller.GetPlayerCamera();
|
||||
if (mode && mode.Players()) s_Request.m_Life = mode.Players().CurrentActor(actor);
|
||||
if (s_Request.m_Life) s_Request.m_Account = s_Request.m_Life.AccountId;
|
||||
if (!ERR_OrdinaryInputObserverRules.HeldScope(nonce, s_Request.m_HeldAction))
|
||||
{ s_Request.Cancel(index, now, "held-option-binding-lost"); return; }
|
||||
}
|
||||
s_Request.Observe(mode, world, controller, actor, nonce, started, index, now);
|
||||
}
|
||||
@@ -60,7 +64,7 @@ class ERR_OrdinaryLookObserver
|
||||
protected bool Context()
|
||||
{
|
||||
if (!GetGame() || !m_Mode || !m_World || !m_Controller || !m_Actor || !m_Camera || !m_Life) return false;
|
||||
if (m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || m_Mode.Phase() != ERR_Phase.PRISON) return false;
|
||||
if (m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return false;
|
||||
if (GetGame().GetWorld() != m_World || m_Mode.GetWorld() != m_World || m_Actor.GetWorld() != m_World || m_Camera.GetWorld() != m_World) return false;
|
||||
if (GetGame().GetPlayerController() != m_Controller || m_Controller.GetControlledEntity() != m_Actor || m_Controller.GetPlayerCamera() != m_Camera) return false;
|
||||
PlayerManager manager = GetGame().GetPlayerManager();
|
||||
@@ -70,6 +74,12 @@ class ERR_OrdinaryLookObserver
|
||||
CameraManager cameras = GetGame().GetCameraManager();
|
||||
if (!cameras || cameras.CurrentCamera() != m_Camera) return false;
|
||||
if (!GetGame().GetInputManager() || !GetGame().GetMenuManager() || GetGame().GetMenuManager().GetTopMenu()) return false;
|
||||
bool held;
|
||||
if (!ERR_OrdinaryInputObserverRules.HeldScope(m_Nonce, held) || held != m_HeldAction) return false;
|
||||
if (held && CompartmentAccessComponent.GetVehicleIn(m_Actor)) return false;
|
||||
int phase = m_Mode.Phase();
|
||||
if (!ERR_OrdinaryInputObserverRules.PhaseAllowed(phase, m_ObservedPhase, held)) return false;
|
||||
m_ObservedPhase = phase;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user