from pathlib import Path p=Path('/tmp/erri-pair-hq-0054/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c') s=p.read_text() s=s.replace('// One physical escort may intercept a road ahead of delivered travel history.','// One or two original escorts may intercept a road ahead of delivered travel history.') s='''// Detached planning choice; the route remains strongly owned before admission. class ERR_HQMountedChoice { ERR_HQUnit Unit; ERR_ConvoyTask Task; ERR_ConvoyVehicle Vehicle; ref ERR_HQRoute Route; vector Destination; float Distance; } '''+s s=s.replace(' protected int m_MountedSurveyCursor;', ''' protected ref ERR_ConvoyInterceptOrder m_MountedInterceptPartner; protected ERR_HQUnit m_MountedInterceptPartnerUnit; protected ref ERR_HQRoute m_MountedInterceptPartnerRoute; protected bool m_MountedInterceptReceipt; protected bool m_MountedInterceptPartnerReceipt; protected int m_MountedTokenCount; protected int m_MountedSurveyCursor;''') s=s.replace(' return result + "\\n" + string.Format(', ' if (m_MountedInterceptPartner) result += "\\nPaired mounted interception (two original escorts)";\n return result + "\\n" + string.Format(',1) s=s.replace(' if (m_MountedIntercept) count++;',' if (m_MountedIntercept) count++;\n if (m_MountedInterceptPartner) count++;') s=s.replace(' if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);',' if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);\n if (m_MountedInterceptPartner && unit == m_MountedInterceptPartnerUnit) reserved.Insert(m_MountedInterceptPartner.Position);') def method(sig,new): global s a=s.index(sig);b=s.index('{',a);depth=1;i=b+1 while depth: depth+=(s[i]=='{')-(s[i]=='}');i+=1 s=s[:a]+new+s[i:] method(' protected void ReleaseMountedIntercept(',''' protected void ReleaseMountedIntercept(float now, string reason) { ref ERR_ConvoyInterceptOrder order = m_MountedIntercept; ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner; ERR_HQUnit unit = m_MountedInterceptUnit; ERR_HQUnit partnerUnit = m_MountedInterceptPartnerUnit; ERR_ConvoyTask task = m_MountedInterceptTask; // Detach BOTH first: physical release may synchronously unregister a group. m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null; m_MountedInterceptPartner = null; m_MountedInterceptPartnerUnit = null; m_MountedInterceptRoute = null; m_MountedInterceptPartnerRoute = null; m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false; m_MountedInterceptPartnerReceipt = false; if (!order) return; if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now); if (partner && m_Convoys) m_Convoys.ReleaseIntercept(task, partner, now); if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0; if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = 0; m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId); }''') s=s.replace('if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept', 'if (m_MountedIntercept && (unit == m_MountedInterceptUnit || unit == m_MountedInterceptPartnerUnit)) ReleaseMountedIntercept') method(' protected override void TickMountedIntercept(',''' protected bool MountedMemberCurrent(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order) { if (!order || !unit || !m_Units.Contains(unit) || unit.Group != order.Group) return false; if (unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) return false; return m_Convoys && m_Convoys.InterceptCurrent(m_MountedInterceptTask, order); } protected bool MountedMemberReady(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order, float now) { if (!MountedMemberCurrent(unit, order)) return false; ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex]; return MountedInterceptStatus(unit, vehicle, now) && !ConvoyContact(unit, now); } protected override void TickMountedIntercept(float now) { if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return; ref ERR_ConvoyInterceptOrder order = m_MountedIntercept; ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner; int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit")); if (now < order.IssuedAt || now >= order.ExpiresAt || !MissionActive() || m_Stopping || Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit) || !MountedMemberCurrent(m_MountedInterceptUnit, order)) { ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; } if (partner && !MountedMemberCurrent(m_MountedInterceptPartnerUnit, partner)) { ReleaseMountedIntercept(now, "partner ownership changed"); return; } if (m_MountedInterceptDelivered) return; ERR_HQBelief belief = m_Memory.GetById(order.BeliefId); if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) { ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; } if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now)) { ReleaseMountedIntercept(now, "partner command expired"); return; } if (!m_MountedInterceptReceipt) { if (!m_MountedInterceptRoute) { ReleaseMountedIntercept(now, "missing radio route"); return; } int delivered = m_MountedInterceptRoute.Advance(now, m_Journal); if (m_MountedIntercept != order) return; if (delivered < 0) { ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; } if (delivered > 0) { m_MountedInterceptReceipt = true; m_MountedInterceptRoute = null; } } if (partner && !m_MountedInterceptPartnerReceipt) { if (!m_MountedInterceptPartnerRoute) { ReleaseMountedIntercept(now, "missing partner route"); return; } int partnerDelivered = m_MountedInterceptPartnerRoute.Advance(now, m_Journal); if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return; if (partnerDelivered < 0) { ReleaseMountedIntercept(now, "partner radio interrupted"); return; } if (partnerDelivered > 0) { m_MountedInterceptPartnerReceipt = true; m_MountedInterceptPartnerRoute = null; } } if (!m_MountedInterceptReceipt || (partner && !m_MountedInterceptPartnerReceipt)) return; if (!MountedMemberReady(m_MountedInterceptUnit, order, now)) { ReleaseMountedIntercept(now, "command refused by original crew"); return; } if (partner && !MountedMemberReady(m_MountedInterceptPartnerUnit, partner, now)) { ReleaseMountedIntercept(now, "command refused by original partner"); return; } bool applied; if (partner) applied = m_Convoys.ApplyInterceptPair(m_MountedInterceptTask, order, partner, now); else applied = m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now); if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return; if (!applied) { ReleaseMountedIntercept(now, "physical command refused"); return; } m_MountedInterceptDelivered = true; m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escorts ordered to remembered road positions", order.ReportId); }''') # extract reusable per-task candidate selection and standalone queue. a=s.index(' protected override void PlanMountedIntercept(') b=s.index(' override void Stop()',a) s=s[:a]+''' protected ERR_HQMountedChoice MountedChoice(ERR_ConvoyTask task, vector destination, ERR_HQUnit excluded, float now) { if (!MountedInterceptTaskAvailable(task)) return null; ref ERR_HQMountedChoice best; foreach (ERR_ConvoyVehicle vehicle : task.Vehicles) { if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5) || vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue; foreach (ERR_HQUnit unit : m_Units) { if (unit == excluded || unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now) || !MountedInterceptStatus(unit, vehicle, now)) continue; ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId); float distance = vector.DistanceXZ(own.Position, destination); if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || (best && distance >= best.Distance)) continue; ref ERR_HQRoute route = m_Network.Route(unit.Radio, true); if (!route) continue; best = new ERR_HQMountedChoice(); best.Unit = unit; best.Task = task; best.Vehicle = vehicle; best.Route = route; best.Destination = destination; best.Distance = distance; } } return best; } protected bool HasMountedPairCandidate(float now) { // Cheap original/status gate; no geometry or claims before both roles exist. for (int t = 0; t < m_Convoys.Count(); t++) { ERR_ConvoyTask task = m_Convoys.At(t); if (!MountedInterceptTaskAvailable(task)) continue; ERR_HQUnit firstUnit; foreach (ERR_ConvoyVehicle vehicle : task.Vehicles) { if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5) || vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue; foreach (ERR_HQUnit unit : m_Units) { if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now) || !MountedInterceptStatus(unit, vehicle, now)) continue; if (firstUnit && firstUnit != unit && firstUnit.Group != unit.Group) return true; firstUnit = unit; } } } return false; } protected ERR_ConvoyInterceptOrder MountedOrder(ERR_HQMountedChoice choice, ERR_HQBelief belief, float now, int token) { ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder(); order.Token = token; order.Group = choice.Unit.Group; order.SourceId = choice.Task.Receipt.SourceId; order.VehicleIndex = choice.Vehicle.Index; order.Position = choice.Destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision; order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt; order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000; return order; } protected void QueueMountedIntercept(ERR_HQMountedChoice choice, ERR_HQMountedChoice partnerChoice, ERR_HQBelief belief, float now) { int members = 1; if (partnerChoice) members = 2; if (m_MountedTokenCount + members > 256) return; ref ERR_ConvoyInterceptOrder order = MountedOrder(choice, belief, now, 400001 + m_MountedTokenCount); ref ERR_ConvoyInterceptOrder partner; if (partnerChoice) partner = MountedOrder(partnerChoice, belief, now, 400002 + m_MountedTokenCount); if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) return; if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now)) return; // IDs and this revision remain spent after refusal; no post-claim fallback. m_MountedTokenCount += members; m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision); choice.Unit.LogisticsReservation = order.Token; if (partnerChoice) partnerChoice.Unit.LogisticsReservation = partner.Token; bool admitted; if (partner) admitted = m_Convoys.ReserveInterceptPair(choice.Task, order, partner, now); else admitted = m_Convoys.ReserveIntercept(choice.Task, order, now); if (!admitted) { m_Convoys.ReleaseIntercept(choice.Task, order, now); if (partner) m_Convoys.ReleaseIntercept(choice.Task, partner, now); if (choice.Unit.LogisticsReservation == order.Token) choice.Unit.LogisticsReservation = 0; if (partnerChoice && partnerChoice.Unit.LogisticsReservation == partner.Token) partnerChoice.Unit.LogisticsReservation = 0; return; } m_MountedIntercept = order; m_MountedInterceptUnit = choice.Unit; m_MountedInterceptTask = choice.Task; m_MountedInterceptRoute = choice.Route; m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false; m_MountedInterceptPartner = partner; m_MountedInterceptPartnerReceipt = false; if (partnerChoice) { m_MountedInterceptPartnerUnit = partnerChoice.Unit; m_MountedInterceptPartnerRoute = partnerChoice.Route; } choice.Route.MessageId = m_Journal.Message(); choice.Route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, order.Position, order.BeliefId, order.BeliefRevision, order.ReportId); m_Journal.Write("MOUNTED INTERCEPT QUEUED", choice.Route.Payload, choice.Route.MessageId); if (partnerChoice) { partnerChoice.Route.MessageId = m_Journal.Message(); partnerChoice.Route.Payload = string.Format("original partner %1 to road %2; report M%3", partner.VehicleIndex, partner.Position, partner.ReportId); m_Journal.Write("MOUNTED INTERCEPT QUEUED", partnerChoice.Route.Payload, partnerChoice.Route.MessageId); } } protected override void PlanMountedIntercept(float now) { if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256 || m_MountedTokenCount >= 256) return; int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit")); int freeGlobal = globalLimit - Assigned(); int freeHunt = ERR_HQPursuitRules.Limit(globalLimit) - HuntingAssigned(); if (freeGlobal < 1 || freeHunt < 1 || !HasMountedInterceptCandidate(now)) return; bool pairPossible = freeGlobal >= 2 && freeHunt >= 2 && m_MountedTokenCount <= 254 && HasMountedPairCandidate(now); array reserved = {}; int first = -1; int second = -1; foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second); int count = m_Memory.Count(); if (count == 0) return; int start = m_MountedSurveyCursor % count; int queries; for (int scanned = 0; scanned < count && queries < 2; scanned++) { int b = (start + scanned) % count; m_MountedSurveyCursor = (b + 1) % count; ERR_HQBelief belief = m_Memory.At(b); if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue; if (pairPossible) { vector positionA; vector positionB; queries++; if (ERR_HQMountedInterceptRules.SelectPair(belief, now, reserved, positionA, positionB)) { ref ERR_HQMountedChoice bestA; ref ERR_HQMountedChoice bestB; for (int t = 0; t < m_Convoys.Count(); t++) { ERR_ConvoyTask task = m_Convoys.At(t); ref ERR_HQMountedChoice candidateA = MountedChoice(task, positionA, null, now); if (!candidateA) continue; ref ERR_HQMountedChoice candidateB = MountedChoice(task, positionB, candidateA.Unit, now); if (!candidateB || candidateA.Unit.Group == candidateB.Unit.Group || candidateA.Route == candidateB.Route) continue; if (bestA && candidateA.Distance + candidateB.Distance >= bestA.Distance + bestB.Distance) continue; bestA = candidateA; bestB = candidateB; } if (bestA) { QueueMountedIntercept(bestA, bestB, belief, now); return; } } } // Only pre-admission fallback, sharing the SAME two-survey pass budget. if (queries >= 2) continue; vector destination; queries++; if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue; ref ERR_HQMountedChoice best; for (int t = 0; t < m_Convoys.Count(); t++) { ref ERR_HQMountedChoice candidate = MountedChoice(m_Convoys.At(t), destination, null, now); if (candidate && (!best || candidate.Distance < best.Distance)) best = candidate; } if (best) { QueueMountedIntercept(best, null, belief, now); return; } } } '''+s[b:] p.write_text(s)