from pathlib import Path import hashlib,json,subprocess R=Path('/home/jakub/devel/escapistsrereintroduced'); P=Path('/tmp/erri-launcher-resupply-1140'); names=['ERR_InfantryResupply.c','ERR_InfantryAmmoTransfer.c','ERR_HQ.c'];baseline={} for name in names: rel='addons/core/Scripts/Game/ERR/10_AI/'+name;src=R/rel for dest in (P/rel,P/'baseline'/rel):dest.parent.mkdir(parents=True,exist_ok=True);dest.write_bytes(src.read_bytes()) baseline[rel]={'working_sha256':hashlib.sha256(src.read_bytes()).hexdigest(),'index_sha256':hashlib.sha256(subprocess.check_output(['git','show',':'+rel],cwd=R)).hexdigest()} (P/'baseline.json').write_text(json.dumps(baseline,indent=2)+'\n') p=P/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c';s=p.read_text();s=s.replace('protected bool m_RecycleEmpty;','protected bool m_RecycleEmpty;\n protected bool m_LauncherMagazine;') s=s.replace('bool recycleEmpty = false)','bool recycleEmpty = false, bool launcherMagazine = false)').replace('m_RecycleEmpty = recycleEmpty;','m_RecycleEmpty = recycleEmpty; m_LauncherMagazine = launcherMagazine;') s=s.replace('static bool Compatible(IEntity actor, IEntity exactMagazine)','static bool Compatible(IEntity actor, IEntity exactMagazine, bool launcherMagazine = false)') s=s.replace('array weapons = {}; manager.GetWeaponsList(weapons);','if (launcherMagazine && magazine.IsUsed()) return false;\n array weapons = {}; manager.GetWeaponsList(weapons);') s=s.replace('if (kind != EWeaponType.WT_RIFLE && kind != EWeaponType.WT_SNIPERRIFLE && kind != EWeaponType.WT_MACHINEGUN && kind != EWeaponType.WT_HANDGUN) continue;', '''bool smallWeapon = kind == EWeaponType.WT_RIFLE || kind == EWeaponType.WT_SNIPERRIFLE || kind == EWeaponType.WT_MACHINEGUN || kind == EWeaponType.WT_HANDGUN; if (!launcherMagazine && !smallWeapon) continue; if (launcherMagazine && !smallWeapon && kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue; if (launcherMagazine && !StoredUnder(rifle, actor)) continue;''') s=s.replace('if (muzzleKind != EMuzzleType.MT_BaseMuzzle) continue;', '''if (!launcherMagazine && muzzleKind != EMuzzleType.MT_BaseMuzzle) continue; if (launcherMagazine) { if (muzzleKind != EMuzzleType.MT_RPGMuzzle && muzzleKind != EMuzzleType.MT_UGLMuzzle && kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue; // Projectile-only/disposable and entirely missing magazine configurations // are outside this finite magazine slice; never synthesize a projectile. BaseMagazineComponent loaded = muzzle.GetMagazine(); if (!loaded || !loaded.GetOwner() || !loaded.GetMagazineWell() || loaded.GetMaxAmmoCount() <= 0) continue; if (!StoredUnder(loaded.GetOwner(), rifle)) continue; if (loaded.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue; if (magazine.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue; }''') s=s.replace('if (!Compatible(m_Recipient, m_Magazine))','if (!Compatible(m_Recipient, m_Magazine, m_LauncherMagazine))') s=s.replace('if (m_ReturnAccepted &&', 'if ((m_ReturnAccepted || (m_LauncherMagazine && m_ReturnSubmitted)) &&').replace('if (!m_Accepted || !m_Magazine || m_Acknowledgement < 0) return false;', 'if (m_LauncherMagazine && m_Submitted) return m_Magazine && !PhysicalProof();\n if (!m_Accepted || !m_Magazine || m_Acknowledgement < 0) return false;') s=s.replace('if (PhysicalProof())\n {', '''// Native callbacks may synchronously cancel this leaf or revoke ownership. if (m_LauncherMagazine && m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State; if (m_LauncherMagazine && !Context()) return Fail("launcher custody authority changed during native submission"); if (PhysicalProof()) {''') p.write_text(s) p=P/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c';s=p.read_text();anchor=' static bool MinePair(' s=s.replace(anchor,''' // A zero aggregate without a supported physical magazine is not launcher evidence. 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.LauncherMagazines > 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; } '''+anchor) s=s.replace('protected bool m_MineTrip;','protected bool m_MineTrip;\n protected bool m_LauncherTrip;') s=s.replace('if (m_State <= 1 && m_Move && !m_Move.Unresolved()) m_Move = null;', '''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"); }''') s=s.replace('if (m_MineTrip) reason = "mine top-up: " + reason;', 'if (m_MineTrip) reason = "mine top-up: " + reason;\n if (m_LauncherTrip) reason = "depleted launcher magazine: " + reason;') s=s.replace('float now, bool mineTrip = false)','float now, bool mineTrip = false, bool launcherTrip = false)') s=s.replace('if (m_Truck.Combat.Held || m_Truck.SupplyToken > 0 || !recipient || !donor)', 'if ((mineTrip && launcherTrip) || m_Truck.Combat.Held || m_Truck.SupplyToken > 0 || !recipient || !donor)') s=s.replace('else if (!ERR_InfantryResupplyRules.Pair(demand, donorStatus, now)) return false;', '''else if (launcherTrip) { if (!ERR_InfantryResupplyRules.LauncherPair(demand, donorStatus, now)) return false; } else if (!ERR_InfantryResupplyRules.Pair(demand, donorStatus, now)) return false;''') s=s.replace('m_MineTrip = mineTrip; m_MineRecipientSlot', 'm_MineTrip = mineTrip; m_LauncherTrip = launcherTrip; m_MineRecipientSlot') s=s.replace('if (mineTrip) purpose += "; one finite PMN4 top-up for an existing carrier";', 'if (mineTrip) purpose += "; one finite PMN4 top-up for an existing carrier";\n if (launcherTrip) purpose += "; finite compatible magazine for a reported depleted launcher";') s=s.replace(' bool TryPlanMines(',''' 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(''') s=s.replace('if (m_MineMove && m_MineMove.Unresolved()) { m_State = 7; return; }', 'if ((m_MineMove && m_MineMove.Unresolved()) || (m_LauncherTrip && m_Move && m_Move.Unresolved())) { m_State = 7; return; }') s=s.replace('m_State = 1; m_MineTrip = false;', 'm_State = 1; m_MineTrip = false; m_LauncherTrip = false;') s=s.replace('if (!local.Complete || local.SmallArmsMuzzles <= 0 || local.SmallArmsRounds + local.SmallArmsChambers > 30) continue;', '''if (m_LauncherTrip) { if (!local.Valid(1) || !local.LauncherComplete || local.Members != 1 || local.LauncherMagazines <= 0 || local.LauncherRounds != 0 || local.LauncherChambers != 0) continue; } else if (!local.Complete || local.SmallArmsMuzzles <= 0 || local.SmallArmsRounds + local.SmallArmsChambers > 30) continue;''') s=s.replace('ERR_InfantryAmmoTransfer.Compatible(actor, magazine)', 'ERR_InfantryAmmoTransfer.Compatible(actor, magazine, m_LauncherTrip)') s=s.replace('actor, m_Recipient.Group, magazine, true);','actor, m_Recipient.Group, magazine, true, m_LauncherTrip);') p.write_text(s) p=P/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c';s=p.read_text().replace(' // Optional finite mines are last:', ''' // Supported depleted launcher magazines follow rifle/mounted demand and // share the same finite stock, radio replay and physical rendezvous service. foreach (ERR_HQUnit launcherRecipient : m_Units) if (supply.TryPlanLauncher(launcherRecipient, donor, m_Watch.Find(launcherRecipient.ForceSourceId), donorStatus, now)) return true; // Optional finite mines are last:''');p.write_text(s)