"""Extract map data: headers, block layouts, objects, warps, signs. Sources: data/maps/headers/.asm -> map_header (tileset), connection directives data/maps/objects/.asm -> border block, warp/bg/object events maps/.blk -> width*height block indices data/maps/names.asm -> display names (town map names, where mapped) constants/map_constants.asm -> dimensions (parsed by constants.py) Output: data/generated/maps.lua Coordinates are in 16x16 "walk grid" cells, matching the macros' arguments. """ import os import re from . import util from .util import parse_number, read_asm, split_args, warn def parse_header(path): hdr = {"connections": {}} for lineno, line in read_asm(path): s = line.strip() m = re.match(r"map_header\s+(\w+),\s*(\w+),\s*(\w+)", s) if m: hdr["label"] = m.group(1) hdr["const"] = m.group(2) hdr["tileset"] = m.group(3) hdr["line"] = lineno continue m = re.match(r"connection\s+(\w+),\s*(\w+),\s*(\w+),\s*(-?[\w$%]+)", s) if m: hdr["connections"][m.group(1)] = { "map": m.group(3), "offset": parse_number(m.group(4)), } return hdr def parse_objects(path): """Parse a data/maps/objects/*.asm file.""" out = {"warps": [], "signs": [], "objects": [], "borderBlock": 0, "objectNames": []} obj_index = 0 for lineno, line in read_asm(path): s = line.strip() m = re.match(r"const_export\s+(\w+)$", s) if m: out["objectNames"].append(m.group(1)) continue m = re.match(r"db\s+(\$?[0-9a-fA-F]+)$", s) if m: out["borderBlock"] = parse_number(m.group(1)) continue m = re.match(r"warp_event\s+(.*)$", s) if m: a = split_args(m.group(1)) out["warps"].append({ "x": parse_number(a[0]), "y": parse_number(a[1]), "destMap": a[2], "destWarp": parse_number(a[3]), }) continue m = re.match(r"bg_event\s+(.*)$", s) if m: a = split_args(m.group(1)) out["signs"].append({ "x": parse_number(a[0]), "y": parse_number(a[1]), "text": a[2], }) continue m = re.match(r"object_event\s+(.*)$", s) if m: a = split_args(m.group(1)) obj_index += 1 obj = { "index": obj_index, "x": parse_number(a[0]), "y": parse_number(a[1]), "sprite": a[2], "movement": a[3], # STAY / WALK "range": a[4], # facing (STAY) or roam range (WALK) "text": a[5], } # Extra args: trainers carry (OPP_class, party), static wild # Pokémon carry (species, level), items carry (item). if len(a) == 8: if a[6].startswith("OPP_"): obj["trainerClass"] = a[6] obj["trainerParty"] = parse_number(a[7]) if re.match(r"^[\d$%]", a[7]) else a[7] else: obj["pokemon"] = a[6] obj["level"] = parse_number(a[7]) elif len(a) == 7: obj["item"] = a[6] out["objects"].append(obj) continue return out def read_blk(path, width, height, border_block): with open(path, "rb") as f: raw = f.read() if len(raw) < width * height: # e.g. UndergroundPathNorthSouth.blk is 92 bytes for a 4x24 map; the # original ROM reads past the file into whatever data follows it. warn(f"{path}: expected {width * height} blocks, got {len(raw)}; padding with border block") raw = raw + bytes([border_block]) * (width * height - len(raw)) elif len(raw) > width * height: util.die(f"{path}: expected {width * height} blocks, got {len(raw)}") return list(raw) def parse_toggleable_objects(pokered): """data/maps/toggleable_objects.asm: initial ON/OFF state per object. Objects marked OFF exist in the object list but start hidden (e.g. Oak in his lab, cuttable trees' post-cut states...). """ path = os.path.join(pokered, "data/maps/toggleable_objects.asm") states = {} current = None for lineno, line in read_asm(path): s = line.strip() m = re.match(r"toggleable_objects_for\s+(\w+)$", s) if m: current = m.group(1) states[current] = {} continue m = re.match(r"toggle_object_state\s+(\w+),\s*(ON|OFF)$", s) if m and current: states[current][m.group(1)] = m.group(2) return states def parse_blocks_files(pokered): """maps.asm: `