#!/usr/bin/env python3 """Repaint the tile plane of every map.bin with varied VINYLGOD terrain. Surgical: only the tile plane is touched — resources, size and header are left exactly as they are, so it is safe to re-run on any map at any time. Layout logic (all tiles are terrain type Clear, so pathing is unchanged): * asphalt base everywhere, random variant per cell * concrete slabs — a few rectangular pads (old foundations / car parks) * dirt — organic blobs * grooves — concentric rings radiating out of each wax field, as if the Black Wax etched the ground on its way up * rubble — scatter, denser toward the map edges """ import os, struct, glob, random TPL = {'asphalt': (255, 16), 'concrete': (1, 9), 'dirt': (2, 9), 'grooves': (3, 9), 'rubble': (4, 9)} MAPS = sorted(glob.glob(os.path.join(os.path.dirname(__file__), '..', 'mods', 'vinylgod', 'maps', '*'))) def paint(path, seed): d = bytearray(open(path, 'rb').read()) fmt, w, h = d[0], *struct.unpack_from(' template name # wax field centres, for the groove rings wax = [(x, y) for x in range(w) for y in range(h) if d[res_off + 2 * (x * h + y)] != 0] seeds = [] if wax: seen = set() for s in wax: if s in seen: continue stack, blob = [s], [] seen.add(s) while stack: cx, cy = stack.pop() blob.append((cx, cy)) for n in ((cx+1, cy), (cx-1, cy), (cx, cy+1), (cx, cy-1)): if n in set(wax) - seen if False else (n in wax and n not in seen): seen.add(n) stack.append(n) if len(blob) >= 6: seeds.append((sum(p[0] for p in blob) / len(blob), sum(p[1] for p in blob) / len(blob))) # concrete pads for _ in range(max(1, w // 30)): px, py = rng.randrange(w), rng.randrange(h) pw, ph = rng.randint(3, 6), rng.randint(3, 6) for x in range(px, min(w, px + pw)): for y in range(py, min(h, py + ph)): surface[(x, y)] = 'concrete' # dirt blobs for _ in range(max(2, w // 18)): cx, cy, r = rng.randrange(w), rng.randrange(h), rng.randint(3, 7) for x in range(cx - r, cx + r + 1): for y in range(cy - r, cy + r + 1): if 0 <= x < w and 0 <= y < h and (x - cx) ** 2 + (y - cy) ** 2 <= r * r + rng.randint(-3, 3): surface[(x, y)] = 'dirt' # groove patches: etched ground sitting just off each wax field. # (tried concentric rings first — arcs don't line up across tiles and it # reads as random squares, so coherent blobs it is) import math as _m for cx, cy in seeds: for _ in range(2): a = rng.random() * 2 * _m.pi dist = rng.randint(6, 10) bx, by = int(cx + dist * _m.cos(a)), int(cy + dist * _m.sin(a)) r = rng.randint(2, 3) for x in range(bx - r, bx + r + 1): for y in range(by - r, by + r + 1): if 0 <= x < w and 0 <= y < h and (x - bx) ** 2 + (y - by) ** 2 <= r * r: surface[(x, y)] = 'grooves' # rubble: a few coherent clumps (never per-cell scatter — that checkerboards) for _ in range(max(2, w // 16)): cx, cy, r = rng.randrange(w), rng.randrange(h), rng.randint(2, 4) for x in range(cx - r, cx + r + 1): for y in range(cy - r, cy + r + 1): if 0 <= x < w and 0 <= y < h and (x - cx) ** 2 + (y - cy) ** 2 <= r * r: surface[(x, y)] = 'rubble' for x in range(w): for y in range(h): tid, n = TPL[surface.get((x, y), 'asphalt')] o = tiles_off + 3 * (x * h + y) struct.pack_into('