// HQ-owned ground rendezvous using delivered positions and existing finite cargo. class ERR_InfantryResupplyRules { static bool Fresh(ERR_HQWatchEntry entry, float now) { return entry && !entry.Closed && entry.Count > 0 && entry.Fit > 0 && ERR_ForceRules.Finite(now) && entry.ObservedAt >= 0 && now >= entry.ObservedAt && now - entry.ObservedAt <= 120000 && ERR_ForceRules.FiniteVector(entry.Position); } static bool Need(ERR_HQWatchEntry entry, float now) { return Fresh(entry, now) && entry.AmmoState && entry.AmmoState.Valid(entry.Count) && entry.AmmoState.Complete && entry.AmmoState.Members == entry.Count && entry.AmmoState.SmallArmsMuzzles > 0 && entry.AmmoState.SmallArmsRounds + entry.AmmoState.SmallArmsChambers <= entry.Count * 30; } // Only positively sampled reloadable magazine-well muzzles establish launcher demand. static bool LauncherNeed(ERR_HQWatchEntry entry, float now) { return Fresh(entry, now) && entry.AmmoState && entry.AmmoState.Valid(entry.Count) && entry.AmmoState.LauncherComplete && entry.AmmoState.Members == entry.Count && entry.AmmoState.LauncherMuzzles > 0 && entry.AmmoState.LauncherRounds == 0 && entry.AmmoState.LauncherChambers == 0; } static bool LauncherPair(ERR_HQWatchEntry recipient, ERR_HQWatchEntry donor, float now) { return LauncherNeed(recipient, now) && Fresh(donor, now) && recipient.SourceId != donor.SourceId && vector.DistanceXZ(recipient.Position, donor.Position) <= 200; } static bool MinePair(ERR_HQWatchEntry recipient, ERR_HQWatchEntry donor, float now) { return Fresh(recipient, now) && Fresh(donor, now) && recipient.SourceId != donor.SourceId && recipient.AmmoState && recipient.AmmoState.Valid(recipient.Count) && recipient.AmmoState.MineComplete && recipient.AmmoState.MineMembers == recipient.Count && recipient.AmmoState.ReadyMines <= 1 && vector.DistanceXZ(recipient.Position, donor.Position) <= 200; } static bool Pair(ERR_HQWatchEntry recipient, ERR_HQWatchEntry donor, float now) { return Need(recipient, now) && Fresh(donor, now) && recipient.SourceId != donor.SourceId && vector.DistanceXZ(recipient.Position, donor.Position) <= 200; } } class ERR_InfantryResupply { protected ERR_HQ m_HQ; protected ERR_GameMode m_Mode; protected ERR_ConvoyTask m_Convoy; protected int m_ConvoySlot = -1; protected ERR_ConvoyVehicle m_Truck; protected ERR_HQUnit m_Recipient; protected ERR_HQUnit m_Donor; protected ref array m_Stock = {}; protected ref array m_Spent = {}; protected ref array m_MineStock = {}; protected ref ERR_ConvoyMineStock m_MineBatch; protected bool m_MineStockCaptured; protected bool m_MineTrip; protected bool m_LauncherTrip; protected int m_MineRecipientSlot = -1; protected bool m_EmptyMineReplacement; protected int m_MinesDelivered; protected ref array m_Members = {}; protected ref array m_Served = {}; protected ref map m_Consumed = new map(); protected ref map m_NextAttempt = new map(); protected ref ERR_HQRoute m_Order; protected ref ERR_InfantryAmmoTransfer m_Move; protected ref ERR_InfantryMineTransfer m_MineMove; protected ref ERR_MountedResupply m_Mounted; protected IEntity m_Receiving; protected AIWaypoint m_MoveWaypoint; protected AIWaypoint m_HoldWaypoint; protected vector m_Rendezvous; protected int m_State; // 0 stock startup, 1 idle, 2 truck downlink, 3 stopped status, 4 infantry downlink, 5 transfer, 7 unresolved mine custody protected int m_Trip; protected int m_Token; protected int m_ReportId; protected int m_Delivered; protected int m_TripDelivered; protected float m_Deadline; protected float m_StockDeadline = -1; protected float m_HeldAt; protected float m_NextTick; protected bool m_Stopped; protected string m_Reason; void ERR_InfantryResupply(ERR_HQ hq, ERR_GameMode mode) { m_HQ = hq; m_Mode = mode; } // Bind before startup; every coordinator retains its exact finite convoy identity. bool Bind(ERR_ConvoyTask task, int slot, map consumed, map nextAttempt) { if (!task || !consumed || !nextAttempt || !m_HQ || !m_HQ.Convoys() || slot < 0 || slot >= 20 || slot >= m_HQ.Convoys().Count() || m_HQ.Convoys().At(slot) != task) return false; if (m_Stopped || m_Convoy || m_ConvoySlot >= 0 || m_State != 0 || m_Trip != 0 || m_NextTick != 0 || m_StockDeadline >= 0 || !m_Stock.IsEmpty() || m_Move || m_Truck) return false; m_Convoy = task; m_ConvoySlot = slot; m_Consumed = consumed; m_NextAttempt = nextAttempt; return true; } ERR_ConvoyTask Task() { return m_Convoy; } int State() { if (m_Mounted && m_Mounted.Busy()) return 6; return m_State; } ERR_MountedResupply Mounted() { return m_Mounted; } bool Unresolved() { return (m_Move && m_Move.Unresolved()) || (m_MineMove && m_MineMove.Unresolved()) || (m_Mounted && m_Mounted.Unresolved()); } protected void PollTransfers(float now) { if (m_Mounted) m_Mounted.Tick(now); if (m_Move && !m_Move.Unresolved() && (m_State <= 1 || (m_State == 7 && m_LauncherTrip))) { m_Move = null; if (m_State == 7) Finish("launcher custody settled; original claims released without retry or refund"); } if (m_MineMove && !m_MineMove.Unresolved() && (m_State <= 1 || m_State == 7)) { m_MineMove = null; if (m_State == 7) Finish("mine custody settled; original claims released without retry or refund"); } } int Delivered() { return m_Delivered; } int MinesDelivered() { return m_MinesDelivered; } int StockCount() { return m_Stock.Count(); } // Admission uses the existing array object so an already-created mounted // coordinator observes this exact finite addition without a replacement ledger. protected int m_DepotImports; bool DepotCurrent() { return Current() && m_State == 1 && !m_Move && !m_MineMove && !Unresolved() && (!m_Mounted || !m_Mounted.Busy()) && TruckValid(true); } bool DepotAvailable() { return DepotCurrent() && m_Stock.Count() < 96 && m_Spent.Count() < 96; } bool DepotNeeded() { if (!DepotAvailable()) return false; int committed; foreach (IEntity item : m_Spent) if (item && m_Stock.Contains(item)) committed++; return committed > m_DepotImports; } ERR_ConvoyVehicle DepotTruck() { if (!DepotCurrent()) return null; return m_Truck; } bool AdmitDepot(ERR_GroundDepotLoading job, IEntity item) { if (!job || !DepotAvailable() || !item || m_Stock.Contains(item) || m_Spent.Contains(item)) return false; if (!job.DeliveredTo(this, item) || !Contains(m_Truck.Entity, item)) return false; m_Stock.Insert(item); m_DepotImports++; Audit("one exact finite camp magazine admitted after proved physical courier delivery"); return true; } protected void Audit(string reason) { if (m_MineTrip) reason = "mine top-up: " + reason; if (m_LauncherTrip) reason = "depleted launcher magazine: " + reason; m_Reason = reason; m_HQ.Journal().Write("INFANTRY SUPPLY", string.Format("convoy-slot=%1 trip=%2 state=%3 moved=%4 spent=%5/%6; %7", m_ConvoySlot, m_Trip, m_State, m_Delivered, m_Spent.Count(), m_Stock.Count(), reason), m_ReportId); } protected bool Current() { return !m_Stopped && Replication.IsServer() && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active(); } protected bool Contains(IEntity owner, IEntity item) { if (!owner || !item) return false; InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(owner.FindComponent(InventoryStorageManagerComponent)); if (!inventory) return false; array items = {}; ERR_Inventory.Items(inventory, items); return items.Count() <= 128 && items.Contains(item); } protected bool ConvoyCurrent() { if (!m_HQ || !m_Convoy) return false; if (m_ConvoySlot < 0) return m_HQ.ConvoyTask() == m_Convoy; return m_HQ.Convoys() && m_ConvoySlot < m_HQ.Convoys().Count() && m_HQ.Convoys().At(m_ConvoySlot) == m_Convoy; } protected bool TruckValid(bool allowArrived = false) { if (!m_Convoy || m_Convoy.Stopped || !m_Convoy.Ready || !m_Convoy.Receipt || !m_Convoy.Receipt.Consumed || !ConvoyCurrent() || m_Convoy.Vehicles.Count() != 6 || m_Convoy.Vehicles[1] != m_Truck) return false; if (!m_Truck || m_Truck.Index != 1 || !m_Truck.Registered || m_Truck.Released || (m_Truck.Arrived && !allowArrived) || !m_Truck.CrewValid(m_Mode) || ERR_ConvoyRules.Captured(m_Truck.Entity, m_Truck.Group)) return false; ERR_ForceRecord record = m_Mode.Forces().FindGroup(m_Truck.Group); return record && record.Physical && !record.Eliminated && record.Kind == ERR_ForceKind.CONVOY && m_Convoy.Receipt.Records.Count() >= 6 && record.Id == m_Convoy.Receipt.Records[1]; } protected bool Quiet(ERR_HQUnit unit) { return unit && unit.Group && unit.Utility && !unit.Group.IsSlave() && unit.Group.GetPlayerCount(true) == 0 && unit.Group.GetFaction() == m_Mode.EnemyFaction() && (!unit.Utility.m_Perception || unit.Utility.m_Perception.m_aTargets.IsEmpty()); } protected bool Roster() { if (!m_Recipient || !Quiet(m_Recipient) || !m_Recipient.StatusSampler.OwnsRoster(m_Recipient.Group)) return false; int strength = m_Recipient.StatusSampler.InitialStrength(); if (strength < 1 || strength > 32) return false; array agents = {}; m_Recipient.Group.GetAgents(agents); if (agents.IsEmpty() || agents.Count() > 32) return false; foreach (AIAgent agent : agents) if (!agent || !m_Members.Contains(agent.GetControlledEntity())) return false; bool living; foreach (IEntity actor : m_Members) { if (!actor || !ERR_Actors.Alive(actor)) continue; // Returning to the group cannot undo a sampler's permanent release of an // original slot after transfer or player control during this trip. bool attributed = false; for (int slot = 0; slot < strength; slot++) { if (m_Recipient.StatusSampler.InitialMember(slot) != actor) continue; attributed = m_Recipient.StatusSampler.CanAttribute(slot, actor); break; } if (!attributed) return false; if (ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != m_Recipient.Group || SCR_FactionManager.SGetFaction(actor) != m_Mode.EnemyFaction() || CompartmentAccessComponent.GetVehicleIn(actor)) return false; if (ERR_Actors.CanFight(actor)) living = true; } return living; } protected void Stock(float now) { if (!m_Convoy) m_Convoy = m_HQ.ConvoyTask(); if (m_Convoy && !ConvoyCurrent()) { Stop(); return; } if (!m_Convoy || !m_Convoy.Ready || m_Convoy.Vehicles.Count() != 6) return; m_Truck = m_Convoy.Vehicles[1]; if (!TruckValid()) { Stop(); return; } // Cargo has a later, separately bounded lifecycle than native crew admission. // Supply convoys first request faction weapons, then their matching magazines. if (m_StockDeadline < 0) m_StockDeadline = now + 90000; if (now >= m_StockDeadline) { Stop(); return; } if (!m_Convoy.CargoIssued) return; ERR_SupplyConvoyTask supply = ERR_SupplyConvoyTask.Cast(m_Convoy); if (supply && !supply.MagazinesIssued) return; if (supply) m_MineBatch = supply.MineStock; foreach (ERR_ConvoyCargoCallback operation : m_Convoy.Cargo) if (!operation.Finished) return; InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(m_Truck.Entity.FindComponent(InventoryStorageManagerComponent)); if (!inventory) { Stop(); return; } array items = {}; ERR_Inventory.Items(inventory, items); if (items.Count() > 128) { Stop(); return; } foreach (IEntity item : items) { if (!item || !item.FindComponent(BaseMagazineComponent)) continue; if (m_Stock.Count() >= 96) { Stop(); return; } m_Stock.Insert(item); } m_State = 1; Audit("truck magazine identities captured once after startup; absent/looted stock is never replaced"); } bool TryPlan(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now, bool mineTrip = false, bool launcherTrip = false) { if (!Current() || m_State != 1 || m_Move || m_MineMove || (m_Mounted && m_Mounted.Busy()) || m_Trip >= 128 || !TruckValid()) return false; if ((mineTrip && launcherTrip) || m_Truck.Combat.Held || m_Truck.SupplyToken > 0 || !recipient || !donor) return false; if (donor.Group != m_Truck.Group || recipient.HomeGuard || !Quiet(recipient) || !Quiet(donor)) return false; if (donor.Downlink || donor.PendingOrder || donor.PendingConvoyOrder) return false; if (mineTrip) { if (!ERR_InfantryResupplyRules.MinePair(demand, donorStatus, now)) return false; // Empty reports authorize only an original startup-designated engineer. // This reads retained entitlement, never live positions/inventory for planning. if (demand.AmmoState.ReadyMines == 0) { bool designated; for (int member = 0; member < recipient.StatusSampler.InitialStrength() && member < 32; member++) { IEntity original = recipient.StatusSampler.InitialMember(member); if (original && recipient.StatusSampler.CanAttribute(member, original) && m_HQ.MineSupplyReady(recipient.Group, original, true)) { designated = true; break; } } if (!designated) return false; } } else if (launcherTrip) { if (!ERR_InfantryResupplyRules.LauncherPair(demand, donorStatus, now)) return false; } else if (!ERR_InfantryResupplyRules.Pair(demand, donorStatus, now)) return false; if (demand.SourceId != recipient.ForceSourceId || donorStatus.SourceId != donor.ForceSourceId) return false; if (demand.MessageId <= m_Consumed.Get(demand.SourceId) || now < m_NextAttempt.Get(demand.SourceId)) return false; bool available; if (mineTrip) { if (!CaptureMines()) return false; foreach (IEntity mine : m_MineStock) if (ERR_InfantryMineTransfer.ReadyMine(mine) && !m_Spent.Contains(mine) && Contains(m_Truck.Entity, mine)) { available = true; break; } } else if (launcherTrip) available = LauncherStock(recipient); else foreach (IEntity magazine : m_Stock) if (magazine && !m_Spent.Contains(magazine) && Contains(m_Truck.Entity, magazine)) { available = true; break; } if (m_Spent.Count() >= 96) return false; if (!available) return false; m_Members.Clear(); for (int i = 0; i < recipient.StatusSampler.InitialStrength(); i++) { IEntity actor = recipient.StatusSampler.InitialMember(i); if (actor && recipient.StatusSampler.CanAttribute(i, actor)) m_Members.Insert(actor); } m_Recipient = recipient; m_Donor = donor; if (!Roster()) { m_Recipient = null; m_Donor = null; return false; } // Disjoint bounded token range from the existing mortar courier's trip IDs. int slot = m_ConvoySlot; if (slot < 0) slot = 0; // Isolated legacy fixture uses the first convoy's range. int token = 100000 + slot * 128 + m_Trip + 1; if (!m_HQ.ReserveLogistics(recipient.Group, token)) { m_Recipient = null; m_Donor = null; return false; } m_MineTrip = mineTrip; m_LauncherTrip = launcherTrip; m_MineRecipientSlot = -1; m_Receiving = null; m_EmptyMineReplacement = false; m_Trip++; m_Token = token; m_Truck.SupplyToken = token; m_ReportId = demand.MessageId; m_TripDelivered = 0; m_Served.Clear(); m_Consumed.Set(demand.SourceId, demand.MessageId); m_NextAttempt.Set(demand.SourceId, now + 180000); m_Deadline = Math.Min(now + 120000, demand.ObservedAt + 180000); m_State = 2; string purpose = "stop existing ammo truck and report position"; if (mineTrip) purpose += "; one finite PMN4 top-up for an existing carrier"; if (launcherTrip) purpose += "; finite compatible magazine for a reported depleted launcher"; m_Order = m_HQ.Downlink(donor.Group, "INFANTRY SUPPLY HOLD", purpose, m_ReportId); if (!m_Order) Finish("truck radio downlink unavailable"); else Audit("existing patrol reserved; truck hold awaits radio delivery"); return true; } // Optional mine startup must never delay normal magazine readiness. Pin the // original batch during Stock(), then copy its closed exact identities once. protected bool CaptureMines() { ERR_SupplyConvoyTask supply = ERR_SupplyConvoyTask.Cast(m_Convoy); if (!supply || !m_MineBatch || supply.MineStock != m_MineBatch || !m_MineBatch.Complete()) return false; if (m_MineStockCaptured) return !m_MineStock.IsEmpty(); m_MineStockCaptured = true; int count = m_MineBatch.Count(); if (count < 0 || count > 2) return false; for (int index = 0; index < count; index++) { IEntity item = m_MineBatch.At(index); if (item && !m_MineStock.Contains(item)) m_MineStock.Insert(item); } return true; } // Compatibility-only preflight of original own carriers; HQ still uses the // delivered report for demand and positions. No item is reserved or moved here. protected bool LauncherStock(ERR_HQUnit recipient) { if (!recipient || !recipient.StatusSampler || m_Stock.Count() > 96) return false; int strength = recipient.StatusSampler.InitialStrength(); if (strength < 1 || strength > 32) return false; int checks; foreach (IEntity magazine : m_Stock) { if (!magazine || m_Spent.Contains(magazine) || !Contains(m_Truck.Entity, magazine)) continue; for (int slot = 0; slot < strength; slot++) { if (++checks > 128) return false; IEntity actor = recipient.StatusSampler.InitialMember(slot); if (!actor || !recipient.StatusSampler.CanAttribute(slot, actor) || !ERR_Actors.CanFight(actor)) continue; if (ERR_InfantryAmmoTransfer.Compatible(actor, magazine, true)) return true; } } return false; } bool TryPlanLauncher(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now) { return TryPlan(recipient, donor, demand, donorStatus, now, false, true); } bool TryPlanMines(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now) { return TryPlan(recipient, donor, demand, donorStatus, now, true); } // Share exact frozen inventory identities and global radio replay state. bool TryPlanMounted(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now) { if (!Current() || m_State != 1 || m_Move || m_MineMove || m_ConvoySlot < 0 || !TruckValid(true)) return false; if (m_Mounted && m_Mounted.Busy()) return false; if (!m_Mounted) m_Mounted = new ERR_MountedResupply(m_HQ, m_Mode, m_Convoy, m_ConvoySlot, m_Stock, m_Spent, m_Consumed, m_NextAttempt); return m_Mounted.Begin(recipient, donor, demand, donorStatus, now); } protected bool Waypoints() { m_MoveWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), ERR_Actors.Ground(m_Rendezvous))); m_HoldWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.DefendWaypoint(), ERR_Actors.Ground(m_Rendezvous))); if (!m_MoveWaypoint || !m_HoldWaypoint) return false; // The reported rendezvous is 3 m from the donor report. A 2 m move // radius allowed arrival 5 m away, beyond the transfer's physical reach. m_MoveWaypoint.SetCompletionRadius(0.5); m_HoldWaypoint.SetCompletionRadius(0.5); m_Recipient.Group.AddWaypointAt(m_HoldWaypoint, 0); m_Recipient.Group.AddWaypointAt(m_MoveWaypoint, 0); m_Recipient.Group.ActivateAI(); return true; } protected void ClearWaypoints() { if (m_MoveWaypoint) { if (m_Recipient && m_Recipient.Group) m_Recipient.Group.RemoveWaypoint(m_MoveWaypoint); ERR_Actors.DeleteSpawned(m_MoveWaypoint); } if (m_HoldWaypoint) { if (m_Recipient && m_Recipient.Group) m_Recipient.Group.RemoveWaypoint(m_HoldWaypoint); ERR_Actors.DeleteSpawned(m_HoldWaypoint); } m_MoveWaypoint = null; m_HoldWaypoint = null; } protected void Finish(string reason) { Audit(reason); if (m_Move) m_Move.Stop(); if (m_MineMove) m_MineMove.Stop(); ClearWaypoints(); m_Order = null; // A late native move must not race another truck's newer report. Retain both // exact claims until this one submitted identity proves custody or deletion. if ((m_MineMove && m_MineMove.Unresolved()) || (m_LauncherTrip && m_Move && m_Move.Unresolved())) { m_State = 7; return; } if (m_Recipient) m_HQ.ReleaseLogistics(m_Recipient.Group, m_Token); if (m_Truck && m_Truck.SupplyToken == m_Token) { m_Truck.SupplyToken = 0; m_Truck.SupplyHeld = false; } m_Recipient = null; m_Donor = null; m_Order = null; m_Receiving = null; m_State = 1; m_MineTrip = false; m_LauncherTrip = false; m_MineRecipientSlot = -1; } protected void TransferMine(float now) { if (m_MineMove) { int result = m_MineMove.Tick(now); if (result == 0) return; if (result < 0) { Finish("native mine transfer failed or expired; exact submitted identity stays spent"); return; } m_MineMove = null; m_MinesDelivered++; Finish("one exact finite PMN4 confirmed in the original carrier inventory"); return; } if (!m_Receiving) { int strength = m_Recipient.StatusSampler.InitialStrength(); for (int slot = 0; slot < strength && slot < 32; slot++) { IEntity actor = m_Recipient.StatusSampler.InitialMember(slot); if (!actor || !m_Members.Contains(actor) || !m_Recipient.StatusSampler.CanAttribute(slot, actor, false)) continue; if (!ERR_Actors.CanFight(actor) || vector.DistanceXZ(actor.GetOrigin(), m_Truck.Entity.GetOrigin()) > 4) continue; if (!m_HQ.MineSupplyReady(m_Recipient.Group, actor)) continue; IEntity carried; int count = ERR_InfantryMineTransfer.CarriedCount(actor, m_Recipient.Group, carried); if (count != 1 && (count != 0 || !m_HQ.MineSupplyReady(m_Recipient.Group, actor, true))) continue; m_EmptyMineReplacement = count == 0; m_Receiving = actor; m_MineRecipientSlot = slot; break; } if (!m_Receiving) return; } if (!m_HQ.MineSupplyReady(m_Recipient.Group, m_Receiving, m_EmptyMineReplacement)) return; IEntity carried; int count = ERR_InfantryMineTransfer.CarriedCount(m_Receiving, m_Recipient.Group, carried); if (!m_Recipient.StatusSampler.CanAttribute(m_MineRecipientSlot, m_Receiving, false) || (m_EmptyMineReplacement && count != 0) || (!m_EmptyMineReplacement && count != 1)) { Finish("original mine recipient lost or its pinned zero/one stock changed"); return; } foreach (IEntity mine : m_MineStock) { if (!ERR_InfantryMineTransfer.ReadyMine(mine) || m_Spent.Contains(mine) || !Contains(m_Truck.Entity, mine)) continue; ERR_FiniteMagazineReservation reservation = new ERR_FiniteMagazineReservation(m_MineStock, m_Spent, mine); if (!reservation.Reserve()) continue; m_MineMove = new ERR_InfantryMineTransfer(m_Mode, m_Truck.Entity, m_Truck.Group, m_Receiving, m_Recipient.Group, mine, reservation, m_Recipient.StatusSampler, m_MineRecipientSlot, m_EmptyMineReplacement); m_MineMove.Tick(now); // The leaf commits before the one native move. Only an unsubmitted refusal // may release its tentative claim while the same carrier approaches/retries. if (m_MineMove.Submitted()) return; reservation.ReleaseUnsubmitted(); m_MineMove = null; break; } } protected void Transfer(float now) { if (m_Move) { int result = m_Move.Tick(now); if (result == 0) return; if (result < 0) { Finish("native transfer rejected, expired or lost authority; no item retry"); return; } m_Move = null; m_Delivered++; m_TripDelivered++; m_Served.Insert(m_Receiving); m_Receiving = null; Audit("exact finite magazine confirmed in original soldier inventory"); if (m_TripDelivered >= 6) { Finish("six-magazine trip allowance reached"); return; } } foreach (IEntity actor : m_Members) { if (!ERR_Actors.CanFight(actor) || m_Served.Contains(actor) || vector.DistanceXZ(actor.GetOrigin(), m_Truck.Entity.GetOrigin()) > 4) continue; ERR_HQAmmoSnapshot local = new ERR_HQAmmoSnapshot(); local.Complete = true; local.LauncherComplete = true; ERR_HQAmmoSampler.Inspect(actor, m_Recipient.Group, local); if (m_LauncherTrip) { if (!local.Valid(1) || !local.LauncherComplete || local.Members != 1 || local.LauncherMuzzles <= 0 || local.LauncherRounds != 0 || local.LauncherChambers != 0) continue; } else if (!local.Complete || local.SmallArmsMuzzles <= 0 || local.SmallArmsRounds + local.SmallArmsChambers > 30) continue; foreach (IEntity magazine : m_Stock) { if (!magazine || m_Spent.Contains(magazine) || !Contains(m_Truck.Entity, magazine) || !ERR_InfantryAmmoTransfer.Compatible(actor, magazine, m_LauncherTrip)) continue; ERR_FiniteMagazineReservation reservation = new ERR_FiniteMagazineReservation(m_Stock, m_Spent, magazine); if (!reservation.Reserve()) continue; m_Receiving = actor; m_Move = new ERR_InfantryAmmoTransfer(m_Mode, m_Truck.Entity, m_Truck.Group, actor, m_Recipient.Group, magazine, !m_LauncherTrip, m_LauncherTrip); m_Move.Tick(now); if (m_Move.Submitted()) { reservation.Commit(); return; } // No native operation exists yet: approach geometry, stopped state or // capacity may improve while the same bounded rendezvous remains valid. // Keep every submitted identity spent, including a synchronous rejection. reservation.ReleaseUnsubmitted(); m_Move = null; m_Receiving = null; break; // At most one unsubmitted preflight per member on this slow tick. } } } void Tick(float now) { if (!ERR_ForceRules.Finite(now) || now < 0) return; // Stopped services retain uncertain leaf refs until exact late proof settles. if (m_Stopped) { PollTransfers(now); return; } if (!Current() || now < m_NextTick) return; m_NextTick = now + 1000; PollTransfers(now); if (m_Mounted && m_Mounted.Busy()) return; if (m_State == 7) return; if (m_State == 0) { Stock(now); return; } if (m_State == 1) { if (m_Move && !m_Move.Unresolved()) m_Move = null; return; } if (now >= m_Deadline || !TruckValid() || !Quiet(m_Donor) || !Roster() || m_Truck.Combat.Held || m_HQ.LogisticsToken(m_Recipient.Group) != m_Token) { Finish("rendezvous expired, contact appeared or original ownership changed"); return; } if (m_Truck.SupplyToken != m_Token) { Finish("truck hold authority lost"); return; } if (m_State == 2) { int delivered = m_Order.Advance(now, m_HQ.Journal()); if (delivered == 0) return; if (delivered < 0 || m_Truck.SupplyToken != m_Token) { Finish("truck hold downlink interrupted or reservation lost"); return; } m_Truck.SupplyHeld = true; m_Truck.ClearOrder(); m_HeldAt = -1; m_Order = null; m_State = 3; Audit("truck received stop; waiting for delivered stopped-position report"); return; } if (m_State == 3) { // The delivered hold asks the original crew to report only after physically stopping. if (m_HeldAt < 0) { Physics physics = m_Truck.Entity.GetPhysics(); if (!physics) return; float speed = physics.GetVelocity().Length(); float angular = physics.GetAngularVelocity().Length(); if (!(speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1)) return; m_HeldAt = now; m_Donor.NextStatus = now; return; } ERR_HQWatchEntry stopped = m_HQ.Watch().Find(m_Donor.ForceSourceId); if (!ERR_InfantryResupplyRules.Fresh(stopped, now) || stopped.ObservedAt < m_HeldAt) return; m_Rendezvous = stopped.Position + "3 0 0"; m_Order = m_HQ.Downlink(m_Recipient.Group, "INFANTRY SUPPLY RENDEZVOUS", string.Format("walk to reported stopped ammo truck at %1", m_Rendezvous), m_ReportId); if (!m_Order) { Finish("infantry radio downlink unavailable"); return; } m_State = 4; return; } if (m_State == 4) { int delivered = m_Order.Advance(now, m_HQ.Journal()); if (delivered == 0) return; if (delivered < 0 || !Waypoints()) { Finish("infantry order interrupted or native waypoint creation failed"); return; } m_Order = null; m_State = 5; Audit("infantry received reported rendezvous; native movement and physical transfer required"); } if (m_State == 5) { if (m_MineTrip) TransferMine(now); else Transfer(now); } } string Status() { string detail = string.Format("Ground supply: state=%1 trips=%2 infantry moved=%3 stock=%4 spent=%5", State(), m_Trip, m_Delivered, m_Stock.Count(), m_Spent.Count()); return detail + string.Format("; mines moved=%1 initial=%2; %3", m_MinesDelivered, m_MineStock.Count(), m_Reason); } void Stop() { if (m_Stopped) return; if (m_Mounted) m_Mounted.Stop(); Finish("service stopped; original forces and all remaining cargo retained"); m_Stopped = true; } }