Round 2: 16-facing rotating vehicles, projectile/explosion effects, death booms, real terrain + wax crystal art, Pressing District 4-player map

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-20 16:48:13 +10:00
parent 2362a04605
commit 7a02ca19fd
43 changed files with 300 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

View File

@ -0,0 +1,62 @@
MapFormat: 12
RequiresMod: vinylgod
Title: Pressing District
Author: Monster Robot Party
Tileset: VINYLGOD
MapSize: 66,66
Bounds: 1,1,64,64
Visibility: Lobby
Categories: Maps
Players:
PlayerReference@Neutral:
Name: Neutral
OwnsWorld: True
NonCombatant: True
Faction: Random
PlayerReference@Creeps:
Name: Creeps
NonCombatant: True
Faction: Random
PlayerReference@Multi0:
Name: Multi0
Playable: True
Faction: Random
Enemies: Creeps
PlayerReference@Multi1:
Name: Multi1
Playable: True
Faction: Random
Enemies: Creeps
PlayerReference@Multi2:
Name: Multi2
Playable: True
Faction: Random
Enemies: Creeps
PlayerReference@Multi3:
Name: Multi3
Playable: True
Faction: Random
Enemies: Creeps
Actors:
Actor0: mpspawn
Owner: Multi0
Location: 8,8
Actor1: mpspawn
Owner: Multi1
Location: 57,8
Actor2: mpspawn
Owner: Multi2
Location: 8,57
Actor3: mpspawn
Owner: Multi3
Location: 57,57

View File

@ -9,6 +9,8 @@
spinner:
Inherits: ^ArmedUnit
-WithSpriteBody:
WithFacingSpriteBody:
Buildable:
Queue: Vehicle
BuildPaletteOrder: 40
@ -29,6 +31,8 @@ spinner:
stacktank:
Inherits: ^ArmedUnit
-WithSpriteBody:
WithFacingSpriteBody:
Buildable:
Queue: Vehicle
BuildPaletteOrder: 50
@ -49,6 +53,8 @@ stacktank:
basscannon:
Inherits: ^ArmedUnit
-WithSpriteBody:
WithFacingSpriteBody:
Buildable:
Queue: Vehicle
BuildPaletteOrder: 60

View File

@ -10,6 +10,8 @@
^Unit:
Inherits: ^EconSprite
FireWarheadsOnDeath:
Weapon: UnitExplode
Targetable:
TargetTypes: GroundActor
MustBeDestroyed:
@ -22,6 +24,8 @@
digger:
Inherits: ^Unit
-WithSpriteBody:
WithFacingSpriteBody:
Buildable:
Queue: Vehicle
BuildPaletteOrder: 20
@ -49,6 +53,8 @@ digger:
popupvan:
Inherits: ^Unit
-WithSpriteBody:
WithFacingSpriteBody:
Buildable:
Queue: Vehicle
BuildPaletteOrder: 30
@ -91,6 +97,8 @@ djgrunt:
^EconBuilding:
Inherits: ^EconSprite
FireWarheadsOnDeath:
Weapon: BuildingExplode
Targetable:
TargetTypes: GroundActor, Structure
MustBeDestroyed:

View File

@ -2,6 +2,8 @@
streamvan:
Inherits: ^Unit
-WithSpriteBody:
WithFacingSpriteBody:
Buildable:
Queue: Vehicle
BuildPaletteOrder: 31
@ -121,6 +123,8 @@ printfarm:
scraper:
Inherits: ^Unit
-WithSpriteBody:
WithFacingSpriteBody:
Buildable:
Queue: Vehicle
BuildPaletteOrder: 21
@ -168,6 +172,8 @@ bufferbot:
drmwalker:
Inherits: ^ArmedUnit
-WithSpriteBody:
WithFacingSpriteBody:
Buildable:
Queue: Vehicle
BuildPaletteOrder: 51

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 B

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -9,12 +9,14 @@ clock:
digger:
idle:
Filename: sequences/assets/digger.png
Facings: 16
icon:
Filename: sequences/assets/icon_digger.png
popupvan:
idle:
Filename: sequences/assets/popupvan.png
Facings: 16
icon:
Filename: sequences/assets/icon_popupvan.png
@ -63,18 +65,21 @@ garage:
spinner:
idle:
Filename: sequences/assets/spinner.png
Facings: 16
icon:
Filename: sequences/assets/icon_spinner.png
stacktank:
idle:
Filename: sequences/assets/stacktank.png
Facings: 16
icon:
Filename: sequences/assets/icon_stacktank.png
basscannon:
idle:
Filename: sequences/assets/basscannon.png
Facings: 16
icon:
Filename: sequences/assets/icon_basscannon.png
@ -93,6 +98,7 @@ mastering:
streamvan:
idle:
Filename: sequences/assets/streamvan.png
Facings: 16
icon:
Filename: sequences/assets/icon_streamvan.png
@ -129,6 +135,7 @@ printfarm:
scraper:
idle:
Filename: sequences/assets/scraper.png
Facings: 16
icon:
Filename: sequences/assets/icon_scraper.png
@ -141,5 +148,19 @@ bufferbot:
drmwalker:
idle:
Filename: sequences/assets/drmwalker.png
Facings: 16
icon:
Filename: sequences/assets/icon_drmwalker.png
seven_inch_proj:
idle:
Filename: sequences/assets/seven_inch_proj.png
Length: 4
explosion:
pop:
Filename: sequences/assets/pop.png
Length: 8
bigboom:
Filename: sequences/assets/bigboom.png
Length: 12

Binary file not shown.

Before

Width:  |  Height:  |  Size: 516 B

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -7,6 +7,9 @@
tonearm_blade:
Inherits: ^GroundWeapon
Warhead@2Eff: CreateEffect
Explosions: pop
ValidTargets: Ground, GroundActor
ReloadDelay: 12
Range: 1c512
Warhead@1Dam: SpreadDamage
@ -21,11 +24,16 @@ tonearm_blade:
seven_inch:
Inherits: ^GroundWeapon
Warhead@2Eff: CreateEffect
Explosions: pop
ValidTargets: Ground, GroundActor
ReloadDelay: 30
Range: 4c0
Projectile: Bullet
Speed: 512
LaunchAngle: 30
Image: seven_inch_proj
Sequences: idle
Warhead@1Dam: SpreadDamage
Spread: 96
Damage: 3000
@ -38,6 +46,9 @@ seven_inch:
feedback_burst:
Inherits: ^GroundWeapon
Warhead@2Eff: CreateEffect
Explosions: pop
ValidTargets: Ground, GroundActor
ReloadDelay: 50
Range: 5c0
Warhead@1Dam: SpreadDamage
@ -52,6 +63,9 @@ feedback_burst:
bass_ring:
Inherits: ^GroundWeapon
Warhead@2Eff: CreateEffect
Explosions: bigboom
ValidTargets: Ground, GroundActor
ReloadDelay: 120
Range: 9c0
MinRange: 2c0
@ -71,6 +85,9 @@ bass_ring:
ping:
Inherits: ^GroundWeapon
Warhead@2Eff: CreateEffect
Explosions: pop
ValidTargets: Ground, GroundActor
ReloadDelay: 15
Range: 3c512
Warhead@1Dam: SpreadDamage
@ -82,3 +99,13 @@ ping:
Wood: 35
Light: 45
Heavy: 15
UnitExplode:
Warhead@Eff: CreateEffect
Explosions: pop
ValidTargets: Ground, GroundActor
BuildingExplode:
Warhead@Eff: CreateEffect
Explosions: bigboom
ValidTargets: Ground, GroundActor

82
tools/gen_map.py Normal file
View File

@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Generate 'Pressing District' — 64x64 4-player skirmish map (yaml + bin + preview)."""
import os, struct
from PIL import Image
W = H = 66 # 64 playable + 1 border each side
MAP = os.path.join(os.path.dirname(__file__), '..', 'mods', 'vinylgod', 'maps', 'district')
os.makedirs(MAP, exist_ok=True)
SPAWNS = [(8, 8), (57, 8), (8, 57), (57, 57)]
WAX = [(16, 16, 5), (49, 16, 5), (16, 49, 5), (49, 49, 5), (32, 32, 7), (32, 10, 3), (32, 55, 3), (10, 32, 3), (55, 32, 3)]
# map.bin: fmt2 header, tiles (template 255 idx 0), resources
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)
players = '\n'.join(f""" PlayerReference@Multi{i}:
Name: Multi{i}
Playable: True
Faction: Random
Enemies: Creeps""" for i in range(4))
actors = '\n'.join(f""" Actor{i}: mpspawn
Owner: Multi{i}
Location: {x},{y}""" for i, (x, y) in enumerate(SPAWNS))
open(os.path.join(MAP, 'map.yaml'), 'w').write(f"""MapFormat: 12
RequiresMod: vinylgod
Title: Pressing District
Author: Monster Robot Party
Tileset: VINYLGOD
MapSize: {W},{H}
Bounds: 1,1,64,64
Visibility: Lobby
Categories: Maps
Players:
PlayerReference@Neutral:
Name: Neutral
OwnsWorld: True
NonCombatant: True
Faction: Random
PlayerReference@Creeps:
Name: Creeps
NonCombatant: True
Faction: Random
{players}
Actors:
{actors}
""")
# preview
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 x, y in SPAWNS:
im.putpixel((x, y), (255, 255, 255))
im.resize((W * 3, H * 3), Image.NEAREST).save(os.path.join(MAP, 'map.png'))
print('district map written')

88
tools/mbfacings.py Normal file
View File

@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Top-down vehicle renders -> 16-facing rotation sheets.
flux renders each vehicle facing NORTH from directly above; PIL rotates into 16
frames (OpenRA facing order: north first, counter-clockwise), packed into one
horizontal strip with FrameSize metadata. Geometrically correct, unlike rotating
a 3/4-view sprite. Also: terrain tile + wax crystal density sheet.
"""
import os, sys
sys.path.insert(0, os.path.dirname(__file__))
from mbgen import run, upload # noqa: E402
from PIL import Image # noqa: E402
from PIL.PngImagePlugin import PngInfo # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), '..')
RENDERS = os.path.join(ROOT, 'assets_src', 'renders_topdown')
ASSETS = os.path.join(ROOT, 'mods', 'vinylgod', 'sequences', 'assets')
os.makedirs(RENDERS, exist_ok=True)
MRP = 'rusty junkyard robot aesthetic, rust metal with hot pink trim, '
STR = 'glossy seamless white tech aesthetic, soft blue ring lights, '
SUF = ', viewed from directly above, top-down aerial view, vehicle pointing straight up toward the top of the image, single object centered, video game sprite, plain flat grey background, no text, no shadow'
VEHICLES = [
('digger', 26, MRP + 'forklift harvester truck carrying crates of vinyl records'),
('popupvan', 26, MRP + 'delivery truck covered in band stickers with roof loudspeaker'),
('spinner', 20, MRP + 'small round robot riding a single vinyl record wheel with blade arms'),
('stacktank', 30, MRP + 'heavy tracked tank made of stacked speaker cabinets with subwoofer turret'),
('basscannon', 30, MRP + 'long six-wheeled flatbed truck with one colossal subwoofer cannon'),
('streamvan', 26, STR + 'sleek white autonomous delivery van with blue play-button roof logo'),
('scraper', 24, STR + 'white autonomous street sweeper vehicle with glowing blue front intake'),
('drmwalker', 30, STR + 'white quad-legged mech with one blue ring light, seen from above'),
]
def sheet16(im, size, out):
im = im.crop(im.getbbox() or (0, 0, im.width, im.height))
im.thumbnail((size * 4, size * 4), Image.LANCZOS) # rotate at 4x, downscale after
strip = Image.new('RGBA', (size * 16, size), (0, 0, 0, 0))
for i in range(16):
fr = im.rotate(i * 22.5, expand=False, resample=Image.BICUBIC)
fr.thumbnail((size, size), Image.LANCZOS)
strip.paste(fr, (i * size + (size - fr.width) // 2, (size - fr.height) // 2), fr)
meta = PngInfo()
meta.add_text('FrameSize', f'{size},{size}')
strip.save(out, pnginfo=meta)
print(f'[facings] {out}', flush=True)
for name, size, prompt in VEHICLES:
raw = os.path.join(RENDERS, f'{name}_raw.png')
cut = os.path.join(RENDERS, f'{name}.png')
if not os.path.exists(cut):
run('flux_local', {'prompt': prompt + SUF}, raw)
run('bg_remove_local', {}, cut, asset_id=upload(raw))
sheet16(Image.open(cut).convert('RGBA'), size, os.path.join(ASSETS, f'{name}.png'))
# terrain tile: seamless ground texture -> 32x32
traw = os.path.join(RENDERS, 'terrain_raw.png')
if not os.path.exists(traw):
run('flux_local', {'prompt': 'seamless tileable texture, cracked dark asphalt with faint dust and tiny speckles, muted grey brown, flat even lighting, top down, no objects'}, traw)
t = Image.open(traw).convert('RGBA').resize((64, 64), Image.LANCZOS).crop((16, 16, 48, 48))
t.save(os.path.join(ROOT, 'mods', 'vinylgod', 'tilesets', 'vinylgod', 'vinylgod.png'))
print('[terrain] tile written', flush=True)
# wax crystals: one cluster render -> 12 density frames
wraw = os.path.join(RENDERS, 'wax_raw.png')
wcut = os.path.join(RENDERS, 'wax.png')
if not os.path.exists(wcut):
run('flux_local', {'prompt': 'cluster of jagged black vinyl crystal shards glowing hot pink from the cracks, viewed from directly above, top down, video game resource sprite, single cluster centered, plain flat grey background'}, wraw)
run('bg_remove_local', {}, wcut, asset_id=upload(wraw))
w = Image.open(wcut).convert('RGBA')
w = w.crop(w.getbbox() or (0, 0, w.width, w.height))
strip = Image.new('RGBA', (32 * 12, 32), (0, 0, 0, 0))
spots = [(16, 16), (7, 9), (24, 10), (9, 24), (24, 24), (16, 6), (5, 17), (27, 18), (16, 26), (11, 13), (21, 15), (16, 20)]
for f in range(12):
for i in range(f + 1):
s = 10 + (6 if i == 0 else 0)
c = w.copy()
c.thumbnail((s, s), Image.LANCZOS)
c = c.rotate(i * 47, expand=False)
x, y = spots[i]
strip.paste(c, (f * 32 + x - c.width // 2, y - c.height // 2), c)
meta = PngInfo()
meta.add_text('FrameSize', '32,32')
strip.save(os.path.join(ASSETS, 'wax.png'), pnginfo=meta)
print('[wax] density sheet written', flush=True)
print('ALL DONE')