Add a painting entrance, Freedoom combat and Doom-style HUD. Persist the one-time clear reward in signed saves without changing file sizes. Include native regression checks and synchronized game/launcher builds.
96 lines
3.8 KiB
Python
96 lines
3.8 KiB
Python
"""Compile the authored 32x32 hell map and validate progression offline."""
|
|
from pathlib import Path
|
|
from collections import deque
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
grid = [[185 for _ in range(32)] for _ in range(32)]
|
|
|
|
|
|
def room(x0,y0,x1,y1):
|
|
for y in range(y0,y1+1):
|
|
for x in range(x0,x1+1):
|
|
grid[y][x] = 0
|
|
|
|
|
|
# Winding route through five arenas, with locked final sanctum.
|
|
room(2,23,10,29) # entry
|
|
room(3,14,10,21) # barracks
|
|
room(5,21,6,23)
|
|
room(12,13,21,22) # lava court
|
|
room(10,16,12,17)
|
|
room(13,3,21,10) # key crypt
|
|
room(17,10,18,13)
|
|
room(24,3,29,20) # final approach
|
|
room(21,6,24,7)
|
|
room(23,23,29,29) # boss
|
|
room(26,20,27,23)
|
|
# Pillars and cover; enough width for movement and projectile dodging.
|
|
for x,y in [(5,17),(8,18),(15,16),(19,19),(16,6),(19,7),(26,10),(27,16),(25,26)]:
|
|
grid[y][x] = 186
|
|
# Two discoverable secret doors.
|
|
room(2,7,8,11)
|
|
room(5,11,6,14)
|
|
room(24,23,25,23)
|
|
room(18,25,21,28)
|
|
room(21,26,23,27)
|
|
doors = [(5,12,0),(6,12,0),(22,26,1),(22,27,1),(22,6,2),(22,7,2)]
|
|
for x,y,_ in doors:
|
|
grid[y][x] = 188
|
|
for y in range(1,31):
|
|
for x in range(1,31):
|
|
if grid[y][x] == 185:
|
|
if x >= 23 and y >= 22:
|
|
grid[y][x] = 190 # infernal sanctum
|
|
elif x >= 23:
|
|
grid[y][x] = 187 # flesh-lined approach
|
|
elif x >= 12 and y <= 11:
|
|
grid[y][x] = 186 # crypt stone
|
|
|
|
# kind: 0 soldier, 1 melee, 2 imp, 3 boss. Coordinates at tile centres.
|
|
enemies = [(7,25,0),(8,28,0),(4,20,0),(8,16,1),(9,20,2),(4,15,0),
|
|
(14,20,2),(20,21,0),(20,14,2),(13,14,1),(18,17,1),(15,18,0),
|
|
(14,8,2),(20,4,2),(15,4,0),(20,9,1),(18,5,0),
|
|
(25,5,0),(28,8,2),(25,12,1),(28,15,0),(25,19,2),(28,19,1),
|
|
(24,25,2),(28,25,2),(27,28,3),(4,9,1),(7,8,0)]
|
|
# item kinds correspond to items.bin: health, armour, bullets, shells, shotgun, key, secret.
|
|
items = [(3,27,4),(4,28,3),(9,24,2),(3,19,0),(8,14,3),(9,15,2),
|
|
(13,21,1),(14,14,3),(20,20,0),(19,14,2),(14,4,0),(20,3,5),
|
|
(17,9,3),(25,4,2),(28,11,0),(25,17,3),(28,20,3),
|
|
(24,24,0),(29,28,3),(24,28,2),(3,8,1),(7,10,3),(19,26,0),(20,27,3)]
|
|
for x,y,_ in enemies+items:
|
|
assert grid[y][x] == 0, (x,y)
|
|
|
|
|
|
def reachable(open_groups=()):
|
|
seen = {(3,26)}
|
|
todo = deque(seen)
|
|
while todo:
|
|
x,y = todo.popleft()
|
|
for nx,ny in [(x-1,y),(x+1,y),(x,y-1),(x,y+1)]:
|
|
if (nx,ny) in seen or not (0<=nx<32 and 0<=ny<32):
|
|
continue
|
|
if grid[ny][nx] and (nx,ny) not in [(x,y) for x,y,group in doors if group in open_groups]:
|
|
continue
|
|
seen.add((nx,ny))
|
|
todo.append((nx,ny))
|
|
return seen
|
|
|
|
|
|
assert (20,3) in reachable() and (27,28) not in reachable()
|
|
assert (27,28) not in reachable((0,1)), 'Secrets must not bypass the key gate'
|
|
assert (27,28) in reachable((2,)), 'Secrets must remain optional'
|
|
assert all((x,y) in reachable((0,1,2)) for x,y,_ in enemies+items)
|
|
# Conservative ammunition budget: half the pellets land at minimum damage.
|
|
bullets = 80 + 40*sum(kind == 2 for _,_,kind in items)
|
|
shells = 16 + 12*sum(kind == 3 for _,_,kind in items)
|
|
enemy_hp = sum((55,105,85,1100)[kind] for _,_,kind in enemies)
|
|
assert (bullets + shells*7)*8//2 > enemy_hp
|
|
lines = ['; Generated by tools/generators/make_hell_level.py.','.data','hfMapTemplate label byte']
|
|
lines += [' db '+','.join(map(str,row)) for row in grid]
|
|
lines += ['hfEnemySeeds label dword']
|
|
lines += [f' dd {x}*FP_1+FP_HALF,{y}*FP_1+FP_HALF,{kind}' for x,y,kind in enemies]
|
|
lines += [f'HF_ENEMIES equ {len(enemies)}','hfItemSeeds label dword']
|
|
lines += [f' dd {x}*FP_1+FP_HALF,{y}*FP_1+FP_HALF,{kind}' for x,y,kind in items]
|
|
lines += [f'HF_ITEMS equ {len(items)}','.code']
|
|
(ROOT/'src_graphics/hell_fps_map.inc').write_text('\n'.join(lines)+'\n',encoding='ascii')
|
|
print(f'Hell map: {len(enemies)} enemies, {len(items)} pickups, key gate and two secrets validated')
|