#!/usr/bin/env python3 """Regenerate reviewable Rust constants from the preserved GPL engine source. This development tool is not used by the Rust game at build or runtime. """ from pathlib import Path import re PACKAGE=Path(__file__).resolve().parents[1] ROOT=PACKAGE.parent ids={k:int(v,16) for k,v in re.findall(r'#define\s+(FILE\w+)\s+(0x[0-9a-fA-F]+)',(ROOT/'raptor/src/fileids.h').read_text())} enum=(ROOT/'raptor/src/objects.h').read_text().split('S_FORWARD_GUNS',1)[1].split('}OBJ_TYPE')[0] names=['S_FORWARD_GUNS']+re.findall(r'^\s*(S_\w+)\s*[,\n]',enum,re.M) names=[n for n in names if n!='S_LAST_OBJECT'] values={**ids,**{n:i for i,n in enumerate(names)},'S_ALL':0,'S_AIR':1,'S_GROUND':2,'S_GRALL':3,'S_GTILE':4,'S_SUCK':5,'S_SHOOT':0,'S_LINE':1,'S_BEAM':2,'MAX_SHIELD':100} def number(s,default=0): s=s.strip() if s in values:return values[s] if re.fullmatch(r'-?\d+',s):return int(s) if re.fullmatch(r'0x[0-9a-fA-F]+',s):return int(s,16) if '/' in s: a,b=s.split('/') return number(a)//number(b) raise ValueError(f'Unsupported numeric expression: {s}') def blocks(file,var,start_pattern): text=(ROOT/file).read_text(); starts=list(re.finditer(start_pattern,text));result={} for i,m in enumerate(starts): block=text[m.end():starts[i+1].start() if i+1(\w+)\s*=\s*([^;]+);',block): if k in wanted: f[k]=f.get(v.strip().removeprefix(var+'->')) if v.strip().startswith(var+'->') else number(v) result[m[1]]=f return result objects=blocks('raptor/src/objects.cpp','lib',r'lib\s*=\s*&obj_lib\[(S_\w+)\];') weapons=blocks('raptor/src/shots.cpp','slib',r'slib\s*=\s*&shot_lib\[(S_\w+)\];') id_of=lambda n:'base:'+n[2:].lower().replace('missle','missile') name_of=lambda n:n[2:].replace('_',' ').title().replace('Missle','Missile') out=['// Generated by tools/generate_legacy_tables.py; source is GPL-2.0-or-later.','pub struct LegacyObject{pub index:u32,pub id:&\'static str,pub name:&\'static str,pub asset:u32,pub frames:u32,pub cost:u32,pub max_count:u32,pub initial_count:u32,pub special:bool}','pub struct LegacyWeapon{pub index:u32,pub id:&\'static str,pub name:&\'static str,pub asset:u32,pub frames:u32,pub damage:i32,pub speed:i32,pub cooldown:u32,pub target:u32,pub maxspeed:i32,pub startframe:u32,pub delay:bool,pub plot:bool,pub moving:bool,pub follow_x:bool,pub follow_y:bool,pub effect:bool,pub beam:u32}','pub fn objects()->Vec{vec!['] for n in names: f=objects.get(n,{}) out.append(f'LegacyObject{{index:{values[n]},id:"{id_of(n)}",name:"{name_of(n)}",asset:{max(0,f.get("item",0))},frames:{f.get("numframes",1)},cost:{max(0,f.get("cost",0))*(f.get("start_cnt",1) if f.get("onlyflag") else 1)},max_count:{max(1,f.get("max_cnt",1))},initial_count:{max(1,f.get("start_cnt",1))},special:{str(bool(f.get("specialw",0))).lower()}}},') out+= [']}','pub fn weapons()->Vec{vec!['] for n,f in weapons.items(): out.append(f'LegacyWeapon{{index:{values[n]},id:"{id_of(n)}",name:"{name_of(n)}",asset:{max(0,f.get("lumpnum",0))},frames:{f.get("numframes",1)},damage:{f.get("hits",1)},speed:{f.get("speed",0)},cooldown:{f.get("shoot_rate",1)},target:{f.get("ht",0)},maxspeed:{f.get("maxspeed",0)},startframe:{f.get("startframe",0)},delay:{str(bool(f.get("delayflag",0))).lower()},plot:{str(bool(f.get("use_plot",0))).lower()},moving:{str(bool(f.get("move_flag",0))).lower()},follow_x:{str(bool(f.get("fplrx",0))).lower()},follow_y:{str(bool(f.get("fplry",0))).lower()},effect:{str(bool(f.get("meffect",0))).lower()},beam:{f.get("beam",0)}}},') out +=[']}'] rap=(ROOT/'raptor/src/rap.cpp').read_text() for name in ['o_gun1','o_gun2','o_gun3']: block=re.search(rf'{name}\[8\]\s*=\s*\{{(.*?)\}};',rap,re.S)[1] nums=[int(x.strip(),0) for x in block.split(',') if x.strip()] out.append(f'pub const {name.upper()}: [i32; 8] = ['+', '.join(map(str,nums))+'];') fx=(ROOT/'raptor/src/fx.cpp').read_text() starts=list(re.finditer(r'lib\s*=\s*&fx_items\[(FX_\w+)\];',fx)) out.append("pub struct LegacySound {pub name: &'static str,pub asset:u32,pub pitch:i32,pub random:bool,pub volume:i32,pub priority:i32}") out.append('pub fn sounds()->Vec{vec![') for i,m in enumerate(starts): block=fx[m.end():starts[i+1].start() if i+1item\s*=\s*GLB_GetItemID\("(\w+)"\)',block) if not item: continue matches=[v for k,v in ids.items() if k.endswith('_'+item[1])] if len(matches)!=1: raise ValueError(f'Ambiguous sound {item[1]}: {matches}') pitch=int(re.search(r'lib->pitch\s*=\s*(-?\d+)',block)[1]) random=int(re.search(r'lib->rpflag\s*=\s*(\d+)',block)[1]) volume=int(re.search(r'lib->vol\s*=\s*(-?\d+)',block)[1]) priority=int(re.search(r'lib->pri\s*=\s*(-?\d+)',block)[1]) out.append(f'LegacySound{{name:"{m[1]}",asset:{matches[0]},pitch:{pitch},random:{str(bool(random)).lower()},volume:{volume},priority:{priority}}},') out.append(']}') audio=(ROOT/'raptor/src/i_oplmusic.cpp').read_text() for name,rust in [('frequency_curve','FREQUENCY_CURVE'),('volume_mapping_table','VOLUME_CURVE')]: block=re.search(rf'{name}\[\]\s*=\s*\{{(.*?)\}};',audio,re.S)[1] block=re.sub(r'//[^\n]*','',block) nums=[int(x.strip(),0) for x in block.split(',') if x.strip()] out.append(f'pub const {rust}: [u16; {len(nums)}] = ['+', '.join(map(str,nums))+'];') (PACKAGE/'src/legacy_tables.rs').write_text('\n'.join(out)+'\n') print(f'Generated {len(names)} items and {len(weapons)} weapons')