// 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 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 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; } }