generate: add pokemon

This also includes a handful of functions for creating instances of a
pokemon species, though certainly not complete.
This commit is contained in:
Zack Buhman 2023-08-02 23:49:42 +00:00
parent 9e94179a53
commit 853457a79d
17 changed files with 686 additions and 19 deletions

View File

@ -30,10 +30,11 @@ res_png = $(subst pokered/,res/,$(patsubst %.png,%.$(1).o,$(wildcard $(2)*.png))
GFX_TILESETS = $(call res_png,2bpp,pokered/gfx/tilesets/)
GFX_BLOCKSETS = $(call res,bst,pokered/gfx/blocksets/)
GFX_SPRITES = $(call res_png,2bpp,pokered/gfx/sprites/)
GFX_POKEMON = $(call res_png,2bpp,pokered/gfx/pokemon/front/) $(call res_png,2bpp,pokered/gfx/pokemon/back/)
MAPS_BLOCKS = $(call res,blk,pokered/maps/)
FONTS = res/font.2bpp.o
GENERATED = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(GFX_SPRITES) $(MAPS_BLOCKS) $(GEN_SRC) $(FONTS)
GENERATED = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(GFX_SPRITES) $(GFX_POKEMON) $(MAPS_BLOCKS) $(GEN_SRC) $(FONTS)
OBJ = $(patsubst %.cpp,%.o,$(SRC) $(GENERATED))
@ -63,6 +64,11 @@ define PNG_TO_2BPP
python tools/png_to_nbpp.py 2 $@ $<
endef
define PNG_TO_2BPP_SPRITE
@mkdir -p $(dir $@)
python tools/png_to_nbpp_sprite.py 2 $@ $<
endef
res/%.2bpp: derived/%.png
$(PNG_TO_2BPP)
@ -74,8 +80,10 @@ res/.1bpp: pokered/%.png
python tools/png_to_nbpp.py 1 $@ $<
res/gfx/sprites/%.2bpp: pokered/gfx/sprites/%.png
@mkdir -p $(dir $@)
python tools/png_to_nbpp_sprite.py 2 $@ $<
$(PNG_TO_2BPP_SPRITE)
res/gfx/pokemon/%.2bpp: pokered/gfx/pokemon/%.png
$(PNG_TO_2BPP_SPRITE)
%.2bpp.h:
$(BUILD_BINARY_H)

81
pokemon.hpp Normal file
View File

@ -0,0 +1,81 @@
#include <cstdint>
#include "gen/pokemon/moves.hpp"
#include "gen/pokemon/types.hpp"
#include "start_size.hpp"
enum struct stat_t {
hit_points,
attack,
defense,
speed,
special,
};
struct base_stat_values_t {
struct {
uint8_t hit_points;
uint8_t attack;
uint8_t defense;
uint8_t speed;
uint8_t special;
};
};
static_assert((sizeof (base_stat_values_t)) == 5);
struct level_move_t {
uint8_t level;
enum move_t::move move;
};
enum struct growth_t {
medium_fast,
slightly_fast,
slightly_slow,
medium_slow,
fast,
slow,
};
struct pokemon_t {
#include "gen/pokemon/pokemon_enum.inc.hpp"
struct evolution_t {
enum type {
item,
level,
trade
};
enum type type;
uint8_t required_item; // fixme
uint8_t required_level;
enum pokemon pokemon;
};
const uint8_t * name;
base_stat_values_t base_stat_values;
type_t types[2];
uint8_t catch_rate;
uint8_t base_exp;
growth_t growth_rate;
struct {
start_size_t front;
start_size_t back;
} pic;
struct {
const level_move_t * moves;
uint8_t length;
} by_level;
struct {
const enum move_t::move * moves;
uint8_t length;
} by_tmhm;
struct {
const evolution_t * evolution;
uint8_t length;
} evolutions;
};
extern const pokemon_t pokemon[];

75
pokemon_instance.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "pokemon_instance.hpp"
static uint16_t _determine_stat(const uint32_t base_stat_value,
const uint32_t determinant_value,
const uint32_t stat_experience,
const uint32_t level)
{
const uint32_t x = base_stat_value + determinant_value;
const uint32_t y = sqrt_ceil<uint16_t>(stat_experience) / 4;
const uint32_t z = (x * 2 + y) * level;
return (z / 100);
}
static_assert(_determine_stat(106, 0b0100, 0, 70) == (234 - 10 - 70));
static_assert(_determine_stat(110, 0b1110, 0, 70) == (178 - 5));
static_assert(_determine_stat( 90, 0b0101, 0, 70) == (138 - 5));
static_assert(_determine_stat(130, 0b1000, 0, 70) == (198 - 5));
static_assert(_determine_stat(154, 0b0110, 0, 70) == (229 - 5));
constexpr inline uint16_t
pokemon_instance_t::determine_stat(enum stat_t stat)
{
switch (stat) {
default:
case stat_t::hit_points:
return _determine_stat(pokemon[species].base_stat_values.hit_points,
determinant_values.hit_points(),
stat_experience.hit_points,
level)
+ 10 + level;
case stat_t::attack:
return _determine_stat(pokemon[species].base_stat_values.attack,
determinant_values.attack(),
stat_experience.attack,
level)
+ 5;
case stat_t::defense:
return _determine_stat(pokemon[species].base_stat_values.defense,
determinant_values.defense(),
stat_experience.defense,
level)
+ 5;
case stat_t::speed:
return _determine_stat(pokemon[species].base_stat_values.speed,
determinant_values.speed(),
stat_experience.speed,
level)
+ 5;
case stat_t::special:
return _determine_stat(pokemon[species].base_stat_values.special,
determinant_values.special(),
stat_experience.special,
level)
+ 5;
}
}
constexpr inline uint16_t
pokemon_instance_t::learn_move(enum move_t::move move, int32_t index)
{
switch (index) {
case 0: [[fallthrough]];
case 1: [[fallthrough]];
case 2: [[fallthrough]];
case 3:
moves[index] = move;
break;
default:
moves[0] = moves[1];
moves[1] = moves[2];
moves[2] = moves[3];
moves[3] = move;
break;
}
}

83
pokemon_instance.hpp Normal file
View File

@ -0,0 +1,83 @@
#include <cstdint>
#include "sqrt.hpp"
#include "gen/pokemon/moves.hpp"
#include "pokemon.hpp"
struct determinant_values_t {
uint16_t dvs;
inline constexpr uint8_t attack()
{
return (dvs >> 12) & 0b1111;
}
inline constexpr uint8_t defense()
{
return (dvs >> 8) & 0b1111;
}
inline constexpr uint8_t speed()
{
return (dvs >> 4) & 0b1111;
}
inline constexpr uint8_t special()
{
return (dvs >> 0) & 0b1111;
}
inline constexpr uint8_t hit_points()
{
return ((attack() & 1) << 3)
| ((defense() & 1) << 2)
| ((speed() & 1) << 1)
| ((special() & 1) << 0);
}
};
struct stat_experience_t {
uint16_t hit_points;
uint16_t attack;
uint16_t defense;
uint16_t speed;
uint16_t special;
};
// unlike base_stat_values, stat_values is uint16_t
struct stat_values_t {
union {
struct {
uint16_t hit_points;
uint16_t attack;
uint16_t defense;
uint16_t speed;
uint16_t special;
};
uint16_t value[5];
};
};
static_assert((sizeof (stat_values_t)) == 10);
struct pokemon_instance_t {
enum pokemon_t::pokemon species;
uint8_t * nickname[12];
uint8_t level;
uint32_t experience; // total experience
determinant_values_t determinant_values;
stat_values_t stat_values;
stat_experience_t stat_experience;
enum move_t::move moves[4];
uint16_t current_hit_points;
// missing attributes:
// - status modifiers (burn, poison, etc...)
// "fluff" attributes:
// - id number
// - original trainer
constexpr inline uint16_t determine_stat(enum stat_t stat);
constexpr inline void learn_move(enum move_t::move move, uint32_t index);
};

43
sqrt.hpp Normal file
View File

@ -0,0 +1,43 @@
template <typename T>
inline constexpr T div_ceil(T a, T b)
{
return (a / b) + ((a % b) != 0);
}
static_assert(div_ceil<int>(2, 2) == 1);
static_assert(div_ceil<int>(3, 2) == 2);
static_assert(div_ceil<int>(55, 8) == 7);
static_assert(div_ceil<int>(55, 11) == 5);
template <typename T>
inline constexpr T sqrt_floor(T a)
{
if (a == 0) return 0;
constexpr int num_bits = (sizeof (T)) * 8;
T x = 1 << div_ceil<T>(num_bits, 2);
while (true) {
T y = (x + (a / x)) / 2;
if (y >= x) return x;
x = y;
}
}
static_assert(sqrt_floor<int>(4) == 2);
static_assert(sqrt_floor<int>(9) == 3);
static_assert(sqrt_floor<int>(7056) == 84);
static_assert(sqrt_floor<int>(7211) == 84);
static_assert(sqrt_floor<int>(7225) == 85);
static_assert(sqrt_floor<int>(7226) == 85);
template <typename T>
inline constexpr T sqrt_ceil(T a)
{
T q = sqrt_floor<T>(a);
if (q * q != a) q += 1;
return q;
}
static_assert(sqrt_ceil<int>(7056) == 84);
static_assert(sqrt_ceil<int>(7211) == 85);
static_assert(sqrt_ceil<int>(7225) == 85);
static_assert(sqrt_ceil<int>(7226) == 86);

View File

@ -10,6 +10,7 @@ def generate(base_path, target_path):
if path != target_path:
continue
buf = func().getvalue()
path.parents[0].mkdir(parents=False, exist_ok=True)
with open(path, 'w') as f:
f.write(buf)

View File

@ -5,7 +5,9 @@ from generate import tilesets
from generate import collision_tile_ids
from generate import text
from generate import text_pointers
from generate.move import moves
from generate.pokemon import moves
from generate.pokemon import types
from generate.pokemon import pokemon
files = [
(maps.generate_header, "maps.hpp"),
@ -22,6 +24,9 @@ files = [
(text.generate_source, "text.cpp"),
(text_pointers.generate_header, "text_pointers.hpp"),
(text_pointers.generate_source, "text_pointers.cpp"),
(moves.generate_header, "moves.hpp"),
(moves.generate_source, "moves.cpp"),
(moves.generate_header, "pokemon/moves.hpp"),
(moves.generate_source, "pokemon/moves.cpp"),
(types.generate_header, "pokemon/types.hpp"),
(pokemon.generate_enum_inc, "pokemon/pokemon_enum.inc.hpp"),
(pokemon.generate_source, "pokemon/pokemon.cpp"),
]

View File

@ -0,0 +1,319 @@
"""
- constants
pokedex indicies:
- dex_constants
by constants index:
- evos_moves (pointer table)
- names
- dex_entries (pointer table)
by dex_constants value:
- base_stats (DEX_*)
(references Front/Back pic)
translation from constants to dex_constants:
- dex_order
from pic to gfx/ filename:
- pic
"""
from itertools import chain, starmap
from generate.generate import renderer
from generate.binary import start_size_value
from parse import parse
#from pprint import pprint
#print(parse.pokemon_dex_constants_list())
#print(parse.pokemon_constants_list())
#print(parse.pokemon_names_list())
#print(parse.pokemon_dex_entries_list())
#print(parse.pokemon_dex_order_list())
#pprint(parse.pokemon_base_stats_list())
"""
struct base_stat_values_t {
uint8_t hit_points;
uint8_t attack;
uint8_t defense;
uint8_t speed;
uint8_t special;
};
struct level_move_t {
uint8_t level;
enum move_t::move move;
};
struct evolution_t {
enum type {
item,
level,
trade
};
enum type type;
uint8_t item; // fixme
uint8_t level;
enum pokemon_t::pokemon pokemon;
};
struct pokemon_t {
base_stat_values_t stat_values;
type_t types[2];
uint8_t catch_rate;
uint8_t base_exp;
uint8_t growth_rate;
struct {
uint8_t front;
uint8_t back;
} pics;
struct {
level_move_t * moves;
uint8_t length;
} by_level;
struct {
enum move_t::move * moves;
uint8_t length;
} by_tmhm;
struct {
evolution_t * evolution; // might be null
uint8_t length;
} evolutions;
};
[pokemon_t::pidgey] = {
.base_stat_values = {10, 15, 20, 42, 66},
.types = {type_t::normal, type_t::flying},
.catch_rate = 80,
.base_exp = 100,
.growth_rate = 0, // fixme
.pics = {
.front = 0, // fixme
.back = 0, // fixme
},
.by_level = {
.moves = {
{
.level = 1,
.move = move_t::peck,
},
},
.length = 1,
},
.by_tmhm = {
.moves = {
move_t::surf,
},
.length = 1,
},
.evolutions = {
.evolutions = {
.type = pokemon_t::evolution_t::level,
.required_item = 0, // fixme
.required_level = 10,
.pokemon = pokemon_t::pidgeotto,
},
.length = 1,
},
};
"""
def dex_constant_to_enum_name(dex_constant):
assert dex_constant.startswith("DEX_")
name = dex_constant.removeprefix("DEX_")
return name.lower()
def enum_inc():
yield "enum pokemon {"
for dex_constant in parse.pokemon_dex_constants_list():
name = dex_constant_to_enum_name(dex_constant)
yield f"{name.lower()},"
yield "};"
def generate_enum_inc():
render, out = renderer()
render(enum_inc())
return out
def level_move(level, move):
return [
"{",
f".level = {level},",
f".move = move_t::{move.lower()},",
"},",
]
def moves_by_level(level_1_learnset, learnset):
"""
level_1_learnset=('TACKLE', 'GROWL', 'NO_MOVE', 'NO_MOVE')
learnset=[(7, 'LEECH_SEED'),
(13, 'VINE_WHIP'),
(20, 'POISONPOWDER'),
(27, 'RAZOR_LEAF'),
(34, 'GROWTH'),
(41, 'SLEEP_POWDER'),
(48, 'SOLARBEAM')]
"""
_learnset = [(1, s) for s in level_1_learnset
if s != 'NO_MOVE'] + learnset
return [
".by_level = {",
".moves = (level_move_t[]){",
*chain.from_iterable(starmap(level_move, _learnset)),
"},",
f".length = {len(_learnset)},",
"},",
]
def moves_by_tmhm(tmhm):
moves = [
f"move_t::{move.lower()},"
for move in tmhm
if move != 'UNUSED'
]
return [
".by_tmhm = {",
".moves = (enum move_t::move[]){",
*moves,
"},",
f".length = {len(moves)},",
"},"
]
def evolution(ev):
type, _item_name, level, pokemon_name = ev
assert type.startswith('EV_')
type = type.removeprefix('EV_').lower()
return [
"{",
f".type = pokemon_t::evolution_t::{type},",
".required_item = 0, // fixme",
f".required_level = {level},",
f".pokemon = pokemon_t::{pokemon_name.lower()},",
"},",
]
def evolutions(evs):
return [
".evolutions = {",
".evolution = (pokemon_t::evolution_t[]){",
*chain.from_iterable(evolution(ev) for ev in evs),
"},",
f".length = {len(evs)},",
"},",
]
def stat_values(stat_values):
return [
f".hit_points = {stat_values.hit_points},",
f".attack = {stat_values.attack},",
f".defense = {stat_values.defense},",
f".speed = {stat_values.speed},",
f".special = {stat_values.special},",
]
def pic_path(path):
assert path.endswith('.pic')
return f"{path.removesuffix('.pic')}.2bpp"
def pics(pic_front_back):
front, back = pic_front_back
front_path = pic_path(parse.pic_list()[front])
back_path = pic_path(parse.pic_list()[back])
return [
".front = {",
*start_size_value(front_path),
"},",
".back = {",
*start_size_value(back_path),
"},",
]
def growth_name(growth_constant):
assert growth_constant.startswith('GROWTH_')
growth_rate = growth_constant.removeprefix('GROWTH_')
return growth_rate.lower()
def dex_constant_name_to_constant_index(dex_constant_name):
return parse.pokemon_dex_order_list().index(dex_constant_name)
def pokemon(base_stats, evos_moves):
constant_index = dex_constant_name_to_constant_index(base_stats.pokedex_id)
pokemon_name = parse.pokemon_names_list()[constant_index]
enum_name = dex_constant_to_enum_name(base_stats.pokedex_id)
types = ", ".join(
f"type_t::{type.lower()}"
for type in base_stats.types
)
growth_rate = growth_name(base_stats.growth_rate)
return [
f"[pokemon_t::{enum_name}] = {{",
f'.name = reinterpret_cast<const uint8_t *>("{pokemon_name}"),',
".base_stat_values = {",
*stat_values(base_stats.stat_values),
"},",
f".types = {{{types}}},",
f".catch_rate = {base_stats.catch_rate},",
f".base_exp = {base_stats.base_exp},",
f".growth_rate = growth_t::{growth_rate},",
".pic = {",
*pics(base_stats.pic_front_back),
"},",
*moves_by_level(base_stats.level_1_learnset, evos_moves.learnset),
*moves_by_tmhm(base_stats.tmhm),
*evolutions(evos_moves.evolutions),
"},"
]
def evos_moves(constant_index):
evos_moves_list = parse.pokemon_evos_moves_list()
pointer_table = evos_moves_list[0]
evos_moves = evos_moves_list[1:]
by_label = {em.label: em for em in evos_moves}
return by_label[pointer_table[constant_index]]
def base_stats_by_dex_id():
by_dex_id = {
base_stat.pokedex_id: base_stat
for base_stat in parse.pokemon_base_stats_list()
}
assert len(by_dex_id) == len(parse.pokemon_base_stats_list())
return by_dex_id
def pokemon_initializers():
by_dex_id = base_stats_by_dex_id()
for dex_constant_name in parse.pokemon_dex_constants_list():
base_stats = by_dex_id[dex_constant_name]
constant_index = dex_constant_name_to_constant_index(dex_constant_name)
yield from pokemon(base_stats, evos_moves(constant_index))
def source_includes():
yield "#include <cstdint>"
yield ""
yield '#include "../../pokemon.hpp"'
yield ""
for dex_constant_name in parse.pokemon_dex_constants_list():
by_dex_id = base_stats_by_dex_id()
base_stats = by_dex_id[dex_constant_name]
front, back = base_stats.pic_front_back
front_path = pic_path(parse.pic_list()[front])
back_path = pic_path(parse.pic_list()[back])
yield f'#include "../../res/{front_path}.h"'
yield f'#include "../../res/{back_path}.h"'
yield ""
def pokemon_source():
yield "const pokemon_t pokemon[] = {"
yield from pokemon_initializers()
yield "};"
def generate_source():
render, out = renderer()
render(source_includes())
render(pokemon_source())
return out

View File

@ -0,0 +1,21 @@
from generate.generate import renderer
from parse import parse
def includes_header():
yield "#pragma once"
yield ""
def struct_type_t():
_type_constants = parse.type_constants_list()
_type_constants_str = (f"{s.lower()}," for s in _type_constants)
return [
"enum struct type_t {",
*_type_constants_str,
"};",
]
def generate_header():
render, out = renderer()
render(includes_header())
render(struct_type_t())
return out

View File

@ -11,6 +11,8 @@ def flatten(tokens):
yield None
elif t[0] == 'const_def':
continue
elif t[0] == 'const_next':
continue
else:
assert False, t

View File

@ -29,6 +29,11 @@ from parse import move # constants
# moves
# names
from parse import types # constants
# names (fixme)
# matchups (fixme)
# battle_constants.asm
from parse import text
from parse import scripts
from parse import pic
@ -87,4 +92,7 @@ move_moves_list = memoize(lambda: move.moves.parse(prefix))
move_names_list = memoize(lambda: move.names.parse(prefix))
# pic
pic_list = memoize(lambda: pic.parse(prefix))
pic_list = memoize(lambda: pic.parse_all(prefix))
# type
type_constants_list = memoize(lambda: types.constants.parse(prefix))

View File

@ -8,13 +8,25 @@ def tokenize_lines(lines):
if '::' in line:
yield tokenize.block(line, delim='::')
def parse(prefix):
path = prefix / 'gfx/pics.asm'
def parse(path):
with open(path) as f:
tokens = tokenize_lines(f.read().split('\n'))
l = list(flatten(tokens,
l = flatten(tokens,
endings=['Front', 'Back', 'Pic'],
base_path='gfx/'))
base_path='gfx/')
return l
def _parse_all(prefix):
pics = [
"gfx/pics.asm",
"data/pokemon/mew.asm"
]
for pic in pics:
path = prefix / pic
yield from parse(path)
def parse_all(prefix):
l = list(_parse_all(prefix))
d = dict(l)
assert len(l) == len(d)
return d

View File

@ -28,7 +28,7 @@ class BaseStats:
types: tuple[str, str]
catch_rate: int
base_exp: int
pic_font_back: tuple[str, str]
pic_front_back: tuple[str, str]
level_1_learnset: tuple[str, str, str, str]
growth_rate: str
tmhm: list[str]
@ -73,5 +73,4 @@ def parse(path):
def parse_all(prefix):
base_path = prefix / 'data/pokemon/base_stats'
paths = [p for p in base_path.iterdir() if p.is_file()]
# in pokedex order
return [parse(path) for path in paths]

View File

@ -26,10 +26,10 @@ def parse_ev(tokens):
ev_type, *rest = tokens
if ev_type == 'EV_ITEM':
item_name, level_requirement, pokemon_name = rest
return item_name, number.parse(level_requirement), pokemon_name
return ev_type, item_name, number.parse(level_requirement), pokemon_name
elif ev_type == 'EV_LEVEL' or ev_type == 'EV_TRADE':
level_requirement, pokemon_name = rest
return number.parse(level_requirement), pokemon_name
return ev_type, 0, number.parse(level_requirement), pokemon_name
else:
assert False, ev_type
@ -74,6 +74,11 @@ def build_tables(tokens):
ix += 1
# yield last evos_moves at the end of parsing
if evos_moves.label != None:
yield evos_moves
def parse(prefix):
path = prefix / "data/pokemon/evos_moves.asm"
with open(path) as f:

View File

@ -0,0 +1 @@
from parse.types import constants

View File

@ -0,0 +1,4 @@
from functools import partial
from parse.generic import constants
parse = partial(constants.parse, path='constants/type_constants.asm')