mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
134 lines
4.9 KiB
Python
134 lines
4.9 KiB
Python
"""Extract trainer classes and parties.
|
|
|
|
Sources:
|
|
constants/trainer_constants.asm -> trainer class ids (OPP_*)
|
|
data/trainers/names.asm -> class display names
|
|
data/trainers/parties.asm -> parties per class:
|
|
db level, mon, mon, ..., 0 (all same level)
|
|
db $FF, lvl, mon, lvl, mon, ..., 0 (mixed levels)
|
|
gfx/trainers/*.png -> class battle pics (via gfx/pics.asm)
|
|
|
|
Output:
|
|
data/generated/trainers.lua
|
|
assets/generated/battle/trainers/*.png
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
|
|
from . import gfx, util
|
|
from .util import parse_number, read_asm, split_args, warn
|
|
|
|
|
|
def parse_trainer_consts(pokered):
|
|
path = os.path.join(pokered, "constants/trainer_constants.asm")
|
|
names = []
|
|
for lineno, line in read_asm(path):
|
|
m = re.match(r"trainer_const\s+(\w+)|const\s+(OPP_\w+)", line.strip())
|
|
if m:
|
|
names.append(m.group(1) or m.group(2))
|
|
return names
|
|
|
|
|
|
def parse_parties(pokered):
|
|
"""parties.asm: XData: labels with db rows until next label."""
|
|
path = os.path.join(pokered, "data/trainers/parties.asm")
|
|
order, parties = [], {}
|
|
current = None
|
|
for lineno, line in read_asm(path):
|
|
s = line.strip()
|
|
m = re.match(r"(\w+)Data:{1,2}\s*$", s)
|
|
if m:
|
|
current = m.group(1)
|
|
order.append(current)
|
|
parties[current] = []
|
|
continue
|
|
m = re.match(r"db\s+(.*)$", s)
|
|
if m and current:
|
|
a = split_args(m.group(1))
|
|
if a and a[-1] == "0":
|
|
a = a[:-1]
|
|
if not a:
|
|
continue
|
|
party = []
|
|
if a[0] in ("$FF", "-1"):
|
|
it = iter(a[1:])
|
|
for lvl, mon in zip(it, it):
|
|
party.append({"level": parse_number(lvl), "species": mon})
|
|
else:
|
|
lvl = parse_number(a[0])
|
|
for mon in a[1:]:
|
|
party.append({"level": lvl, "species": mon})
|
|
parties[current].append(party)
|
|
return order, parties
|
|
|
|
|
|
def parse_move_choices(pokered):
|
|
"""move_choices.asm: AI modification layers (1/2/3) per class."""
|
|
mods = []
|
|
for lineno, line in read_asm(os.path.join(pokered, "data/trainers/move_choices.asm")):
|
|
m = re.match(r"move_choices(?:\s+(.*))?$", line.strip())
|
|
if m is not None and not line.strip().startswith("MACRO"):
|
|
args = [int(a) for a in split_args(m.group(1) or "") if a.strip().isdigit()]
|
|
mods.append(args)
|
|
return mods
|
|
|
|
|
|
def extract(pokered, out_dir, assets_dir):
|
|
consts = parse_trainer_consts(pokered)
|
|
consts = [c for c in consts if c and c != "NOBODY"]
|
|
move_choices = parse_move_choices(pokered)
|
|
|
|
names = []
|
|
for lineno, line in read_asm(os.path.join(pokered, "data/trainers/names.asm")):
|
|
m = re.match(r'li\s+"([^"]*)"', line.strip())
|
|
if m:
|
|
names.append(m.group(1).replace("#", "POKé"))
|
|
|
|
order, parties = parse_parties(pokered)
|
|
|
|
# pic labels via gfx/pics.asm: YoungsterPic:: INCBIN "gfx/trainers/youngster.pic"
|
|
pics = {}
|
|
for lineno, line in read_asm(os.path.join(pokered, "gfx/pics.asm")):
|
|
m = re.match(r'(\w+)Pic::?\s+INCBIN\s+"(gfx/trainers/[^"]+)"', line.strip())
|
|
if m:
|
|
pics[m.group(1)] = re.sub(r"\.pic$", ".png", m.group(2))
|
|
|
|
# base prize money: reward = baseMoney * level of last enemy mon
|
|
money = []
|
|
for lineno, line in read_asm(os.path.join(pokered, "data/trainers/pic_pointers_money.asm")):
|
|
m = re.match(r"pic_money\s+\w+,\s*(\d+)", line.strip())
|
|
if m:
|
|
money.append(int(m.group(1)) // 100)
|
|
|
|
out = {}
|
|
for i, label in enumerate(order):
|
|
const = "OPP_" + consts[i] if i < len(consts) else None
|
|
if const is None:
|
|
warn(f"parties.asm: no trainer constant for {label}Data")
|
|
continue
|
|
pic_src = pics.get(label)
|
|
pic_dst = None
|
|
if pic_src:
|
|
base = os.path.splitext(os.path.basename(pic_src))[0]
|
|
pic_dst = f"assets/generated/battle/trainers/{base}.png"
|
|
gfx.convert_png(os.path.join(pokered, pic_src),
|
|
os.path.join(assets_dir, "battle/trainers", base + ".png"),
|
|
transparent_matte=True)
|
|
out[const] = {
|
|
"id": const,
|
|
"index": i + 1,
|
|
"name": names[i] if i < len(names) else label,
|
|
"source": "data/trainers/parties.asm",
|
|
"pic": pic_dst,
|
|
"baseMoney": money[i] if i < len(money) else 0,
|
|
"aiMods": move_choices[i] if i < len(move_choices) else [],
|
|
"parties": parties[label],
|
|
}
|
|
if "OPP_YOUNGSTER" not in out or not out["OPP_YOUNGSTER"]["parties"]:
|
|
util.die("trainer extraction sanity check failed")
|
|
util.write_lua(os.path.join(out_dir, "trainers.lua"), out,
|
|
header="Sources: data/trainers/parties.asm, names.asm; parties indexed 1-based\n"
|
|
"as used by object_event trainer args.")
|
|
return out
|