from pathlib import Path import hashlib,json,subprocess P=Path('/tmp/erri-launcher-resupply-1140');R=Path('/home/jakub/devel/escapistsrereintroduced');D=P/'addons/core/Scripts/Game/ERR/10_AI';rel='addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c';s=(R/rel).read_text();(P/'baseline'/rel).write_text(s) b=json.loads((P/'baseline.json').read_text());b[rel]={'working_sha256':hashlib.sha256((R/rel).read_bytes()).hexdigest(),'index_sha256':hashlib.sha256(subprocess.check_output(['git','show',':'+rel],cwd=R)).hexdigest()};(P/'baseline.json').write_text(json.dumps(b,indent=2)+'\n') s=s.replace(' int LauncherRounds;',' int LauncherMuzzles;\n int LauncherRounds;').replace('copy.LauncherRounds = LauncherRounds;', 'copy.LauncherMuzzles = LauncherMuzzles; copy.LauncherRounds = LauncherRounds;') s=s.replace(' if (LauncherRounds < 0', ' if (LauncherMuzzles < 0 || LauncherMuzzles > Members * 32) return false;\n if (LauncherRounds < 0') s=s.replace('else launchers.Insert(muzzle);', '''else { launchers.Insert(muzzle); if (muzzle.GetMagazineWell() && !muzzle.IsDisposable()) result.LauncherMuzzles++; else result.LauncherComplete = false; }''');(P/rel).write_text(s) p=D/'ERR_InfantryResupply.c';s=p.read_text().replace('A zero aggregate without a supported physical magazine is not launcher evidence.', 'Only positively sampled reloadable magazine-well muzzles establish launcher demand.') s=s.replace('entry.AmmoState.LauncherMagazines > 0','entry.AmmoState.LauncherMuzzles > 0').replace('local.LauncherMagazines <= 0','local.LauncherMuzzles <= 0');p.write_text(s) p=D/'ERR_InfantryAmmoTransfer.c';s=p.read_text();s=s.replace(''' // 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; InventoryItemComponent loadedItem = InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent)); if (!loadedItem || !loadedItem.GetParentSlot()) continue; InventoryStorageSlot loadedSlot = loadedItem.GetParentSlot(); if (loadedSlot.GetParentContainer() != muzzle || loadedSlot.GetAttachedEntity() != loaded.GetOwner()) continue; if (loaded.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;''', ''' // An expended single-round item may no longer exist. The real reloadable // muzzle/well proves capability; existing loaded items need exact custody. if (muzzle.IsDisposable()) continue; BaseMagazineComponent loaded = muzzle.GetMagazine(); if (loaded) { if (!loaded.GetOwner() || !loaded.GetMagazineWell() || loaded.GetMaxAmmoCount() <= 0) continue; InventoryItemComponent loadedItem = InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent)); if (!loadedItem || !loadedItem.GetParentSlot()) continue; InventoryStorageSlot loadedSlot = loadedItem.GetParentSlot(); if (loadedSlot.GetParentContainer() != muzzle || loadedSlot.GetAttachedEntity() != loaded.GetOwner()) continue; if (loaded.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue; }''');p.write_text(s) for name in ('test_infantry_ammo_transfer.py','test_infantry_resupply_planning.py'): p=P/'tests'/name;s=p.read_text().replace('LauncherMagazines=1,','LauncherMuzzles=1,LauncherMagazines=1,') if name=='test_infantry_ammo_transfer.py': s=s.replace('struct Muzzle{Magazine*', 'struct Muzzle{bool disposable=false;bool IsDisposable(){return disposable;}Magazine*') s=s.replace('if(bad==2)f.muzzle.loaded=nullptr;', 'if(bad==2)f.muzzle.disposable=true;') s=s.replace('int main(){', '''int main(){ {LauncherFixture f;f.muzzle.loaded=nullptr;assert(ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity,true));f.c.Transfer(0);assert(f.source.calls==1&&f.c.m_Move->Submitted());} ''',1) else:s=s.replace('if(bad==1)f.ammo.LauncherMagazines=0;', 'if(bad==1)f.ammo.LauncherMuzzles=0;').replace('f.ammo.SmallArmsRounds=999;assert(!f.Plan());', 'f.ammo.SmallArmsRounds=999;f.ammo.LauncherMagazines=0;assert(!f.Plan());') p.write_text(s) name='test_hq_ammo_sampler.py';s=(R/'tests'/name).read_text();(P/'baseline/tests'/name).write_text(s) s=s.replace('struct Muzzle{Well*', 'struct Muzzle{bool disposable=false;bool IsDisposable(){return disposable;}Well*') s=s.replace('assert(mixed.LauncherMagazines==1&&mixed.KnownSmallArmsEmpty());', '''assert(mixed.LauncherMagazines==1&&mixed.LauncherMuzzles==1&&mixed.KnownSmallArmsEmpty()); auto launcherWire=mixed.Snapshot();mixed.LauncherMuzzles=0;assert(launcherWire->LauncherMuzzles==1); launcher.magazine=nullptr;inv.items.clear();auto expended=Fresh();ERR_HQAmmoSampler::Inspect(&actor,&group,&expended); assert(expended.LauncherComplete&&expended.LauncherMuzzles==1&&expended.LauncherRounds==0&&expended.LauncherMagazines==0); launcher.disposable=true;auto disposable=Fresh();ERR_HQAmmoSampler::Inspect(&actor,&group,&disposable);assert(!disposable.LauncherComplete&&disposable.LauncherMuzzles==0);launcher.disposable=false; launcher.magazine=&unrelated;inv.items={&loadedEntity,&spareEntity,&unrelatedEntity};''') # names above depend on existing exact fixture names; use original variable spelling. print([l for l in s.splitlines() if 'Entity actor' in l or 'Entity loaded' in l]);(P/'tests'/name).write_text(s)