// Explicit finite retention only. A future HQ owner supplies existing assets and // delivered orders, retains this service, and ticks it even on an empty server. class ERR_AircraftServiceEntry { Vehicle Aircraft; SCR_AIGroup Group; IEntity Pilot; PilotCompartmentSlot Seat; ref ERR_AircraftFlight Flight; int Token; float Deadline; vector Destination; bool Pending; bool CancelRequested; bool Retired; string Reason; } class ERR_AircraftService { static const int MAX_AIRCRAFT = 4; static const int MAX_ORDERS = 128; protected static ref map s_Retainers = new map(); protected ERR_GameMode m_Mode; protected BaseWorld m_World; protected ref array m_Entries = {}; protected ref set m_UsedTokens = new set(); protected bool m_Stopping; void ERR_AircraftService(ERR_GameMode mode) { m_Mode = mode; if (GetGame()) m_World = GetGame().GetWorld(); if (!Context()) m_Stopping = true; } protected bool Context() { return Replication.IsServer() && GetGame() && m_World && GetGame().GetWorld() == m_World && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.GetWorld() == m_World && m_Mode.Active(); } protected ERR_AircraftServiceEntry Find(Vehicle aircraft) { if (!aircraft) return null; foreach (ERR_AircraftServiceEntry entry : m_Entries) if (entry.Aircraft == aircraft) return entry; return null; } protected bool Original(ERR_AircraftServiceEntry entry) { if (!Context() || !entry || !entry.Aircraft || !entry.Group || !entry.Pilot || !entry.Seat || !GetGame().GetPlayerManager()) return false; if (entry.Aircraft.GetWorld() != m_World || entry.Pilot.GetWorld() != m_World || entry.Group.GetWorld() != m_World) return false; if (entry.Group.GetFaction() != m_Mode.EnemyFaction() || SCR_FactionManager.SGetFaction(entry.Aircraft) != m_Mode.EnemyFaction() || ERR_Actors.Group(entry.Pilot) != entry.Group) return false; if (entry.Group.GetPlayerCount(true) > 0 || ERR_Actors.Player(entry.Pilot)) return false; HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(entry.Aircraft.FindComponent(HelicopterControllerComponent)); if (!controller || controller.GetOwner() != entry.Aircraft || controller.GetPilotCompartmentSlot() != entry.Seat || entry.Seat.GetOccupant() != entry.Pilot || CompartmentAccessComponent.GetVehicleIn(entry.Pilot) != entry.Aircraft) return false; array players = {}; GetGame().GetPlayerManager().GetPlayers(players); foreach (int id : players) { IEntity actor = GetGame().GetPlayerManager().GetPlayerControlledEntity(id); if (actor && (actor == entry.Pilot || CompartmentAccessComponent.GetVehicleIn(actor) == entry.Aircraft)) return false; } return true; } protected bool PhysicalReady(ERR_AircraftServiceEntry entry) { if (!Original(entry) || !ERR_Actors.Alive(entry.Aircraft) || !ERR_Actors.CanFight(entry.Pilot) || !ERR_CasualtyLoadingRules.AircraftReady(entry.Aircraft)) return false; HelicopterControllerComponent controller = HelicopterControllerComponent.Cast(entry.Aircraft.FindComponent(HelicopterControllerComponent)); VehicleHelicopterSimulation simulation = controller.GetSimulation(); FuelManagerComponent fuel = FuelManagerComponent.Cast(entry.Aircraft.FindComponent(FuelManagerComponent)); SCR_VehicleDamageManagerComponent damage = SCR_VehicleDamageManagerComponent.Cast(entry.Aircraft.FindComponent(SCR_VehicleDamageManagerComponent)); Physics physics = entry.Aircraft.GetPhysics(); if (controller.IsEngineOn() || !simulation || !simulation.IsValid() || !simulation.HasAnyGroundContact() || simulation.RotorCount() != 2) return false; if (!fuel || !fuel.HasFuel() || !damage || !damage.GetEngineFunctional() || damage.GetMovementDamage() >= 1 || damage.IsOnFire()) return false; if (!physics || !physics.IsDynamic() || !physics.IsActive()) return false; for (int rotor = 0; rotor < 2; rotor++) if (simulation.RotorGetState(rotor) == RotorState.DESTROYED) return false; return true; } bool Register(Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat) { if (!Context() || m_Stopping || !aircraft || !group || !pilot || !seat || m_Entries.Count() >= MAX_AIRCRAFT || Find(aircraft) || s_Retainers.Get(aircraft)) return false; foreach (ERR_AircraftServiceEntry known : m_Entries) if (known.Pilot == pilot || known.Group == group || known.Seat == seat) return false; ERR_AircraftServiceEntry entry = new ERR_AircraftServiceEntry(); entry.Aircraft = aircraft; entry.Group = group; entry.Pilot = pilot; entry.Seat = seat; if (!PhysicalReady(entry)) return false; m_Entries.Insert(entry); s_Retainers.Set(aircraft, this); return true; } bool Ready(Vehicle aircraft) { ERR_AircraftServiceEntry entry = Find(aircraft); if (!Context() || m_Stopping || !entry || entry.Pending || entry.Retired || s_Retainers.Get(aircraft) != this) return false; return PhysicalReady(entry); } // Both explicit kinds share one attempt history and physical reservation. protected ERR_AircraftServiceEntry BeginAttempt(Vehicle aircraft, int token, float deadline) { if (!Context() || m_Stopping || token <= 0 || m_UsedTokens.Contains(token) || m_UsedTokens.Count() >= MAX_ORDERS || !Ready(aircraft)) return null; float now = m_World.GetWorldTime(); if (!ERR_AircraftFlightRules.Deadline(now, deadline)) return null; ERR_AircraftServiceEntry entry = Find(aircraft); // Commit the attempt before calling the native-backed controller. No retry of // this token, including a partially initialized or conflicting Start refusal. m_UsedTokens.Insert(token); entry.Token = token; entry.Deadline = deadline; entry.CancelRequested = false; return entry; } bool StartRoute(Vehicle aircraft, ERR_AircraftRouteEnvelope qualification, int token, float deadline) { if (!qualification) return false; ERR_AircraftServiceEntry entry = BeginAttempt(aircraft, token, deadline); if (!entry) return false; entry.Destination = qualification.Destination(); ERR_AircraftRouteFlight flight = new ERR_AircraftRouteFlight(); entry.Flight = flight; entry.Pending = true; bool started = flight.StartRoute(m_Mode, entry.Aircraft, entry.Group, entry.Pilot, entry.Seat, qualification, token, deadline); return RecordStart(entry, started); } // This explicit prototype entry point accepts only a field certificate and // constructs its known controller. No arbitrary caller-owned flight is adopted. bool StartField(Vehicle aircraft, ERR_AircraftFieldEnvelope qualification, int token, float deadline) { if (!qualification) return false; ERR_AircraftServiceEntry entry = BeginAttempt(aircraft, token, deadline); if (!entry) return false; entry.Destination = qualification.Destination(); ERR_AircraftFieldFlight flight = new ERR_AircraftFieldFlight(); entry.Flight = flight; entry.Pending = true; bool started = flight.StartField(m_Mode, entry.Aircraft, entry.Group, entry.Pilot, entry.Seat, qualification, token, deadline); return RecordStart(entry, started); } protected bool RecordStart(ERR_AircraftServiceEntry entry, bool started) { if (!started) { entry.Retired = true; entry.Reason = "flight start refused; no automatic retry or replacement"; // A started lease must not be dropped even if a future controller reports // refusal after committing it. Unclaimed IDLE refusals have no active flight. entry.Pending = entry.Flight.OrderToken() > 0 && !entry.Flight.Finished(); if (entry.Pending) { entry.CancelRequested = true; entry.Flight.Cancel(entry.Reason); } Finish(entry); return false; } entry.Reason = "flight active"; return true; } protected void Finish(ERR_AircraftServiceEntry entry) { if (!entry || !entry.Pending || !entry.Flight || !entry.Flight.Finished()) return; entry.Pending = false; int state = entry.Flight.State(); entry.Reason = entry.Flight.Reason(); if (state != ERR_AircraftFlightState.COMPLETE && state != ERR_AircraftFlightState.CANCELLED) { entry.Retired = true; if (entry.Reason.IsEmpty()) entry.Reason = "flight ended without verified landing"; } // COMPLETE/CANCELLED is historical. Ready always rechecks the physical asset. } bool Cancel(Vehicle aircraft, int token, string reason) { if (!Context()) return false; ERR_AircraftServiceEntry entry = Find(aircraft); if (!entry || !entry.Pending || !entry.Flight || entry.Token != token || token <= 0 || entry.CancelRequested) return false; entry.CancelRequested = true; entry.Flight.Cancel(reason); Finish(entry); return true; } void Tick() { if (!Replication.IsServer()) return; bool context = Context(); if (!context) m_Stopping = true; foreach (ERR_AircraftServiceEntry entry : m_Entries) { if (entry.Pending && entry.Flight) { // Controller owns expiry/capture/terminal rules. It must keep receiving Tick // during cancellation; there is no no-player pause in this service. entry.Flight.Tick(); Finish(entry); } if (!context || !Original(entry)) { entry.Retired = true; if (entry.Reason.IsEmpty()) entry.Reason = "original mission or asset ownership lost"; } } ReleaseRetainers(); } void Stop() { if (!Replication.IsServer() || m_Stopping) return; m_Stopping = true; foreach (ERR_AircraftServiceEntry entry : m_Entries) { if (!entry.Pending || !entry.Flight) continue; entry.CancelRequested = true; entry.Flight.Cancel("aircraft service stopping"); Finish(entry); } // Closing admissions is not flight completion. Caller retains/ticks until // Drained; terminal-mode teardown is reported by the controller as relinquish. ReleaseRetainers(); } protected void ReleaseRetainers() { if (!m_Stopping || !Drained()) return; foreach (ERR_AircraftServiceEntry entry : m_Entries) if (entry.Aircraft && s_Retainers.Get(entry.Aircraft) == this) s_Retainers.Remove(entry.Aircraft); } bool Drained() { foreach (ERR_AircraftServiceEntry entry : m_Entries) if (entry.Pending) return false; return true; } bool Stopping() { return m_Stopping; } int RegisteredCount() { return m_Entries.Count(); } int AttemptedOrders() { return m_UsedTokens.Count(); } bool Pending(Vehicle aircraft) { ERR_AircraftServiceEntry entry = Find(aircraft); return entry && entry.Pending; } bool Retired(Vehicle aircraft) { ERR_AircraftServiceEntry entry = Find(aircraft); return !entry || entry.Retired; } int OrderToken(Vehicle aircraft) { int token = 0; ERR_AircraftServiceEntry entry = Find(aircraft); if (entry) token = entry.Token; return token; } float OrderDeadline(Vehicle aircraft) { float deadline = -1; ERR_AircraftServiceEntry entry = Find(aircraft); if (entry) deadline = entry.Deadline; return deadline; } int FlightState(Vehicle aircraft) { int state = -1; ERR_AircraftServiceEntry entry = Find(aircraft); if (entry && entry.Flight) state = entry.Flight.State(); return state; } string Reason(Vehicle aircraft) { string reason = "unregistered aircraft"; ERR_AircraftServiceEntry entry = Find(aircraft); if (entry) reason = entry.Reason; return reason; } vector Destination(Vehicle aircraft) { vector destination = "0 0 0"; ERR_AircraftServiceEntry entry = Find(aircraft); if (entry) destination = entry.Destination; return destination; } void ~ERR_AircraftService() { // Destruction is teardown, never proof of landing. Normal shutdown must use // Stop + retained Tick until Drained. Entity ownership stays with its creator. Stop(); foreach (ERR_AircraftServiceEntry entry : m_Entries) if (entry.Aircraft && s_Retainers.Get(entry.Aircraft) == this) s_Retainers.Remove(entry.Aircraft); } }