--- /dev/null +++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSupplyTravelProbe.c @@ -0,0 +1,256 @@ +#ifdef WORKBENCH +// Observation-only qualification of one naturally admitted production convoy. +// Baselines are taken at first ready observation, not at vehicle spawn. +class ERR_NativeSupplyTravelFormation +{ + ERR_ConvoyVehicle Escort; + ERR_SupplyTeam Team; + Vehicle Root; + SCR_AIGroup Group; + IEntity Driver; + ref array Members = {}; + ref array Seats = {}; + ERR_ForceRecord Record; + int RecordId; + vector Start; + bool Moved; +} + +class ERR_NativeSupplyTravelProbe +{ + protected ERR_GameMode m_Mode; + protected ERR_HQ m_HQ; + protected ERR_SupplyConvoyService m_Service; + protected ref ERR_SupplyConvoyTask m_Task; + protected ref ERR_ConvoyReceipt m_Receipt; + protected string m_SourceId; + protected int m_ReceiptId; + protected int m_TaskIndex = -1; + protected ref array m_Formations = {}; + protected float m_AdmissionDeadline; + protected float m_TravelDeadline; + protected float m_LastNow; + protected float m_NextLog; + protected bool m_Stopped; + protected bool m_Terminal; + + void ERR_NativeSupplyTravelProbe(ERR_GameMode mode, float now) + { + m_Mode = mode; + m_LastNow = now; + m_AdmissionDeadline = now + 120000; + if (!ERR_ForceRules.Finite(now) || now < 0) m_Stopped = true; + } + protected int Fail(string reason, int slot = -1) + { + if (!m_Terminal && !m_Stopped) + { + PrintFormat("ERR SUPPLY TRAVEL FAIL: task=%1 slot=%2 reason=%3", m_TaskIndex, slot, reason); + if (m_Task) + { + int tactics = -1; + if (m_Task.Tactics) tactics = m_Task.Tactics.State(); + PrintFormat("ERR SUPPLY TRAVEL TASK: source=%1 ready=%2 stopped=%3 reason=%4 tactics=%5; observation does not establish cause", m_SourceId, m_Task.Ready, m_Task.Stopped, m_Task.Reason, tactics); + } + if (slot >= 0 && slot < 6 && slot < m_Formations.Count()) + { + ERR_NativeSupplyTravelFormation saved = m_Formations[slot]; + bool alive = ERR_Actors.Alive(saved.Root); + bool fit = ERR_Actors.CanFight(saved.Driver); + bool group = saved.Driver && ERR_Actors.Group(saved.Driver) == saved.Group; + bool aboard = saved.Driver && ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(saved.Driver), saved.Root); + bool released; + if (saved.Escort) released = saved.Escort.Released; + PrintFormat("ERR SUPPLY TRAVEL SLOT: rootAlive=%1 driverFit=%2 driverOriginalGroup=%3 driverAboard=%4 released=%5", alive, fit, group, aboard, released); + } + } + m_Terminal = true; + return -1; + } + protected bool Owner(ERR_HQ hq) + { + if (!Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || !m_Mode.Forces()) return false; + if (!hq || m_Mode.Command() != hq || !hq.SupplyConvoys()) return false; + ERR_SupplyConvoyService service = ERR_SupplyConvoyService.Cast(hq.Convoys()); + if (!service) return false; + if (m_HQ && (m_HQ != hq || m_Service != service)) return false; + return true; + } + protected bool RecordCurrent(ERR_NativeSupplyTravelFormation saved, int slot) + { + if (!saved.Group || saved.Group.GetFaction() != m_Mode.EnemyFaction() || saved.Group.GetPlayerCount(true) > 0) return false; + ERR_ForceRecord record = m_Mode.Forces().FindGroup(saved.Group); + if (!record || record != saved.Record || record.Id != saved.RecordId || record.Id != m_Receipt.Records[slot]) return false; + return record.Physical && !record.Eliminated && record.Group == saved.Group && record.Kind == ERR_ForceKind.CONVOY + && record.SourceId == m_SourceId + "-vehicle-" + slot.ToString(); + } + protected bool RosterCurrent(ERR_NativeSupplyTravelFormation saved, array members, array seats) + { + if (members.Count() != saved.Members.Count() || seats.Count() != saved.Seats.Count()) return false; + array agents = {}; saved.Group.GetAgents(agents); + if (agents.Count() != saved.Members.Count()) return false; + array observed = {}; + foreach (AIAgent agent : agents) + { + if (!agent) return false; + IEntity actor = agent.GetControlledEntity(); + if (!actor || !saved.Members.Contains(actor) || observed.Contains(actor)) return false; + observed.Insert(actor); + } + for (int i = 0; i < saved.Members.Count(); i++) + { + IEntity original = saved.Members[i]; + if (!original || members[i] != original || !saved.Seats[i] || seats[i] != saved.Seats[i]) return false; + if (!ERR_Actors.Alive(original) || ERR_Actors.Player(original) || ERR_Actors.Group(original) != saved.Group) return false; + } + return true; + } + protected int Validate() + { + if (!m_Task || m_Task.Stopped || !m_Task.Ready || m_Task.Receipt != m_Receipt) return Fail("original ready task lost"); + if (!m_Receipt || !m_Receipt.Consumed || m_Receipt.Id != m_ReceiptId || m_Receipt.SourceId != m_SourceId) return Fail("original receipt changed"); + if (m_Receipt.Records.Count() != 8 || m_Receipt.OutstandingBodies != 0 || m_Receipt.OutstandingGroups != 0) return Fail("original ledger admission changed"); + if (m_Task.Vehicles.Count() != 6 || m_Task.Teams.Count() != 2 || m_Formations.Count() != 8) return Fail("original formation count changed"); + for (int slot = 0; slot < 8; slot++) + { + ERR_NativeSupplyTravelFormation saved = m_Formations[slot]; + if (!RecordCurrent(saved, slot)) return Fail("original force record or group lost", slot); + if (slot < 6) + { + ERR_ConvoyVehicle escort = m_Task.Vehicles[slot]; + if (!escort || escort != saved.Escort || escort.Entity != saved.Root || escort.Group != saved.Group + || escort.Index != slot || escort.Driver != saved.Driver || escort.Released || !escort.Registered) return Fail("original vehicle ownership changed", slot); + if (!ERR_Actors.Alive(saved.Root) || ERR_ConvoyRules.Captured(saved.Root, saved.Group)) return Fail("original vehicle lost or captured", slot); + if (!ERR_ForceRules.FiniteVector(saved.Root.GetOrigin())) return Fail("nonfinite original vehicle position", slot); + if (!RosterCurrent(saved, escort.Members, escort.Seats)) return Fail("original vehicle roster or seat identity changed", slot); + if (!ERR_Actors.CanFight(saved.Driver) || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(saved.Driver), saved.Root)) return Fail("original driver not fit and aboard", slot); + int driverSlot = saved.Members.Find(saved.Driver); + if (driverSlot < 0 || saved.Seats[driverSlot].GetOccupant() != saved.Driver) return Fail("original driver seat lost", slot); + } + else + { + ERR_SupplyTeam team = m_Task.Teams[slot - 6]; + if (!team || team != saved.Team || team.Group != saved.Group || team.Released || !team.Registered) return Fail("original infantry team lost", slot); + if (!RosterCurrent(saved, team.Members, team.Seats)) return Fail("original infantry roster or seat identity changed", slot); + } + } + return 0; + } + protected int Pin(ERR_SupplyConvoyTask task, int index, float now) + { + m_Task = task; m_TaskIndex = index; + m_Receipt = task.Receipt; + if (!m_Receipt || m_Receipt.Records.Count() != 8 || task.Vehicles.Count() != 6 || task.Teams.Count() != 2) return Fail("first ready production task has invalid shape"); + m_SourceId = m_Receipt.SourceId; m_ReceiptId = m_Receipt.Id; + array originals = {}; + array groups = {}; + array originalSeats = {}; + array roots = {}; + for (int slot = 0; slot < 8; slot++) + { + ERR_NativeSupplyTravelFormation saved = new ERR_NativeSupplyTravelFormation(); + m_Formations.Insert(saved); + if (slot < 6) + { + saved.Escort = task.Vehicles[slot]; + if (!saved.Escort) return Fail("missing original vehicle", slot); + saved.Root = saved.Escort.Entity; saved.Group = saved.Escort.Group; saved.Driver = saved.Escort.Driver; + saved.Members.Copy(saved.Escort.Members); saved.Seats.Copy(saved.Escort.Seats); + if (!saved.Root || roots.Contains(saved.Root)) return Fail("missing or duplicate original vehicle", slot); + roots.Insert(saved.Root); saved.Start = saved.Root.GetOrigin(); + if (!ERR_ForceRules.FiniteVector(saved.Start)) return Fail("invalid ready-position baseline", slot); + int expected = 2; + if (slot == 1 || slot == 2) expected = 1; + if (slot == 4) expected = 3; + if (saved.Members.Count() != expected) return Fail("unexpected original vehicle crew count", slot); + } + else + { + saved.Team = task.Teams[slot - 6]; + if (!saved.Team) return Fail("missing original infantry team", slot); + saved.Group = saved.Team.Group; saved.Members.Copy(saved.Team.Members); saved.Seats.Copy(saved.Team.Seats); + if (saved.Members.Count() != 7) return Fail("unexpected original infantry count", slot); + } + if (!saved.Group || groups.Contains(saved.Group)) return Fail("missing or shared original group", slot); + groups.Insert(saved.Group); + if (saved.Seats.Count() != saved.Members.Count()) return Fail("original seat count differs", slot); + foreach (BaseCompartmentSlot seat : saved.Seats) + { + if (!seat || originalSeats.Contains(seat)) return Fail("missing or duplicated original seat", slot); + originalSeats.Insert(seat); + } + foreach (IEntity original : saved.Members) + { + if (!original || originals.Contains(original)) return Fail("missing or duplicated original actor", slot); + originals.Insert(original); + } + saved.Record = m_Mode.Forces().FindGroup(saved.Group); + if (!saved.Record) return Fail("original force record absent", slot); + saved.RecordId = saved.Record.Id; + } + if (originals.Count() != 25) return Fail("expected exactly25 original crew and infantry"); + if (Validate() < 0) return -1; + m_TravelDeadline = now + 180000; + PrintFormat("ERR SUPPLY TRAVEL ADMITTED: task=%1 source=%2 six vehicles and25 original actors; baseline=first-ready-observation deadline=%3", index, m_SourceId, m_TravelDeadline); + return 0; + } + int Tick(ERR_HQ hq, float now) + { + if (m_Stopped || m_Terminal) return -1; + if (!ERR_ForceRules.Finite(now) || now < m_LastNow) return Fail("invalid or reversed observation clock"); + m_LastNow = now; + if (!Owner(hq)) return Fail("current production mission or service unavailable"); + if (!m_HQ) { m_HQ = hq; m_Service = ERR_SupplyConvoyService.Cast(hq.Convoys()); } + int count = m_Service.Count(); + if (count < 0 || count > 20) return Fail("production task count outside configured bound"); + if (!m_Task) + { + if (now >= m_AdmissionDeadline) return Fail("admission deadline; no ready production convoy observed"); + for (int index = 0; index < count; index++) + { + ERR_SupplyConvoyTask task = ERR_SupplyConvoyTask.Cast(m_Service.At(index)); + if (!task) return Fail("nonproduction convoy in production service"); + if (!task.Ready) continue; + return Pin(task, index, now); + } + if (now >= m_NextLog) + { + PrintFormat("ERR SUPPLY TRAVEL ADMISSION: ready convoy pending; %1", hq.ConvoyStatus()); + m_NextLog = now + 10000; + } + return 0; + } + if (m_TaskIndex < 0 || m_TaskIndex >= count || m_Service.At(m_TaskIndex) != m_Task) return Fail("original task no longer at pinned service slot"); + if (now >= m_TravelDeadline) return Fail("travel deadline before all six originals moved20m"); + if (Validate() < 0) return -1; + int moved; + for (int slot = 0; slot < 6; slot++) + { + ERR_NativeSupplyTravelFormation saved = m_Formations[slot]; + if (!saved.Moved && vector.DistanceXZ(saved.Root.GetOrigin(), saved.Start) >= 20) + { + saved.Moved = true; + PrintFormat("ERR SUPPLY TRAVEL MOVED: task=%1 slot=%2 original entity travelled20m from first-ready baseline", m_TaskIndex, slot); + } + if (saved.Moved) moved++; + } + if (moved == 6) + { + m_Terminal = true; + Print("ERR SUPPLY TRAVEL PASS: all six original production vehicles moved20m from first-ready baselines with original living crews and seated drivers; no tactics or full-route completion claim"); + return 1; + } + if (now >= m_NextLog) + { + PrintFormat("ERR SUPPLY TRAVEL PROGRESS: task=%1 moved=%2/6 fixedDeadline=%3", m_TaskIndex, moved, m_TravelDeadline); + m_NextLog = now + 10000; + } + return 0; + } + void Stop() + { + m_Stopped = true; + m_Formations.Clear(); m_Task = null; m_Receipt = null; m_HQ = null; m_Service = null; m_Mode = null; + } +} +#endif