tools: Replace use of datagen_species.cpp with speciesproc.c
Some checks are pending
build / build (push) Waiting to run

This commit is contained in:
Rachel 2026-03-18 16:39:03 -07:00
parent 41c0c2f864
commit d92f23036b
24 changed files with 2157 additions and 1144 deletions

View File

@ -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" ],

View File

@ -121,6 +121,7 @@
"MOVE_KNOCK_OFF"
]
},
"evolutions": [ ],
"pokedex_data": {
"height": 0,
"weight": 0,

View File

@ -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" ],

View File

@ -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

View File

@ -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"
}
}
]

View File

@ -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" ],

View File

@ -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,

View File

@ -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" ]

View File

@ -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) {

View File

@ -1,791 +0,0 @@
/*
* datagen-species
*
* Usage: datagen-species <OUT_DIR> <ROOT_DIR> <FORMS_REGISTRY> <TUTOR_SCHEMA>
*
* This program is responsible for generating data archive from species data files
* (res/pokemon/<species>/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.
*
* <FORMS_REGISTRY> 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.
*
* <TUTOR_SCHEMA> 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 <algorithm>
#include <cctype>
#include <cstddef>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <optional>
#include <regex>
#include <string>
#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/<species>/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<EvolutionMethod>(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<u16>(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<u16>(-1),
.level = static_cast<u16>(-1),
};
#pragma GCC diagnostic pop
result.size = AlignToWord(i * sizeof(SpeciesLearnsetEntry));
return result;
}
static std::optional<SpeciesPalPark> 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<u16> offspringData, fs::path path)
{
std::ofstream ofs(path);
ofs.write(reinterpret_cast<char *>(&offspringData[0]), offspringData.size() * sizeof(u16));
}
static std::vector<Move> 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<Move> tutorables;
rapidjson::Value &moves = doc["moves"];
for (const auto &entry : moves.GetObject()) {
Move tutorable = static_cast<Move>(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<Move> &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<u8> tutorableLearnset(tutorableLearnsetSize);
for (const auto &entry : byTutorLearnset.GetArray()) {
Move tutorable = static_cast<Move>(LookupConst(entry.GetString(), Move));
std::vector<Move>::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<std::string, std::vector<std::string>> 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<std::byte>(backOffsets["male"].GetUint());
frontMale = static_cast<std::byte>(frontOffsets["male"].GetUint());
}
if (genderRatio == GENDER_RATIO_MALE_ONLY || genderRatio == GENDER_RATIO_NO_GENDER) {
femaleSize = 0;
} else {
backFemale = static_cast<std::byte>(backOffsets["female"].GetUint());
frontFemale = static_cast<std::byte>(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<Move> 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<std::string> speciesRegistry = ReadRegistryEnvVar("SPECIES");
std::map<std::string, std::vector<std::string>> 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<std::string> 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<SpeciesPalPark> palParkData;
std::vector<u16> offspringData;
std::vector<SpeciesSpriteData> 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<SpeciesPalPark> 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<std::byte *>(&data), sizeof(data));
evo.append(reinterpret_cast<std::byte *>(&evos), sizeof(evos));
wotbl.append(reinterpret_cast<std::byte *>(&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;
}

View File

@ -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: [

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,
)

413
tools/dataproc/src/common.c Normal file
View File

@ -0,0 +1,413 @@
#include "common.h"
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}

122
tools/dataproc/src/common.h Normal file
View File

@ -0,0 +1,122 @@
#ifndef DATAPROC_COMMON_H
#define DATAPROC_COMMON_H
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#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

273
tools/dataproc/src/enum.c Normal file
View File

@ -0,0 +1,273 @@
#include "enum.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 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;
}

78
tools/dataproc/src/enum.h Normal file
View File

@ -0,0 +1,78 @@
#ifndef ENUM_H
#define ENUM_H
#include <stdbool.h>
#include <stddef.h>
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

View File

@ -0,0 +1,746 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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", &registry, &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);
}

View File

@ -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",