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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user