papermario/tools/build/item_data.py

342 lines
12 KiB
Python
Raw Permalink Normal View History

#!/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]|(?!^)(?<!_)[A-Z](?=[a-z]))", r"_\1", item.name).upper()
item_enum.append(name)
fout.write(f" {name:39} = 0x{idx:03X},\n")
fout.write("};\n")
fout.write("\n")
# determine itemID ranges for each category
min_cat: Dict[str, int] = {}
max_cat: Dict[str, int] = {}
for idx, item in enumerate(items):
cat = item.category
if cat not in min_cat:
min_cat[cat] = idx
max_cat[cat] = idx
fout.write(f"#define ITEM_FIRST_KEY {item_enum[min_cat['KEY']]}\n")
fout.write(f"#define ITEM_LAST_KEY {item_enum[max_cat['KEY']]}\n")
fout.write(f"#define ITEM_FIRST_CONSUMABLE {item_enum[min_cat['CONSUMABLE']]}\n")
fout.write(f"#define ITEM_LAST_CONSUMABLE {item_enum[max_cat['CONSUMABLE']]}\n")
fout.write(f"#define ITEM_FIRST_BADGE {item_enum[min_cat['BADGE']]}\n")
fout.write(f"#define ITEM_LAST_BADGE {item_enum[max_cat['BADGE']]}\n")
fout.write("\n")
fout.write("#define ITEM_NUM_KEYS (ITEM_LAST_KEY - ITEM_FIRST_KEY + 1)\n")
fout.write("#define ITEM_NUM_CONSUMABLES (ITEM_LAST_CONSUMABLE - ITEM_FIRST_CONSUMABLE + 1)\n")
fout.write("\n")
fout.write("#define IS_ITEM(itemID) (itemID >= 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)