// Successor-owned measured envelope, independent of aircraft yaw/pitch/roll. // Adapted from the separately qualified native vertical fixture; no native hook. // All distances are around the native physics origin, not the rotor centre. class ERR_AircraftFlightFootprintRules { static float CylinderSphere(float originDistance, float radius, float thickness) { // Triangle inequality deliberately overbounds the one-sided rotor cylinder. return originDistance + radius + thickness; } static bool Fits(float radius, float initialDrift, float initialHeight) { if (!(radius > 0 && radius < 32 && initialDrift >= 0 && initialDrift < 32)) return false; if (!(initialHeight >= -3 && initialHeight <= 5)) return false; return radius + initialDrift + 8 + 1 <= 40 && radius + initialHeight + 18 + 1 <= 60; } } class ERR_AircraftFlightFootprint { static const int MAX_ENTITIES = 256; static const int MAX_DEPTH = 16; static const int MAX_HITZONES = 512; protected static bool Refuse(string reason) { Print("ERR AIRCRAFT FOOTPRINT: refused " + reason); return false; } protected static bool PointRadius(vector point, vector origin, inout float radius) { float distance = vector.Distance(point, origin); if (!(distance >= 0 && distance < 1000)) return false; radius = Math.Max(radius, distance); return true; } static bool Measure(Vehicle aircraft, VehicleHelicopterSimulation simulation, vector pad, out vector origin, out float radius) { radius = 0; if (!aircraft || !simulation || !simulation.IsValid() || !aircraft.GetPrefabData()) return Refuse("missing initialized native airframe"); if (aircraft.GetPrefabData().GetPrefabName() != "{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et") return Refuse("different airframe resource"); // Model bounds and damage pivots are world-space. The simulation getter // exposes internal physics space (identity at the airport in 100422), so // use this same physical aircraft's actual entity world transform. vector worldTransform[4]; aircraft.GetWorldTransform(worldTransform); origin = worldTransform[3]; for (int axis = 0; axis < 3; axis++) if (!(Math.AbsFloat(worldTransform[axis].Length() - 1) < 0.001)) return Refuse("non-unit native entity world basis"); array entities = {aircraft}; array depths = {0}; array seenZones = {}; int meshes, mainSweeps, tailSweeps; float modelRadius, damageRadius, simulationRadius; for (int index = 0; index < entities.Count(); index++) { IEntity entity = entities[index]; if (!entity) return Refuse("hierarchy changed during measurement"); if (entity.GetVObject()) { vector mins, maxs; entity.GetWorldBounds(mins, maxs); for (int dimension = 0; dimension < 3; dimension++) if (!(mins[dimension] <= maxs[dimension])) return Refuse("invalid model bounds"); // A sphere around every corner encloses the complete world AABB. Its radius // remains conservative under any rigid orientation of the whole aircraft. for (int x = 0; x < 2; x++) for (int y = 0; y < 2; y++) for (int z = 0; z < 2; z++) { vector corner = mins; if (x == 1) corner[0] = maxs[0]; if (y == 1) corner[1] = maxs[1]; if (z == 1) corner[2] = maxs[2]; if (!PointRadius(corner, origin, modelRadius)) return Refuse("non-finite or excessive model bounds"); } meshes++; } HitZoneContainerComponent container = HitZoneContainerComponent.Cast(entity.FindComponent(HitZoneContainerComponent)); if (container) { int count = container.CountAllHitZones(); if (count < 0 || count + seenZones.Count() > MAX_HITZONES) return Refuse("hitzone traversal bound"); array zones = {}; if (container.GetAllHitZones(zones) != count || zones.Count() != count) return Refuse("hitzones changed during measurement"); foreach (HitZone zone : zones) { if (!zone || seenZones.Contains(zone)) continue; seenZones.Insert(zone); SCR_RotorHitZone rotorZone = SCR_RotorHitZone.Cast(zone); if (!rotorZone || rotorZone.GetRotorRadius() <= 0) continue; // transmission zones have no sweep int rotorIndex = rotorZone.GetRotorIndex(); float expectedRadius, thickness; if (rotorIndex == 0) { expectedRadius = 12; thickness = 1.5; mainSweeps++; } else if (rotorIndex == 1) { expectedRadius = 2.3; thickness = 0.75; tailSweeps++; } else return Refuse("unknown rotor sweep index"); // Stock thickness is protected with no getter. Scope to the exact prefab // and verify its public sweep radius against the audited stock constants. if (!(Math.AbsFloat(rotorZone.GetRotorRadius() - expectedRadius) < 0.001)) return Refuse("stock damage sweep radius changed"); PointInfo point = rotorZone.GetPointInfo(); if (!point) return Refuse("missing actual rotor sweep pivot"); vector rotorWorld[4]; point.GetWorldTransform(rotorWorld); // Frobenius norm bounds the transform's maximum stretch even with scale // or shear; do not assume the PointInfo basis is orthonormal. float scaleSquared = 0; for (int basis = 0; basis < 3; basis++) scaleSquared += vector.Dot(rotorWorld[basis], rotorWorld[basis]); float scale = Math.Sqrt(scaleSquared); if (!(scale > 0 && scale < 4)) return Refuse("invalid damage sweep basis"); float distance = vector.Distance(origin, rotorWorld[3]); float sweepBound = ERR_AircraftFlightFootprintRules.CylinderSphere(distance, expectedRadius * scale, thickness * scale); if (!(sweepBound > 0 && sweepBound < 1000)) return Refuse("invalid damage sweep extent"); damageRadius = Math.Max(damageRadius, sweepBound); PrintFormat("ERR AIRCRAFT FOOTPRINT ROTOR: index=%1 pivot=%2 damageRadius=%3 thickness=%4 scale=%5 sphereBound=%6", rotorIndex, rotorWorld[3], expectedRadius, thickness, scale, sweepBound); } } IEntity child = entity.GetChildren(); while (child) { if (entities.Count() >= MAX_ENTITIES || depths[index] >= MAX_DEPTH || entities.Contains(child)) return Refuse("entity/depth/cycle traversal bound"); entities.Insert(child); depths.Insert(depths[index] + 1); child = child.GetSibling(); } } if (meshes == 0 || mainSweeps != 1 || tailSweeps != 1) return Refuse("complete model and both actual damage sweeps required"); if (simulation.RotorCount() != 2) return Refuse("stock simulation rotor count changed"); for (int rotor = 0; rotor < 2; rotor++) { vector localRotor[4]; simulation.RotorGetTransform(rotor, localRotor); float physicalRadius = simulation.RotorGetRadius(rotor); float physicalBound = localRotor[3].Length() + physicalRadius; if (!(physicalRadius > 0 && physicalBound > 0 && physicalBound < 1000)) return Refuse("invalid native simulation rotor extent"); simulationRadius = Math.Max(simulationRadius, physicalBound); PrintFormat("ERR AIRCRAFT FOOTPRINT PHYSICS ROTOR: index=%1 localPivot=%2 radius=%3 sphereBound=%4", rotor, localRotor[3], physicalRadius, physicalBound); } radius = Math.Max(modelRadius, Math.Max(damageRadius, simulationRadius)); float initialDrift = vector.DistanceXZ(origin, pad); float initialHeight = origin[1] - pad[1]; PrintFormat("ERR AIRCRAFT FOOTPRINT: entities=%1 meshes=%2 zones=%3 modelSphere=%4 damageSphere=%5 simulationSphere=%6 completeSphere=%7 initialDrift=%8 initialHeight=%9", entities.Count(), meshes, seenZones.Count(), modelRadius, damageRadius, simulationRadius, radius, initialDrift, initialHeight); if (!ERR_AircraftFlightFootprintRules.Fits(radius, initialDrift, initialHeight)) return Refuse("measured footprint plus initial placement,8m drift and1m margin exceeds40m corridor or60m ceiling"); return true; } } // Issued only by physical qualification; fields have no public mutators. // One claim binds this short-lived record to one original aircraft/pilot/order. class ERR_AircraftFlightEnvelope { protected ERR_GameMode m_Mode; protected Vehicle m_Aircraft; protected IEntity m_Pilot; protected vector m_Home; protected float m_Radius; protected float m_QualifiedAt; protected int m_Claim; vector Home() { return m_Home; } float Radius() { return m_Radius; } bool ClaimedBy(Vehicle aircraft, IEntity pilot, int token) { return m_Claim > 0 && m_Claim == token && m_Aircraft == aircraft && m_Pilot == pilot; } bool Claim(ERR_GameMode mode, Vehicle aircraft, IEntity pilot, int token) { if (!GetGame() || !GetGame().GetWorld()) return false; float now = GetGame().GetWorld().GetWorldTime(); if (!Replication.IsServer() || !mode || mode != m_Mode || mode != ERR_GameMode.Get() || !mode.Active() || m_Claim != 0 || token <= 0) return false; if (aircraft != m_Aircraft || pilot != m_Pilot || !aircraft || !pilot || now < m_QualifiedAt || now - m_QualifiedAt > 2000) return false; if (vector.Distance(aircraft.GetOrigin(), m_Home) > 0.1) return false; m_Claim = token; return true; } protected static bool Hierarchy(IEntity root, array excluded) { if (!root) return false; array pending = {root}; array depths = {0}; for (int i = 0; i < pending.Count(); i++) { IEntity entity = pending[i]; if (!entity || excluded.Count() >= 512) return false; if (!excluded.Contains(entity)) excluded.Insert(entity); IEntity child = entity.GetChildren(); while (child) { if (pending.Count() >= 256 || depths[i] >= 16 || pending.Contains(child)) return false; pending.Insert(child); depths.Insert(depths[i] + 1); child = child.GetSibling(); } } return true; } protected static bool ClearColumn(Vehicle aircraft, IEntity pilot, vector pad) { array excluded = {}; if (!Hierarchy(aircraft, excluded) || !Hierarchy(pilot, excluded)) return false; for (int edge = 0; edge < 4; edge++) { float side = edge * Math.PI / 2; vector point = pad + Vector(Math.Cos(side) * 14, 0, Math.Sin(side) * 14); if (!(Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(point) - pad[1]) <= 0.8)) return false; } // Same four finite diagonal cylinder traces as the native fixture, with // exclusions restricted to this already-existing aircraft and seated pilot. array diagonals = {"40 2 0", "-40 2 0", "0 2 40", "0 2 -40"}; foreach (vector diagonal : diagonals) { TraceParam trace = new TraceParam(); trace.Start = pad + "0 2.5 0" + diagonal; trace.End = pad + "0 2.5 0" - diagonal; trace.Flags = TraceFlags.ENTS; trace.ExcludeArray = excluded; if (!(aircraft.GetWorld().TraceMove(trace, null) >= 1)) return false; } TraceBox clearance = new TraceBox(); clearance.Start = pad + "0 3 0"; clearance.End = pad + "0 60 0"; clearance.Mins = "-40 -1 -40"; clearance.Maxs = "40 1 40"; clearance.Flags = TraceFlags.ENTS | TraceFlags.WORLD; clearance.ExcludeArray = excluded; return aircraft.GetWorld().TraceMove(clearance, null) >= 1; } static ERR_AircraftFlightEnvelope Qualify(ERR_GameMode mode, Vehicle aircraft, IEntity pilot) { if (!GetGame() || !GetGame().GetWorld() || !GetGame().GetPlayerManager()) return null; if (!Replication.IsServer() || !mode || mode != ERR_GameMode.Get() || !mode.Active() || !aircraft || !pilot || ERR_Actors.Player(pilot)) return null; if (CompartmentAccessComponent.GetVehicleIn(pilot) != aircraft || !ERR_Actors.Alive(aircraft) || !ERR_Actors.CanFight(pilot)) return null; HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(aircraft.FindComponent(HelicopterControllerComponent)); if (!controller || controller.IsEngineOn() || !controller.GetSimulation() || !controller.GetSimulation().IsValid()) return null; PilotCompartmentSlot seat = controller.GetPilotCompartmentSlot(); if (!seat || seat.GetOccupant() != pilot) return null; if (!ERR_CasualtyLoadingRules.AircraftReady(aircraft)) return null; vector pad = ERR_Actors.Ground(aircraft.GetOrigin()); if (!(pad[1] >= 2) || !ClearColumn(aircraft, pilot, pad)) return null; vector origin; float radius; if (!ERR_AircraftFlightFootprint.Measure(aircraft, controller.GetSimulation(), pad, origin, radius)) return null; ERR_AircraftFlightEnvelope record = new ERR_AircraftFlightEnvelope(); record.m_Mode = mode; record.m_Aircraft = aircraft; record.m_Pilot = pilot; record.m_Home = origin; record.m_Radius = radius; record.m_QualifiedAt = GetGame().GetWorld().GetWorldTime(); return record; } }