Crusader_Decomp/tools/render_all_maps.py

121 lines
4.3 KiB
Python
Raw Normal View History

from __future__ import annotations
import argparse
import os
import struct
import subprocess
import sys
from pathlib import Path
if __package__ in (None, ""):
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from tools.crusader_map.formats import collect_render_items, load_globs, load_map_items, load_typeflags
def get_map_count(fixed_dat: Path) -> int:
data = fixed_dat.read_bytes()
return struct.unpack_from("<H", data, 0x54)[0]
def has_world_rect(extra_args: list[str]) -> bool:
return "--world-rect" in extra_args
def render_game(repo_root: Path, python_exe: str, game: str, start: int | None, end: int | None, extra_args: list[str]) -> int:
out_dir = repo_root / "out" / game
out_dir.mkdir(parents=True, exist_ok=True)
if game == "regret":
static_dir = repo_root / "STATIC_REGRET"
else:
static_dir = repo_root / "STATIC"
fixed_dat = static_dir / "FIXED.DAT"
if not fixed_dat.exists():
print(f"Missing {fixed_dat}", file=sys.stderr)
return 1
map_count = get_map_count(fixed_dat)
shape_infos = load_typeflags(static_dir / "TYPEFLAG.DAT")
globs = load_globs(static_dir / "GLOB.FLX")
batch_max_items = int(os.environ.get("BATCH_MAX_ITEMS", "0"))
world_rect_requested = has_world_rect(extra_args)
start_index = 0 if start is None else max(0, start)
end_index = map_count - 1 if end is None else min(end, map_count - 1)
if start_index > end_index:
print(f"Invalid map range {start_index}..{end_index} for {game} ({map_count} maps)", file=sys.stderr)
return 1
print(f"Rendering {game} maps {start_index}..{end_index} into {out_dir}")
failed = False
script_path = repo_root / "tools" / "render_crusader_map.py"
for map_index in range(start_index, end_index + 1):
print(f"[{game}] Rendering map {map_index}...")
output_png = out_dir / f"map-{map_index}.png"
output_json = out_dir / f"map-{map_index}.json"
base_items = load_map_items(fixed_dat, map_index)
render_items = collect_render_items(
base_items,
shape_infos,
globs,
include_editor=True,
expand_globs=True,
world_rect=None,
include_roofs=False,
include_hidden_markers=True,
)
if not render_items:
print(f"[{game}] Skipping empty map {map_index}.")
output_png.unlink(missing_ok=True)
output_json.unlink(missing_ok=True)
continue
if batch_max_items > 0 and not world_rect_requested and len(render_items) > batch_max_items:
print(
f"[{game}] Skipping map {map_index}: {len(render_items)} render items exceed batch threshold {batch_max_items}. "
"Set BATCH_MAX_ITEMS=0 to disable or use RENDER_ARGS=--world-rect ... for bounded runs.",
file=sys.stderr,
)
output_png.unlink(missing_ok=True)
output_json.unlink(missing_ok=True)
continue
command = [
python_exe,
str(script_path),
"--game",
game,
"--map",
str(map_index),
"--output",
str(output_png),
"--metadata",
str(output_json),
*extra_args,
]
result = subprocess.run(command, cwd=repo_root)
if result.returncode != 0:
print(f"[{game}] Map {map_index} failed.", file=sys.stderr)
failed = True
return 1 if failed else 0
def main() -> int:
parser = argparse.ArgumentParser(description="Render every Crusader fixed map for one or both games.")
parser.add_argument("--game", choices=("remorse", "regret", "all"), required=True)
parser.add_argument("--start", type=int, help="Optional starting map index.")
parser.add_argument("--end", type=int, help="Optional ending map index.")
args, extra_args = parser.parse_known_args()
repo_root = Path(__file__).resolve().parents[1]
python_exe = os.environ.get("PYTHON_EXE") or sys.executable
games = [args.game] if args.game != "all" else ["remorse", "regret"]
exit_code = 0
for game in games:
exit_code |= render_game(repo_root, python_exe, game, args.start, args.end, extra_args)
return exit_code
if __name__ == "__main__":
raise SystemExit(main())