91 lines
5.0 KiB
C
91 lines
5.0 KiB
C
// 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;
|
|
}
|
|
}
|