Files
EscapistsReintroduced/docs/evidence/airdrop-frequency-2026-10-09/baseline/ERR_AirAssaultLoad.c
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

366 lines
15 KiB
C

class ERR_AirPassenger
{
IEntity Actor;
BaseCompartmentSlot Seat;
bool Locked = true;
bool Jumped;
ERR_AssaultParachute Chute;
}
class ERR_AirAssaultTeam
{
SCR_AIGroup Group;
ref array<ref ERR_AirPassenger> Members = {};
ref ERR_AirDropOrder Order;
bool Released;
void Landed() {} // Canopy completion is observed by the owning load for the entire original team.
void Update()
{
if (Released || !Group || !Order || Group.GetPlayerCount(true) > 0) return;
bool survivor;
foreach (ERR_AirPassenger member : Members)
{
if (!ERR_Actors.Alive(member.Actor)) continue;
if (!member.Jumped || CompartmentAccessComponent.GetVehicleIn(member.Actor) || member.Actor.GetOrigin()[1] - SCR_TerrainHelper.GetTerrainY(member.Actor.GetOrigin()) > 3) return;
survivor = survivor || ERR_Actors.CanFight(member.Actor);
}
if (!survivor) return;
Released = true;
Group.ActivateAI();
Group.SetPermanentLOD(-1);
ERR_HQ.Get().LandAirTeam(Group, Order);
}
}
class ERR_AirDropGeometry
{
static string LastFailure;
static float Along(vector position, vector center, vector direction)
{ vector offset = position - center;
offset[1] = 0;
return vector.Dot(offset, direction);
}
static bool Corridor(vector position, vector center, vector direction, vector velocity)
{
float along = Along(position, center, direction);
vector lateral = position - center - direction * along;
lateral[1] = 0;
velocity[1] = 0;
return along >= -175 && along <= 250 && lateral.Length() <= 45 && velocity.Length() >= 15 && vector.Dot(velocity.Normalized(), direction) >= 0.8;
}
static bool LandingLine(vector center, vector direction)
{
LastFailure = "clear";
for (int step = -4; step <= 5; step++)
{
vector point = ERR_Actors.Ground(center + direction * (step * 50));
if (point[1] < 2 || ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), point)) { LastFailure = "water/shore"; return false; }
if (!ERR_HQRoadIntercept.Standable(point)) { LastFailure = "landing ground not standable"; return false; }
TraceBox trace = new TraceBox();
trace.Start = point + "0 3 0";
trace.End = point + "0 190 0";
trace.Mins = "-4 -1 -4";
trace.Maxs = "4 1 4";
trace.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
if (GetGame().GetWorld().TraceMove(trace, null) < 1) { LastFailure = "obstructed descent column"; return false; }
}
return true;
}
}
// Eight original passengers, two original teams, one consumed load per aircraft.
class ERR_AirAssaultLoad
{
static const ResourceName PARACHUTE = "{C31F210ABE0781A0}Prefabs/ERR/AssaultParachute.et";
protected ERR_GameMode m_Mode;
protected ERR_ReconFlightComponent m_Flight;
protected Vehicle m_Aircraft;
protected int m_Serial, m_NextPassenger;
protected ref array<BaseCompartmentSlot> m_Seats = {};
protected ref array<ref ERR_AirPassenger> m_Passengers = {};
protected ref array<ref ERR_AirAssaultTeam> m_Teams = {};
protected ref ERR_AirDropOrder m_Order;
protected float m_CoverUntil;
bool Cover(float now, out vector center) { if (!m_Order || !m_Used || m_Approaching || now >= m_CoverUntil) return false;
center = m_Order.Target;
return true;
}
bool NeedsService(float now) { return m_Used && !m_Approaching && now >= m_CoverUntil;
}
protected float m_LoadDeadline, m_NextBoard, m_NextJump, m_AirspaceUntil;
protected bool m_Ready, m_Failed, m_Stopped, m_Used, m_Approaching, m_RunCommitted;
protected vector m_Center, m_Direction, m_RunStart, m_RunEnd;
static void SetLock(IEntity actor, bool locked)
{
ERR_HQ hq = ERR_HQ.Get();
if (hq && hq.AirAssault()) hq.AirAssault().Lock(actor, locked);
}
void ReuseRemaining()
{
m_Used = false;
m_Order = null;
m_CoverUntil = 0;
foreach (ERR_AirPassenger member : m_Passengers)
if (member.Actor && CompartmentAccessComponent.GetVehicleIn(member.Actor) == m_Aircraft) { member.Jumped = false;
member.Locked = true;
}
}
int Count() { return m_Passengers.Count();
}
ERR_AirPassenger At(int index) { return m_Passengers[index];
}
bool Used() { return m_Used;
}
bool Preparing() { return !m_Ready && !m_Failed && !m_Stopped;
}
bool Ready()
{
return m_Ready && !m_Failed && !m_Stopped && !m_Used && m_Flight && !m_Flight.Lost()
&& (m_Flight.Phase() == ERR_ReconPhase.SEARCH || m_Flight.Phase() == ERR_ReconPhase.SHADOW) && Aboard() > 0;
}
void ERR_AirAssaultLoad(ERR_GameMode mode, ERR_ReconFlightComponent flight, int serial)
{ m_Mode = mode;
m_Flight = flight;
m_Aircraft = flight.Aircraft();
m_Serial = serial;
}
protected void Seats(IEntity entity)
{
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(entity.FindComponent(BaseCompartmentManagerComponent));
if (manager)
{
array<BaseCompartmentSlot> seats = {};
manager.GetCompartments(seats);
foreach (BaseCompartmentSlot seat : seats)
if (seat && seat.GetType() == ECompartmentType.CARGO && seat.IsCompartmentAccessible() && !seat.IsOccupied() && !m_Seats.Contains(seat)) m_Seats.Insert(seat);
}
IEntity child = entity.GetChildren();
while (child) { Seats(child);
child = child.GetSibling();
}
}
protected void Failure(string reason)
{
m_Failed = true;
m_Approaching = false;
foreach (ERR_AirPassenger member : m_Passengers) member.Locked = false;
ERR_HQ.Event("AIR LOAD UNAVAILABLE", reason + "; original partial entities retained, reconnaissance remains available");
}
void Load(vector pad)
{
m_LoadDeadline = GetGame().GetWorld().GetWorldTime() + 30000;
Seats(m_Aircraft);
ResourceName soldier = m_Mode.FactionSelection().Soldier();
Resource chute = Resource.Load(PARACHUTE);
if (m_Seats.Count() < 8 || soldier.IsEmpty() || !chute || !chute.IsValid()) { Failure("eight usable cargo seats, soldier or canopy unavailable");
return;
}
for (int t = 0; t < 2; t++)
{
ERR_AirAssaultTeam team = new ERR_AirAssaultTeam();
team.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn("{C31F210ABE078195}Prefabs/ERR/ReconCrew.et", pad));
m_Teams.Insert(team);
if (!team.Group) { Failure("passenger group spawn failed");
return;
}
m_Mode.Own(team.Group);
team.Group.SetFaction(m_Mode.EnemyFaction());
team.Group.SetPermanentLOD(0);
}
for (int i = 0; i < 8; i++)
{
ERR_AirPassenger member = new ERR_AirPassenger();
member.Seat = m_Seats[i];
AIGroup group = m_Teams[i % 2].Group;
member.Actor = member.Seat.SpawnCharacterInCompartment(soldier, group, "{C31F210ABE078195}Prefabs/ERR/ReconCrew.et");
m_Passengers.Insert(member);
m_Teams[i % 2].Members.Insert(member);
if (!member.Actor || group != m_Teams[i % 2].Group) { Failure("native passenger creation or group identity failed");
return;
}
}
}
protected void Boarding(float now)
{
foreach (ERR_AirAssaultTeam loadingTeam : m_Teams)
{
if (!loadingTeam.Group) continue;
loadingTeam.Group.ActivateAI();
array<AIAgent> loadingAgents = {};
loadingTeam.Group.GetAgents(loadingAgents);
foreach (AIAgent loadingAgent : loadingAgents) { loadingAgent.SetPermanentLOD(0);
loadingAgent.ActivateAI();
}
}
if (now >= m_LoadDeadline) { Failure(string.Format("native passenger boarding timed out; seated=%1/%2", Aboard(), Count()));
return;
}
bool all = m_Passengers.Count() == 8;
foreach (ERR_AirPassenger member : m_Passengers)
{
if (!ERR_Actors.CanFight(member.Actor)) { all = false;
continue;
}
if (CompartmentAccessComponent.GetVehicleIn(member.Actor) == m_Aircraft && member.Seat.GetOccupant() == member.Actor) continue;
all = false;
if (now < m_NextBoard || CompartmentAccessComponent.GetVehicleIn(member.Actor) || member.Seat.IsOccupied()) continue;
SCR_CompartmentAccessComponent access = SCR_CompartmentAccessComponent.Cast(member.Actor.FindComponent(SCR_CompartmentAccessComponent));
if (access) access.MoveInVehicle(m_Aircraft, ECompartmentType.CARGO, false, member.Seat);
}
if (now >= m_NextBoard) m_NextBoard = now + 1000;
if (!all) return;
for (int t = 0; t < m_Teams.Count(); t++)
{
ERR_AirAssaultTeam team = m_Teams[t];
foreach (ERR_AirPassenger original : team.Members)
if (ERR_Actors.Group(original.Actor) != team.Group || !ERR_Actors.SetFaction(original.Actor, m_Mode.EnemyFaction())) { Failure("original passenger faction/group changed");
return;
}
ERR_ForceRecord record = m_Mode.Forces().Register(string.Format("airborne-%1-team-%2", m_Serial, t), ERR_ForceKind.PATROL, m_Aircraft.GetOrigin(), 4, null, team.Group, now);
if (!record) { Failure("finite passenger ledger refused registration");
return;
}
record.Pinned = true;
m_Mode.RegisterGroup(team.Group);
ERR_HQ.Get().ReserveGroup(team.Group);
team.Group.ActivateAI();
array<AIAgent> agents = {};
team.Group.GetAgents(agents);
foreach (AIAgent agent : agents) { agent.SetPermanentLOD(0);
agent.ActivateAI();
}
}
m_Ready = true;
ERR_HQ.Event("AIR LOAD READY", "eight original passengers seated in two four-person teams");
}
int Aboard()
{
if (!m_Aircraft) return 0;
int count;
foreach (ERR_AirPassenger member : m_Passengers)
if (ERR_Actors.CanFight(member.Actor) && !ERR_Actors.Player(member.Actor) && CompartmentAccessComponent.GetVehicleIn(member.Actor) == m_Aircraft) count++;
return count;
}
bool HoldsSeat(IEntity actor)
{
if (m_Stopped || m_Failed || !ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor)) return false;
foreach (ERR_AirAssaultTeam team : m_Teams)
foreach (ERR_AirPassenger member : team.Members)
if (member.Actor == actor && member.Locked && ERR_Actors.Group(actor) == team.Group)
return (m_Flight && m_Flight.Phase() < ERR_ReconPhase.PARKED && CompartmentAccessComponent.GetVehicleIn(actor) == m_Aircraft) || (member.Chute && CompartmentAccessComponent.GetVehicleIn(actor) == member.Chute);
return false;
}
void Lock(IEntity actor, bool locked)
{ foreach (ERR_AirPassenger member : m_Passengers) if (member.Actor == actor) member.Locked = locked;
}
bool Begin(ERR_AirDropOrder order, float now)
{
if (!Ready() || !order || now >= order.ExpiresAt) return false;
vector radial = m_Aircraft.GetOrigin() - order.Target;
radial[1] = 0;
float heading = Math.Atan2(radial[0], radial[2]);
bool found;
int surveyed;
map<string, int> rejected = new map<string, int>();
// Several distances and both runway directions provide alternatives around
// woodland while retaining every landing/descent safety check.
for (int attempt = 0; attempt < 72; attempt++)
{
int bearing = attempt % 12;
int ring = Math.Floor(attempt / 24.0);
float radius = 300 + ring * 200;
float angle = heading + bearing * Math.PI / 6;
radial = Vector(Math.Sin(angle), 0, Math.Cos(angle));
m_Center = ERR_Actors.Ground(order.Target + radial * radius);
m_Direction = Vector(-radial[2], 0, radial[0]);
if ((attempt % 24) >= 12) m_Direction = radial;
surveyed++;
if (ERR_AirDropGeometry.LandingLine(m_Center, m_Direction)) { found = true; break; }
int count; rejected.Find(ERR_AirDropGeometry.LastFailure, count);
rejected.Set(ERR_AirDropGeometry.LastFailure, count + 1);
}
string summary;
foreach (string reason, int failures : rejected) summary += reason + "=" + failures.ToString() + "; ";
ERR_HQ.Event("AIR DROP SURVEY", string.Format("target=%1 checked=%2 accepted=%3 center=%4; %5", order.Target, surveyed, found, m_Center, summary));
if (!found) return false;
m_RunStart = m_Center - m_Direction * 350;
m_RunEnd = m_Center + m_Direction * 450;
m_CoverUntil = now + 300000;
m_Order = order;
m_Used = true;
m_Approaching = true;
m_RunCommitted = false;
m_NextPassenger = 0;
m_NextJump = now;
foreach (ERR_AirAssaultTeam team : m_Teams) team.Order = order;
m_AirspaceUntil = order.ExpiresAt + 150000;
return true;
}
bool Approach(out vector point)
{
point = m_RunStart;
if (m_RunCommitted) point = m_RunEnd;
return m_Approaching && !m_Stopped;
}
void Abort()
{ if (m_Approaching && m_Order) m_CoverUntil = GetGame().GetWorld().GetWorldTime() + 300000;
m_Approaching = false;
m_RunCommitted = false;
}
bool BlocksMortar(vector impact, float now)
{
if (!m_Order || vector.DistanceXZ(impact, m_Order.Target) >= 1200) return false;
foreach (ERR_AirPassenger member : m_Passengers)
if (member.Chute && member.Actor && CompartmentAccessComponent.GetVehicleIn(member.Actor) == member.Chute) return true;
return now < m_AirspaceUntil;
}
void Update(float now)
{
if (m_Stopped || !Replication.IsServer() || !m_Mode.Active()) return;
foreach (ERR_AirAssaultTeam team : m_Teams) team.Update();
if (Preparing()) { Boarding(now);
return;
}
if (!m_Approaching) return;
if (!m_Flight || m_Flight.Lost() || m_Flight.Phase() >= ERR_ReconPhase.RETURNING || !ERR_Actors.Alive(m_Aircraft) || now >= m_Order.ExpiresAt)
{ Abort();
ERR_HQ.Event("AIR DROP ABORTED", "authorization expired or aircraft unavailable; no new passengers created");
return;
}
foreach (ERR_AirAssaultTeam originalTeam : m_Teams)
if (!originalTeam.Group || originalTeam.Group.GetPlayerCount(true) > 0 || originalTeam.Group.GetFaction() != m_Mode.EnemyFaction()) { Abort();
return;
}
vector position = m_Aircraft.GetOrigin();
if (!m_RunCommitted && vector.DistanceXZ(position, m_RunStart) <= 80) m_RunCommitted = true;
if (m_RunCommitted && ERR_AirDropGeometry.Along(position, m_Center, m_Direction) > 300) { Abort();
return;
}
float height = position[1] - SCR_TerrainHelper.GetTerrainY(position);
if (!m_RunCommitted || now < m_NextJump || height < 90 || height > 180 || !m_Aircraft.GetPhysics()
|| !ERR_AirDropGeometry.Corridor(position, m_Center, m_Direction, m_Aircraft.GetPhysics().GetVelocity())) return;
while (m_NextPassenger < m_Passengers.Count())
{
int index = m_NextPassenger++;
ERR_AirPassenger passenger = m_Passengers[index];
if (passenger.Jumped || !ERR_Actors.CanFight(passenger.Actor) || ERR_Actors.Player(passenger.Actor) || CompartmentAccessComponent.GetVehicleIn(passenger.Actor) != m_Aircraft) continue;
vector exit = m_Aircraft.CoordToParent("12 -4 0");
ERR_AssaultParachute chute = ERR_AssaultParachute.Cast(ERR_Actors.Spawn(PARACHUTE, exit));
m_NextJump = now + 2000;
if (!chute) { ERR_HQ.Event("AIR DROP FAILED", "canopy creation refused; original passenger retained");
continue;
}
m_Mode.Own(chute);
passenger.Chute = chute;
vector landing = m_Center + m_Direction * ERR_AirDropGeometry.Along(position, m_Center, m_Direction);
passenger.Jumped = chute.BeginJump(passenger.Actor, m_Teams[index % 2], landing);
ERR_HQ.Event("AIR DROP JUMP", string.Format("original passenger=%1 transfer requested=%2", index, passenger.Jumped));
break;
}
if (m_NextPassenger >= m_Passengers.Count()) Abort();
}
void Stop()
{
m_Stopped = true;
Abort();
foreach (ERR_AirPassenger passenger : m_Passengers) passenger.Locked = false;
}
}