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

101 lines
5.3 KiB
C

// Experimental direct leg only; the original point envelope is unchanged.
class ERR_AircraftRouteRules
{
static bool Length(float distance) { return distance >= 18 && distance <= 20.05; }
// Candidate route-only gain. Desired speed and cyclic magnitude remain bounded
// exactly as in the short-translation controller; landing keeps its old gain.
static vector Demand(vector target, vector position, vector velocity)
{
vector error = target - position; error[1] = 0;
vector desired = error * 0.25;
float speed = desired.Length();
if (speed > 0.5) desired = desired * (0.5 / speed);
velocity[1] = 0;
vector demand = (desired - velocity) * 0.10;
float magnitude = demand.Length();
if (magnitude > 0.03) demand = demand * (0.03 / magnitude);
return demand;
}
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 (!Length(vector.DistanceXZ(start, end))) return false;
float height = point[1] - start[1];
return height >= -3 && height <= 18 && upright >= 0.85 && vector.DistanceXZ(point, Nearest(start, end, point)) < 8;
}
static bool Held(bool contact, float height, float speed, float vertical, float upright, float horizontal, float error)
{
return !contact && ERR_AircraftFlightRules.Hover(height, speed, vertical, upright) && horizontal >= 0 && horizontal < 0.25 && error >= 0 && error <= 0.75;
}
}
// Inherits the exact single-claim identity/clock guard, not caller-supplied validity.
class ERR_AircraftRouteEnvelope : ERR_AircraftFlightEnvelope
{
protected vector m_Destination;
vector Destination() { return m_Destination; }
bool Contains(vector position, float upright) { return ERR_AircraftRouteRules.Contains(m_Home, m_Destination, position, upright); }
// This deliberately qualifies a larger enclosing rectangle, not just endpoints.
// Whole-model/rotor radius+8m deviation+1m margin already fits the 40m half-width.
static bool Survey(vector start, vector end, array<IEntity> excluded = null)
{
if (!GetGame() || !GetGame().GetWorld() || !ERR_AircraftRouteRules.Length(vector.DistanceXZ(start, end))) 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]);
}
// Near-level bare-ground route. Grid covers both pads and every possible
// corridor landing centre plus the native gear footprint, at <=2m spacing.
float spanX = upper[0] - lower[0] + 44, spanZ = upper[2] - lower[2] + 44;
int columns = Math.Ceil(spanX / 2), rows = Math.Ceil(spanZ / 2);
if (columns > 34 || rows > 34) return false;
for (int x = 0; x <= columns; x++)
for (int z = 0; z <= rows; z++)
{
vector sample = Vector(lower[0] - 22 + spanX * x / columns, 0, lower[2] - 22 + spanZ * z / rows);
if (!(Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(sample) - start[1]) <= 0.3)) return false;
}
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;
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;
// Separate low object sweep makes intermediate emergency landing ground as
// conservative as both endpoints; WORLD is above because the ground is wanted.
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_AircraftRouteEnvelope QualifyRoute(ERR_GameMode mode, Vehicle aircraft, IEntity pilot, vector destination)
{
// Existing native qualification supplies health, seat, complete rotor/model
// footprint, near-ground and exact original-airframe checks; no bypass.
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 (!ERR_AircraftRouteRules.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;
ERR_AircraftRouteEnvelope record = new ERR_AircraftRouteEnvelope();
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];
record.m_QualifiedAt = GetGame().GetWorld().GetWorldTime();
return record;
}
}