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

147 lines
7.1 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;
}
#ifdef WORKBENCH
// At most the first refusal of each exact survey call is logged. No query,
// predicate, retry or resource allocation is added to the qualification.
protected static bool SurveyDiagnostics()
{
string enabled, outcome;
return System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "aircraft-field";
}
protected static void SurveyTraceFailure(string stage, float fraction, TraceBox trace)
{
if (!SurveyDiagnostics()) return;
IEntity hit = trace.TraceEnt;
string kind = "none", resource = "";
if (hit)
{
kind = hit.Type().ToString();
if (hit.GetPrefabData()) resource = hit.GetPrefabData().GetPrefabName();
}
if (hit)
{
vector lower, upper; hit.GetWorldBounds(lower, upper);
PrintFormat("ERR AIRCRAFT FIELD SURVEY HIT BOUNDS: lower=%1 upper=%2", lower, upper);
}
PrintFormat("ERR AIRCRAFT FIELD SURVEY REFUSED: stage=%1 fraction=%2 entity=%3 class=%4 prefab=%5 start=%6 end=%7", stage, fraction, hit, kind, resource, trace.Start, trace.End);
}
#endif
static bool Survey(vector start, vector end, array<IEntity> excluded = null)
{
if (!GetGame() || !GetGame().GetWorld() || !FinitePoint(start) || !FinitePoint(end))
{
#ifdef WORKBENCH
if (SurveyDiagnostics()) PrintFormat("ERR AIRCRAFT FIELD SURVEY REFUSED: stage=context-or-finite-points start=%1 end=%2", start, end);
#endif
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))
{
#ifdef WORKBENCH
if (SurveyDiagnostics()) PrintFormat("ERR AIRCRAFT FIELD SURVEY REFUSED: stage=budget-or-endpoints start=%1 end=%2 distance=%3 columns=%4 rows=%5 startHeight=%6 endHeight=%7 delta=%8", start, end, distance, columns, rows, start[1], end[1], Math.AbsFloat(end[1] - start[1]));
#endif
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);
float terrain = SCR_TerrainHelper.GetTerrainY(sample);
if (!(Math.AbsFloat(terrain - start[1]) <= 0.3))
{
#ifdef WORKBENCH
if (SurveyDiagnostics()) PrintFormat("ERR AIRCRAFT FIELD SURVEY REFUSED: stage=terrain x=%1 z=%2 columns=%3 rows=%4 sample=%5 terrainHeight=%6 startHeight=%7 delta=%8", x, z, columns, rows, sample, terrain, start[1], Math.AbsFloat(terrain - start[1]));
#endif
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;
float corridorFraction = GetGame().GetWorld().TraceMove(corridor, null);
if (!(corridorFraction >= 1))
{
#ifdef WORKBENCH
SurveyTraceFailure("corridor", corridorFraction, corridor);
#endif
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;
float lowFraction = GetGame().GetWorld().TraceMove(low, null);
#ifdef WORKBENCH
if (!(lowFraction >= 1)) SurveyTraceFailure("low-objects", lowFraction, low);
#endif
return lowFraction >= 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;
}
}