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>
119 lines
3.5 KiB
Python
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')
|