From d92f23036b5ef011134ea66d08c83dc77ee99d4c Mon Sep 17 00:00:00 2001 From: Rachel Date: Wed, 18 Mar 2026 16:39:03 -0700 Subject: [PATCH] tools: Replace use of datagen_species.cpp with speciesproc.c --- res/pokemon/burmy/data.json | 10 +- res/pokemon/deoxys/forms/defense/data.json | 1 + res/pokemon/gastrodon/data.json | 8 +- res/pokemon/meson.build | 32 +- res/pokemon/move_tutors.json | 578 +++++++------ res/pokemon/shellos/data.json | 8 +- res/pokemon/spiritomb/data.json | 3 +- res/pokemon/unown/data.json | 61 +- src/pokemon_icon.c | 2 +- tools/datagen/datagen_species.cpp | 791 ------------------ tools/datagen/meson.build | 19 - .../data/species_egg_moves.h.template | 15 + .../data/species_footprint_sizes.h.template | 12 + .../data/species_footprint_types.h.template | 16 + .../data/species_icon_palettes.h.template | 59 ++ .../species_learnsets_by_tutor.h.template | 10 + .../dataproc/data/tutorable_moves.h.template | 8 + tools/dataproc/meson.build | 33 + tools/dataproc/src/common.c | 413 +++++++++ tools/dataproc/src/common.h | 122 +++ tools/dataproc/src/enum.c | 273 ++++++ tools/dataproc/src/enum.h | 78 ++ tools/dataproc/src/speciesproc.c | 746 +++++++++++++++++ tools/devtools/gen_compile_commands.py | 3 + 24 files changed, 2157 insertions(+), 1144 deletions(-) delete mode 100644 tools/datagen/datagen_species.cpp create mode 100644 tools/dataproc/data/species_egg_moves.h.template create mode 100644 tools/dataproc/data/species_footprint_sizes.h.template create mode 100644 tools/dataproc/data/species_footprint_types.h.template create mode 100644 tools/dataproc/data/species_icon_palettes.h.template create mode 100644 tools/dataproc/data/species_learnsets_by_tutor.h.template create mode 100644 tools/dataproc/data/tutorable_moves.h.template create mode 100644 tools/dataproc/src/common.c create mode 100644 tools/dataproc/src/common.h create mode 100644 tools/dataproc/src/enum.c create mode 100644 tools/dataproc/src/enum.h create mode 100644 tools/dataproc/src/speciesproc.c diff --git a/res/pokemon/burmy/data.json b/res/pokemon/burmy/data.json index dba1b7a26a..bd48c24629 100644 --- a/res/pokemon/burmy/data.json +++ b/res/pokemon/burmy/data.json @@ -31,11 +31,11 @@ "safari_flee_rate": 0, "body_color": "MON_COLOR_GRAY", "flip_sprite": false, - "icon_palette": { - "base": 1, - "sandy": 1, - "trash": 0 - }, + "icon_palette": [ + [ "base", 1 ], + [ "sandy", 1 ], + [ "trash", 0 ] + ], "learnset": { "by_level": [ [ 1, "MOVE_PROTECT" ], diff --git a/res/pokemon/deoxys/forms/defense/data.json b/res/pokemon/deoxys/forms/defense/data.json index c04ab104a8..3fb44de4f9 100644 --- a/res/pokemon/deoxys/forms/defense/data.json +++ b/res/pokemon/deoxys/forms/defense/data.json @@ -121,6 +121,7 @@ "MOVE_KNOCK_OFF" ] }, + "evolutions": [ ], "pokedex_data": { "height": 0, "weight": 0, diff --git a/res/pokemon/gastrodon/data.json b/res/pokemon/gastrodon/data.json index dd88d184b3..83e9fe2190 100644 --- a/res/pokemon/gastrodon/data.json +++ b/res/pokemon/gastrodon/data.json @@ -31,10 +31,10 @@ "safari_flee_rate": 0, "body_color": "MON_COLOR_PURPLE", "flip_sprite": false, - "icon_palette": { - "base": 0, - "east_sea": 0 - }, + "icon_palette": [ + [ "base", 0 ], + [ "east_sea", 0 ] + ], "learnset": { "by_level": [ [ 1, "MOVE_MUD_SLAP" ], diff --git a/res/pokemon/meson.build b/res/pokemon/meson.build index 1ff4e2b639..938b598bbd 100644 --- a/res/pokemon/meson.build +++ b/res/pokemon/meson.build @@ -182,7 +182,7 @@ species_orders = custom_target('species_orders', pokefoot_order = species_orders[0] -datagen_species_out = custom_target('datagen_species_out', +species_data = custom_target('species_data', output: [ 'pl_personal.narc', 'evo.narc', @@ -198,24 +198,26 @@ datagen_species_out = custom_target('datagen_species_out', 'species_egg_moves.h', 'species_icon_palettes.h', ], + command: [ - datagen_species_exe, - meson.current_build_dir(), + speciesproc_exe, + '-M', '@DEPFILE@', + '-t', files('move_tutors.json'), + '-o', meson.current_build_dir(), pokemon_data_root, - form_registry_json, - files('move_tutors.json'), ], + env: species_env, - depend_files: [ - species_data_files, - ], + depend_files: species_data_files, + depfile: 'species_data.d', ) -h_headers += datagen_species_out[7] -h_headers += datagen_species_out[8] -h_headers += datagen_species_out[9] -h_headers += datagen_species_out[10] -h_headers += datagen_species_out[11] -h_headers += datagen_species_out[12] + +h_headers += species_data[7] +h_headers += species_data[8] +h_headers += species_data[9] +h_headers += species_data[10] +h_headers += species_data[11] +h_headers += species_data[12] # OLD NARCs @@ -410,7 +412,7 @@ pokefoot_narc = custom_target('pokefoot.narc', ) naix_headers += pokefoot_narc[1] -nitrofs_files += datagen_species_out +nitrofs_files += species_data nitrofs_files += pl_poke_icon_narc nitrofs_files += pl_pokegra_narc diff --git a/res/pokemon/move_tutors.json b/res/pokemon/move_tutors.json index 8836b31c1c..3776f59e2d 100644 --- a/res/pokemon/move_tutors.json +++ b/res/pokemon/move_tutors.json @@ -1,274 +1,306 @@ -{ - "static": true, - "const": true, - "type": "TeachableMove", - "name": "sTeachableMoves", - "moves": { - "MOVE_DIVE": { - "redCost": 2, - "blueCost": 4, - "yellowCost": 2, - "greenCost": 0, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_MUD_SLAP": { - "redCost": 4, - "blueCost": 4, - "yellowCost": 0, - "greenCost": 0, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_FURY_CUTTER": { - "redCost": 0, - "blueCost": 8, - "yellowCost": 0, - "greenCost": 0, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_ICY_WIND": { - "redCost": 0, - "blueCost": 6, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_ROLLOUT": { - "redCost": 4, - "blueCost": 2, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_THUNDER_PUNCH": { - "redCost": 2, - "blueCost": 6, - "yellowCost": 0, - "greenCost": 0, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_FIRE_PUNCH": { - "redCost": 2, - "blueCost": 6, - "yellowCost": 0, - "greenCost": 0, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_SUPERPOWER": { - "redCost": 8, - "blueCost": 0, - "yellowCost": 0, - "greenCost": 0, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_ICE_PUNCH": { - "redCost": 2, - "blueCost": 6, - "yellowCost": 0, - "greenCost": 0, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_IRON_HEAD": { - "redCost": 6, - "blueCost": 0, - "yellowCost": 2, - "greenCost": 0, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_AQUA_TAIL": { - "redCost": 6, - "blueCost": 0, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_OMINOUS_WIND": { - "redCost": 0, - "blueCost": 6, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_GASTRO_ACID": { - "redCost": 4, - "blueCost": 0, - "yellowCost": 2, - "greenCost": 2, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_SNORE": { - "redCost": 2, - "blueCost": 0, - "yellowCost": 4, - "greenCost": 2, - "location": "TUTOR_LOCATION_SNOWPOINT_CITY" - }, - "MOVE_SPITE": { - "redCost": 0, - "blueCost": 0, - "yellowCost": 8, - "greenCost": 0, - "location": "TUTOR_LOCATION_SNOWPOINT_CITY" - }, - "MOVE_AIR_CUTTER": { - "redCost": 2, - "blueCost": 4, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_HELPING_HAND": { - "redCost": 2, - "blueCost": 0, - "yellowCost": 4, - "greenCost": 2, - "location": "TUTOR_LOCATION_SNOWPOINT_CITY" - }, - "MOVE_ENDEAVOR": { - "redCost": 4, - "blueCost": 0, - "yellowCost": 4, - "greenCost": 0, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_OUTRAGE": { - "redCost": 6, - "blueCost": 0, - "yellowCost": 2, - "greenCost": 0, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_ANCIENT_POWER": { - "redCost": 6, - "blueCost": 0, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_SYNTHESIS": { - "redCost": 0, - "blueCost": 0, - "yellowCost": 2, - "greenCost": 6, - "location": "TUTOR_LOCATION_SNOWPOINT_CITY" - }, - "MOVE_SIGNAL_BEAM": { - "redCost": 2, - "blueCost": 2, - "yellowCost": 2, - "greenCost": 2, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_ZEN_HEADBUTT": { - "redCost": 0, - "blueCost": 4, - "yellowCost": 4, - "greenCost": 0, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_VACUUM_WAVE": { - "redCost": 2, - "blueCost": 4, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_EARTH_POWER": { - "redCost": 6, - "blueCost": 0, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_GUNK_SHOT": { - "redCost": 4, - "blueCost": 2, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_TWISTER": { - "redCost": 6, - "blueCost": 0, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_SEED_BOMB": { - "redCost": 4, - "blueCost": 0, - "yellowCost": 0, - "greenCost": 4, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_IRON_DEFENSE": { - "redCost": 4, - "blueCost": 2, - "yellowCost": 2, - "greenCost": 0, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_MAGNET_RISE": { - "redCost": 0, - "blueCost": 2, - "yellowCost": 4, - "greenCost": 2, - "location": "TUTOR_LOCATION_SNOWPOINT_CITY" - }, - "MOVE_LAST_RESORT": { - "redCost": 0, - "blueCost": 0, - "yellowCost": 0, - "greenCost": 8, - "location": "TUTOR_LOCATION_SNOWPOINT_CITY" - }, - "MOVE_BOUNCE": { - "redCost": 4, - "blueCost": 0, - "yellowCost": 2, - "greenCost": 2, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_TRICK": { - "redCost": 0, - "blueCost": 4, - "yellowCost": 4, - "greenCost": 0, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_HEAT_WAVE": { - "redCost": 4, - "blueCost": 2, - "yellowCost": 0, - "greenCost": 2, - "location": "TUTOR_LOCATION_SURVIVAL_AREA" - }, - "MOVE_KNOCK_OFF": { - "redCost": 4, - "blueCost": 4, - "yellowCost": 0, - "greenCost": 0, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_SUCKER_PUNCH": { - "redCost": 0, - "blueCost": 6, - "yellowCost": 2, - "greenCost": 0, - "location": "TUTOR_LOCATION_ROUTE_212" - }, - "MOVE_SWIFT": { - "redCost": 0, - "blueCost": 2, - "yellowCost": 2, - "greenCost": 4, - "location": "TUTOR_LOCATION_SNOWPOINT_CITY" - }, - "MOVE_UPROAR": { - "redCost": 0, - "blueCost": 0, - "yellowCost": 6, - "greenCost": 2, - "location": "TUTOR_LOCATION_SNOWPOINT_CITY" - } +[ + { + "move": "MOVE_DIVE", + "redCost": 2, + "blueCost": 4, + "yellowCost": 2, + "greenCost": 0, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_MUD_SLAP", + "redCost": 4, + "blueCost": 4, + "yellowCost": 0, + "greenCost": 0, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_FURY_CUTTER", + "redCost": 0, + "blueCost": 8, + "yellowCost": 0, + "greenCost": 0, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_ICY_WIND", + "redCost": 0, + "blueCost": 6, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_ROLLOUT", + "redCost": 4, + "blueCost": 2, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_THUNDER_PUNCH", + "redCost": 2, + "blueCost": 6, + "yellowCost": 0, + "greenCost": 0, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_FIRE_PUNCH", + "redCost": 2, + "blueCost": 6, + "yellowCost": 0, + "greenCost": 0, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_SUPERPOWER", + "redCost": 8, + "blueCost": 0, + "yellowCost": 0, + "greenCost": 0, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_ICE_PUNCH", + "redCost": 2, + "blueCost": 6, + "yellowCost": 0, + "greenCost": 0, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_IRON_HEAD", + "redCost": 6, + "blueCost": 0, + "yellowCost": 2, + "greenCost": 0, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_AQUA_TAIL", + "redCost": 6, + "blueCost": 0, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_OMINOUS_WIND", + "redCost": 0, + "blueCost": 6, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_GASTRO_ACID", + "redCost": 4, + "blueCost": 0, + "yellowCost": 2, + "greenCost": 2, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_SNORE", + "redCost": 2, + "blueCost": 0, + "yellowCost": 4, + "greenCost": 2, + "location": "TUTOR_LOCATION_SNOWPOINT_CITY" + }, + { + "move": "MOVE_SPITE", + "redCost": 0, + "blueCost": 0, + "yellowCost": 8, + "greenCost": 0, + "location": "TUTOR_LOCATION_SNOWPOINT_CITY" + }, + { + "move": "MOVE_AIR_CUTTER", + "redCost": 2, + "blueCost": 4, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_HELPING_HAND", + "redCost": 2, + "blueCost": 0, + "yellowCost": 4, + "greenCost": 2, + "location": "TUTOR_LOCATION_SNOWPOINT_CITY" + }, + { + "move": "MOVE_ENDEAVOR", + "redCost": 4, + "blueCost": 0, + "yellowCost": 4, + "greenCost": 0, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_OUTRAGE", + "redCost": 6, + "blueCost": 0, + "yellowCost": 2, + "greenCost": 0, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_ANCIENT_POWER", + "redCost": 6, + "blueCost": 0, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_SYNTHESIS", + "redCost": 0, + "blueCost": 0, + "yellowCost": 2, + "greenCost": 6, + "location": "TUTOR_LOCATION_SNOWPOINT_CITY" + }, + { + "move": "MOVE_SIGNAL_BEAM", + "redCost": 2, + "blueCost": 2, + "yellowCost": 2, + "greenCost": 2, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_ZEN_HEADBUTT", + "redCost": 0, + "blueCost": 4, + "yellowCost": 4, + "greenCost": 0, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_VACUUM_WAVE", + "redCost": 2, + "blueCost": 4, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_EARTH_POWER", + "redCost": 6, + "blueCost": 0, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_GUNK_SHOT", + "redCost": 4, + "blueCost": 2, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_TWISTER", + "redCost": 6, + "blueCost": 0, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_SEED_BOMB", + "redCost": 4, + "blueCost": 0, + "yellowCost": 0, + "greenCost": 4, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_IRON_DEFENSE", + "redCost": 4, + "blueCost": 2, + "yellowCost": 2, + "greenCost": 0, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_MAGNET_RISE", + "redCost": 0, + "blueCost": 2, + "yellowCost": 4, + "greenCost": 2, + "location": "TUTOR_LOCATION_SNOWPOINT_CITY" + }, + { + "move": "MOVE_LAST_RESORT", + "redCost": 0, + "blueCost": 0, + "yellowCost": 0, + "greenCost": 8, + "location": "TUTOR_LOCATION_SNOWPOINT_CITY" + }, + { + "move": "MOVE_BOUNCE", + "redCost": 4, + "blueCost": 0, + "yellowCost": 2, + "greenCost": 2, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_TRICK", + "redCost": 0, + "blueCost": 4, + "yellowCost": 4, + "greenCost": 0, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_HEAT_WAVE", + "redCost": 4, + "blueCost": 2, + "yellowCost": 0, + "greenCost": 2, + "location": "TUTOR_LOCATION_SURVIVAL_AREA" + }, + { + "move": "MOVE_KNOCK_OFF", + "redCost": 4, + "blueCost": 4, + "yellowCost": 0, + "greenCost": 0, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_SUCKER_PUNCH", + "redCost": 0, + "blueCost": 6, + "yellowCost": 2, + "greenCost": 0, + "location": "TUTOR_LOCATION_ROUTE_212" + }, + { + "move": "MOVE_SWIFT", + "redCost": 0, + "blueCost": 2, + "yellowCost": 2, + "greenCost": 4, + "location": "TUTOR_LOCATION_SNOWPOINT_CITY" + }, + { + "move": "MOVE_UPROAR", + "redCost": 0, + "blueCost": 0, + "yellowCost": 6, + "greenCost": 2, + "location": "TUTOR_LOCATION_SNOWPOINT_CITY" } -} +] diff --git a/res/pokemon/shellos/data.json b/res/pokemon/shellos/data.json index 8b2ab7a705..9bc6fc5d1d 100644 --- a/res/pokemon/shellos/data.json +++ b/res/pokemon/shellos/data.json @@ -31,10 +31,10 @@ "safari_flee_rate": 0, "body_color": "MON_COLOR_PURPLE", "flip_sprite": false, - "icon_palette": { - "base": 0, - "east_sea": 0 - }, + "icon_palette": [ + [ "base", 0 ], + [ "east_sea", 0 ] + ], "learnset": { "by_level": [ [ 1, "MOVE_MUD_SLAP" ], diff --git a/res/pokemon/spiritomb/data.json b/res/pokemon/spiritomb/data.json index 3bda1b69d2..85501abd8b 100644 --- a/res/pokemon/spiritomb/data.json +++ b/res/pokemon/spiritomb/data.json @@ -110,8 +110,7 @@ "footprint": { "has": false, "size": "FOOTPRINT_LARGE", - "type": "FOOTPRINT_TYPE_SCARY", - "has_type": true + "type": "FOOTPRINT_TYPE_SCARY" }, "pokedex_data": { "height": 10, diff --git a/res/pokemon/unown/data.json b/res/pokemon/unown/data.json index 89662eb0ab..bb4b3ed93f 100644 --- a/res/pokemon/unown/data.json +++ b/res/pokemon/unown/data.json @@ -31,36 +31,37 @@ "safari_flee_rate": 0, "body_color": "MON_COLOR_BLACK", "flip_sprite": true, - "icon_palette": { - "base": 0, - "b": 0, - "c": 0, - "d": 0, - "e": 0, - "f": 0, - "g": 0, - "h": 0, - "i": 0, - "j": 0, - "k": 0, - "l": 0, - "m": 0, - "n": 0, - "o": 0, - "p": 0, - "q": 0, - "r": 0, - "s": 0, - "t": 0, - "u": 0, - "v": 0, - "w": 0, - "x": 0, - "y": 0, - "z": 0, - "exc": 0, - "que": 0 - }, + "icon_palette": [ + [ "base", 0 ], + [ "a", 0 ], + [ "b", 0 ], + [ "c", 0 ], + [ "d", 0 ], + [ "e", 0 ], + [ "f", 0 ], + [ "g", 0 ], + [ "h", 0 ], + [ "i", 0 ], + [ "j", 0 ], + [ "k", 0 ], + [ "l", 0 ], + [ "m", 0 ], + [ "n", 0 ], + [ "o", 0 ], + [ "p", 0 ], + [ "q", 0 ], + [ "r", 0 ], + [ "s", 0 ], + [ "t", 0 ], + [ "u", 0 ], + [ "v", 0 ], + [ "w", 0 ], + [ "x", 0 ], + [ "y", 0 ], + [ "z", 0 ], + [ "exc", 0 ], + [ "que", 0 ] + ], "learnset": { "by_level": [ [ 1, "MOVE_HIDDEN_POWER" ] diff --git a/src/pokemon_icon.c b/src/pokemon_icon.c index 435b025343..e6eb5d350c 100644 --- a/src/pokemon_icon.c +++ b/src/pokemon_icon.c @@ -126,7 +126,7 @@ const u8 PokeIconPaletteIndex(u32 species, u32 form, u32 isEgg) if (species == SPECIES_DEOXYS) { species = ICON_DEOXYS_ATTACK + form - 1; } else if (species == SPECIES_UNOWN) { - species = ICON_UNOWN_BASE + form - 1; + species = ICON_UNOWN_A + form - 1; } else if (species == SPECIES_BURMY) { species = ICON_BURMY_SANDY + form - 1; } else if (species == SPECIES_WORMADAM) { diff --git a/tools/datagen/datagen_species.cpp b/tools/datagen/datagen_species.cpp deleted file mode 100644 index 101b9355b8..0000000000 --- a/tools/datagen/datagen_species.cpp +++ /dev/null @@ -1,791 +0,0 @@ -/* - * datagen-species - * - * Usage: datagen-species - * - * This program is responsible for generating data archive from species data files - * (res/pokemon//data.json). Individual files to be polled for packing are - * drawn from an environment var SPECIES, which should be a semicolon-delimited list - * of subdirectories of res/pokemon. - * - * is expected to be a listing of additional subdirectories - * belonging to individual species which have distinct data files. These special - * forms have their own base stats, types, level-up learnsets, etc., as any base - * species form would. - * - * is expected to be a JSON file defining the listing of moves that - * can be taught by a given move tutor, agnostic of species. This file is only - * consulted to restrict the set of valid moves in a species' tutorable learnset. - * - * The following files are generated by this program: - * - pl_personal.narc - * - evo.narc - * - wotbl.narc - * - ppark.narc - * - height.narc - * - pl_poke_data.narc - * - pms.narc - * - tutorable_moves.h - * - species_learnsets_by_tutor.h - * - species_footprint_sizes.h - * - species_footprint_types.h - * - species_egg_moves.h - * - species_icon_palettes.h - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "datagen.h" - -#define POKEPLATINUM_GENERATED_ENUM -#define POKEPLATINUM_GENERATED_LOOKUP -#define POKEPLATINUM_GENERATED_LOOKUP_IMPL - -#include "generated/abilities.h" -#include "generated/egg_groups.h" -#include "generated/evolution_methods.h" -#include "generated/exp_rates.h" -#include "generated/gender_ratios.h" -#include "generated/items.h" -#include "generated/moves.h" -#include "generated/pal_park_land_area.h" -#include "generated/pal_park_water_area.h" -#include "generated/pokemon_colors.h" -#include "generated/pokemon_types.h" -#include "generated/shadow_sizes.h" -#include "generated/species.h" - -#include "struct_defs/species.h" -#include "struct_defs/species_sprite_data.h" - -#define NUM_TMS 92 // TODO: Move this to a more accessible location, maybe? - -// This struct serves only one purpose: to ensure that the size of the data written -// to file-iamges in evo.narc is word-aligned. The vanilla structures pad to word- -// alignment with 0s, making for two extra 0-bytes after the array. Without this -// manual alignment, the NARC packing routine would instead pad the virtual files -// with `FF FF`, which obviously breaks matching. -struct SpeciesEvolutionList { - ALIGN_4 SpeciesEvolution entries[MAX_EVOLUTIONS]; -}; - -// Entries in `wotbl.narc` are dynamically-sized with a terminating sentinel value -// for each entry. So, we need to know how large the learnset itself is for a -// proper malloc and memcpy while packing to the VFS. -struct SpeciesLearnsetWithSize { - SpeciesLearnset learnset; - unsigned long size; -}; - -static const std::string sHeaderMessage = "" - "/*\n" - " * This header was generated by datagen-species; DO NOT MODIFY IT!!!\n" - " */" - ""; - -static void Usage(std::ostream &ostr) -{ - ostr << "Usage: datagen-species OUT_DIR ROOT_DIR FORMS_REGISTRY TUTOR_SCHEMA" << std::endl; - ostr << std::endl; - ostr << "Generates data archives from species data files (res/pokemon//data.json)" << std::endl; - ostr << "Species data files to be polled for packing are drawn from the environment var\n" - << "SPECIES, which must be a semicolon-delimited list of subdirectories of res/pokemon\n" - << "to be crawled at execution." << std::endl; -} - -static SpeciesData ParseSpeciesData(rapidjson::Document &root) -{ - SpeciesData species = { 0 }; - - rapidjson::Value &baseStats = root["base_stats"]; - species.baseStats.hp = baseStats["hp"].GetUint(); - species.baseStats.attack = baseStats["attack"].GetUint(); - species.baseStats.defense = baseStats["defense"].GetUint(); - species.baseStats.speed = baseStats["speed"].GetUint(); - species.baseStats.spAttack = baseStats["special_attack"].GetUint(); - species.baseStats.spDefense = baseStats["special_defense"].GetUint(); - - rapidjson::Value &evYields = root["ev_yields"]; - species.evYields.hp = evYields["hp"].GetUint(); - species.evYields.attack = evYields["attack"].GetUint(); - species.evYields.defense = evYields["defense"].GetUint(); - species.evYields.speed = evYields["speed"].GetUint(); - species.evYields.spAttack = evYields["special_attack"].GetUint(); - species.evYields.spDefense = evYields["special_defense"].GetUint(); - - rapidjson::Value &abilities = root["abilities"]; - species.abilities[0] = LookupConst(abilities[0].GetString(), Ability); - species.abilities[1] = LookupConst(abilities[1].GetString(), Ability); - - rapidjson::Value &types = root["types"]; - species.types[0] = LookupConst(types[0].GetString(), PokemonType); - species.types[1] = LookupConst(types[1].GetString(), PokemonType); - - rapidjson::Value &heldItems = root["held_items"]; - species.wildHeldItems.common = LookupConst(heldItems["common"].GetString(), Item); - species.wildHeldItems.rare = LookupConst(heldItems["rare"].GetString(), Item); - - rapidjson::Value &eggGroups = root["egg_groups"]; - species.eggGroups[0] = LookupConst(eggGroups[0].GetString(), EggGroup); - species.eggGroups[1] = LookupConst(eggGroups[1].GetString(), EggGroup); - - species.baseExpReward = root["base_exp_reward"].GetUint(); - species.baseFriendship = root["base_friendship"].GetUint(); - species.bodyColor = LookupConst(root["body_color"].GetString(), PokemonColor); - species.catchRate = root["catch_rate"].GetUint(); - species.expRate = LookupConst(root["exp_rate"].GetString(), ExpRate); - species.flipSprite = root["flip_sprite"].GetBool(); - species.genderRatio = LookupConst(root["gender_ratio"].GetString(), GenderRatio); - species.hatchCycles = root["hatch_cycles"].GetUint(); - species.safariFleeRate = root["safari_flee_rate"].GetUint(); - - rapidjson::Value &tmLearnset = root["learnset"]["by_tm"]; - for (auto &tmEntry : tmLearnset.GetArray()) { - std::string entry = tmEntry.GetString(); - int id; - if (entry[0] == 'T' && entry[1] == 'M') { - id = std::stoi(entry.substr(2)) - 1; - } else if (entry[0] == 'H' && entry[1] == 'M') { - id = std::stoi(entry.substr(2)) - 1 + NUM_TMS; - } else { - throw std::invalid_argument("unrecognized TM learnset entry " + entry); - } - species.tmLearnsetMasks[id / 32] |= (1 << (id % 32)); - } - - return species; -} - -static SpeciesEvolutionList ParseEvolutions(rapidjson::Document &root) -{ - SpeciesEvolutionList evos = { 0 }; - if (!root.HasMember("evolutions")) { - return evos; - } - - rapidjson::Value &evoList = root["evolutions"]; - int i = 0; - for (auto &evoEntry : evoList.GetArray()) { - EvolutionMethod method = static_cast(LookupConst(evoEntry[0].GetString(), EvolutionMethod)); - - u16 param; - int speciesIdx = 2; - switch (method) { - case EVO_NONE: - case EVO_LEVEL_HAPPINESS: - case EVO_LEVEL_HAPPINESS_DAY: - case EVO_LEVEL_HAPPINESS_NIGHT: - case EVO_TRADE: - case EVO_LEVEL_MAGNETIC_FIELD: - case EVO_LEVEL_MOSS_ROCK: - case EVO_LEVEL_ICE_ROCK: - param = 0; - speciesIdx = 1; - break; - - case EVO_LEVEL: - case EVO_LEVEL_ATK_GT_DEF: - case EVO_LEVEL_ATK_EQ_DEF: - case EVO_LEVEL_ATK_LT_DEF: - case EVO_LEVEL_PID_LOW: - case EVO_LEVEL_PID_HIGH: - case EVO_LEVEL_NINJASK: - case EVO_LEVEL_SHEDINJA: - case EVO_LEVEL_MALE: - case EVO_LEVEL_FEMALE: - case EVO_LEVEL_BEAUTY: - param = evoEntry[1].GetUint(); - break; - - case EVO_TRADE_WITH_HELD_ITEM: - case EVO_USE_ITEM: - case EVO_USE_ITEM_MALE: - case EVO_USE_ITEM_FEMALE: - case EVO_LEVEL_WITH_HELD_ITEM_DAY: - case EVO_LEVEL_WITH_HELD_ITEM_NIGHT: - param = LookupConst(evoEntry[1].GetString(), Item); - break; - - case EVO_LEVEL_KNOW_MOVE: - param = LookupConst(evoEntry[1].GetString(), Move); - break; - - case EVO_LEVEL_SPECIES_IN_PARTY: - param = LookupConst(evoEntry[1].GetString(), Species); - break; - } - - u16 target = LookupConst(evoEntry[speciesIdx].GetString(), Species); - evos.entries[i++] = SpeciesEvolution { - .method = static_cast(method), - .param = param, - .targetSpecies = target, - }; - } - - return evos; -} - -static SpeciesLearnsetWithSize ParseLevelUpLearnset(rapidjson::Document &root) -{ - SpeciesLearnsetWithSize result = {}; - - rapidjson::Value &byLevel = root["learnset"]["by_level"]; - int i = 0; - for (auto &byLevelEntry : byLevel.GetArray()) { - u16 level = byLevelEntry[0].GetUint(); - u16 move = LookupConst(byLevelEntry[1].GetString(), Move); - result.learnset.entries[i++] = { - .move = move, - .level = level, - }; - - if (i == MAX_LEARNSET_ENTRIES) { - break; - } - } - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" // bitfield-constant-conversion is clang-specific; GCC should ignore it -#pragma GCC diagnostic ignored "-Woverflow" -#pragma GCC diagnostic ignored "-Wbitfield-constant-conversion" - result.learnset.entries[i++] = { - .move = static_cast(-1), - .level = static_cast(-1), - }; -#pragma GCC diagnostic pop - - result.size = AlignToWord(i * sizeof(SpeciesLearnsetEntry)); - return result; -} - -static std::optional TryParsePalPark(rapidjson::Document &root) -{ - if (!root.HasMember("catching_show")) { - return std::nullopt; - } - - rapidjson::Value &catchingShow = root["catching_show"]; - SpeciesPalPark palPark; - palPark.landArea = LookupConst(catchingShow["pal_park_land_area"].GetString(), PalParkLandArea); - palPark.waterArea = LookupConst(catchingShow["pal_park_water_area"].GetString(), PalParkWaterArea); - palPark.catchingPoints = catchingShow["catching_points"].GetUint(); - palPark.rarity = catchingShow["rarity"].GetUint(); - palPark.unused.asU16 = catchingShow["unused"].GetUint(); - - return palPark; -} - -static u16 TryParseOffspring(rapidjson::Document &root, u16 personalValue) -{ - if (!root.HasMember("offspring")) { - return personalValue; - } - - return LookupConst(root["offspring"].GetString(), Species); -} - -static void PackOffspring(std::vector offspringData, fs::path path) -{ - std::ofstream ofs(path); - ofs.write(reinterpret_cast(&offspringData[0]), offspringData.size() * sizeof(u16)); -} - -static std::vector EmitTutorableMoves(fs::path &tutorSchemaFname, fs::path outFname) -{ - std::string tutorSchema = ReadWholeFile(tutorSchemaFname); - rapidjson::Document doc; - doc.Parse(tutorSchema.c_str()); - - std::ofstream header(outFname, std::ios::out | std::ios::trunc | std::ios::ate); - header << sHeaderMessage << "\n" - << "#ifndef POKEPLATINUM_GENERATED_TUTORABLE_MOVES\n" - << "#define POKEPLATINUM_GENERATED_TUTORABLE_MOVES\n" - << "\n" - << "static const TeachableMove sTeachableMoves[] = {" - << std::endl; - - std::vector tutorables; - rapidjson::Value &moves = doc["moves"]; - for (const auto &entry : moves.GetObject()) { - Move tutorable = static_cast(LookupConst(entry.name.GetString(), Move)); - if (std::find(tutorables.begin(), tutorables.end(), tutorable) == tutorables.end()) { - tutorables.push_back(tutorable); - } - - const rapidjson::Value &moveObj = entry.value; - header << " { " << entry.name.GetString() << ", " - << moveObj["redCost"].GetUint() << ", " - << moveObj["blueCost"].GetUint() << ", " - << moveObj["yellowCost"].GetUint() << ", " - << moveObj["greenCost"].GetUint() << ", " - << moveObj["location"].GetString() << ", }, " - << std::endl; - } - - header << "};\n" - << "\n" - << "#endif // POKEPLATINUM_GENERATED_TUTORABLE_MOVES" - << std::endl; - header.close(); - return tutorables; -} - -static void TryEmitTutorableLearnset(rapidjson::Document &root, std::ofstream &ofs, std::vector &tutorableMoves, std::size_t tutorableLearnsetSize) -{ - const rapidjson::Value &learnsets = root["learnset"]; - if (!learnsets.HasMember("by_tutor")) { - return; - } - - const rapidjson::Value &byTutorLearnset = learnsets["by_tutor"]; - std::vector tutorableLearnset(tutorableLearnsetSize); - for (const auto &entry : byTutorLearnset.GetArray()) { - Move tutorable = static_cast(LookupConst(entry.GetString(), Move)); - std::vector::iterator it = std::find(tutorableMoves.begin(), tutorableMoves.end(), tutorable); - if (it == tutorableMoves.end()) { - std::stringstream ss; - ss << "Move " << entry.GetString() << " is not available via move tutors"; - throw std::invalid_argument(ss.str()); - } - - // The mask-index of this move is just the move's index / 8 - // The bit-index within that mask is the move's index % 8 - std::size_t idx = it - tutorableMoves.begin(); - tutorableLearnset[idx / 8] |= (1 << (idx % 8)); - } - - ofs << " { "; - for (const auto &mask : tutorableLearnset) { - ofs << "0x" << std::setfill('0') << std::setw(2) << (int)mask << ", "; - } - ofs << "},\n"; -} - -static void TryEmitEggMoves(rapidjson::Document &root, std::ofstream &ofs, std::string species) -{ - const rapidjson::Value &learnsets = root["learnset"]; - if (!learnsets.HasMember("egg_moves")) { - return; - } - - std::transform(species.begin(), species.end(), species.begin(), ::toupper); - ofs << " SPECIES_" - << species - << " + EGG_MOVES_SPECIES_OFFSET,\n"; - - const rapidjson::Value &eggMoves = learnsets["egg_moves"]; - for (const auto &entry : eggMoves.GetArray()) { - ofs << " " - << entry.GetString() - << ",\n"; - } - - ofs << "\n"; -} - -static std::string FormPathToName(std::string path) -{ - path = std::regex_replace(path, std::regex("/FORMS/"), "_"); - return path; -} - -static void EmitIconEnum(std::ofstream &ofs, std::string species, std::string form) -{ - std::transform(species.begin(), species.end(), species.begin(), ::toupper); - std::transform(form.begin(), form.end(), form.begin(), ::toupper); - ofs << " ICON_" - << species - << "_" - << form - << ",\n"; -} - -static void EmitIconPaletteData(rapidjson::Document &root, std::ofstream &ofs, std::string species, u16 personalValue, std::map> iconRegistry) -{ - const rapidjson::Value &iconPallete = root["icon_palette"]; - - std::string speciesUpper = species; - std::transform(speciesUpper.begin(), speciesUpper.end(), speciesUpper.begin(), ::toupper); - - int iconIdx; - if (iconPallete.IsInt()) { - iconIdx = iconPallete.GetInt(); - } else { - iconIdx = iconPallete["base"].GetInt(); - - for (auto &form : iconRegistry.at(species)) { - const rapidjson::Value &formIcon = iconPallete[(char *)form.c_str()]; - std::transform(form.begin(), form.end(), form.begin(), ::toupper); - ofs << " [ICON_" - << speciesUpper - << "_" - << form - << "] = " - << formIcon.GetInt() - << ",\n"; - } - } - - ofs << " ["; - - if (personalValue == SPECIES_BAD_EGG) { - ofs << "ICON_MANAPHY_EGG"; - } else { - if (personalValue < SPECIES_EGG) { - ofs << "SPECIES_" - << speciesUpper; - } else { - ofs << "ICON_" - << FormPathToName(speciesUpper); - } - } - - ofs << "] = " - << iconIdx - << ",\n"; -} - -static void PackHeights(NarcBuilder &builder, rapidjson::Document &root, u8 genderRatio) -{ - const rapidjson::Value &backOffsets = root["back"]["y_offset"]; - const rapidjson::Value &frontOffsets = root["front"]["y_offset"]; - - std::byte backFemale, backMale, frontFemale, frontMale; - u32 femaleSize = 1, maleSize = 1; - - if (genderRatio == GENDER_RATIO_FEMALE_ONLY) { - maleSize = 0; - } else { - backMale = static_cast(backOffsets["male"].GetUint()); - frontMale = static_cast(frontOffsets["male"].GetUint()); - } - - if (genderRatio == GENDER_RATIO_MALE_ONLY || genderRatio == GENDER_RATIO_NO_GENDER) { - femaleSize = 0; - } else { - backFemale = static_cast(backOffsets["female"].GetUint()); - frontFemale = static_cast(frontOffsets["female"].GetUint()); - } - - builder.append(&backFemale, femaleSize); - builder.append(&backMale, maleSize); - builder.append(&frontFemale, femaleSize); - builder.append(&frontMale, maleSize); -} - -static SpriteAnimFrame ParseSpriteAnimationFrame(const rapidjson::Value &frame) -{ - SpriteAnimFrame data = { 0 }; - data.spriteFrame = frame["sprite_frame"].GetInt(); - data.frameDelay = frame["frame_delay"].GetUint(); - data.xOffset = frame["x_shift"].GetInt(); - data.yOffset = frame["y_shift"].GetInt(); - - return data; -} - -static SpeciesSpriteAnim ParseSpeciesSpriteAnim(const rapidjson::Value &face) -{ - SpeciesSpriteAnim data = { 0 }; - data.animation = face["animation"].GetUint(); - data.cryDelay = face["cry_delay"].GetUint(); - data.startDelay = face["start_delay"].GetUint(); - - int i = 0; - for (auto &frame : face["frames"].GetArray()) { - data.frames[i++] = ParseSpriteAnimationFrame(frame); - } - - return data; -} - -static void TryEmitFootprint(const rapidjson::Document &root, std::string species, std::ofstream &ofs_size, std::ofstream &ofs_type) -{ - if (!root.HasMember("footprint")) { - return; - } - - std::transform(species.begin(), species.end(), species.begin(), ::toupper); - - const rapidjson::Value &footprint = root["footprint"]; - BOOL hasFootprintType = footprint["has"].GetBool(); - if (footprint.HasMember("has_type")) { // this is just for Spiritomb - hasFootprintType = footprint["has_type"].GetBool(); - } - - ofs_size << " { " - << (footprint["has"].GetBool() ? "TRUE, " : "FALSE, ") - << footprint["size"].GetString() << ", },\n"; - ofs_type << " [SPECIES_" - << species - << "] = { " - << footprint["type"].GetString() - << (hasFootprintType ? ", TRUE" : ", FALSE") - << " },\n"; -} - -static SpeciesSpriteData ParseSpeciesSpriteData(const rapidjson::Document &root) -{ - SpeciesSpriteData data = { 0 }; - - const rapidjson::Value &front = root["front"]; - const rapidjson::Value &back = root["back"]; - const rapidjson::Value &shadow = root["shadow"]; - - data.faceAnims[0] = ParseSpeciesSpriteAnim(front); - data.faceAnims[1] = ParseSpeciesSpriteAnim(back); - data.yOffset = front["addl_y_offset"].GetInt(); - data.xOffsetShadow = shadow["x_offset"].GetInt(); - data.shadowSize = LookupConst(shadow["size"].GetString(), ShadowSize); - - return data; -} - -int main(int argc, char **argv) -{ - if (argc == 1) { - Usage(std::cout); - return EXIT_SUCCESS; - } - - fs::path outputRoot = argv[1]; - fs::path dataRoot = argv[2]; - fs::path formsRegistryFname = argv[3]; - fs::path tutorSchemaFname = argv[4]; - - // Determine what moves are tutorable and output the corresponding C header. - std::vector tutorableMoves = EmitTutorableMoves(tutorSchemaFname, outputRoot / "tutorable_moves.h"); - - // Bootstrap the by-tutor learnsets header. - std::ofstream byTutorMovesets(outputRoot / "species_learnsets_by_tutor.h"); - byTutorMovesets << sHeaderMessage << "\n" - << "#ifndef POKEPLATINUM_GENERATED_SPECIES_LEARNSETS_BY_TUTOR_H\n" - << "#define POKEPLATINUM_GENERATED_SPECIES_LEARNSETS_BY_TUTOR_H\n" - << "\n" - << "#include \"tutor_movesets.h\"\n" - << "\n" - << "static const MovesetMask sSpeciesLearnsetsByTutor[MOVESET_MAX] = {\n"; - byTutorMovesets << std::hex << std::setiosflags(std::ios::uppercase); // render all numeric inputs to the stream as hexadecimal - - // Bootstrap the egg moves header. - std::ofstream eggMoves(outputRoot / "species_egg_moves.h"); - eggMoves << sHeaderMessage << "\n" - << "#ifndef POKEPLATINUM_GENERATED_SPECIES_EGG_MOVES_H\n" - << "#define POKEPLATINUM_GENERATED_SPECIES_EGG_MOVES_H\n" - << "\n" - << "#include \"constants/species.h\"\n" - << "#include \"generated/moves.h\"\n" - << "\n" - << "#define EGG_MOVES_SPECIES_OFFSET 20000\n" - << "#define EGG_MOVES_TERMINATOR 0xFFFF\n" - << "\n" - << "static const u16 sEggMoves[] = {\n"; - - // Bootstrap the footprint sizes header. - std::ofstream footprint_sizes(outputRoot / "species_footprint_sizes.h"); - footprint_sizes << sHeaderMessage << "\n" - << "#ifndef POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_SIZES_H\n" - << "#define POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_SIZES_H\n" - << "\n" - << "#include \"constants/species.h\"\n" - << "#include \"generated/footprint_sizes.h\"\n" - << "\n" - << "#include \"overlay113/footprint_data.h\"\n" - << "\n" - << "static const FootprintData sSpeciesFootprints[NATIONAL_DEX_COUNT + 1] = {\n"; - - // Bootstrap the footprint types header. - std::ofstream footprint_types(outputRoot / "species_footprint_types.h"); - footprint_types << sHeaderMessage << "\n" - << "#ifndef POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_TYPES_H\n" - << "#define POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_TYPES_H\n" - << "\n" - << "#include \"constants/footstep_house.h\"\n" - << "#include \"constants/species.h\"\n" - << "\n" - << "typedef struct FootprintType {\n" - << " u16 type;\n" - << " u16 hasPrint;\n" - << "} FootprintType;\n" - << "\n" - << "static const FootprintType sFootprintTypes[NATIONAL_DEX_COUNT + 1] = {\n"; - - // Bootstrap the icon palette offset header. - std::ofstream iconPalettes(outputRoot / "species_icon_palettes.h"); - iconPalettes << sHeaderMessage << "\n" - << "#ifndef POKEPLATINUM_GENERATED_SPECIES_ICON_PALETTES_H\n" - << "#define POKEPLATINUM_GENERATED_SPECIES_ICON_PALETTES_H\n" - << "\n" - << "#include \"constants/species.h\"\n" - << "\n" - << "enum FormIconIndex {\n" - << " ICON_EGG = SPECIES_EGG,\n" - << " ICON_MANAPHY_EGG,\n"; - - // Tutorable learnsets are stored as an array of bitmasks; each bit in the mask - // denotes if a tutorable move can be learned by a given species. - std::size_t tutorableLearnsetSize = (tutorableMoves.size() + 7) / 8; - - // Prepare loop contents. - std::vector speciesRegistry = ReadRegistryEnvVar("SPECIES"); - std::map> iconRegistry; - // read form data - std::string formsRegistryJson = ReadWholeFile(formsRegistryFname); - rapidjson::Document formRegDoc; - rapidjson::ParseResult formOk = formRegDoc.Parse(formsRegistryJson.c_str(), formsRegistryJson.length()); - if (!formOk) { - ReportJsonError(formOk, formsRegistryJson, formsRegistryFname); - std::exit(EXIT_FAILURE); - } - for (auto &formRegSpecies : formRegDoc.GetObject()) { - std::string speciesName = formRegSpecies.name.GetString(); - std::vector speciesIcons; - - if (formRegSpecies.value.HasMember("__dupe_base_icon")) { - EmitIconEnum(iconPalettes, speciesName, "base"); - speciesIcons.push_back("base"); - } - - for (auto &form : formRegSpecies.value.GetObject()) { - std::string formName = form.name.GetString(); - if (formName[0] == '_') { - continue; - } - std::string formType = form.value.GetString(); - if (formType.compare("data") == 0) { - EmitIconEnum(iconPalettes, speciesName, formName); - - formName = formRegSpecies.name.GetString(); - formName.append("/forms/"); - formName.append(form.name.GetString()); - speciesRegistry.push_back(formName); - } - if (formType.compare("icon") == 0) { - EmitIconEnum(iconPalettes, speciesName, formName); - - speciesIcons.push_back(formName); - } - } - - if (speciesIcons.size() > 0) { - iconRegistry.insert({ speciesName, speciesIcons }); - } - } - iconPalettes << "};\n" - << "\n" - << "/**\n" - << " * @brief This table defines which index of the shared palette file is used\n" - << " * for the icons used by Pokemon in PC boxes, the party list, etc. Alternate\n" - << " * forms are listed after the full National Dex using a custom ordering.\n" - << " */\n" - << "static const u8 sPokemonIconPaletteIndex[] = {\n"; - - // Prepare VFSes for each NARC to be output. - NarcBuilder personal { speciesRegistry.size() }; - NarcBuilder evo { speciesRegistry.size() }; - NarcBuilder wotbl { speciesRegistry.size() }; - NarcBuilder height { 4 * SPECIES_EGG }; // NOTE: using SPECIES_EGG as an effective NatDex number - std::vector palParkData; - std::vector offspringData; - std::vector speciesSpriteData; - - u16 personalValue = 0; - rapidjson::Document doc; - for (auto &species : speciesRegistry) { - fs::path speciesDataPath = dataRoot / species / "data.json"; - std::string json = ReadWholeFile(speciesDataPath); - rapidjson::ParseResult ok = doc.Parse(json.c_str(), json.length()); - if (!ok) { - ReportJsonError(ok, json, speciesDataPath); - std::exit(EXIT_FAILURE); - } - - try { - SpeciesData data = ParseSpeciesData(doc); - SpeciesEvolutionList evos = ParseEvolutions(doc); - SpeciesLearnsetWithSize sizedLearnset = ParseLevelUpLearnset(doc); - std::optional palPark = TryParsePalPark(doc); - u16 offspring = TryParseOffspring(doc, personalValue); - TryEmitTutorableLearnset(doc, byTutorMovesets, tutorableMoves, tutorableLearnsetSize); - TryEmitEggMoves(doc, eggMoves, species); - TryEmitFootprint(doc, species, footprint_sizes, footprint_types); - EmitIconPaletteData(doc, iconPalettes, species, personalValue, iconRegistry); - - personal.append(reinterpret_cast(&data), sizeof(data)); - evo.append(reinterpret_cast(&evos), sizeof(evos)); - wotbl.append(reinterpret_cast(&sizedLearnset.learnset), sizedLearnset.size); - - if (palPark.has_value()) { - palParkData.emplace_back(palPark.value()); - } - - offspringData.emplace_back(offspring); - - // Mechanically-distinct forms do not have sprite_data.json files - fs::path speciesSpriteDataPath = dataRoot / species / "sprite_data.json"; - std::ifstream spriteDataIFS(speciesSpriteDataPath, std::ios::in); - if (spriteDataIFS.good()) { - std::string spriteData = ReadWholeFile(spriteDataIFS); - ok = doc.Parse(spriteData.c_str()); - if (!ok) { - ReportJsonError(ok, json, speciesSpriteDataPath); - std::exit(EXIT_FAILURE); - } - - u8 genderRatio = species != "none" ? data.genderRatio : GENDER_RATIO_FEMALE_50; // treat SPECIES_NONE as if it has two genders. - PackHeights(height, doc, genderRatio); - - SpeciesSpriteData speciesSprite = ParseSpeciesSpriteData(doc); - speciesSpriteData.emplace_back(speciesSprite); - } - } catch (const std::exception &e) { - std::cerr << e.what() << std::endl; - std::exit(EXIT_FAILURE); - } - personalValue += 1; - } - - byTutorMovesets << "};\n" - << "\n" - << "#endif // POKEPLATINUM_GENERATED_SPECIES_LEARNSETS_BY_TUTOR_H\n"; - byTutorMovesets.close(); - - eggMoves << " EGG_MOVES_TERMINATOR,\n" - << "};\n" - << "\n" - << "#endif // POKEPLATINUM_GENERATED_SPECIES_EGG_MOVES_H\n"; - eggMoves.close(); - - footprint_sizes << "};\n" - << "\n" - << "#endif // POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_SIZES_H\n"; - footprint_sizes.close(); - - footprint_types << "};\n" - << "\n" - << "#endif // POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_TYPES_H\n"; - footprint_types.close(); - - iconPalettes << "};\n" - << "\n" - << "#endif // POKEPLATINUM_GENERATED_SPECIES_ICON_PALETTES_H\n"; - iconPalettes.close(); - - personal.write(outputRoot / "pl_personal.narc"); - evo.write(outputRoot / "evo.narc"); - wotbl.write(outputRoot / "wotbl.narc"); - height.write(outputRoot / "height.narc"); - - NarcBuilder::write(palParkData, outputRoot / "ppark.narc"); - NarcBuilder::write(speciesSpriteData, outputRoot / "pl_poke_data.narc"); - PackOffspring(offspringData, outputRoot / "pms.narc"); - return EXIT_SUCCESS; -} diff --git a/tools/datagen/meson.build b/tools/datagen/meson.build index 63bdc71e64..6d6377ca80 100644 --- a/tools/datagen/meson.build +++ b/tools/datagen/meson.build @@ -16,25 +16,6 @@ datagen_cpp_args = [ '-O3', ] -datagen_species_exe = executable( - 'datagen-species', - sources: [ - files('datagen_species.cpp'), - c_consts_generators, - ], - cpp_args: datagen_cpp_args, - implicit_include_directories: true, - include_directories: [ - public_includes, - toplevel_includes, - ], - dependencies: [ - nitroarc_dep, - rapidjson_dep, - ], - native: true, -) - datagen_trainer_exe = executable( 'datagen-trainer', sources: [ diff --git a/tools/dataproc/data/species_egg_moves.h.template b/tools/dataproc/data/species_egg_moves.h.template new file mode 100644 index 0000000000..df66ffabc1 --- /dev/null +++ b/tools/dataproc/data/species_egg_moves.h.template @@ -0,0 +1,15 @@ +#ifndef POKEPLATINUM_GENERATED_SPECIES_EGG_MOVES_H +#define POKEPLATINUM_GENERATED_SPECIES_EGG_MOVES_H + +#include "constants/species.h" +#include "generated/moves.h" + +#define EGG_MOVES_SPECIES_OFFSET 20000 +#define EGG_MOVES_TERMINATOR 0xFFFF + +static const u16 sEggMoves[] = { +/* =========== MAGIC CONTENT MARKER =========== */ + EGG_MOVES_TERMINATOR, +}; + +#endif // POKEPLATINUM_GENERATED_SPECIES_EGG_MOVES_H diff --git a/tools/dataproc/data/species_footprint_sizes.h.template b/tools/dataproc/data/species_footprint_sizes.h.template new file mode 100644 index 0000000000..a44cd07433 --- /dev/null +++ b/tools/dataproc/data/species_footprint_sizes.h.template @@ -0,0 +1,12 @@ +#ifndef POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_SIZES_H +#define POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_SIZES_H + +#include "constants/species.h" +#include "generated/footprint_sizes.h" +#include "overlay113/footprint_data.h" + +static const FootprintData sSpeciesFootprints[NATIONAL_DEX_COUNT + 1] = { +/* =========== MAGIC CONTENT MARKER =========== */ +}; + +#endif // POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_SIZES_H diff --git a/tools/dataproc/data/species_footprint_types.h.template b/tools/dataproc/data/species_footprint_types.h.template new file mode 100644 index 0000000000..4fbd12d2cc --- /dev/null +++ b/tools/dataproc/data/species_footprint_types.h.template @@ -0,0 +1,16 @@ +#ifndef POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_TYPES_H +#define POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_TYPES_H + +#include "constants/footstep_house.h" +#include "constants/species.h" + +typedef struct FootprintType { + u16 type; + u16 hasPrint; +} FootprintType; + +static const FootprintType sFootprintTypes[NATIONAL_DEX_COUNT + 1] = { +/* =========== MAGIC CONTENT MARKER =========== */ +}; + +#endif // POKEPLATINUM_GENERATED_SPECIES_FOOTPRINT_TYPES_H diff --git a/tools/dataproc/data/species_icon_palettes.h.template b/tools/dataproc/data/species_icon_palettes.h.template new file mode 100644 index 0000000000..aa5d245a97 --- /dev/null +++ b/tools/dataproc/data/species_icon_palettes.h.template @@ -0,0 +1,59 @@ +#ifndef POKEPLATINUM_GENERATED_SPECIES_ICON_PALETTES_H +#define POKEPLATINUM_GENERATED_SPECIES_ICON_PALETTES_H + +#include "constants/species.h" + +enum FormIconIndex { + ICON_EGG = SPECIES_EGG, + ICON_MANAPHY_EGG, + ICON_DEOXYS_ATTACK, + ICON_DEOXYS_DEFENSE, + ICON_DEOXYS_SPEED, + ICON_UNOWN_A, + ICON_UNOWN_B, + ICON_UNOWN_C, + ICON_UNOWN_D, + ICON_UNOWN_E, + ICON_UNOWN_F, + ICON_UNOWN_G, + ICON_UNOWN_H, + ICON_UNOWN_I, + ICON_UNOWN_J, + ICON_UNOWN_K, + ICON_UNOWN_L, + ICON_UNOWN_M, + ICON_UNOWN_N, + ICON_UNOWN_O, + ICON_UNOWN_P, + ICON_UNOWN_Q, + ICON_UNOWN_R, + ICON_UNOWN_S, + ICON_UNOWN_T, + ICON_UNOWN_U, + ICON_UNOWN_V, + ICON_UNOWN_W, + ICON_UNOWN_X, + ICON_UNOWN_Y, + ICON_UNOWN_Z, + ICON_UNOWN_EXC, + ICON_UNOWN_QUE, + ICON_BURMY_SANDY, + ICON_BURMY_TRASH, + ICON_WORMADAM_SANDY, + ICON_WORMADAM_TRASH, + ICON_SHELLOS_EAST_SEA, + ICON_GASTRODON_EAST_SEA, + ICON_GIRATINA_ORIGIN, + ICON_SHAYMIN_SKY, + ICON_ROTOM_HEAT, + ICON_ROTOM_WASH, + ICON_ROTOM_FROST, + ICON_ROTOM_FAN, + ICON_ROTOM_MOW, +}; + +static const u8 sPokemonIconPaletteIndex[] = { +/* =========== MAGIC CONTENT MARKER =========== */ +}; + +#endif // POKEPLATINUM_GENERATED_SPECIES_ICON_PALETTES_H diff --git a/tools/dataproc/data/species_learnsets_by_tutor.h.template b/tools/dataproc/data/species_learnsets_by_tutor.h.template new file mode 100644 index 0000000000..a42712cdda --- /dev/null +++ b/tools/dataproc/data/species_learnsets_by_tutor.h.template @@ -0,0 +1,10 @@ +#ifndef POKEPLATINUM_GENERATED_SPECIES_LEARNSETS_BY_TUTOR_H +#define POKEPLATINUM_GENERATED_SPECIES_LEARNSETS_BY_TUTOR_H + +#include "tutor_movesets.h" + +static const MovesetMask sSpeciesLearnsetsByTutor[MOVESET_MAX] = { +/* =========== MAGIC CONTENT MARKER =========== */ +}; + +#endif // POKEPLATINUM_GENERATED_SPECIES_LEARNSETS_BY_TUTOR_H diff --git a/tools/dataproc/data/tutorable_moves.h.template b/tools/dataproc/data/tutorable_moves.h.template new file mode 100644 index 0000000000..87ed843b01 --- /dev/null +++ b/tools/dataproc/data/tutorable_moves.h.template @@ -0,0 +1,8 @@ +#ifndef POKEPLATINUM_GENERATED_TUTORABLE_MOVES +#define POKEPLATINUM_GENERATED_TUTORABLE_MOVES + +static const TeachableMove sTeachableMoves[] = { +/* =========== MAGIC CONTENT MARKER =========== */ +}; + +#endif // POKEPLATINUM_GENERATED_TUTORABLE_MOVES diff --git a/tools/dataproc/meson.build b/tools/dataproc/meson.build index 580f8ab147..11e11fcf8b 100644 --- a/tools/dataproc/meson.build +++ b/tools/dataproc/meson.build @@ -24,3 +24,36 @@ dataproc_dep = declare_dependency( native: true, ), ) + +repo_include = meson.global_source_root() / 'include' +repo_build = meson.global_build_root() +dataproc_templates_dir = meson.current_source_dir() / 'data' + +commonproc_dep = declare_dependency( + link_with: static_library( + 'commonproc', + sources: files('src/common.c', 'src/enum.c'), + + c_args: [ + dataproc_cflags, + f'-DREPO_INCLUDE="@repo_include@"', + f'-DREPO_BUILD="@repo_build@"', + f'-DTEMPLATES_DIR="@dataproc_templates_dir@"', + ], + + dependencies: [ dataproc_dep, nitroarc_dep ], + native: true, + ), + dependencies: [ dataproc_dep, nitroarc_dep ], +) + +speciesproc_exe = executable( + 'speciesproc', + sources: [ files('src/speciesproc.c'), c_consts_generators ], + + c_args: dataproc_cflags, + + include_directories: [ public_includes, toplevel_includes ], + dependencies: commonproc_dep, + native: true, +) diff --git a/tools/dataproc/src/common.c b/tools/dataproc/src/common.c new file mode 100644 index 0000000000..b4f21ae3c7 --- /dev/null +++ b/tools/dataproc/src/common.c @@ -0,0 +1,413 @@ +#include "common.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dataproc.h" +#include "enum.h" +#include "nitroarc.h" + +#define MAX_LOADED_ENUMS 128 + +static enum_t loaded_enums[MAX_LOADED_ENUMS] = { 0 }; +static size_t num_loaded_enums = 0; + +static void unload_enums(void); +static void finish_headers(void); +static FILE* open_depfile(const char *depfile_path); +static void load_header_template(header_template_t *h, FILE *depfile); +static enum_t dp_include( + const char *from_file, + const char *with_prefix, + const char *for_type, + bool from_defs, + FILE *depfile +); + +static archive_template_t *s_archives = NULL; +static header_template_t *s_headers = NULL; +static const char *s_output_dir = NULL; + +static void* crt_malloc(void *ctx, unsigned items, unsigned size) { + (void)ctx; + return malloc(items * size); +} + +static void* crt_realloc(void *ctx, void *p, unsigned items, unsigned size) { + (void)ctx; + return realloc(p, items * size); +} + +static void crt_free(void *ctx, void *p, unsigned items, unsigned size) { + (void)ctx; + (void)items; + (void)size; + free(p); +} + +#define default_packer() (nitroarc_packer_t){ \ + .malloc = crt_malloc, \ + .realloc = crt_realloc, \ + .free = crt_free, \ +} + +static const char *header_comment = "" + "/*\n" + " * This header was generated by %s\n" + " * DO NOT MODIFY IT!!!\n" + " */\n"; + +static FILE* open_depfile(const char *depfile_path) { + FILE *depfile = fopen(depfile_path, "wb"); + if (depfile == NULL) { + fprintf(stderr, "error opening depfile '%s': %s\n", + depfile_path, strerror(errno)); + exit(EXIT_FAILURE); + } + + return depfile; +} + +void common_init( + enum format format, + enum_template_t *lookups, + archive_template_t *archives, + header_template_t *headers, + const char *source_name, + const char *depfile_path, + const char *output_dir, + void (*pre_init_hook)(void), + void (*post_init_hook)(void) +) { + dp_init(format); + if (pre_init_hook) pre_init_hook(); + + s_archives = archives; + s_headers = headers; + s_output_dir = output_dir; + atexit(unload_enums); + atexit(finish_headers); + + FILE *depfile = open_depfile(depfile_path); + + // Initialize all the requested iteratively-packed archives + for (archive_template_t *to_init = archives; to_init && to_init->out_filename; to_init++) { + to_init->packer = default_packer(); + int errc = nitroarc_pinit( + &to_init->packer, + to_init->num_files, + to_init->named, + to_init->stripped + ); + + if (errc) { + fprintf(stderr, "nitroarc initialization error: %s\n", + nitroarc_errs(errc)); + exit(EXIT_FAILURE); + } + + char *full_path = pathjoin(output_dir, NULL, to_init->out_filename); + fputs(full_path, depfile); + fputc(' ', depfile); + free(full_path); + } + + // Prepare iteratively-written headers + for (header_template_t *to_init = headers; to_init && to_init->out_filename; to_init++) { + char *full_path = pathjoin(output_dir, NULL, to_init->out_filename); + to_init->out_file = fopen(full_path, "wb"); + if (to_init->out_file == NULL) { + fprintf(stderr, "could not open output file '%s': %s", + full_path, strerror(errno)); + exit(EXIT_FAILURE); + } + + fprintf(to_init->out_file, header_comment, source_name); + + fputs(full_path, depfile); + fputc(' ', depfile); + free(full_path); + } + + fputs(": ", depfile); + + // Load all the requested lookup-tables from their sources + for (enum_template_t *to_load = lookups; to_load && to_load->from_file; to_load++) { + loaded_enums[num_loaded_enums++] = dp_include( + to_load->from_file, + to_load->with_prefix, + to_load->for_type, + to_load->from_defs, + depfile + ); + } + + // Second pass: load a header template as a dependency + for (header_template_t *to_init = headers; to_init && to_init->out_filename; to_init++) { + load_header_template(to_init, depfile); + fputs(to_init->header, to_init->out_file); + } + + if (post_init_hook) post_init_hook(); + fclose(depfile); +} + +#define HEADER_TEMPLATE_SUFFIX ".template" +#define HEADER_TEMPLATE_MAGIC "/* =========== MAGIC CONTENT MARKER =========== */" + +static void load_header_template(header_template_t *h, FILE *depfile) { + size_t len_out_fname = strlen(h->out_filename); + char *template_fname = calloc(len_out_fname + sizeof(HEADER_TEMPLATE_SUFFIX) + 1, 1); + assert(template_fname); + + if (template_fname == NULL) exit(EXIT_FAILURE); + memcpy(template_fname, h->out_filename, len_out_fname); + memcpy(template_fname + len_out_fname, HEADER_TEMPLATE_SUFFIX, sizeof(HEADER_TEMPLATE_SUFFIX)); + + char *full_path = pathjoin(TEMPLATES_DIR, NULL, template_fname); + char *template = fload(full_path); + char *marker = strstr(template, HEADER_TEMPLATE_MAGIC); + char *footer = marker + sizeof(HEADER_TEMPLATE_MAGIC); + *marker = '\0'; + + h->header = template; + h->footer = footer; + + fputs(full_path, depfile); + fputc(' ', depfile); + + free(full_path); + free(template_fname); +} + +static void unload_enums(void) { + for (size_t i = 0; i < num_loaded_enums; i++) enum_free(&loaded_enums[i]); +} + +static void finish_headers(void) { + for (header_template_t *h = s_headers; h && h->out_filename; h++) { + if (h->out_file) { + fputs(h->footer, h->out_file); + fclose(h->out_file); + free(h->header); + } + } +} + +int common_done(int errc, int (*addl_done_hook)(void)) { + for (archive_template_t *a = s_archives; a && a->out_filename; a++) { + if (fdump_narc(&a->packer, a->out_filename, errc == 0)) errc = EXIT_FAILURE; + } + + if (addl_done_hook) errc = addl_done_hook() || errc; + return errc; +} + +char* strremove(char *s, const char *sub) { + size_t len = strlen(sub); + if (len == 0) return s; + + char *p = s; + while ((p = strstr(p, sub)) != NULL) { + memmove(p, p + len, strlen(p + len) + 1); + } + + return s; +} + +char* strreplace(char *s, char r, char c) { + for (char *p = s; *p; p++) { + if (*p == r) *p = c; + } + + return s; +} + +char* strupper(const char *s) { + char *capped = calloc(strlen(s) + 1, 1); + for (size_t i = 0; i < strlen(s); i++) { + capped[i] = s[i]; + if (capped[i] >= 'a' && capped[i] <= 'z') capped[i] -= ('a' - 'A'); + } + + return capped; +} + +void splitenv(const char *name, char ***target, size_t *target_len, const char **extra, size_t extra_len) { + char *envvar = getenv(name); + if (envvar == NULL) { + fprintf(stderr, "environment variable %s is undefined\n", name); + exit(EXIT_FAILURE); + } + + *target_len = 1; + for (char *p = envvar; *p; p++) { + if (*p == ';') (*target_len)++; + } + + char *envcopy = malloc(strlen(envvar) + 1); + memcpy(envcopy, envvar, strlen(envvar) + 1); + + *target = calloc(*target_len + extra_len, sizeof(char*)); + char *p = envcopy; + for (size_t i = 0; i < *target_len; i++) { + (*target)[i] = p; + + while (*p != ';' && *p != 0) p++; + *p = 0; + p++; + } + + if (extra) { + for (size_t i = 0; i < extra_len; i++) { + (*target)[*target_len + i] = (char *)extra[i]; + } + + *target_len += extra_len; + } + + if (*target_len > UINT16_MAX) { + fprintf(stderr, "too many values defined; max: %u, got: %zu\n", UINT16_MAX, *target_len); + exit(EXIT_FAILURE); + } +} + +char* fload(const char *filename) { + char *buf = NULL; + FILE *f = fopen(filename, "rb"); + if (f == NULL) { + fprintf(stderr, "could not open file '%s': %s\n", filename, strerror(errno)); + return NULL; + } + + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + if (fsize < 0) { + fprintf(stderr, "could not open file '%s': %s\n", filename, strerror(errno)); + goto cleanup; + } + + if ((buf = malloc(fsize + 1)) == NULL) { + fprintf(stderr, "allocation failure for file '%s': %s", filename, strerror(errno)); + goto cleanup; + } + + fread(buf, 1, fsize, f); + buf[fsize] = 0; + +cleanup: + fclose(f); + return buf; +} + +char* pathjoin(const char *basedir, const char *subdir, const char *file) { + size_t size = strlen(basedir) + 1 + strlen(file) + 1 + (subdir ? strlen(subdir) + 1 : 0); + char *path = malloc(size); + if (subdir) snprintf(path, size, "%s/%s/%s", basedir, subdir, file); + else snprintf(path, size, "%s/%s", basedir, file); + return path; +} + +char* guardify(const char *path) { + char *guarded = strupper(path); + for (size_t i = 0; i < strlen(guarded); i++) { + if (guarded[i] >= '0' && guarded[i] <= '9') continue; + if (guarded[i] >= 'A' && guarded[i] <= 'Z') continue; + guarded[i] = '_'; + } + + return guarded; +} + +int fdump_blob(const void *data, size_t size, const char *dest) { + char *full_path = pathjoin(s_output_dir, NULL, dest); + FILE *outf = fopen(full_path, "wb"); + if (!outf) { + fprintf(stderr, "error opening '%s': %s\n", full_path, strerror(errno)); + return EXIT_FAILURE; + } + + long written = fwrite(data, 1, size, outf); + if (written != (long)size) { + fprintf(stderr, "incomplete write to '%s': %s\n", full_path, strerror(errno)); + fclose(outf); + return EXIT_FAILURE; + } + + fclose(outf); + free(full_path); + return EXIT_SUCCESS; +} + +int fdump_narc(nitroarc_packer_t *p, const char *dest, bool ok) { + char *data = NULL; + u32 size = 0; + + int errc = nitroarc_pseal(p, (void **)&data, &size); + if (errc) { + fprintf(stderr, "error sealing archive '%s': %s\n", dest, nitroarc_errs(errc)); + return errc; + } + + if (ok) errc = fdump_blob(data, size, dest); + + free(data); + return errc; +} + +int fdump_blobnarc(const void *data, u32 size, const char *name) { + nitroarc_packer_t p = default_packer(); + nitroarc_pinit(&p, 1, false, false); + nitroarc_ppack(&p, (void *)data, size, NULL); + return fdump_narc(&p, name, true); +} + +#define MAX_INCLUDES 16 + +static const char *include_paths[MAX_INCLUDES] = { + REPO_INCLUDE, + REPO_BUILD, +}; + +static enum_t dp_include( + const char *from_file, + const char *with_prefix, + const char *for_type, + bool from_defs, + FILE *depfile +) { + assert(from_file && "included filename must not be NULL"); + + char *found_file = NULL; + for (int i = 0; i < MAX_INCLUDES && include_paths[i]; i++) { + char *full_path = pathjoin(include_paths[i], NULL, from_file); + if (access(full_path, F_OK) == 0) { found_file = full_path; break; } + else free(full_path); + } + + if (!found_file) { + fprintf(stderr, "could not find included file '%s'\n", from_file); + exit(EXIT_FAILURE); + } + + char *buf = fload(found_file); + enum_t result = from_defs + ? enum_parse_def(buf, with_prefix, ENUM_F_SORT | ENUM_F_CONVERT) + : enum_parse_one(buf, ENUM_F_SORT | ENUM_F_CONVERT, NULL); + + dp_register((lookup_t *)result.syms, result.len, for_type); + fputs(found_file, depfile); + fputc(' ', depfile); + + free(buf); + free(found_file); + return result; +} diff --git a/tools/dataproc/src/common.h b/tools/dataproc/src/common.h new file mode 100644 index 0000000000..7eb4a5ad10 --- /dev/null +++ b/tools/dataproc/src/common.h @@ -0,0 +1,122 @@ +#ifndef DATAPROC_COMMON_H +#define DATAPROC_COMMON_H + +#include +#include +#include + +#include "dataproc.h" +#include "nitroarc.h" + +#define countof(_a) (sizeof(_a)/sizeof(*(_a))) + +#define POKEPLATINUM_GENERATED_ENUM +#define POKEPLATINUM_GENERATED_LOOKUP +#define POKEPLATINUM_GENERATED_LOOKUP_IMPL + +#define ALIGN_4 __attribute__((aligned(4))) + +#define dp_regmetang(type) dp_register((lookup_t *)lookup__##type, lengthof__##type, #type) + +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; + +#define BOOL bool +#define TRUE true +#define FALSE false + +#define maxbit(bits) ((1 << bits) - 1) +#define boolean(path) (dp_bool(dp_get(df, path))) +#define u8(path) (dp_u8(dp_get(df, path))) +#define s8(path) (dp_s8(dp_get(df, path))) +#define u8_maxbits(path, bits) (u8)(dp_u8range(dp_get(df, path), 0, maxbit(bits)) & maxbit(bits)) +#define enum_u8(path, type) (dp_u8(dp_lookup(dp_get(df, path), #type))) +#define enum_u16(path, type) (dp_u16(dp_lookup(dp_get(df, path), #type))) +#define string(path) (dp_string(dp_get(df, path))) + +// Template-struct for an enum-based lookup-table to be used for type-checking +// string identifiers. Provide the base filename and the type-name that should +// be used for reporting errors. Optionally, provide a prefix to filter the +// values that are loaded from the input file. +// +// When `from_defs` is set to `true`, the lookup-table will be generated from +// `#define`d constants rather than `enum` members. +typedef struct enum_template enum_template_t; +struct enum_template { + const char *from_file; + const char *with_prefix; + const char *for_type; + + bool from_defs; +}; + +// Template-struct for an archive to be written. Specify the output filename +// and the expected number of files, as well as if you want a named or stripped +// archive. +typedef struct archive_template archive_template_t; +struct archive_template { + nitroarc_packer_t packer; + + const char *out_filename; + uint16_t num_files; + unsigned named; + unsigned stripped; +}; + +// Template-struct for a header file to be written. Specify the output filename +// and an optional header and footer for the content. +typedef struct header_template header_template_t; +struct header_template { + const char *out_filename; + FILE *out_file; + char *header; + char *footer; +}; + +// Common initialization routine. Instantiate requested lookup-tables and +// prepare requested outputs. Output header-files will also search the data +// directory for a file with ".template" suffixed to the output's basename. +// +// `pre_init_hook`, when provided, will be called after the base library is +// initialized, but before any requests are fulfilled. +// +// `post_init_hook`, when provided, will be called as the function exits, after +// all requests have been fulfilled. +void common_init( + enum format format, + enum_template_t *lookups, + archive_template_t *archives, + header_template_t *headers, + const char *source_name, + const char *depfile_name, + const char *output_dir, + void (*pre_init_hook)(void), + void (*post_init_hook)(void) +); + +// Common completion routine. Write the footer-content for each output header, +// seal the initialized archives, and dump everything to disk. +// +// `addl_done_hook`, when provided, will be called right before the routine +// completes, and any failure-code returned by it will propagate into the return +// for this function. +int common_done(int errc, int (*addl_done_hook)(void)); + +char* strremove(char *s, const char *sub); +char* strreplace(char *s, char r, char c); +char* strupper(const char *s); + +void splitenv(const char *name, char ***target, size_t *target_len, const char **extra, size_t extra_len); +char* fload(const char *filename); +char* pathjoin(const char *basedir, const char *subdir, const char *file); +char* guardify(const char *path); + +int fdump_blob(const void *data, size_t size, const char *dest); // write_blob +int fdump_narc(nitroarc_packer_t *p, const char *dest, bool ok); // write_narc +int fdump_blobnarc(const void *data, u32 size, const char *dest); // write_narc_onefile + +#endif // DATAPROC_COMMON_H diff --git a/tools/dataproc/src/enum.c b/tools/dataproc/src/enum.c new file mode 100644 index 0000000000..1e4d534ad0 --- /dev/null +++ b/tools/dataproc/src/enum.c @@ -0,0 +1,273 @@ +#include "enum.h" + +#include +#include +#include +#include +#include + +// Duplicate a string onto the heap. +static char* strdup_(const char *s) { + size_t l = strlen(s); + char *d = calloc(l + 1, sizeof(*d)); + memcpy(d, s, l); + return d; +} + +// Find the first occurrence of `c` (or `\0`) within `s`. +static char* strchrnul_(const char *s, int c) { + while (s && *s && *s != c) s++; + return (char *)s; +} + +// Find the first occurrence of a character OTHER than `c` (or `\0`) within `s`. +static char* strnchr(const char *s, int c) { + while (s && *s && *s == c) s++; + return (char *)s; +} + +// Locates the first occurrence of `c` (or `\0`) within `*s` and replaces it +// with `\0`. `*s` is then advanced to the succeeding character, and the +// original value of `*s` is returned. +// +// If `*s` is NULL, then NULL is returned immediately. +static char* strcsep(char **s, char c) { + char *p = *s; + + char *e = strchrnul_(p, c); + if (*e) *e++ = 0; else e = 0; + + *s = e; + return p; +} + +// Locates the first occurrence of any character from `charset` within `*s` and +// replaces it with `\0`. `*s` is then advanced to the succeeding character, and +// the original value of `*s` is returned. +// +// If `*s` is NULL, then NULL is returned immediately. +// +// Generalization of `strcsep` for multiple-characters. +static char* strssep(char **s, const char *charset) { + char *p = *s; + if (!p) return NULL; + + char *e = p + strcspn(p, charset); + if (*e) *e++ = 0; else e = 0; + + *s = e; + return p; +} + +#define INIT_CAP 256 + +static int push_symval(enum_t *table, const char *tok, const char *val, unsigned flags) { + if (table->len + 1 >= table->cap) { + size_t new_cap = table->cap * 3 / 2; + symb_t *new_sym = realloc(table->syms, new_cap * sizeof(*table->syms)); + if (new_sym == NULL) return 1; + + table->syms = new_sym; + table->cap = new_cap; + } + + if (flags & ENUM_F_CONVERT) { // if val == NULL, get previous (syms[len-1]) and increment + if (val == NULL) { + const symb_t *prev_sym = &table->syms[table->len - 1]; + table->syms[table->len++] = (symb_t){ + .tok = (char *)tok, + .val_int = prev_sym->val_int + 1, + }; + } + else { + table->syms[table->len++] = (symb_t){ + .tok = (char *)tok, + .val_int = strtol(val, NULL, 0), + }; + } + } + else { + table->syms[table->len++] = (symb_t){ + .tok = (char *)tok, + .val_lit = (char *)val + }; + } + + return 0; +} + +static int compare_tok(const void *lhs, const void *rhs) { + const symb_t *sym_lhs = lhs; + const symb_t *sym_rhs = rhs; + return strcmp(sym_lhs->tok, sym_rhs->tok); +} + +static int push_define(enum_t *table, char **s, unsigned flags) { + char *sym = strcsep(s, ' '); + char *val = strnchr(*s, ' '); + + return (val && *val) ? push_symval(table, sym, val, flags) : 0; +} + +static void push_enum_member(enum_t *table, char **s, unsigned flags) { + char *line = strnchr(strcsep(s, '\n'), ' '); + char *sym = strssep(&line, " ,"); + char *equ = strnchr(line, ' '); + char *val = equ && *equ == '=' ? strnchr(equ + 1, ' ') : NULL; + char *end = strchrnul_(val, ','); + + if (end) *end = 0; + if (*sym) push_symval(table, sym, val, flags); +} + +enum_t enum_parse_def(const char *buf, const char *prefix, unsigned flags) { + enum_t ret = { + .name = prefix ? (char *)prefix : NULL, + .pool = strdup_(buf), + .syms = calloc(INIT_CAP, sizeof(*ret.syms)), + .cnv = !!(flags & ENUM_F_CONVERT), + .len = 0, + .cap = INIT_CAP, + }; + + char *s = ret.pool; + size_t len = prefix ? strlen(prefix) : 0; + + while (*s) { + char *line = strcsep(&s, '\n'); + + if (strncmp(line, "#define ", 8) != 0) continue; else line += 8; + if (prefix && strncmp(line, prefix, len) != 0) continue; + if (push_define(&ret, &line, flags) != 0) break; + } + + if (flags & ENUM_F_SORT) qsort(ret.syms, ret.len, sizeof(*ret.syms), compare_tok); + return ret; +} + +enum_t enum_parse_one(const char *buf, unsigned flags, char **endptr) { + enum_t ret = { + .name = NULL, + .pool = strdup_(buf), + .syms = calloc(INIT_CAP, sizeof(*ret.syms)), + .cnv = !!(flags & ENUM_F_CONVERT), + .len = 0, + .cap = INIT_CAP, + }; + + char *s = ret.pool; + while (*s) { + char *line = strcsep(&s, '\n'); + + if (strncmp(line, "enum ", 5) != 0) continue; else line += 5; + if (*line != '{') { + ret.name = strssep(&line, " {"); + } + + // NOTE: assumption: opening brace is on the same line, and members + // are defined one-per-line + + while (*s && strncmp(s, "};", 2) != 0) { + push_enum_member(&ret, &s, flags); + } + + s += 2; + break; // stop after the one `enum` is found + } + + if (flags & ENUM_F_SORT) qsort(ret.syms, ret.len, sizeof(*ret.syms), compare_tok); + if (endptr) *endptr = (char *)buf + (s - ret.pool); + return ret; +} + +static enum_t* push_subtable(enums_t *tables, const char *name, unsigned flags) { + if (tables->len + 1 >= tables->cap) { + size_t new_cap = tables->cap * 3 / 2; + enum_t *new_arr = realloc(tables->enums, new_cap * sizeof(*tables->enums)); + if (new_arr == NULL) return NULL; + + tables->enums = new_arr; + tables->cap = new_cap; + } + + enum_t *next = &tables->enums[tables->len++]; + next->name = (char *)name; + next->pool = NULL; + next->syms = calloc(INIT_CAP, sizeof(*next->syms)); + next->cnv = !!(flags & ENUM_F_CONVERT); + next->len = 0; + next->cap = INIT_CAP; + + return next; +} + +static int compare_table(const void *lhs, const void *rhs) { + const enum_t *table_lhs = lhs; + const enum_t *table_rhs = rhs; + return strcmp(table_lhs->name, table_rhs->name); +} + +enums_t enum_parse_all(const char *buf, unsigned flags) { + enums_t ret = { + .enums = calloc(INIT_CAP, sizeof(*ret.enums)), + .pool = strdup_(buf), + .len = 0, + .cap = INIT_CAP, + }; + + push_subtable(&ret, NULL, flags); + + char *s = ret.pool; + while (*s) { + char *line = strcsep(&s, '\n'); + enum_t *sub = &ret.enums[0]; + + if (strncmp(line, "enum ", 5) == 0) { + line += 5; + if (*line != '{') { + sub = push_subtable(&ret, strssep(&line, " {"), flags); + if (sub == NULL) break; + } + + while (*s && strncmp(s, "};", 2) != 0) { + push_enum_member(sub, &s, flags); + } + } + else if (strncmp(line, "#define ", 8) == 0) { + line += 8; + push_define(sub, &line, flags); + } + else continue; + } + + if (flags & ENUM_F_SORT) { + for (size_t i = 0; i < ret.len; i++) { + qsort(ret.enums[i].syms, + ret.enums[i].len, + sizeof(*ret.enums[i].syms), + compare_tok); + } + + qsort(ret.enums + 1, ret.len - 1, sizeof(*ret.enums), compare_table); + } + return ret; +} + +void enum_free(enum_t *table) { + free(table->pool); + free(table->syms); + + table->name = NULL; + table->pool = NULL; + table->syms = NULL; +} + +void enum_free_all(enums_t *tables) { + for (size_t i = 0; i < tables->len; i++) enum_free(&tables->enums[i]); + + free(tables->enums); + free(tables->pool); + + tables->enums = NULL; + tables->pool = NULL; +} diff --git a/tools/dataproc/src/enum.h b/tools/dataproc/src/enum.h new file mode 100644 index 0000000000..d9fec70887 --- /dev/null +++ b/tools/dataproc/src/enum.h @@ -0,0 +1,78 @@ +#ifndef ENUM_H +#define ENUM_H + +#include +#include + +typedef struct symb_t symb_t; +struct symb_t { + union { + char *val_lit; + long val_int; + }; + char *tok; +}; + +typedef struct enum_t enum_t; +struct enum_t { + char *name; + char *pool; + symb_t *syms; + bool cnv; + size_t len; + size_t cap; +}; + +typedef struct enums_t enums_t; +struct enums_t { + enum_t *enums; + char *pool; + size_t len; + size_t cap; +}; + +#define ENUM_F_SORT (1 << 0) +#define ENUM_F_CONVERT (1 << 1) + +// Parse a C file `buf` for defined preprocesor tokens with replacement values +// and return them as a symbol-table. If `prefix` is given as non-`NULL`, it +// will be used to filter preprocessor tokens from `buf` that are included in +// the output. +// +// If `flags` contains `ENUM_F_SORT`, then symbols in the output table will be +// sorted lexicographically by their tokens. +// +// If `flags` contains `ENUM_F_CONVERT`, then numeric strings will be parsed +// into integer values. +enum_t enum_parse_def(const char *buf, const char *prefix, unsigned flags); + +// Parse a C file `buf` for a named `enum` and return its member-names as a +// symbol-table. If `e_name` is given as `NULL`, then this routine will return +// an empty symbol-table; otherwise, its value is used to find a matching `enum` +// from `buf` whose member-names shall be loaded into the output. +// +// If `flags` contains `ENUM_F_SORT`, then symbols in the output table will be +// sorted lexicographically by their tokens. +// +// If `flags` contains `ENUM_F_CONVERT`, then numeric strings will be parsed +// into integer values. +// +// If `endptr` is not `NULL`, then `*endptr` will be set to the next unprocessed +// character in `buf` upon exit. +enum_t enum_parse_one(const char *buf, unsigned flags, char **endptr); + +// Parse a C file `buf` for both `enum`s and defined preprocessor tokens and +// return them as a table of symbol-tables. All preprocessor tokens and members +// of unnamed `enum`s will be present in the symbol-table at `enums[0]`; members +// of named `enum`s will be present in their own individual symbol-tables. +// +// If `flags` contains `ENUM_F_SORT`, then symbols in the output tables will be +// sorted lexicographically by their tokens, and all named output tables wiil be +// sorted lexicographically by their names. +enums_t enum_parse_all(const char *buf, unsigned flags); + +// Free allocations in a symbol-table. +void enum_free(enum_t *table); +void enum_free_all(enums_t *tables); + +#endif // ENUM_H diff --git a/tools/dataproc/src/speciesproc.c b/tools/dataproc/src/speciesproc.c new file mode 100644 index 0000000000..2488e7b601 --- /dev/null +++ b/tools/dataproc/src/speciesproc.c @@ -0,0 +1,746 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "dataproc.h" +#include "nitroarc.h" + +// IWYU pragma: begin_keep +#include "constants/items.h" +#include "generated/abilities.h" +#include "generated/egg_groups.h" +#include "generated/exp_rates.h" +#include "generated/evolution_methods.h" +#include "generated/footprint_sizes.h" +#include "generated/gender_ratios.h" +#include "generated/moves.h" +#include "generated/pal_park_land_area.h" +#include "generated/pal_park_water_area.h" +#include "generated/pokemon_colors.h" +#include "generated/pokemon_types.h" +#include "generated/shadow_sizes.h" +#include "generated/species.h" +#include "generated/tutor_locations.h" +// IWYU pragma: end_keep + +#include "struct_defs/species.h" +#include "struct_defs/species_sprite_data.h" +#include "struct_defs/sprite_animation_frame.h" + +typedef struct SpeciesLearnsetSized { + SpeciesLearnset data; + size_t size; +} SpeciesLearnsetSized; + +typedef struct SpeciesEvolutionList { + SpeciesEvolution data[MAX_EVOLUTIONS] ALIGN_4; +} SpeciesEvolutionList; + +typedef struct SpeciesHeights { + u8 back_female; + u8 back_male; + u8 front_female; + u8 front_male; + bool has_female; + bool has_male; +} SpeciesHeights; + +typedef struct Container { + SpeciesData personal; + SpeciesEvolutionList evolutions; + SpeciesLearnsetSized levelup_learnset; + SpeciesPalPark palpark; + SpeciesHeights heights; + SpeciesSpriteData spritedata; + enum Species offspring; +} Container; + +static SpeciesData proc_personal(datafile_t *df); +static SpeciesEvolutionList proc_evolutions(datafile_t *df); +static SpeciesLearnsetSized proc_lvlearnset(datafile_t *df); +static SpeciesPalPark proc_palpark(datafile_t *df, size_t i); +static enum Species proc_offspring(datafile_t *df, size_t i); +static SpeciesHeights proc_heights(datafile_t *df, enum GenderRatio ratio); +static SpeciesSpriteData proc_spritedata(datafile_t *df); + +static void emit_tutorables(datafile_t *df, size_t i); +static void emit_eggmoves(datafile_t *df, const char *species_upper); +static void emit_footprints(datafile_t *df, size_t i, const char *species_upper); +static void emit_iconpalettes(datafile_t *df, size_t i, const char *species); +static void pack(Container *species, size_t i); + +static void pre_init(void); +static void post_init(void); +static int addl_done(void); +static void parse_args(int *pargc, char ***pargv); + +static char *program_name = NULL; +static char *base_dir = NULL; +static char *depfile_fpath = "species_data.d"; +static char *tutorables_fpath = "move_tutors.json"; +static char *output_dir = "."; +static char **registry = NULL; +static size_t len_registry = 0; + +static enum_template_t enums[] = { + { .from_file = "constants/footstep_house.h", .with_prefix = "FOOTPRINT_TYPE", .for_type = "FOOTPRINT_TYPE", .from_defs = true }, + { .from_file = NULL }, +}; + +static archive_template_t archives[] = { + { .out_filename = "pl_personal.narc" }, + { .out_filename = "evo.narc" }, + { .out_filename = "wotbl.narc" }, + { .out_filename = "height.narc" }, + { .out_filename = NULL }, +}; + +static header_template_t headers[] = { + { .out_filename = "tutorable_moves.h" }, + { .out_filename = "species_learnsets_by_tutor.h" }, + { .out_filename = "species_egg_moves.h" }, + { .out_filename = "species_footprint_sizes.h" }, + { .out_filename = "species_footprint_types.h" }, + { .out_filename = "species_icon_palettes.h" }, + { .out_filename = NULL, }, +}; + +static const char *alt_forms_with_data[] = { // NOTE: also implicitly defines the ordering of these forms + "deoxys/forms/attack", + "deoxys/forms/defense", + "deoxys/forms/speed", + "wormadam/forms/sandy", + "wormadam/forms/trash", + "giratina/forms/origin", + "shaymin/forms/sky", + "rotom/forms/heat", + "rotom/forms/wash", + "rotom/forms/frost", + "rotom/forms/fan", + "rotom/forms/mow", +}; + +#define NATIONAL_DEX_MAX SPECIES_EGG + +#define SOURCE_NAME "tools/dataproc/src/speciesproc.c" + +int main(int argc, char **argv) { + parse_args(&argc, &argv); + + splitenv("SPECIES", ®istry, &len_registry, alt_forms_with_data, countof(alt_forms_with_data)); + archives[0].num_files = (u16)len_registry; + archives[1].num_files = (u16)len_registry; + archives[2].num_files = (u16)len_registry; + archives[3].num_files = (u16)(4 * NATIONAL_DEX_MAX); + + common_init(DATAPROC_F_JSON, enums, archives, headers, __FILE__, depfile_fpath, output_dir, pre_init, post_init); + + datafile_t df_d = { 0 }; + datafile_t df_s = { 0 }; + unsigned errc = EXIT_SUCCESS; + + for (size_t i = 0; i < len_registry; i++) { + char *path_d = pathjoin(base_dir, registry[i], "data.json"); + char *path_s = pathjoin(base_dir, registry[i], "sprite_data.json"); + + if (dp_load(&df_d, path_d) == 0) { + char *capped = strupper(registry[i]); + + Container species = { + .personal = proc_personal(&df_d), + .evolutions = proc_evolutions(&df_d), + .levelup_learnset = proc_lvlearnset(&df_d), + .palpark = proc_palpark(&df_d, i), + .offspring = proc_offspring(&df_d, i), + }; + + if (i < NATIONAL_DEX_MAX && dp_load(&df_s, path_s) == 0) { + enum GenderRatio ratio = species.personal.genderRatio; + if (i == SPECIES_NONE) ratio = GENDER_RATIO_FEMALE_50; + + species.heights = proc_heights(&df_s, ratio); + species.spritedata = proc_spritedata(&df_s); + } + + emit_tutorables(&df_d, i); + emit_eggmoves(&df_d, capped); + emit_footprints(&df_d, i, capped); + emit_iconpalettes(&df_d, i, registry[i]); + pack(&species, i); + + free(capped); + } + + if (df_d.diag_head && dp_report(&df_d) == DIAG_ERROR) errc = EXIT_FAILURE; + if (df_s.diag_head && dp_report(&df_s) == DIAG_ERROR) errc = EXIT_FAILURE; + + free(path_d); + free(path_s); + dp_free(&df_d); + dp_free(&df_s); + } + + return common_done(errc, addl_done); +} + +static u8 *palpark = NULL; +static u16 *offspring = NULL; +static u8 *sprite_data = NULL; + +static void pre_init(void) { + dp_regmetang(Ability); + dp_regmetang(EggGroup); + dp_regmetang(ExpRate); + dp_regmetang(EvolutionMethod); + dp_regmetang(FootprintSize); + dp_regmetang(GenderRatio); + dp_regmetang(Item); + dp_regmetang(Move); + dp_regmetang(PalParkLandArea); + dp_regmetang(PalParkWaterArea); + dp_regmetang(PokemonColor); + dp_regmetang(PokemonType); + dp_regmetang(ShadowSize); + dp_regmetang(Species); + dp_regmetang(TutorLocation); + + palpark = calloc(NATIONAL_DEX_MAX - 1, sizeof(SpeciesPalPark)); // NOTE: SPECIES_NONE does not have an entry + offspring = calloc(len_registry, sizeof(u16)); + sprite_data = calloc(NATIONAL_DEX_MAX, sizeof(SpeciesSpriteData)); +} + +static datafile_t df_tutorables = { 0 }; +static const char **tutorable_moves = NULL; +static size_t len_tutorable_moves = 0; +static size_t tutorset_size = 0; +static u8 *tutorset = NULL; // NOTE: shared and cleared between species + +static void free_tutorables(void) { + free(tutorable_moves); + free(tutorset); + dp_free(&df_tutorables); +} + +static void post_init(void) { + static FILE **f_tutorables = &headers[0].out_file; + + if (dp_load(&df_tutorables, tutorables_fpath) == 0) { + datanode_t array = dp_get(&df_tutorables, "."); + len_tutorable_moves = dp_arrlen(array); + tutorable_moves = calloc(len_tutorable_moves, sizeof(*tutorable_moves)); + + for (size_t i = 0; i < len_tutorable_moves; i++) { + datanode_t elem = dp_arrelem(array, i); + datanode_t move = dp_objmemb(elem, "move"); + if (dp_lookup(move, "Move").type == DATAPROC_T_ERR) continue; + + tutorable_moves[i] = dp_string(move); + fprintf(*f_tutorables, " { %s, %u, %u, %u, %u, %s },\n", + tutorable_moves[i], + dp_u8(dp_objmemb(elem, "redCost")), + dp_u8(dp_objmemb(elem, "blueCost")), + dp_u8(dp_objmemb(elem, "yellowCost")), + dp_u8(dp_objmemb(elem, "greenCost")), + dp_string(dp_lookup_s(dp_objmemb(elem, "location"), "TutorLocation"))); + } + + tutorset_size = (len_tutorable_moves + 7) / 8; + tutorset = calloc(tutorset_size, sizeof(*tutorset)); + } + + if (df_tutorables.diag_head && dp_report(&df_tutorables) == DIAG_ERROR) { + exit(EXIT_FAILURE); + } + + atexit(free_tutorables); +} + +static void proc_tmlearnset(datafile_t *df, SpeciesData *out); + +static SpeciesData proc_personal(datafile_t *df) { + SpeciesData personal = { + .baseStats = { + .hp = u8(".base_stats.hp"), + .attack = u8(".base_stats.attack"), + .defense = u8(".base_stats.defense"), + .speed = u8(".base_stats.speed"), + .spAttack = u8(".base_stats.special_attack"), + .spDefense = u8(".base_stats.special_defense"), + }, + + .types = { + enum_u8(".types[0]", PokemonType), + enum_u8(".types[1]", PokemonType), + }, + + .abilities = { + enum_u8(".abilities[0]", Ability), + enum_u8(".abilities[1]", Ability), + }, + + .evYields = { + .hp = u8_maxbits(".ev_yields.hp", 2), + .attack = u8_maxbits(".ev_yields.attack", 2), + .defense = u8_maxbits(".ev_yields.defense", 2), + .speed = u8_maxbits(".ev_yields.speed", 2), + .spAttack = u8_maxbits(".ev_yields.special_attack", 2), + .spDefense = u8_maxbits(".ev_yields.special_defense", 2), + }, + + .wildHeldItems = { + .common = enum_u16(".held_items.common", Item), + .rare = enum_u16(".held_items.rare", Item), + }, + + .eggGroups = { + enum_u8(".egg_groups[0]", EggGroup), + enum_u8(".egg_groups[1]", EggGroup), + }, + + .baseExpReward = u8(".base_exp_reward"), + .baseFriendship = u8(".base_friendship"), + .bodyColor = (u8)(enum_u8(".body_color", PokemonColor) & maxbit(7)), + .catchRate = u8(".catch_rate"), + .expRate = enum_u8(".exp_rate", ExpRate), + .flipSprite = boolean(".flip_sprite"), + .genderRatio = enum_u8(".gender_ratio", GenderRatio), + .hatchCycles = u8(".hatch_cycles"), + .safariFleeRate = u8(".safari_flee_rate"), + + .tmLearnsetMasks = { 0 }, + }; + + proc_tmlearnset(df, &personal); + return personal; +} + +static void proc_tmlearnset(datafile_t *df, SpeciesData *out) { + datanode_t tm_learnset = dp_get(df, ".learnset.by_tm"); + for (size_t i = 0; i < dp_arrlen(tm_learnset); i++) { + datanode_t dn = dp_arrelem(tm_learnset, i); + const char *entry = dp_string(dn); + + long value = 0; + char *endptr = NULL; + if (entry == NULL) continue; // not a string, error is already recorded + if ((entry[0] == 'T' || entry[0] == 'H') && entry[1] == 'M') { + value = strtol(&entry[2], &endptr, 10); + if (*endptr != 0 || value < 1) goto errmarker; + if (entry[0] == 'T' && (size_t)value > NUM_TMS) goto errmarker; + if (entry[0] == 'H' && (size_t)value > NUM_HMS) goto errmarker; + + value -= 1; + value += NUM_TMS * (entry[0] == 'H'); + } + else { + errmarker: + dp_error(&dn, "expected entry to be a valid TM or HM marker"); + continue; + } + + size_t idx = value / 32; + size_t bit = value % 32; + out->tmLearnsetMasks[idx] |= ((unsigned)1 << bit); // NOTE: cast is required here to avoid SIGILL + } +} + +static SpeciesEvolutionList proc_evolutions(datafile_t *df) { + SpeciesEvolutionList result = { 0 }; + + datanode_t evolutions = dp_get(df, ".evolutions"); + size_t len_evos = dp_arrlen(evolutions); + if (len_evos > MAX_EVOLUTIONS) { + dp_error(&evolutions, + "expected at most %u entries, but found %zu", + MAX_LEARNSET_ENTRIES, len_evos); + return result; + } + + for (size_t i = 0; i < len_evos; i++) { + datanode_t entry = dp_arrelem(evolutions, i); + datanode_t method = dp_lookup(dp_arrelem(entry, 0), "EvolutionMethod"); + if (method.type == DATAPROC_T_ERR) continue; // Don't bother if the evolution method is invalid + + datanode_t param = { .type = DATAPROC_T_MAPPED, .mapped = 0 }; + datanode_t species = { 0 }; + switch ((enum EvolutionMethod)dp_u16(method)) { + case EVO_NONE: + case EVO_LEVEL_HAPPINESS: + case EVO_LEVEL_HAPPINESS_DAY: + case EVO_LEVEL_HAPPINESS_NIGHT: + case EVO_TRADE: + case EVO_LEVEL_MAGNETIC_FIELD: + case EVO_LEVEL_MOSS_ROCK: + case EVO_LEVEL_ICE_ROCK: + species = dp_lookup(dp_arrelem(entry, 1), "Species"); + break; + + case EVO_LEVEL: + case EVO_LEVEL_ATK_GT_DEF: + case EVO_LEVEL_ATK_EQ_DEF: + case EVO_LEVEL_ATK_LT_DEF: + case EVO_LEVEL_PID_LOW: + case EVO_LEVEL_PID_HIGH: + case EVO_LEVEL_NINJASK: + case EVO_LEVEL_SHEDINJA: + case EVO_LEVEL_BEAUTY: + case EVO_LEVEL_MALE: + case EVO_LEVEL_FEMALE: + param = dp_arrelem(entry, 1); + species = dp_lookup(dp_arrelem(entry, 2), "Species"); + break; + + case EVO_TRADE_WITH_HELD_ITEM: + case EVO_USE_ITEM: + case EVO_USE_ITEM_MALE: + case EVO_USE_ITEM_FEMALE: + case EVO_LEVEL_WITH_HELD_ITEM_DAY: + case EVO_LEVEL_WITH_HELD_ITEM_NIGHT: + param = dp_lookup(dp_arrelem(entry, 1), "Item"); + species = dp_lookup(dp_arrelem(entry, 2), "Species"); + break; + + case EVO_LEVEL_KNOW_MOVE: + param = dp_lookup(dp_arrelem(entry, 1), "Move"); + species = dp_lookup(dp_arrelem(entry, 2), "Species"); + break; + + case EVO_LEVEL_SPECIES_IN_PARTY: + param = dp_lookup(dp_arrelem(entry, 1), "Species"); + species = dp_lookup(dp_arrelem(entry, 2), "Species"); + break; + + default: + // NOTE: This is a sanity-check for users that add a new EvolutionMethod + // but fail to add a case to this switch. + dp_error(&method, "no handler implemented for EvolutionMethod member '%s'", + dp_string(dp_arrelem(entry, 0))); + break; + } + + result.data[i] = (SpeciesEvolution){ + .method = dp_u16(method), + .param = dp_u16(param), + .targetSpecies = dp_u16(species), + }; + } + + return result; +} + +static SpeciesLearnsetSized proc_lvlearnset(datafile_t *df) { + SpeciesLearnsetSized result = { 0 }; + + datanode_t lv_learnset = dp_get(df, ".learnset.by_level"); + size_t len_learnset = dp_arrlen(lv_learnset); + if (len_learnset > MAX_LEARNSET_ENTRIES) { + dp_error(&lv_learnset, + "expected at most %u entries, but found %zu", + MAX_LEARNSET_ENTRIES, len_learnset); + return result; + } + + for (size_t i = 0; i < len_learnset; i++) { + datanode_t entry = dp_arrelem(lv_learnset, i); + datanode_t level = dp_arrelem(entry, 0); + datanode_t move = dp_arrelem(entry, 1); + + result.data.entries[result.size].move = (u16)(dp_u16(dp_lookup(move, "Move")) & maxbit(9)); + result.data.entries[result.size].level = (u16)(dp_u8(level) & maxbit(7)); + result.size++; + } + + result.data.entries[result.size].move = (u16)UINT16_MAX & maxbit(9); + result.data.entries[result.size].level = (u16)UINT16_MAX & maxbit(7); + result.size++; + + result.size *= sizeof(SpeciesLearnsetEntry); + result.size += (-result.size & 3); + return result; +} + +static SpeciesPalPark proc_palpark(datafile_t *df, size_t i) { + SpeciesPalPark result = { 0 }; + if (i == SPECIES_NONE || i >= NATIONAL_DEX_MAX) return result; + + datanode_t catching_show = dp_get(df, ".catching_show"); + datanode_t land_area = dp_lookup(dp_objmemb(catching_show, "pal_park_land_area"), "PalParkLandArea"); + datanode_t water_area = dp_lookup(dp_objmemb(catching_show, "pal_park_water_area"), "PalParkWaterArea"); + datanode_t catching_points = dp_objmemb(catching_show, "catching_points"); + datanode_t rarity = dp_objmemb(catching_show, "rarity"); + datanode_t unused_u16 = dp_objmemb(catching_show, "unused"); + + result.landArea = dp_u8(land_area); + result.waterArea = dp_u8(water_area); + result.catchingPoints = dp_u8(catching_points); + result.rarity = dp_u8(rarity); + result.unused.asU16 = dp_u16(unused_u16); + + return result; +} + +static enum Species proc_offspring(datafile_t *df, size_t i) { + return i <= SPECIES_BAD_EGG + ? (enum Species)enum_u16(".offspring", Species) + : (enum Species)i; +} + +static SpeciesHeights proc_heights(datafile_t *df, enum GenderRatio ratio) { + datanode_t back = dp_get(df, ".back.y_offset"); + datanode_t front = dp_get(df, ".front.y_offset"); + + SpeciesHeights result = { 0 }; + + if (ratio != GENDER_RATIO_FEMALE_ONLY) { + result.has_male = true; + result.back_male = dp_u8(dp_objmemb(back, "male")); + result.front_male = dp_u8(dp_objmemb(front, "male")); + } + + if (ratio != GENDER_RATIO_MALE_ONLY && ratio != GENDER_RATIO_NO_GENDER) { + result.has_female = true; + result.back_female = dp_u8(dp_objmemb(back, "female")); + result.front_female = dp_u8(dp_objmemb(front, "female")); + } + + return result; +} + +static SpeciesSpriteAnim proc_spriteanim(datanode_t dn) { + SpeciesSpriteAnim result = { + .animation = dp_u8(dp_objmemb(dn, "animation")), + .cryDelay = dp_u8(dp_objmemb(dn, "cry_delay")), + .startDelay = dp_u8(dp_objmemb(dn, "start_delay")), + }; + + datanode_t frames = dp_objmemb(dn, "frames"); + const size_t num_frames = dp_arrlen(frames); + for (size_t i = 0; i < num_frames; i++) { + datanode_t frame = dp_arrelem(frames, i); + + result.frames[i] = (SpriteAnimFrame){ + .spriteFrame = dp_s8(dp_objmemb(frame, "sprite_frame")), + .frameDelay = dp_u8(dp_objmemb(frame, "frame_delay")), + .xOffset = dp_s8(dp_objmemb(frame, "x_shift")), + .yOffset = dp_s8(dp_objmemb(frame, "y_shift")), + }; + } + + return result; +} + +static SpeciesSpriteData proc_spritedata(datafile_t *df) { + return (SpeciesSpriteData){ + .yOffset = s8(".front.addl_y_offset"), + .xOffsetShadow = s8(".shadow.x_offset"), + .shadowSize = enum_u8(".shadow.size", ShadowSize), + .faceAnims = { + [0] = proc_spriteanim(dp_get(df, ".front")), + [1] = proc_spriteanim(dp_get(df, ".back")), + }, + }; +} + +static FILE **f_tutor_sets = &headers[1].out_file; +static FILE **f_egg_moves = &headers[2].out_file; +static FILE **f_footprint_sizes = &headers[3].out_file; +static FILE **f_footprint_types = &headers[4].out_file; +static FILE **f_icon_palettes = &headers[5].out_file; + +static void emit_tutorables(datafile_t *df, size_t i) { + // NOTE: none of these have any tutorable learnsets, but mechanically-distinct forms + // (e.g., Giratina-Origin) do + if (i == SPECIES_NONE || i == SPECIES_EGG || i == SPECIES_BAD_EGG) return; + + datanode_t dn = dp_get(df, ".learnset.by_tutor"); + memset(tutorset, 0, tutorset_size); + fputs(" { ", *f_tutor_sets); + + for (size_t i = 0; i < dp_arrlen(dn); i++) { + datanode_t move = dp_arrelem(dn, i); + const char *move_s = dp_string(move); + if (dp_lookup(move, "Move").type == DATAPROC_T_ERR) continue; + + const char *found = NULL; + size_t idx = 0; + for (size_t j = 0; j < len_tutorable_moves && !found; j++) { + if (strcmp(tutorable_moves[j], move_s) == 0) { found = tutorable_moves[j]; idx = j; } + } + + if (found) tutorset[idx / 8] |= (u8)(1 << (idx % 8)); + else dp_warn(&move, "'%s' is not available from any move tutors", move_s); + } + + for (size_t i = 0; i < tutorset_size; i++) fprintf(*f_tutor_sets, "0x%02X, ", tutorset[i]); + fputs("},\n", *f_tutor_sets); +} + +static void emit_eggmoves(datafile_t *df, const char *species_upper) { + datanode_t dn = dp_try(df, ".learnset.egg_moves"); + if (dn.type == DATAPROC_T_ERR) return; + + fprintf(*f_egg_moves, " SPECIES_%s + EGG_MOVES_SPECIES_OFFSET,\n", species_upper); + for (size_t i = 0; i < dp_arrlen(dn); i++) { + datanode_t move = dp_arrelem(dn, i); + const char *move_s = dp_string(move); + dp_lookup(move, "Move"); // only to verify that this is a move name + + fprintf(*f_egg_moves, " %s,\n", move_s); + } + + fputc('\n', *f_egg_moves); +} + +static void emit_footprints(datafile_t *df, size_t i, const char *species_upper) { + // NOTE: eggs and alternate-forms do not have any footprint data + if (i >= NATIONAL_DEX_MAX) return; + + datanode_t root = dp_get(df, ".footprint"); + const bool has = dp_bool(dp_objmemb(root, "has")); + const char *size = dp_string(dp_lookup_s(dp_objmemb(root, "size"), "FootprintSize")); + const char *type = dp_string(dp_lookup_s(dp_objmemb(root, "type"), "FOOTPRINT_TYPE")); + + fprintf(*f_footprint_sizes, " { %s, %s },\n", + has ? "TRUE" : "FALSE", + size); + fprintf(*f_footprint_types, " [SPECIES_%s] = { %s, %s },\n", + species_upper, + type, + has || i == SPECIES_SPIRITOMB ? "TRUE" : "FALSE"); +} + +static void emit_iconpalettes(datafile_t *df, size_t i, const char *species) { + // We make a separate-copy to make this fool-proof, since the surrounding functions modify the string + char *capped = strreplace(strremove(strupper(species), "/FORMS"), '/', '_'); + + datanode_t palette = dp_get(df, ".icon_palette"); + if (palette.type == DATAPROC_T_INT) { + long idx = dp_int(palette); + + switch (i) { + default: + fprintf(*f_icon_palettes, " [%s_%s] = %ld,\n", + i > SPECIES_BAD_EGG ? "ICON" : "SPECIES", capped, idx); + break; + + case SPECIES_EGG: fprintf(*f_icon_palettes, " [ICON_EGG] = %ld,\n", idx); break; + case SPECIES_BAD_EGG: fprintf(*f_icon_palettes, " [ICON_MANAPHY_EGG] = %ld,\n", idx); break; + } + + goto cleanup; + } + + // Must be an array of tuples like: [form_name, palette_index] + for (size_t j = 0; j < dp_arrlen(palette); j++) { + datanode_t elem = dp_arrelem(palette, j); + datanode_t form = dp_arrelem(elem, 0); + datanode_t pltt = dp_arrelem(elem, 1); + + if (form.type != DATAPROC_T_ERR && pltt.type != DATAPROC_T_ERR) { + const char *form_s = dp_string(form); + const long pltt_i = dp_int(pltt); + if (strcmp(form_s, "base") == 0) { + fprintf(*f_icon_palettes, " [SPECIES_%s] = %ld,\n", capped, pltt_i); + } + else { + char *form_capped = strupper(form_s); + fprintf(*f_icon_palettes, " [ICON_%s_%s] = %ld,\n", capped, form_capped, pltt_i); + free(form_capped); + } + } + } + +cleanup: + free(capped); +} + +static void pack(Container *species, size_t i) { + nitroarc_ppack(&archives[0].packer, &species->personal, sizeof(SpeciesData), NULL); + nitroarc_ppack(&archives[1].packer, &species->evolutions, sizeof(SpeciesEvolutionList), NULL); + nitroarc_ppack(&archives[2].packer, &species->levelup_learnset.data, (u32)species->levelup_learnset.size, NULL); + + if (i > SPECIES_NONE && i < NATIONAL_DEX_MAX) { + size_t offset = sizeof(SpeciesPalPark) * (i - 1); + memcpy(palpark + offset, &species->palpark, sizeof(SpeciesPalPark)); + } + + if (i < NATIONAL_DEX_MAX) { + nitroarc_ppack(&archives[3].packer, &species->heights.back_female, species->heights.has_female ? 1 : 0, NULL); + nitroarc_ppack(&archives[3].packer, &species->heights.back_male, species->heights.has_male ? 1 : 0, NULL); + nitroarc_ppack(&archives[3].packer, &species->heights.front_female, species->heights.has_female ? 1 : 0, NULL); + nitroarc_ppack(&archives[3].packer, &species->heights.front_male, species->heights.has_male ? 1 : 0, NULL); + + size_t offset = sizeof(SpeciesSpriteData) * i; + memcpy(sprite_data + offset, &species->spritedata, sizeof(SpeciesSpriteData)); + } + + offspring[i] = (u16)species->offspring; +} + +static int addl_done(void) { + return fdump_blobnarc(palpark, sizeof(SpeciesPalPark) * (NATIONAL_DEX_MAX - 1), "ppark.narc") + || fdump_blobnarc(sprite_data, sizeof(SpeciesSpriteData) * NATIONAL_DEX_MAX, "pl_poke_data.narc") + || fdump_blob(offspring, sizeof(u16) * len_registry, "pms.narc") + || EXIT_SUCCESS; +} + +static void usage(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + +static void parse_args(int *pargc, char ***pargv) { + program_name = (*pargv)[0]; + opterr = 0; + + int c = 0; + while ((c = getopt(*pargc, *pargv, "o:t:M:h")) != -1) { + switch (c) { + case 'o': output_dir = optarg; break; + case 't': tutorables_fpath = optarg; break; + case 'M': depfile_fpath = optarg; break; + + case 'h': usage(NULL); break; + case ':': usage("missing argument for '-%c'", optopt); break; + default: usage("unknown option '-%c'", optopt); break; + } + } + + *pargc -= optind; + *pargv += optind; + if (*pargc < 1) usage("missing required argument BASEDIR"); + + base_dir = (*pargv)[0]; +} + +static void usage(const char *fmt, ...) { + FILE *f = stdout; + if (fmt != NULL) { + f = stderr; + + va_list args; + va_start(args, fmt); + fputs(program_name, f); + fputs(": ", f); + vfprintf(f, fmt, args); + fputc('\n', f); + va_end(args); + } + +#define fputf(fmt, ...) fprintf(f, fmt, __VA_ARGS__) + fputf("usage: %s [-M DEPFILE] [-t TUTORABLES_JSON] [-o OUTDIR] BASEDIR\n", program_name); + fputs("\n", f); + fputs("options:\n", f); + fputs(" -o OUTDIR Write output files to OUTDIR. Does not affect DEPFILE.\n", f); + fputs(" Defaults to the current working directory.\n", f); + fputs(" -t TUTORABLES_JSON Specify the full path to the tutorable-moves JSON file.\n", f); + fputs(" Defaults to 'move_tutors.json'.\n", f); + fputs(" -M DEPFILE Specify the full path to an output dependency file.\n", f); + fputs(" Defaults to 'species_data.d'.\n", f); +#undef fputf + + exit(f == stdout ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/tools/devtools/gen_compile_commands.py b/tools/devtools/gen_compile_commands.py index 6cde13a715..0ae2fdebcb 100755 --- a/tools/devtools/gen_compile_commands.py +++ b/tools/devtools/gen_compile_commands.py @@ -321,6 +321,9 @@ dataproc_c_commands = [ f"-I{homedir}/tools/dataproc/lib/include", f"-I{homedir}/include", f"-I{builddir}", + f'-DREPO_INCLUDE="{homedir}/include"', + f'-DREPO_BUILD="{builddir}"', + f'-DTEMPLATES_DIR="{homedir}/tools/dataproc/data"', "-std=gnu17", "-Wall", "-Wextra",