Files
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

170 lines
11 KiB
Python

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<IEntity> m_InterceptMembers = {};
protected ref array<BaseCompartmentSlot> 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<AIAgent> 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)