KAZIK/tools/generators/make_hell_level.py
Игорь Федосеев ce5121debd feat: add native hell expedition
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.
2026-11-01 22:29:09 +03:00

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')