"""Render the track reference page from RPL4.RES. Each plan is what the pod's map screen draws: every placement in the track's instance stream, its model's GaugeImage looked up by name, laid down rotated and positioned, at the LOD the engine would pick for that scale, in the palette the display is configured with. See navmap.py. Usage: build_tracks.py """ import base64, html, io, os, re, sys from PIL import Image, ImageDraw, ImageOps sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import navmap ROOT = 'c:/VWE/RP412' RES = open(ROOT + '/assets/RP411/RPL4.RES', 'rb').read() LISTING, OUT = sys.argv[1], sys.argv[2] TABLE = navmap.resource_table(RES, LISTING) # The nav display's own palette and default colour: # nav(A,ModeAlwaysActive,(448,416),0x00,0x3C,...) on the pod's secondary # port, which L4GAUGE.CFG configures with secpal.pcc. PALETTE = navmap.palette(ROOT + '/assets/RP411/GAUGE/secpal.pcc') STATIC_COLOUR = 0x3C BACKGROUND = 0x00 if PALETTE is None: sys.exit('secpal.pcc has no VGA palette where PCX keeps one') # ------------------------------------------------------------ the console CONSOLE, CONSOLE_ART = {}, {} xml = open(ROOT + '/tools/console-config/RPConfig.xml', encoding='utf-8-sig').read() for m in re.finditer(r' box: k = box / max(im.size) im = im.resize((max(1, int(im.width * k)), max(1, int(im.height * k))), Image.LANCZOS) out = Image.merge('RGBA', (Image.new('L', im.size, 255),) * 3 + (im,)) buf = io.BytesIO() out.save(buf, 'PNG', optimize=True) return ('data:image/png;base64,' + base64.b64encode(buf.getvalue()).decode(), im.width, im.height) # ---------------------------------------------------------------- gather tracks = {} for res in TABLE.values(): m = re.match(r'^(\w+): Map stream of (\d+) instances$', res['desc']) if m: tracks[m.group(1)] = {'key': m.group(1), 'instances': int(m.group(2)), 'addr': res['addr'], 'size': res['size']} for res in TABLE.values(): m = re.match(r'^(\w+): Stream of (\d+) (Cameras|Existance boxes)$', res['desc']) if m and m.group(1) in tracks: field = 'cameras' if m.group(3) == 'Cameras' else 'boxes' tracks[m.group(1)][field] = int(m.group(2)) for key, t in tracks.items(): t['name'] = CONSOLE.get(key, key) t['race'] = key in RACE t['football'] = key in FOOTBALL t['era'], t['eraLabel'] = era(key) drawing = plan(t['addr'], t['size'], t['instances']) if drawing: (t['art'], t['aw'], t['ah'], t['drawn'], t['skipped'], t['mpp'], t['extent']) = drawing else: t['art'], t['drawn'], t['skipped'] = None, 0, t['instances'] t['extent'] = (0, 0) t['console'] = console_plan(CONSOLE_ART[key]) if key in CONSOLE_ART else None print(' %-14s %3d drawn, %3d skipped' % (key, t['drawn'], t['skipped']), file=sys.stderr) # Tracks with identical footprints are almost certainly one built from the # other; say so rather than leaving the reader to notice. twins = {} for key, t in tracks.items(): sig = (t['instances'], round(t['extent'][0]), round(t['extent'][1])) twins.setdefault(sig, []).append(key) ERA_RANK = {'arcade': 0, 'r411': 1, 'later': 2} ORDER = sorted(tracks, key=lambda k: (ERA_RANK[tracks[k]['era']], -tracks[k]['extent'][1])) # ------------------------------------------------------------------ page def chips(t): out = [(t['era'], t['eraLabel'])] if t['race']: out.append(('race', 'DEATH RACE')) if t['football']: out.append(('ball', 'FOOTBALL')) if not t['race'] and not t['football']: out.append(('none', 'NOT IN THE MENU')) return ''.join('%s' % (k, v) for k, v in out) cards = [] for key in ORDER: t = tracks[key] sig = (t['instances'], round(t['extent'][0]), round(t['extent'][1])) kin = [k for k in twins.get(sig, []) if k != key] twin = ('

Same footprint and instance count as ' + ', '.join(tracks[k]['name'] for k in kin) + '

') if kin else '' short = ('

%d of %d placements are drawn; the rest are ' 'models with no map art, which the map screen skips too

' % (t['drawn'], t['instances'])) if t['skipped'] else '' art = ('
%s as the map screen draws it' '
The map screen’s own drawing
' '
' % (t['art'], t['aw'], t['ah'], html.escape(t['name']))) if t['art'] else '' if t['console']: uri, cw, ch = t['console'] art += ('
' '
The console’s picture ' '— mirrored against the game
' % (html.escape(t['name']), uri, cw, ch)) cards.append("""
{art}

{name}

{key}

{chips}

Placements
{inst}
Drawn
{drawn}
Cameras
{cams}
Start boxes
{boxes}
Extent
{ex} × {ez}
{twin}{short}
""".format( tags=t['era'] + (' race' if t['race'] else '') + (' football' if t['football'] else ''), search=html.escape((key + ' ' + t['name']).lower()), art=art, name=html.escape(t['name']), key=html.escape(key), chips=chips(t), inst=t['instances'], drawn=t['drawn'], cams=t.get('cameras', '—'), boxes=t.get('boxes', '—'), ex='%d' % round(t['extent'][0]), ez='%d' % round(t['extent'][1]), twin=twin, short=short)) arcade_n = sum(1 for t in tracks.values() if t['era'] == 'arcade') r411_n = sum(1 for t in tracks.values() if t['era'] == 'r411') here = os.path.dirname(__file__) read = lambda n: open(os.path.join(here, n), encoding='utf-8').read() open(OUT, 'w', encoding='utf-8').write(read('tracks.tpl').format( css=read('tracks.css'), js=read('tracks.js'), cards=''.join(cards), n=len(tracks), arcade=arcade_n, r411=r411_n, later=len(tracks) - arcade_n - r411_n)) print('wrote %s (%d tracks)' % (OUT, len(tracks)), file=sys.stderr)