from pathlib import Path import hashlib,json,subprocess P=Path('/tmp/erri-mounted-intercept-convoy-1247');R=Path('/home/jakub/devel/escapistsrereintroduced');D='addons/core/Scripts/Game/ERR/10_AI/' meta={} for name in ['ERR_ConvoyService.c','ERR_SupplyConvoyService.c','ERR_SupplyConvoyColumn.c','ERR_SupplyConvoyTactics.c']: rel=D+name;b=(R/rel).read_bytes() for base in [P,P/'baseline']:(base/rel).parent.mkdir(parents=True,exist_ok=True);(base/rel).write_bytes(b) meta[rel]={'working_sha256':hashlib.sha256(b).hexdigest(),'index_sha256':hashlib.sha256(subprocess.check_output(['git','show',':'+rel],cwd=R)).hexdigest()} (P/'baseline.json').write_text(json.dumps(meta,indent=2)+'\n') f=P/D/'ERR_ConvoyService.c';s=f.read_text();s=s.replace(' bool SupplyHeld;',''' bool SupplyHeld; int InterceptToken; bool InterceptActive; AIWaypoint InterceptWaypoint; ref ERR_ConvoyInterceptOrder InterceptOrder; protected AIWaypoint m_InterceptWaypoint; protected Vehicle m_InterceptEntity; protected SCR_AIGroup m_InterceptGroup; protected IEntity m_InterceptDriver; protected ref array m_InterceptMembers = {}; protected ref array m_InterceptSeats = {}; protected int m_InterceptToken; protected vector m_InterceptPosition; protected float m_InterceptExpiresAt; protected int m_InterceptRouteIndex; ''',1) marker=' void ClearOrder()' methods=''' // Pending downlink owns a claim only; normal authored travel continues. bool InterceptCrew(ERR_GameMode mode) { if (!mode || !CrewValid(mode) || !Entity || !Group || ERR_ConvoyRules.Captured(Entity, Group)) return false; if (Members.IsEmpty() || Members.Count() > 8 || Seats.Count() != Members.Count()) return false; array agents = {}; Group.GetAgents(agents); if (agents.Count() != Members.Count()) return false; for (int i = 0; i < Members.Count(); i++) { if (!Members[i] || !ERR_Actors.CanFight(Members[i]) || !Seats[i] || Seats[i].GetOccupant() != Members[i]) return false; foreach (AIAgent agent : agents) if (!agent || !Members.Contains(agent.GetControlledEntity())) return false; if (InterceptOrder && (Members[i] != m_InterceptMembers[i] || Seats[i] != m_InterceptSeats[i])) return false; } if (InterceptOrder && (Entity != m_InterceptEntity || Group != m_InterceptGroup || Driver != m_InterceptDriver)) return false; return true; } bool InterceptMatches(ERR_ConvoyInterceptOrder order) { return order && InterceptOrder == order && InterceptToken == m_InterceptToken && InterceptToken == order.Token && Group == m_InterceptGroup && Entity == m_InterceptEntity && order.Group == m_InterceptGroup; } bool ClaimIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order) { if (!order || InterceptToken != 0 || InterceptOrder || InterceptWaypoint || !InterceptCrew(mode)) return false; m_InterceptEntity = Entity; m_InterceptGroup = Group; m_InterceptDriver = Driver; m_InterceptMembers.Clear(); m_InterceptSeats.Clear(); foreach (IEntity member : Members) m_InterceptMembers.Insert(member); foreach (BaseCompartmentSlot seat : Seats) m_InterceptSeats.Insert(seat); m_InterceptToken = order.Token; InterceptToken = order.Token; InterceptOrder = order; m_InterceptPosition = order.Position; m_InterceptExpiresAt = order.ExpiresAt; return true; } void ClearIntercept(ERR_ConvoyInterceptOrder order) { if (!InterceptMatches(order)) return; // Delete only the exact waypoint this owner created, never an adopted handle. if (m_InterceptWaypoint && m_InterceptGroup) m_InterceptGroup.RemoveWaypoint(m_InterceptWaypoint); if (m_InterceptWaypoint) SCR_EntityHelper.DeleteEntityAndChildren(m_InterceptWaypoint); if (InterceptWaypoint == m_InterceptWaypoint) InterceptWaypoint = null; m_InterceptWaypoint = null; InterceptToken = 0; m_InterceptToken = 0; InterceptActive = false; InterceptOrder = null; m_InterceptMembers.Clear(); m_InterceptSeats.Clear(); } bool DeliverIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now) { if (!InterceptMatches(order) || InterceptActive || !InterceptCrew(mode)) return false; if (order.Position != m_InterceptPosition || order.ExpiresAt != m_InterceptExpiresAt || now >= m_InterceptExpiresAt) return false; AIWaypoint detour = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), m_InterceptPosition)); if (!detour) return false; detour.SetCompletionRadius(15); ClearOrder(); m_InterceptRouteIndex = RouteIndex; m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true; Group.AddWaypoint(detour); return true; } // True suppresses ordinary travel only after actual radio delivery. No route // index is consumed by arrival at the detour; removal leaves the seated group held. bool TickIntercept(ERR_GameMode mode, float now) { if (!InterceptOrder) return false; if (!InterceptMatches(InterceptOrder)) return InterceptActive; if (Released || Arrived || now >= m_InterceptExpiresAt || !InterceptCrew(mode) || Combat.Held || SupplyHeld || SupplyToken != 0) { ClearIntercept(InterceptOrder); LegDeadline = now + 600000; return false; } if (!InterceptActive) return false; if (RouteIndex != m_InterceptRouteIndex || InterceptWaypoint != m_InterceptWaypoint) { ClearIntercept(InterceptOrder); return false; } if (m_InterceptWaypoint && vector.DistanceXZ(Entity.GetOrigin(), m_InterceptPosition) <= 15) { Group.RemoveWaypoint(m_InterceptWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(m_InterceptWaypoint); m_InterceptWaypoint = null; InterceptWaypoint = null; } return true; } ''' s=s.replace(marker,methods+marker,1) s=s.replace(' Released = true;\n Combat.Held', ' ClearIntercept(InterceptOrder);\n Released = true;\n Combat.Held',1) s=s.replace('if (Released || Arrived || Combat.Held || SupplyHeld ||', 'if (Released || Arrived || InterceptActive || Combat.Held || SupplyHeld ||',1) s=s.replace(' if (Released || Arrived) return;\n if (!CrewValid', ' if (TickIntercept(mode, now)) return;\n if (Released || Arrived) return;\n if (!CrewValid',1) service=''' protected bool InterceptEligible(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order) { if (!Current() || !task || !order || !m_Tasks.Contains(task) || task.Stopped || !task.Ready || !task.Receipt || !task.Receipt.Consumed) return false; if (order.Token < 400001 || order.Token > 400256 || order.SourceId != task.Receipt.SourceId) return false; if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false; if (order.VehicleIndex >= task.Vehicles.Count() || order.VehicleIndex >= task.Receipt.Records.Count()) return false; ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex]; if (!unit || !unit.Registered || unit.Released || unit.Arrived || unit.Index != order.VehicleIndex || unit.Group != order.Group) return false; if (!unit.InterceptCrew(m_Mode) || !task.Route || unit.RouteIndex < 0 || unit.RouteIndex >= task.Route.Count()) return false; ERR_ForceRecord record = m_Mode.Forces().FindGroup(unit.Group); if (!record || !record.Physical || record.Eliminated || record.Kind != ERR_ForceKind.CONVOY || record.Id != task.Receipt.Records[unit.Index]) return false; foreach (ERR_ConvoyVehicle other : task.Vehicles) if (!other || other.SupplyToken != 0 || other.SupplyHeld || other.Combat.Held) return false; return true; } bool InterceptCurrent(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order) { return InterceptEligible(task, order) && task.Vehicles[order.VehicleIndex].InterceptMatches(order); } bool ReserveIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now) { if (!InterceptEligible(task, order) || !ERR_ForceRules.Finite(now) || now < 0) return false; if (!ERR_ForceRules.Finite(order.ObservedAt) || !ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false; if (order.ObservedAt < 0 || order.IssuedAt < order.ObservedAt || now < order.IssuedAt || now >= order.ExpiresAt) return false; if (order.ExpiresAt > order.ObservedAt + 120000 || !ERR_ForceRules.FiniteVector(order.Position)) return false; if (order.BeliefId <= 0 || order.BeliefRevision <= 0 || order.ReportId <= 0) return false; foreach (ERR_ConvoyTask existing : m_Tasks) foreach (ERR_ConvoyVehicle claimed : existing.Vehicles) if (claimed && claimed.InterceptToken != 0) return false; return task.Vehicles[order.VehicleIndex].ClaimIntercept(m_Mode, order); } bool ApplyIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now) { if (!InterceptCurrent(task, order) || !ERR_ForceRules.Finite(now) || now < order.IssuedAt || now >= order.ExpiresAt) return false; return task.Vehicles[order.VehicleIndex].DeliverIntercept(m_Mode, order, now); } void ReleaseIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now) { if (!task || !order || !m_Tasks.Contains(task) || !ERR_ForceRules.Finite(now)) return; foreach (ERR_ConvoyVehicle unit : task.Vehicles) if (unit && unit.InterceptMatches(order)) { unit.ClearIntercept(order); unit.LegDeadline = now + 600000; } } ''' s=s.replace(' bool ApplyCombatOrder(',service+' bool ApplyCombatOrder(',1) s=s.replace(' if (order.VehicleIndex < 0 || order.VehicleIndex >= task.Vehicles.Count()', ' foreach (ERR_ConvoyVehicle intercept : task.Vehicles) if (intercept && intercept.InterceptToken != 0) return false;\n if (order.VehicleIndex < 0 || order.VehicleIndex >= task.Vehicles.Count()',1) f.write_text(s) f=P/D/'ERR_SupplyConvoyService.c';s=f.read_text().replace('vehicle.SupplyHeld || vehicle.Combat.Held','vehicle.SupplyHeld || vehicle.Combat.Held || vehicle.InterceptToken != 0') s=s.replace(' if (task.Tactics && task.Tactics.Tick(now)) continue;', ' foreach (ERR_ConvoyVehicle intercept : task.Vehicles) intercept.TickIntercept(m_Mode, now);\n if (task.Tactics && task.Tactics.Tick(now)) continue;') s=s.replace(' override bool ApplyCombatOrder(',''' override bool ReserveIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now) { ERR_SupplyConvoyTask supply = ERR_SupplyConvoyTask.Cast(task); if (!supply || !supply.Tactics || supply.Tactics.State() != 0) return false; return super.ReserveIntercept(task, order, now); } override bool ApplyCombatOrder(''');f.write_text(s) f=P/D/'ERR_SupplyConvoyColumn.c';s=f.read_text().replace('if (scan.Released || !scan.Entity) continue;', 'if (scan.Released || !scan.Entity || scan.InterceptActive) continue;').replace('if (vehicle.Released || !vehicle.Entity) continue;', '''if (vehicle.Released || !vehicle.Entity) continue; if (vehicle.InterceptActive) { m_State[index].LastProgress = -1; m_State[index].NextTarget = now; continue; }''');f.write_text(s) f=P/D/'ERR_SupplyConvoyTactics.c';s=f.read_text().replace(' protected void Start(vector contact, float now)\n {',''' protected void Start(vector contact, float now) { // A local threat takes precedence, but may release only this exact detour owner. foreach (ERR_ConvoyVehicle intercept : m_Task.Vehicles) { if (intercept.InterceptToken == 0) continue; intercept.ClearIntercept(intercept.InterceptOrder); if (intercept.InterceptToken != 0) return; }''');f.write_text(s)