diff --git a/assets_src/map_arena.png b/assets_src/map_arena.png new file mode 100644 index 0000000..53e03e8 Binary files /dev/null and b/assets_src/map_arena.png differ diff --git a/assets_src/map_detail.png b/assets_src/map_detail.png new file mode 100644 index 0000000..7617e60 Binary files /dev/null and b/assets_src/map_detail.png differ diff --git a/assets_src/round5_roster.png b/assets_src/round5_roster.png new file mode 100644 index 0000000..851984f Binary files /dev/null and b/assets_src/round5_roster.png differ diff --git a/assets_src/terrain/asphalt.png b/assets_src/terrain/asphalt.png new file mode 100644 index 0000000..b71718a Binary files /dev/null and b/assets_src/terrain/asphalt.png differ diff --git a/assets_src/terrain/concrete.png b/assets_src/terrain/concrete.png new file mode 100644 index 0000000..59a5a38 Binary files /dev/null and b/assets_src/terrain/concrete.png differ diff --git a/assets_src/terrain/dirt.png b/assets_src/terrain/dirt.png new file mode 100644 index 0000000..3f2ce00 Binary files /dev/null and b/assets_src/terrain/dirt.png differ diff --git a/assets_src/terrain/grooves.png b/assets_src/terrain/grooves.png new file mode 100644 index 0000000..57da13a Binary files /dev/null and b/assets_src/terrain/grooves.png differ diff --git a/assets_src/terrain/preview_asphalt.png b/assets_src/terrain/preview_asphalt.png new file mode 100644 index 0000000..7a6cce1 Binary files /dev/null and b/assets_src/terrain/preview_asphalt.png differ diff --git a/assets_src/terrain/rubble.png b/assets_src/terrain/rubble.png new file mode 100644 index 0000000..1a8b7a6 Binary files /dev/null and b/assets_src/terrain/rubble.png differ diff --git a/mods/vinylgod/maps/arena/map.bin b/mods/vinylgod/maps/arena/map.bin index 4c21633..0632ba7 100644 Binary files a/mods/vinylgod/maps/arena/map.bin and b/mods/vinylgod/maps/arena/map.bin differ diff --git a/mods/vinylgod/maps/arena/map.yaml b/mods/vinylgod/maps/arena/map.yaml index 50a3c23..f693aa2 100644 --- a/mods/vinylgod/maps/arena/map.yaml +++ b/mods/vinylgod/maps/arena/map.yaml @@ -60,9 +60,15 @@ Actors: Location: 24,25 waxseed0: waxseed Owner: Neutral - Location: 8,20 + Location: 8,25 waxseed1: waxseed Owner: Neutral - Location: 18,10 + Location: 12,12 + waxseed2: waxseed + Owner: Neutral + Location: 21,21 + waxseed3: waxseed + Owner: Neutral + Location: 25,8 Rules: rules.yaml diff --git a/mods/vinylgod/maps/compression/map.bin b/mods/vinylgod/maps/compression/map.bin index bc8affc..203e898 100644 Binary files a/mods/vinylgod/maps/compression/map.bin and b/mods/vinylgod/maps/compression/map.bin differ diff --git a/mods/vinylgod/maps/district/map.bin b/mods/vinylgod/maps/district/map.bin index 0dac3cd..cff1a24 100644 Binary files a/mods/vinylgod/maps/district/map.bin and b/mods/vinylgod/maps/district/map.bin differ diff --git a/mods/vinylgod/maps/m01/map.bin b/mods/vinylgod/maps/m01/map.bin index c452a99..c75996c 100644 Binary files a/mods/vinylgod/maps/m01/map.bin and b/mods/vinylgod/maps/m01/map.bin differ diff --git a/mods/vinylgod/maps/needledrop/map.bin b/mods/vinylgod/maps/needledrop/map.bin index dc19dfb..1ca6a43 100644 Binary files a/mods/vinylgod/maps/needledrop/map.bin and b/mods/vinylgod/maps/needledrop/map.bin differ diff --git a/mods/vinylgod/maps/shellmap/map.bin b/mods/vinylgod/maps/shellmap/map.bin index 4c21633..2b04a82 100644 Binary files a/mods/vinylgod/maps/shellmap/map.bin and b/mods/vinylgod/maps/shellmap/map.bin differ diff --git a/mods/vinylgod/maps/vinylgod/map.bin b/mods/vinylgod/maps/vinylgod/map.bin index 68e43bd..be92501 100644 Binary files a/mods/vinylgod/maps/vinylgod/map.bin and b/mods/vinylgod/maps/vinylgod/map.bin differ diff --git a/mods/vinylgod/rules/ai.yaml b/mods/vinylgod/rules/ai.yaml index c5b64e9..16a7efd 100644 --- a/mods/vinylgod/rules/ai.yaml +++ b/mods/vinylgod/rules/ai.yaml @@ -37,6 +37,8 @@ player: printfarm: 1 mastering: 1 algorithm: 1 + launchramp: 1 + droneport: 1 BuildingFractions: pressplant: 30 hornpit: 8 @@ -48,6 +50,8 @@ player: printfarm: 1 mastering: 1 algorithm: 1 + launchramp: 2 + droneport: 2 UnitBuilderBotModule: RequiresCondition: enable-wax-ai UnitsToBuild: @@ -60,10 +64,20 @@ player: bufferbot: 60 drmwalker: 45 scraper: 10 + boombike: 20 + crawler: 8 + flyer45: 25 + compressor: 25 + selector: 5 + curator: 5 UnitLimits: digger: 4 scraper: 4 roadie: 2 + boombike: 3 + crawler: 2 + selector: 1 + curator: 1 SupportPowerBotModule: RequiresCondition: enable-wax-ai Decisions: @@ -79,6 +93,7 @@ player: SquadManagerBotModule: RequiresCondition: enable-wax-ai SquadSize: 8 - ExcludeFromSquadsTypes: digger, scraper, popupvan, streamvan, roadie + AirUnitsTypes: flyer45, compressor + ExcludeFromSquadsTypes: digger, scraper, popupvan, streamvan, roadie, crawler ConstructionYardTypes: popupshop, streamhub - ProtectionTypes: digger, scraper, popupvan, streamvan, pressplant, transcoder, popupshop, streamhub, ampstack, datacentre, vault, merchtable, podfarm, garage, printfarm, mastering + ProtectionTypes: launchramp, droneport, digger, scraper, popupvan, streamvan, pressplant, transcoder, popupshop, streamhub, ampstack, datacentre, vault, merchtable, podfarm, garage, printfarm, mastering diff --git a/mods/vinylgod/rules/economy.yaml b/mods/vinylgod/rules/economy.yaml index eac3e02..72458cf 100644 --- a/mods/vinylgod/rules/economy.yaml +++ b/mods/vinylgod/rules/economy.yaml @@ -284,7 +284,7 @@ waxseed: Terrain: Wax SeedsResource: ResourceType: Wax - Interval: 90 + Interval: 20 MaxRange: 60 MapEditorData: Categories: Resource spawn diff --git a/mods/vinylgod/tilesets/vinylgod.yaml b/mods/vinylgod/tilesets/vinylgod.yaml index 1dccce1..83967b5 100644 --- a/mods/vinylgod/tilesets/vinylgod.yaml +++ b/mods/vinylgod/tilesets/vinylgod.yaml @@ -13,8 +13,88 @@ Terrain: Templates: Template@255: Id: 255 - Images: tilesets/vinylgod/vinylgod.png + Images: tilesets/vinylgod/asphalt.png Size: 1,1 + PickAny: True Categories: Terrain Tiles: 0: Clear + 1: Clear + 2: Clear + 3: Clear + 4: Clear + 5: Clear + 6: Clear + 7: Clear + 8: Clear + 9: Clear + 10: Clear + 11: Clear + 12: Clear + 13: Clear + 14: Clear + 15: Clear + Template@1: + Id: 1 + Images: tilesets/vinylgod/concrete.png + Size: 1,1 + PickAny: True + Categories: Terrain + Tiles: + 0: Clear + 1: Clear + 2: Clear + 3: Clear + 4: Clear + 5: Clear + 6: Clear + 7: Clear + 8: Clear + Template@2: + Id: 2 + Images: tilesets/vinylgod/dirt.png + Size: 1,1 + PickAny: True + Categories: Terrain + Tiles: + 0: Clear + 1: Clear + 2: Clear + 3: Clear + 4: Clear + 5: Clear + 6: Clear + 7: Clear + 8: Clear + Template@3: + Id: 3 + Images: tilesets/vinylgod/grooves.png + Size: 1,1 + PickAny: True + Categories: Terrain + Tiles: + 0: Clear + 1: Clear + 2: Clear + 3: Clear + 4: Clear + 5: Clear + 6: Clear + 7: Clear + 8: Clear + Template@4: + Id: 4 + Images: tilesets/vinylgod/rubble.png + Size: 1,1 + PickAny: True + Categories: Terrain + Tiles: + 0: Clear + 1: Clear + 2: Clear + 3: Clear + 4: Clear + 5: Clear + 6: Clear + 7: Clear + 8: Clear diff --git a/mods/vinylgod/tilesets/vinylgod/asphalt.png b/mods/vinylgod/tilesets/vinylgod/asphalt.png new file mode 100644 index 0000000..7b7be0c Binary files /dev/null and b/mods/vinylgod/tilesets/vinylgod/asphalt.png differ diff --git a/mods/vinylgod/tilesets/vinylgod/concrete.png b/mods/vinylgod/tilesets/vinylgod/concrete.png new file mode 100644 index 0000000..9c3850a Binary files /dev/null and b/mods/vinylgod/tilesets/vinylgod/concrete.png differ diff --git a/mods/vinylgod/tilesets/vinylgod/dirt.png b/mods/vinylgod/tilesets/vinylgod/dirt.png new file mode 100644 index 0000000..ee3fcca Binary files /dev/null and b/mods/vinylgod/tilesets/vinylgod/dirt.png differ diff --git a/mods/vinylgod/tilesets/vinylgod/grooves.png b/mods/vinylgod/tilesets/vinylgod/grooves.png new file mode 100644 index 0000000..8548a75 Binary files /dev/null and b/mods/vinylgod/tilesets/vinylgod/grooves.png differ diff --git a/mods/vinylgod/tilesets/vinylgod/rubble.png b/mods/vinylgod/tilesets/vinylgod/rubble.png new file mode 100644 index 0000000..f09508c Binary files /dev/null and b/mods/vinylgod/tilesets/vinylgod/rubble.png differ diff --git a/tools/build_tiles.py b/tools/build_tiles.py new file mode 100644 index 0000000..10a4c65 --- /dev/null +++ b/tools/build_tiles.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Build the VINYLGOD terrain tile atlases from the flux textures. + +One atlas PNG per surface, each holding N 32x32 variants as frames (FrameSize +metadata). Tiles are cut from a downscaled texture and edge-blended against +their own wrap so random placement does not show hard seams. + +All tiles stay terrain type `Clear` — this is a pure visual upgrade, no +pathing/placement changes. +""" +import os, struct, zlib +from PIL import Image, ImageChops + +SRC = os.path.join(os.path.dirname(__file__), '..', 'assets_src', 'terrain') +DST = os.path.join(os.path.dirname(__file__), '..', 'mods', 'vinylgod', 'tilesets', 'vinylgod') +os.makedirs(DST, exist_ok=True) +TILE = 32 + +# surface -> (grid, target average brightness nudge) +SURFACES = { + 'asphalt': 4, # 16 base variants + 'concrete': 3, # 9 + 'dirt': 3, + 'grooves': 3, + 'rubble': 3, +} + + +def seamless(im): + """Cheap wrap-blend so tile edges meet: cross-fade with a half-offset copy.""" + w, h = im.size + off = ImageChops.offset(im, w // 2, h // 2) + mask = Image.new('L', (w, h), 0) + px = mask.load() + fw, fh = w // 6, h // 6 + for y in range(h): + for x in range(w): + ex = min(x, w - 1 - x) + ey = min(y, h - 1 - y) + v = 255 + if ex < fw: + v = min(v, int(255 * ex / fw)) + if ey < fh: + v = min(v, int(255 * ey / fh)) + px[x, y] = 255 - v # strong at edges + return Image.composite(off, im, mask) + + +def chunk(tag, data): + return struct.pack('>I', len(data)) + tag + data + struct.pack('>I', zlib.crc32(tag + data)) + + +def save_frames(path, frames): + w, h = TILE * len(frames), TILE + sheet = Image.new('RGB', (w, h)) + for i, f in enumerate(frames): + sheet.paste(f, (i * TILE, 0)) + raw = sheet.convert('RGBA').tobytes() + rows = b''.join(b'\x00' + raw[y * w * 4:(y + 1) * w * 4] for y in range(h)) + out = b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 6, 0, 0, 0)) + out += chunk(b'tEXt', b'FrameSize\x00' + f'{TILE},{TILE}'.encode()) + out += chunk(b'IDAT', zlib.compress(rows)) + chunk(b'IEND', b'') + open(path, 'wb').write(out) + + +def stats(im): + px = list(im.getdata()) + n = len(px) + out = [] + for c in range(3): + vals = [p[c] for p in px] + m = sum(vals) / n + sd = (sum((v - m) ** 2 for v in vals) / n) ** 0.5 or 1.0 + out.append((m, sd)) + return out + + +def harmonize(im, target, strength=1.0): + """Pull a surface toward the base asphalt's tone so surfaces read as one + material family instead of five clashing ones.""" + src = stats(im) + px = im.load() + w, h = im.size + for y in range(h): + for x in range(w): + r, g, b = px[x, y][:3] + new = [] + for c, v in enumerate((r, g, b)): + sm, ssd = src[c] + tm, tsd = target[c] + z = (v - sm) / ssd + nv = tm + z * tsd + nv = v + (nv - v) * strength + new.append(max(0, min(255, int(nv)))) + px[x, y] = tuple(new) + return im + + +base = Image.open(os.path.join(SRC, 'asphalt.png')).convert('RGB') +TARGET = stats(base.resize((128, 128), Image.LANCZOS)) + +counts = {} +for name, grid in SURFACES.items(): + src = Image.open(os.path.join(SRC, f'{name}.png')).convert('RGB') + src = src.resize((TILE * grid, TILE * grid), Image.LANCZOS) + if name != 'asphalt': + src = harmonize(src, TARGET) + frames = [] + for gy in range(grid): + for gx in range(grid): + t = src.crop((gx * TILE, gy * TILE, (gx + 1) * TILE, (gy + 1) * TILE)) + frames.append(seamless(t)) + save_frames(os.path.join(DST, f'{name}.png'), frames) + counts[name] = len(frames) + print(f'{name}: {len(frames)} tiles') + +print('counts =', counts) diff --git a/tools/jitter_wax.py b/tools/jitter_wax.py new file mode 100644 index 0000000..c94cc3d --- /dev/null +++ b/tools/jitter_wax.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Jitter wax density per cell so neighbouring cells pick different sprite +frames — without it, equal-density cells draw the identical blob and the field +reads as a regular grid.""" +import os, struct, glob, random + +for i, m in enumerate(sorted(glob.glob(os.path.join(os.path.dirname(__file__), '..', 'mods', 'vinylgod', 'maps', '*')))): + p = os.path.join(m, 'map.bin') + if not os.path.exists(p): + continue + d = bytearray(open(p, 'rb').read()) + w, h = 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(' [out.png] +""" +import os, struct, sys +from PIL import Image + +ROOT = os.path.join(os.path.dirname(__file__), '..') +MOD = os.path.join(ROOT, 'mods', 'vinylgod') +TPL = {255: 'asphalt', 1: 'concrete', 2: 'dirt', 3: 'grooves', 4: 'rubble'} +TILE = 32 + +name = sys.argv[1] if len(sys.argv) > 1 else 'arena' +out = sys.argv[2] if len(sys.argv) > 2 else os.path.join(ROOT, 'assets_src', f'map_{name}.png') + +atlas = {} +for tid, n in TPL.items(): + im = Image.open(os.path.join(MOD, 'tilesets', 'vinylgod', f'{n}.png')).convert('RGBA') + atlas[tid] = [im.crop((i * TILE, 0, (i + 1) * TILE, TILE)) for i in range(im.width // TILE)] + +wax_sheet = Image.open(os.path.join(MOD, 'sequences', 'assets', 'wax.png')).convert('RGBA') +wax = [wax_sheet.crop((i * 32, 0, (i + 1) * 32, 32)) for i in range(wax_sheet.width // 32)] + +d = open(os.path.join(MOD, 'maps', name, 'map.bin'), 'rb').read() +fmt, w, h = d[0], *struct.unpack_from(' {out} ({img.width}x{img.height})')