#!/usr/bin/env python3 import re import sys import yaml import argparse from pathlib import Path from typing import List, Dict from io import TextIOWrapper class ItemEntry: def __init__(self, entry): self.name = list(entry.keys())[0] data = entry[self.name] self.category = data.get("category", "") self.nameMsg = data.get("nameMsg", "MSG_NONE") self.fullDescMsg = data.get("fullDescMsg", "MSG_NONE") self.shortDescMsg = data.get("shortDescMsg", "MSG_NONE") self.hudElem = data.get("hudElement", "Item_Present") self.itemEntity = data.get("itemEntity", "Placeholder") self.icon = data.get("icon", "key/Gift") self.sellValue = data.get("sellValue", -1) self.sortValue = data.get("sortValue", 0) self.targetFlags = data.get("targetFlags", []) self.typeFlags = data.get("typeFlags", []) self.moveID = data.get("moveID", "MOVE_NONE") self.potencyA = data.get("potencyA", 0) self.potencyB = data.get("potencyB", 0) self.icon = re.sub("\\W", "_", self.icon) def read_items_yaml(in_yaml: Path) -> List[ItemEntry]: items: List[ItemEntry] = [] with open(in_yaml) as f: entry_list = yaml.load(f.read(), Loader=yaml.SafeLoader) for entry in entry_list: items.append(ItemEntry(entry)) return items def generate_item_table(fout: TextIOWrapper, items: List[ItemEntry], hs_map: Dict[str, int]): fout.write("ItemData gItemTable[] = {\n") def join_flags(flags: List[str]) -> str: if len(flags) > 0: joined = " | ".join(flags) else: joined = "0" return joined for idx, item in enumerate(items): hud_elem_id = hs_map.get(item.hudElem, None) if hud_elem_id == None: raise Exception(f"Item {item.name} requires undefined HudScript: {item.hudElem}") if item.hudElem.startswith("HES_"): hud_elem_str = item.hudElem else: hud_elem_str = "HES_Item_" + item.hudElem fout.write(f" {{ // 0x{idx:03X}: {item.name}\n") fout.write(f" .nameMsg = {item.nameMsg},\n") fout.write(f" .fullDescMsg = {item.fullDescMsg},\n") fout.write(f" .shortDescMsg = {item.shortDescMsg},\n") fout.write(f" .hudElemID = {hud_elem_id}, // {hud_elem_str}\n") fout.write(f" .sellValue = {item.sellValue},\n") fout.write(f" .sortValue = {item.sortValue},\n") fout.write(f" .targetFlags = {join_flags(item.targetFlags)},\n") fout.write(f" .typeFlags = {join_flags(item.typeFlags)},\n") fout.write(f" .moveID = {item.moveID},\n") fout.write(f" .potencyA = {item.potencyA},\n") fout.write(f" .potencyB = {item.potencyB},\n") fout.write(f" }},\n") fout.write("};\n") fout.write("\n") def generate_item_enum(fout: TextIOWrapper, items: List[ItemEntry]): fout.write("#ifndef ITEM_ENUM_H\n") fout.write("#define ITEM_ENUM_H\n") fout.write("\n") fout.write("enum ItemIDs {\n") item_enum: List[str] = [] for idx, item in enumerate(items): # FireFlower -> FIRE_FLOWER # POWBlock -> POW_BLOCK # MagicalSeed1 -> MAGICAL_SEED1 # UnusedLetter_4 -> UNUSED_LETTER_4 # etc name = "ITEM_" + re.sub("((?<=[a-z0-9])[A-Z]|(?!^)(?= ITEM_FIRST_KEY && itemID <= ITEM_LAST_CONSUMABLE)\n") fout.write("#define IS_BADGE(itemID) (itemID >= ITEM_FIRST_BADGE && itemID <= ITEM_LAST_BADGE)\n") fout.write("\n") fout.write("#endif // ITEM_ENUM_H\n") class ItemHudScriptEntry: def __init__(self, script): self.name = script.get("name", None) self.template = script.get("template", None) self.icon = script.get("icon", "") priority_str = script.get("priority", "auto") self.pair = script.get("pair", False) if self.name == None: raise Exception(f"ItemHudScript is missing 'name'") self.extern = self.name.startswith("HES_") if self.extern: self.full_name = self.name else: self.full_name = "HES_Item_" + self.name if self.template == None and not self.extern: raise Exception(f"{self.name} is missing 'template'") self.icon = re.sub("\\W", "_", self.icon) if str(priority_str).lower() == "auto": self.priority = sys.maxsize else: self.priority = int(priority_str) def read_hud_scripts_yaml(in_yaml: Path) -> List[ItemHudScriptEntry]: scripts: List[ItemHudScriptEntry] = [] with open(in_yaml) as f: entry_list = yaml.load(f.read(), Loader=yaml.SafeLoader) for entry in entry_list: scripts.append(ItemHudScriptEntry(entry)) return scripts def generate_hud_scripts(fout: TextIOWrapper, hud_scripts: List[ItemHudScriptEntry]): for entry in hud_scripts: # do not generate scripts for entries defined externally if entry.extern: continue fout.write(f"HudScript {entry.full_name} = HES_TEMPLATE_{entry.template}({entry.icon});\n") if entry.pair: fout.write( f"HudScript {entry.full_name}_disabled = HES_TEMPLATE_{entry.template}({entry.icon}_disabled);\n" ) # there is no item for these scripts, just generate them after the coin script if entry.name == "Coin": fout.write("HudScript HES_Item_CoinSparkleA = HES_COIN_SPARKLE(-8, 0);\n") fout.write("HudScript HES_Item_CoinSparkleB = HES_COIN_SPARKLE( 8, 8);\n") fout.write("HudScript HES_Item_CoinSparkleC = HES_COIN_SPARKLE(-4, 8);\n") fout.write("HudScript HES_Item_CoinSparkleD = HES_COIN_SPARKLE( 6, -6);\n") fout.write("HudScript HES_Item_CoinSparkleE = HES_COIN_SPARKLE( 1, -8);\n") fout.write("HudScript HES_Item_CoinSparkleRandom = HES_TEMPLATE_COIN_SPARKLE();\n") elif entry.name == "StarPoint": fout.write("HudScript HES_SmallStarPoint = HES_TEMPLATE_SMALL_STAR_POINT();\n") fout.write("\n") fout.write("s32 pad_after_item_hudscripts[] = { 0, 0 };\n") fout.write("\n") def generate_hud_scripts_table(fout: TextIOWrapper, hud_scripts: List[ItemHudScriptEntry]): for entry in hud_scripts: if entry.extern: fout.write(f"extern HudScript {entry.full_name};\n") if entry.pair: fout.write(f"extern HudScript {entry.full_name}_disabled;\n") fout.write("\n") fout.write("IconHudScriptPair gItemHudScripts[] = {\n") fout.write(" { .enabled = NULL, .disabled = NULL },\n") # array index 0 is always NULL for entry in hud_scripts: if entry.pair: fout.write(f" {{ .enabled = &{entry.full_name}, .disabled = &{entry.full_name}_disabled }},\n") else: fout.write(f" {{ .enabled = &{entry.full_name}, .disabled = &{entry.full_name} }},\n") fout.write("};\n") fout.write("\n") def generate_item_entity_scripts(fout: TextIOWrapper, ies_yaml: Path): with open(ies_yaml) as f: scripts = yaml.load(f.read(), Loader=yaml.SafeLoader) for script in scripts: name = script.get("name", None) template = script.get("template", None) icon = script.get("icon", None) if name is None: raise Exception("Item entity script is missing 'name'") if template is None: raise Exception(f"{name} is missing 'template'") if icon is None: icon = "" else: icon = re.sub("\\W", "_", icon) fout.write(f"ItemScript IES_{name} = IES_TEMPLATE_{template}({icon});\n") fout.write("\n") def generate_item_entity_scripts_table(fout: TextIOWrapper, items: List[ItemEntry]): fout.write("// indexed by itemID\n") fout.write("s32* gItemEntityScripts[] = {\n") idx = 0 for item in items: fout.write(f" IES_{item.itemEntity},\n") idx += 1 # must pad with IES_Placeholder to match while idx <= 0x180: fout.write(f" IES_{items[0].itemEntity},\n") idx += 1 fout.write("};\n") fout.write("\n") def generate_item_icon_tables(fout: TextIOWrapper, items: List[ItemEntry]): # note: DUMMY items have no icon rasters or palettes fout.write("// indexed by itemID\n") fout.write("s32 gItemIconRasterOffsets[] = {\n") for item in items: if item.category != "DUMMY": fout.write(f" ICON_{item.icon}_raster,\n") fout.write("};\n") fout.write("\n") fout.write("// indexed by itemID\n") fout.write("s32 gItemIconPaletteOffsets[] = {\n") for item in items: if item.category != "DUMMY": fout.write(f" ICON_{item.icon}_palette,\n") fout.write("};\n") fout.write("\n") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generates item table") parser.add_argument("out_data", help="output header file to generate containing the data") parser.add_argument("out_enum", help="output header file to generate containing the enum") parser.add_argument("items_yaml", type=Path, help="input yaml file path") parser.add_argument("ies_yaml", type=Path, help="input yaml file path for item entity scripts") parser.add_argument("hes_yaml", type=Path, help="input yaml file path for item hud element scripts") args = parser.parse_args() items = read_items_yaml(args.items_yaml) hud_scripts = read_hud_scripts_yaml(args.hes_yaml) # sort items by category CATEGORY_ORDER = { "NONE": 0, "GEAR": 1, "QUEST": 2, "KEY": 3, "CONSUMABLE": 4, "UNUSED": 5, "BADGE": 6, "UTILITY": 7, "DUMMY": 1000, # must always be at the end of the item table } items.sort(key=lambda x: CATEGORY_ORDER.get(x.category, 999)) with open(args.out_data, "w") as fout: fout.write("/* This file is auto-generated. Do not edit. */\n") fout.write('#include "common.h"\n') fout.write('#include "message_ids.h"\n') fout.write('#include "hud_element.h"\n') fout.write('#include "item_entity.h"\n') fout.write('#include "icon_offsets.h"\n') fout.write("\n") generate_hud_scripts(fout, hud_scripts) # after writing the hud script sources, sort them according to table order hud_scripts.sort(key=lambda x: x.priority) # get hudElemID for each hud element hs_map: Dict[str, int] = {} for idx, hs in enumerate(hud_scripts): hs_map[hs.name] = idx + 1 generate_item_table(fout, items, hs_map) generate_hud_scripts_table(fout, hud_scripts) generate_item_entity_scripts(fout, args.ies_yaml) generate_item_entity_scripts_table(fout, items) generate_item_icon_tables(fout, items) with open(args.out_enum, "w") as fout: fout.write("/* This file is auto-generated. Do not edit. */\n") fout.write("\n") generate_item_enum(fout, items)