pokeplatinum/tools/scripts/migration/species_data.py
Rachel 2c410b8046 Replace python scripts for packing species archives with C++
This new code is responsible for packing the following archives:

- `pl_personal` -> basic information for each species: stats, types, etc.
- `evo` -> evolution lines for each species
- `wotbl` -> by-level learnsets for each species
- `ppark` -> catching show data for each species
- `height` -> y-offsets for front and back sprites for each species
- `pl_poke_data` -> sprite-rendering data for each species: animation
  ID, frame data, shadow size and offsets, etc.

Additionally, the following headers are generated:

- `res/pokemon/tutorable_moves.h` -> A listing of moves taught by each
  tutor and how much each move costs to be tutored
- `res/pokemon/species_learnsets_by_tutor.h` -> An array of bitmasks for
  each species designating which moves can be tutored to that species
2025-01-21 22:46:39 -08:00

131 lines
4.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""
This is a standalone script for existing end-users to migrate their data
structures to the new species_data format expected by datagen_species.cpp.
New users and users who have not made any changes to species' data.json
files should not need to use it. Any user which *has* made changes to these
files can accept their copy during a merge from main, then run this script
to update all of their files in bulk.
"""
import json
import pathlib
class SpeciesJSONEncoder(json.JSONEncoder):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.indentation_level = 0
def encode(self, o):
if isinstance(o, (list, tuple)):
if self._is_inlinable(o):
return f"[ {', '.join(json.dumps(el) for el in o)} ]"
else:
self.indentation_level += 1
output = [f"{self.indent_str}{self.encode(el)}" for el in o]
self.indentation_level -= 1
return f"[\n{',\n'.join(output)}\n{self.indent_str}]"
elif isinstance(o, dict):
self.indentation_level += 1
output = [
f"{self.indent_str}{json.dumps(k)}: {self.encode(v)}"
for k, v in o.items()
]
self.indentation_level -= 1
return f"{{\n{',\n'.join(output)}\n{self.indent_str}}}"
else:
return json.dumps(o)
def _is_inlinable(self, o):
if isinstance(o, (list, tuple)):
return (
not any(isinstance(el, (list, tuple, dict)) for el in o)
and len(o) <= 2
and len(str(o)) - 2 <= 80
)
@property
def indent_str(self) -> str:
if isinstance(self.indent, int):
return " " * (self.indentation_level * self.indent)
elif isinstance(self.indent, str):
return self.indentation_level * self.indent
else:
raise ValueError(
f"indent must be of type int or str (is: {type(self.indent)})"
)
def iterencode(self, o, **kwargs):
return self.encode(o)
def migrate(d: dict) -> dict:
o = {}
o["name"] = d["name"]
o["base_stats"] = d["base_stats"]
o["types"] = d["types"]
o["catch_rate"] = d["catch_rate"]
o["base_exp_reward"] = d["base_exp_reward"]
o["ev_yields"] = d["ev_yields"]
o["held_items"] = d["held_items"]
o["gender_ratio"] = d["gender_ratio"]
o["hatch_cycles"] = d["hatch_cycles"]
o["base_friendship"] = d["base_friendship"]
o["exp_rate"] = d["exp_rate"]
o["egg_groups"] = d["egg_groups"]
o["abilities"] = d["abilities"]
o["safari_flee_rate"] = d.get("safari_flee_rate", d.get("great_marsh_flee_rate"))
o["body_color"] = d["sprite"]["color"]
o["flip_sprite"] = d["sprite"].get("flip", d["sprite"].get("flip_sprite"))
o["learnset"] = {}
learnsets = d["learnset"]
by_level = []
if "level_up" in learnsets: # old dictionary-based structure
for k, v in learnsets["level_up"].items():
if isinstance(v, list):
for mem in v:
by_level.append([int(k), mem])
else:
by_level.append([int(k), v])
else: # newer list-of-tuples structure
by_level = learnsets["by_level"]
o["learnset"]["by_level"] = by_level
o["learnset"]["by_tm"] = learnsets.get("by_tm", learnsets.get("tms"))
if "tutor" in learnsets:
o["learnset"]["by_tutor"] = learnsets["tutor"]
elif "by_tutor" in learnsets:
o["learnset"]["by_tutor"] = learnsets["by_tutor"]
o["evolutions"] = d.get("evolutions", [])
if "footprint" in d:
o["footprint"] = {}
footprint = d["footprint"]
o["footprint"]["has"] = footprint.get("has", footprint.get("has_footprint"))
o["footprint"]["size"] = footprint.get("size", footprint.get("footprint_size"))
if "pokedex_data" in d:
o["pokedex_data"] = d["pokedex_data"]
if "catching_show_data" in d:
o["catching_show"] = d["catching_show_data"]
return o
parent = pathlib.Path("res/pokemon")
for fname in parent.rglob("**/data.json"):
try:
with open(fname, "r", encoding="utf-8") as f:
j = json.load(f)
d = migrate(j)
with open(fname, "w", encoding="utf-8") as f:
json.dump(d, f, cls=SpeciesJSONEncoder, indent=4, ensure_ascii=False)
except KeyError as e:
print(f"Error parsing {fname}")
raise e