Add new modules for Crusader map rendering and processing
- Implemented `formats.py` to define data structures and functions for handling map data, including reading and decoding shape and map items. - Created `png.py` for generating PNG images from shape frames and pixel data. - Developed `sorting.py` to manage the sorting and rendering order of map items based on their properties and spatial relationships. - Introduced `render_all_maps.py` to facilitate the rendering of all maps for specified games, including command-line argument parsing and subprocess management for rendering tasks.
This commit is contained in:
parent
af5b77ea13
commit
82ae89865a
47 changed files with 1602 additions and 1562 deletions
121
tools/render_all_maps.py
Normal file
121
tools/render_all_maps.py
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue