#!/usr/bin/env python3 """Regenerate the Relic portion of InventoryItemConfig from game tables. Pass a directory containing RelicConfig.json, ItemConfigRelic.json, RelicMainAffixConfig.json, and RelicSubAffixConfig.json from the revision recorded in InventoryItemConfig.json. Existing item and Light Cone entries are preserved; only visible BASIC Relic types from ItemConfigRelic are included. """ from __future__ import annotations import argparse import json from pathlib import Path from typing import Any TABLES = ( "RelicConfig.json", "ItemConfigRelic.json", "RelicMainAffixConfig.json", "RelicSubAffixConfig.json", ) def read_rows(path: Path) -> list[dict[str, Any]]: with path.open("r", encoding="utf-8") as source: rows = json.load(source) if not isinstance(rows, list): raise ValueError(f"{path.name} must contain a JSON array") return rows def build_relic_catalog(source_dir: Path) -> list[dict[str, Any]]: relic_rows = read_rows(source_dir / "RelicConfig.json") item_rows = read_rows(source_dir / "ItemConfigRelic.json") main_rows = read_rows(source_dir / "RelicMainAffixConfig.json") sub_rows = read_rows(source_dir / "RelicSubAffixConfig.json") visible_item_ids = { int(row["ID"]) for row in item_rows if row.get("ItemMainType") == "Relic" and row.get("isVisible") is True } main_by_group: dict[int, list[dict[str, Any]]] = {} for row in main_rows: main_by_group.setdefault(int(row["GroupID"]), []).append(row) sub_by_group: dict[int, list[dict[str, Any]]] = {} for row in sub_rows: sub_by_group.setdefault(int(row["GroupID"]), []).append(row) for rows in (*main_by_group.values(), *sub_by_group.values()): rows.sort(key=lambda row: int(row["AffixID"])) catalog: list[dict[str, Any]] = [] seen: set[int] = set() for relic in sorted(relic_rows, key=lambda row: int(row["ID"])): relic_id = int(relic["ID"]) if relic.get("Mode") != "BASIC" or relic_id not in visible_item_ids: continue if relic_id in seen: raise ValueError(f"duplicate Relic ID {relic_id}") main_options = main_by_group.get(int(relic["MainAffixGroup"]), []) if not main_options: raise ValueError(f"Relic {relic_id} has no MainAffixGroup rows") main = main_options[0] sub_options = [ row for row in sub_by_group.get(int(relic["SubAffixGroup"]), []) if row.get("Property") != main.get("Property") ] if len(sub_options) < 4: raise ValueError(f"Relic {relic_id} has fewer than four valid sub-affixes") catalog.append( { "relic_id": relic_id, "relic_set_id": int(relic["SetID"]), "main_affix_id": int(main["AffixID"]), "sub_affixes": [ { "sub_affix_id": int(row["AffixID"]), "count": 1, "step": 0, } for row in sub_options[:4] ], } ) seen.add(relic_id) if not catalog: raise ValueError("no visible BASIC Relics were found in the supplied tables") return catalog def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--source-dir", type=Path, required=True) parser.add_argument("--inventory-config", type=Path, required=True) args = parser.parse_args() missing = [name for name in TABLES if not (args.source_dir / name).is_file()] if missing: raise SystemExit("Missing source tables: " + ", ".join(missing)) with args.inventory_config.open("r", encoding="utf-8") as source: resource = json.load(source) resource["relics"] = build_relic_catalog(args.source_dir) with args.inventory_config.open("w", encoding="utf-8", newline="\n") as output: json.dump(resource, output, ensure_ascii=False, indent=2) output.write("\n") print(f"Wrote {len(resource['relics'])} Client-configured Relic templates") if __name__ == "__main__": main()