RECORDANDRONCO/tools/gen_mission2.py
type-two c09da56dbe Mission 02: CRATE DIGGERS — timed raid with no base building
Strike force + The Selector against a Stream transcoder outpost; 6-minute
countdown shown via SetMissionText, master lacquer is cut (killed) if the timer
beats you. Adds the lacquer prop (deliberately untargetable) and ScriptTriggers
on all actor templates, without which Trigger.OnKilled throws at map load.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 23:11:20 +10:00

119 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Generate campaign mission 02 — CRATE DIGGERS (timed raid, no base building)."""
import os, struct
from PIL import Image
SIZE = 48
W = H = SIZE + 2
MAP = os.path.join(os.path.dirname(__file__), '..', 'mods', 'vinylgod', 'maps', 'm02')
os.makedirs(MAP, exist_ok=True)
WAX = [(12, 40, 4), (26, 26, 5), (40, 20, 3)]
data = bytearray()
data += struct.pack('<BHH', 2, W, H)
data += struct.pack('<III', 17, 0, 3 * W * H + 17)
data += b'\xff\x00\x00' * (W * H)
res = bytearray(2 * W * H)
for cx, cy, r in WAX:
for x in range(cx - r, cx + r + 1):
for y in range(cy - r, cy + r + 1):
d2 = (x - cx) ** 2 + (y - cy) ** 2
if d2 <= r * r and 0 < x < W - 1 and 0 < y < H - 1:
o = 2 * (x * H + y)
res[o], res[o + 1] = 1, max(2, round(12 * (1 - (d2 ** 0.5) / (r + 1))))
data += res
open(os.path.join(MAP, 'map.bin'), 'wb').write(data)
# names used as lua globals must not collide with engine globals
ACTORS = [
('spawn0', 'mpspawn', 'Neutral', 8, 40),
# strike force
('TheSelector', 'selector', 'Player', 8, 40),
('strike1', 'stacktank', 'Player', 10, 41),
('strike2', 'spinner', 'Player', 7, 42),
('strike3', 'spinner', 'Player', 11, 39),
('strike4', 'djgrunt', 'Player', 9, 43),
('strike5', 'djgrunt', 'Player', 10, 43),
('strike6', 'djgrunt', 'Player', 8, 44),
('strike7', 'roadie', 'Player', 6, 41),
('strike8', 'boombike', 'Player', 12, 42),
# the outpost
('Transcoder', 'transcoder', 'Enemy', 38, 9),
('MasterLacquer', 'lacquer', 'Neutral', 41, 10),
('outpost1', 'datacentre', 'Enemy', 35, 12),
('outpost2', 'podfarm', 'Enemy', 41, 13),
('turret1', 'adblocker', 'Enemy', 34, 9),
('turret2', 'adblocker', 'Enemy', 38, 14),
('guard1', 'bufferbot', 'Enemy', 36, 15),
('guard2', 'bufferbot', 'Enemy', 37, 15),
('guard3', 'bufferbot', 'Enemy', 39, 16),
('guard4', 'drmwalker', 'Enemy', 34, 16),
('guard5', 'crawler', 'Enemy', 30, 20),
# a picket on the approach
('picket1', 'bufferbot', 'Enemy', 24, 28),
('picket2', 'bufferbot', 'Enemy', 25, 29),
('picket3', 'adblocker', 'Enemy', 22, 30),
]
actors = '\n'.join(f'\t{n}: {t}\n\t\tOwner: {o}\n\t\tLocation: {x},{y}' for n, t, o, x, y in ACTORS)
open(os.path.join(MAP, 'map.yaml'), 'w').write(f"""MapFormat: 12
RequiresMod: vinylgod
Title: 02 - Crate Diggers
Author: Monster Robot Party
Tileset: VINYLGOD
MapSize: {W},{H}
Bounds: 1,1,{SIZE},{SIZE}
Visibility: MissionSelector
Categories: Campaign
LockPreview: True
Players:
PlayerReference@Neutral:
Name: Neutral
OwnsWorld: True
NonCombatant: True
Faction: vinylgod
PlayerReference@Player:
Name: Player
Playable: True
Required: True
AllowBots: False
LockFaction: True
Faction: vinylgod
LockSpawn: True
LockTeam: True
Enemies: Enemy
PlayerReference@Enemy:
Name: Enemy
Faction: stream
Enemies: Player
Actors:
{actors}
Rules: vinylgod|rules/campaign.yaml, rules.yaml
FluentMessages: vinylgod|fluent/lua.ftl
""")
im = Image.new('RGB', (W, H), (55, 50, 52))
for cx, cy, r in WAX:
for x in range(cx - r, cx + r + 1):
for y in range(cy - r, cy + r + 1):
if (x - cx) ** 2 + (y - cy) ** 2 <= r * r and 0 <= x < W and 0 <= y < H:
im.putpixel((x, y), (255, 45, 150))
for _, _, o, x, y in ACTORS:
im.putpixel((x, y), (255, 255, 255) if o == 'Player' else (80, 150, 255) if o == 'Enemy' else (240, 210, 90))
im.resize((W * 3, H * 3), Image.NEAREST).save(os.path.join(MAP, 'map.png'))
print('m02 written')