#!/usr/bin/env python3 """Import supported legacy settings without modifying either source or an existing destination.""" import argparse,json,os,tempfile,sys from pathlib import Path SCHEMA={ 'aiDoctrine':(0,0,1), 'aiShadow':(0,0,1), 'planeAltitudeMetres':(250,150,500), 'planeReplacementSeconds':(60,30,1800), 'planeSectorSeconds':(120,30,600), 'planeReconEnabled':(1,0,1), 'roadblockCount':(4,0,30), 'civilianTrafficCount':(3,0,6), 'startMode':(0,0,1), 'keepWeaponOnUnconscious':(1,0,1), 'restartOnEmpty':(1,0,1), 'emptyRestartHours':(5,0,168), 'reportIntervalSeconds':(10,5,60), 'supportSquadLimit':(4,0,8), 'responseRadiusMetres':(2000,500,5000), 'aiIntelChancePercent':(60,0,100), 'intelRevealChancePercent':(15,0,100), 'radioDefenceSeconds':(180,30,1800), 'startTimeMinutes':(840,0,1439), 'timeMultiplier':(1,1,24), 'startDateCode':(0,0,1501231), 'weatherProfile':(0,0,3), 'viewDistanceMetres':(0,0,10000), 'automaticEquipmentChancePercent':(25,0,100), 'antiAirChancePercent':(7,0,100), 'antiPersonnelMineChancePercent':(60,0,100), 'automaticConvoyEnabled':(1,0,1), 'convoyCount':(3,0,20), 'heliTimerMinutes':(15,1,1440), } AI_KEYS=('aiDoctrine','aiShadow','planeAltitudeMetres','planeReplacementSeconds','planeSectorSeconds','planeReconEnabled','reportIntervalSeconds','supportSquadLimit','responseRadiusMetres','automaticEquipmentChancePercent','antiAirChancePercent','antiPersonnelMineChancePercent','automaticConvoyEnabled','heliTimerMinutes') INTEL_KEYS=('aiIntelChancePercent','intelRevealChancePercent') def merge(ai=None,intel=None): result={key:spec[0] for key,spec in SCHEMA.items()};deferred=[] for label,data,keys in [('ai',ai,AI_KEYS),('intel',intel,INTEL_KEYS)]: if data is None:continue if not isinstance(data,dict):raise ValueError(label+' settings must be a JSON object') if 'configVersion' in data and (type(data['configVersion']) not in (int,float) or data['configVersion']!=1): raise ValueError(label+' configVersion must be 1') for key in keys: if key not in data:continue value=data[key];_,lo,hi=SCHEMA[key] if type(value) not in (int,float) or not lo<=value<=hi or int(value)!=value: raise ValueError(f'{key} must be an integer in [{lo},{hi}]') result[key]=int(value) recognized=set(keys)|{'configVersion'} if label=='ai' and 'stingerChanceOneIn' in data: old=data['stingerChanceOneIn'] if type(old) not in (int,float) or not 0<=old<=10000 or int(old)!=old: raise ValueError('stingerChanceOneIn must be an integer in [0,10000]') if 'antiAirChancePercent' not in data:result['antiAirChancePercent']=int(100/old+0.5) if old else 0 recognized.add('stingerChanceOneIn') if label=='ai' and 'convoyCount' in data: if 'automaticConvoyEnabled' in data:raise ValueError('Supply either convoyCount or automaticConvoyEnabled, not both') count=data['convoyCount'] if type(count) not in (int,float) or not 0<=count<=20 or int(count)!=count: raise ValueError('convoyCount must be an integer in [0,20]') result['automaticConvoyEnabled']=int(count>0) result['convoyCount']=int(count) recognized.add('convoyCount') deferred.extend(label+':'+key for key in data if key not in recognized) return result,deferred def write_new(path,text): path=Path(path) with tempfile.NamedTemporaryFile(mode='w',dir=path.parent,prefix='.rereintroduced-',delete=False) as f: tmp=Path(f.name) try: f.write(text);f.flush();os.fsync(f.fileno()) # Atomic installation that cannot overwrite an existing file, even in a race. os.link(tmp,path) finally:tmp.unlink(missing_ok=True) def main(): p=argparse.ArgumentParser(description=__doc__) p.add_argument('--ai',type=Path,help='Read-only escapists-ai.json source') p.add_argument('--intel',type=Path,help='Read-only escapists-intel.json source') p.add_argument('--out',type=Path,help='New rereintroduced.json; omit to preview on stdout') a=p.parse_args() if not a.ai and not a.intel:p.error('Supply --ai and/or --intel') try: result,deferred=merge(json.loads(a.ai.read_text()) if a.ai else None,json.loads(a.intel.read_text()) if a.intel else None) text=json.dumps(result,indent=2,allow_nan=False)+'\n' if a.out:write_new(a.out,text) else:print(text,end='') if deferred:print('Migration notes; unrecognized settings remain deferred (source files retained): '+', '.join(deferred),file=sys.stderr) except (ValueError,OSError) as e:p.error(str(e)) if __name__=='__main__':main()