diff --git a/Makefile b/Makefile index c03e4aa..95c7716 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ SRC += menu.cpp SRC += number.cpp SRC += pokemon_instance.cpp SRC += ailment.cpp +SRC += menu/stats.cpp DEP = $(patsubst %.cpp,%.cpp.d,$(SRC)) diff --git a/derived/font.png b/derived/font.png index 56efba3..ac4957a 100644 Binary files a/derived/font.png and b/derived/font.png differ diff --git a/graphic.cpp b/graphic.cpp index 8845ba4..dafbd31 100644 --- a/graphic.cpp +++ b/graphic.cpp @@ -7,11 +7,6 @@ #include "control.hpp" #include "number.hpp" -#include "gen/pokemon/types.hpp" - -#include "pokemon_instance.hpp" -#include "ailment.hpp" - #define S reinterpret_cast void draw_text(const uint32_t base_pattern, @@ -134,10 +129,10 @@ constexpr inline uint8_t hp_seg(const int32_t hp_48, const int32_t ix) } } -void draw_hp_bar(const uint32_t base_pattern, - const screen_cell_t& top_left, - const uint8_t end_cap, - const int32_t hp_48) +static inline void _draw_hp_bar(const uint32_t base_pattern, + const screen_cell_t& top_left, + const uint8_t end_cap, + const int32_t hp_48) { put_char(base_pattern, top_left.x + 0, top_left.y, hp_bar::hp); put_char(base_pattern, top_left.x + 1, top_left.y, hp_bar::start_cap); @@ -150,148 +145,22 @@ void draw_hp_bar(const uint32_t base_pattern, put_char(base_pattern, top_left.x + 8, top_left.y, end_cap); } -const uint8_t * status_string(const pokemon_instance_t& pokemon_instance) +void draw_hp_bar_with_numbers(const uint32_t base_pattern, + const pokemon_instance_t& pokemon_instance, + const screen_cell_t& pos) { - if (pokemon_instance.current_hit_points == 0) - return S("FNT"); - else - return ailments[pokemon_instance.ailment].name; -} + const int32_t hp_48 = (pokemon_instance.current_hit_points * 48) / pokemon_instance.stat_values.hit_points; + _draw_hp_bar(base_pattern, pos, hp_bar::end_cap_bar, hp_48); -void draw_stats1(const uint32_t base_pattern, - const pokemon_instance_t& pokemon_instance) -{ - // white out the entire screen - draw_box_background(base_pattern, {-1, -1}, {20, 18}); + draw_number_right_align(base_pattern, {pos.x + 1, pos.y + 1}, + pokemon_instance.current_hit_points, + 3, ascii_to_font(' ')); // width, fill - // top status battle box - { - draw_battle_border(base_pattern, {19, 1}, {8, 7}); + put_char(base_pattern, pos.x + 4, pos.y + 1, ascii_to_font('/')); - // hp - { - const int32_t hp_48 = (pokemon_instance.current_hit_points * 48) / pokemon_instance.stat_values.hit_points; - draw_hp_bar(base_pattern, {11, 3}, hp_bar::end_cap_bar, hp_48); - - draw_number_right_align(base_pattern, {12, 4}, - pokemon_instance.current_hit_points, - 3, ascii_to_font(' ')); // width, fill - - put_char(base_pattern, 15, 4, ascii_to_font('/')); - - draw_number_right_align(base_pattern, {16, 4}, - pokemon_instance.stat_values.hit_points, - 3, ascii_to_font(' ')); // width, fill - } - - // name - draw_text(base_pattern, pokemon[pokemon_instance.species].name, 9, 1); - - // level - put_char(base_pattern, 14, 2, battle_border::level); - draw_number_left_align(base_pattern, {15, 2}, - pokemon_instance.level, - 3); // width - - draw_text(base_pattern, S("STATUS/"), 9, 6); - draw_text(base_pattern, status_string(pokemon_instance), 16, 6); - } - - // bottom right border - { - draw_battle_border(base_pattern, {19, 9}, {12, 17}); - - int32_t distinct_types = (pokemon[pokemon_instance.species].types[0] - != pokemon[pokemon_instance.species].types[1]) + 1; - - for (int32_t ix = 0; ix < distinct_types; ix++) { - const enum type_t::type type = pokemon[pokemon_instance.species].types[ix]; - draw_text(base_pattern, S("TYPE"), 10, 9 + (2 * ix)); - put_char(base_pattern, 14, 9 + (2 * ix), ascii_to_font('1' + ix)); - put_char(base_pattern, 15, 9 + (2 * ix), ascii_to_font('/')); - draw_text(base_pattern, types[type].name, 11, 10 + (2 * ix)); - } - } - - { // bottom left dialog box - draw_box_border(base_pattern, {0, 8}, {9, 17}); - - draw_text(base_pattern, S("ATTACK"), 1, 9 + 0); - draw_text(base_pattern, S("DEFENSE"), 1, 9 + 2); - draw_text(base_pattern, S("SPEED"), 1, 9 + 4); - draw_text(base_pattern, S("SPECIAL"), 1, 9 + 6); - - draw_number_right_align(base_pattern, {6, 10 + 0}, - pokemon_instance.stat_values.attack, - 3, ascii_to_font(' ')); // width, fill - - draw_number_right_align(base_pattern, {6, 10 + 2}, - pokemon_instance.stat_values.defense, - 3, ascii_to_font(' ')); // width, fill - - draw_number_right_align(base_pattern, {6, 10 + 4}, - pokemon_instance.stat_values.speed, - 3, ascii_to_font(' ')); // width, fill - - draw_number_right_align(base_pattern, {6, 10 + 6}, - pokemon_instance.stat_values.special, - 3, ascii_to_font(' ')); // width, fill - } // end bottom left dialog box - - // pokedex number - { - put_char(base_pattern, 1, 7, stats::no); - put_char(base_pattern, 2, 7, stats::no_dot); - - const int32_t pokedex_number = static_cast(pokemon_instance.species) + 1; - draw_number_right_align(base_pattern, - {3, 7}, - pokedex_number, - 3, // width - ascii_to_font('0') // fill - ); - } -} - -void dialog_t::draw(const uint32_t base_pattern, - const start_size_t& text) -{ - draw_box_border(base_pattern, top_left, bottom_right); - draw_box_background(base_pattern, top_left, bottom_right); - - uint32_t ix = 0; - int32_t x = top_left.x + 1; - int32_t y = top_left.y + 2; - // ignore C-string null terminator - while (ix < (text.size - 1)) { - const uint8_t c = text.start[ix]; - switch (c) { - case control_t::text: break; - case control_t::done: break; - case control_t::prompt: break; - case control_t::page: break; - - case control_t::next: [[fallthrough]]; - case control_t::line: - while (x < bottom_right.x) { - put_char(base_pattern, x, y, ascii_to_font(' ')); - x++; - } - x = top_left.x + 1; - y += 2; - break; - case control_t::para: [[fallthrough]]; - case control_t::cont: - // fixme: - ix = text.size; - break; - default: - put_char(base_pattern, x, y, ascii_to_font(c)); - x += 1; - break; - } - ix++; - } + draw_number_right_align(base_pattern, {pos.x + 5, pos.y + 1}, + pokemon_instance.stat_values.hit_points, + 3, ascii_to_font(' ')); // width, fill } #undef S diff --git a/graphic.hpp b/graphic.hpp index e3f9aec..0b67d4a 100644 --- a/graphic.hpp +++ b/graphic.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "coordinates.hpp" #include "start_size.hpp" #include "render_map.hpp" // for cell_offset @@ -54,6 +56,8 @@ struct hp_bar { struct stats { static constexpr uint8_t id = 0x70; // ID static constexpr uint8_t no = 0x71; // No + static constexpr uint8_t to = 0x4c; // to + static constexpr uint8_t p = 0x4d; // p (bold) static constexpr uint8_t no_dot = 0x4e; // . }; @@ -79,14 +83,6 @@ void draw_box_background(const uint32_t base_pattern, void draw_battle_border(const uint32_t base_pattern, const screen_cell_t& top_corner, const screen_cell_t& bottom_corner); -void draw_stats1(const uint32_t base_pattern, - const pokemon_instance_t& pokemon_instance); - -struct dialog_t -{ - static constexpr screen_cell_t top_left = { 0, 12}; - static constexpr screen_cell_t bottom_right = {19, 17}; - - static void draw(const uint32_t base_pattern, - const start_size_t& text); -}; +void draw_hp_bar_with_numbers(const uint32_t base_pattern, + const pokemon_instance_t& pokemon_instance, + const screen_cell_t& pos); diff --git a/main.cpp b/main.cpp index aad9bbb..929dceb 100644 --- a/main.cpp +++ b/main.cpp @@ -29,6 +29,8 @@ #include "pokemon.hpp" #include "pokemon_instance.hpp" +#include "menu/stats.hpp" + static int32_t pokemon_raw_index = 0; struct draw_t { @@ -166,7 +168,7 @@ uint32_t pokemon_sprite_dimension(const uint32_t size) } void render_pokemon(const uint32_t ix, const enum pokemon_t::pokemon pokemon_id, - const screen_cell_t& screen_cell) + const screen_cell_t& screen_cell, bool horizontal_inversion) { const uint32_t base_pattern = state.draw.base_pattern.pokemon[pokemon_id].front; const uint32_t character_address = (base_pattern * 16) / 8; @@ -181,7 +183,9 @@ void render_pokemon(const uint32_t ix, const enum pokemon_t::pokemon pokemon_id, case 56: x_offset = 1; y_offset = 0; break; } - vdp1.vram.cmd[ix].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE; + uint16_t dir = horizontal_inversion ? CTRL__DIR__INVERTED_HORIZONTALLY : 0; + + vdp1.vram.cmd[ix].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE | dir; vdp1.vram.cmd[ix].LINK = 0; // The "end code" is 0xf, which is being used in the mai sprite palette. If // both transparency and end codes are enabled, it seems there are only 14 @@ -209,7 +213,8 @@ void render_sprites(const offset_t& offset) ix++; render_pokemon(ix, static_cast(pokemon_raw_index), - {0, 0}); + {0, 0}, + true); // invert horizontally ix++; const object_t& obj = map_objects[state.map]; @@ -470,11 +475,13 @@ void check_sign() const bool position_match = event.position.x == coord.x && event.position.y == coord.y; if (position_match && event.sign_id != 0xff) { const start_size_t& text = map.text_pointers[event.sign_id]; - dialog_t::draw(state.draw.base_pattern.font, text); } } } +static uint32_t stats_page = 1; +static uint8_t level = 70; + void update() { state.player.tick(); @@ -486,14 +493,18 @@ void update() else if (event::cursor_right()) collision_move(maps[state.map], actor_t::right); else if (event::cursor_up() ) collision_move(maps[state.map], actor_t::up); else if (event::cursor_down() ) collision_move(maps[state.map], actor_t::down); + else if (event::button_a() ) check_sign(); */ if (event::cursor_left() ) pokemon_raw_index = (pokemon_raw_index - 1); else if (event::cursor_right()) pokemon_raw_index = (pokemon_raw_index + 1); + else if (event::cursor_up() ) level++; + else if (event::cursor_down() ) level--; + else if (event::button_a() ) stats_page = !stats_page; + if (pokemon_raw_index < 0) pokemon_raw_index = pokemon_t::count - 1; else if (pokemon_raw_index >= pokemon_t::count) pokemon_raw_index = 0; - //else if (event::button_a() ) check_sign(); } static uint32_t frame = 0; @@ -523,14 +534,23 @@ void render() pokemon_instance.species = static_cast(pokemon_raw_index); pokemon_instance.stat_experience = {0, 0, 0, 0, 0}; pokemon_instance.determinant_values.dvs = 0b1110'0101'1000'0110; - pokemon_instance.level = 70; + pokemon_instance.level = level; pokemon_instance.determine_stats(); + pokemon_instance.learn_all_moves(); if (frame % 4 == 0) pokemon_instance.current_hit_points++; if (pokemon_instance.current_hit_points > pokemon_instance.stat_values.hit_points) pokemon_instance.current_hit_points = 0; - draw_stats1(state.draw.base_pattern.font, pokemon_instance); + switch (stats_page) { + default: + case 0: + draw_stats1(state.draw.base_pattern.font, pokemon_instance); + break; + case 1: + draw_stats2(state.draw.base_pattern.font, pokemon_instance); + break; + } } extern "C" diff --git a/menu/dialog.cpp b/menu/dialog.cpp new file mode 100644 index 0000000..808ef17 --- /dev/null +++ b/menu/dialog.cpp @@ -0,0 +1,43 @@ +void draw(const uint32_t base_pattern, + const start_size_t& text) +{ + static constexpr screen_cell_t top_left = { 0, 12}; + static constexpr screen_cell_t bottom_right = {19, 17}; + + draw_box_border(base_pattern, top_left, bottom_right); + draw_box_background(base_pattern, top_left, bottom_right); + + uint32_t ix = 0; + int32_t x = top_left.x + 1; + int32_t y = top_left.y + 2; + // ignore C-string null terminator + while (ix < (text.size - 1)) { + const uint8_t c = text.start[ix]; + switch (c) { + case control_t::text: break; + case control_t::done: break; + case control_t::prompt: break; + case control_t::page: break; + + case control_t::next: [[fallthrough]]; + case control_t::line: + while (x < bottom_right.x) { + put_char(base_pattern, x, y, ascii_to_font(' ')); + x++; + } + x = top_left.x + 1; + y += 2; + break; + case control_t::para: [[fallthrough]]; + case control_t::cont: + // fixme: + ix = text.size; + break; + default: + put_char(base_pattern, x, y, ascii_to_font(c)); + x += 1; + break; + } + ix++; + } +} diff --git a/menu/stats.cpp b/menu/stats.cpp new file mode 100644 index 0000000..b17e05d --- /dev/null +++ b/menu/stats.cpp @@ -0,0 +1,229 @@ +#include "../font.hpp" +#include "../number.hpp" +#include "../graphic.hpp" + +#include "../gen/pokemon/types.hpp" +#include "../gen/pokemon/moves.hpp" + +#include "../pokemon_instance.hpp" +#include "../ailment.hpp" + +#define S reinterpret_cast + +const uint8_t * status_string(const pokemon_instance_t& pokemon_instance) +{ + if (pokemon_instance.current_hit_points == 0) + return S("FNT"); + else + return ailments[pokemon_instance.ailment].name; +} + +static void draw_stats_top_right_border_with_name(const uint32_t base_pattern, + const pokemon_instance_t& pokemon_instance) +{ + draw_battle_border(base_pattern, {19, 1}, {8, 7}); + + // name + draw_text(base_pattern, pokemon[pokemon_instance.species].name, 9, 1); +} + +static void draw_stats_pokedex_number(const uint32_t base_pattern, + const pokemon_instance_t& pokemon_instance) +{ + // pokedex number + put_char(base_pattern, 1, 7, stats::no); + put_char(base_pattern, 2, 7, stats::no_dot); + + const int32_t pokedex_number = static_cast(pokemon_instance.species) + 1; + draw_number_right_align(base_pattern, + {3, 7}, + pokedex_number, + 3, // width + ascii_to_font('0') // fill + ); +} + +void draw_stats1(const uint32_t base_pattern, + const pokemon_instance_t& pokemon_instance) +{ + // white out the entire screen + draw_box_background(base_pattern, {-1, -1}, {20, 18}); + + // front picture label + { + draw_stats_pokedex_number(base_pattern, pokemon_instance); + } + + // top status battle box + { + // name and border + draw_stats_top_right_border_with_name(base_pattern, pokemon_instance); + + // level + put_char(base_pattern, 14, 2, battle_border::level); + draw_number_left_align(base_pattern, {15, 2}, + pokemon_instance.level, + 3); // width + + // hp + draw_hp_bar_with_numbers(base_pattern, pokemon_instance, {11, 3}); + + // status + draw_text(base_pattern, S("STATUS/"), 9, 6); + draw_text(base_pattern, status_string(pokemon_instance), 16, 6); + } + + // bottom right border + { + draw_battle_border(base_pattern, {19, 9}, {12, 17}); + + int32_t distinct_types = (pokemon[pokemon_instance.species].types[0] + != pokemon[pokemon_instance.species].types[1]) + 1; + + for (int32_t ix = 0; ix < distinct_types; ix++) { + const enum type_t::type type = pokemon[pokemon_instance.species].types[ix]; + draw_text(base_pattern, S("TYPE"), 10, 9 + (2 * ix)); + put_char(base_pattern, 14, 9 + (2 * ix), ascii_to_font('1' + ix)); + put_char(base_pattern, 15, 9 + (2 * ix), ascii_to_font('/')); + draw_text(base_pattern, types[type].name, 11, 10 + (2 * ix)); + } + } + + { // bottom left dialog box + draw_box_border(base_pattern, {0, 8}, {9, 17}); + + draw_text(base_pattern, S("ATTACK"), 1, 9 + 0); + draw_text(base_pattern, S("DEFENSE"), 1, 9 + 2); + draw_text(base_pattern, S("SPEED"), 1, 9 + 4); + draw_text(base_pattern, S("SPECIAL"), 1, 9 + 6); + + draw_number_right_align(base_pattern, {6, 10 + 0}, + pokemon_instance.stat_values.attack, + 3, ascii_to_font(' ')); // width, fill + + draw_number_right_align(base_pattern, {6, 10 + 2}, + pokemon_instance.stat_values.defense, + 3, ascii_to_font(' ')); // width, fill + + draw_number_right_align(base_pattern, {6, 10 + 4}, + pokemon_instance.stat_values.speed, + 3, ascii_to_font(' ')); // width, fill + + draw_number_right_align(base_pattern, {6, 10 + 6}, + pokemon_instance.stat_values.special, + 3, ascii_to_font(' ')); // width, fill + } // end bottom left dialog box +} + +void draw_stats_move(const uint32_t base_pattern, + const pokemon_instance_t& pokemon_instance, + const int32_t _ix) +{ + const int32_t ix = _ix & 0b11; + const int32_t y_offset = 2 * ix; + + constexpr int32_t name_x = 2; + constexpr int32_t name_y = 9; + constexpr int32_t pp_x = 11; + constexpr int32_t pp_y = 10; + + // move name + const move_instance_t& move_instance = pokemon_instance.move_instances[ix]; + if (move_instance.type != move_t::no_move) { + const move_t& move = moves[move_instance.type]; + // name + draw_text(base_pattern, move.name, name_x, name_y + y_offset); + + // pp + put_char(base_pattern, pp_x + 0, pp_y + y_offset, stats::p); + put_char(base_pattern, pp_x + 1, pp_y + y_offset, stats::p); + + draw_number_right_align(base_pattern, {pp_x + 3, pp_y + y_offset}, + move_instance.pp, + 2, ascii_to_font(' ')); // width, fill + + put_char(base_pattern, pp_x + 5, pp_y + y_offset, ascii_to_font('/')); + + draw_number_right_align(base_pattern, {pp_x + 6, pp_y + y_offset}, + moves[move_instance.type].pp, + 2, ascii_to_font(' ')); // width, fill + } else { + // name + put_char(base_pattern, name_x, name_y + y_offset, ascii_to_font('-')); + // pp + put_char(base_pattern, pp_x + 0, pp_y + y_offset, ascii_to_font('-')); + put_char(base_pattern, pp_x + 1, pp_y + y_offset, ascii_to_font('-')); + } +} + +void draw_stats2(const uint32_t base_pattern, + const pokemon_instance_t& pokemon_instance) +{ + // white out the entire screen + draw_box_background(base_pattern, {-1, -1}, {20, 18}); + + // front picture label + { + draw_stats_pokedex_number(base_pattern, pokemon_instance); + } + + // top status battle box + { + // name and border + draw_stats_top_right_border_with_name(base_pattern, pokemon_instance); + + // exp points + draw_text(base_pattern, S("EXP POINTS"), 9, 3); + draw_number_right_align(base_pattern, + {13, 4}, + pokemon_instance.total_experience, + 6, // width + ascii_to_font(' ') // fill + ); + + // level up + draw_text(base_pattern, S("LEVEL UP"), 9, 5); + + put_char(base_pattern, 14, 6, stats::to); + put_char(base_pattern, 16, 6, battle_border::level); + + const int32_t next_level = pokemon_instance.level < 100 + ? (pokemon_instance.level + 1) + : 100; + int32_t exp_to = 1234; + + if (next_level < 100) { + draw_number_left_align(base_pattern, + {17, 6}, + next_level, + 2 // width + ); + } else { + draw_number_left_align(base_pattern, + {16, 6}, + next_level, + 3 // width + ); + } + + // exp + draw_number_right_align(base_pattern, + {9, 6}, + exp_to, + 5, // width + ascii_to_font(' ') // fill + ); + } + + // bottom move list + { + draw_box_border(base_pattern, {0, 8}, {19, 17}); + + draw_stats_move(base_pattern, pokemon_instance, 0); + draw_stats_move(base_pattern, pokemon_instance, 1); + draw_stats_move(base_pattern, pokemon_instance, 2); + draw_stats_move(base_pattern, pokemon_instance, 3); + } +} + +#undef S diff --git a/menu/stats.hpp b/menu/stats.hpp new file mode 100644 index 0000000..fcf2574 --- /dev/null +++ b/menu/stats.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "../pokemon_instance.hpp" + +void draw_stats1(const uint32_t base_pattern, + const pokemon_instance_t& pokemon_instance); + +void draw_stats2(const uint32_t base_pattern, + const pokemon_instance_t& pokemon_instance); diff --git a/pokemon_instance.cpp b/pokemon_instance.cpp index 94b169b..13086b2 100644 --- a/pokemon_instance.cpp +++ b/pokemon_instance.cpp @@ -59,13 +59,41 @@ pokemon_instance_t::learn_move(enum move_t::move move, int32_t index) case 1: [[fallthrough]]; case 2: [[fallthrough]]; case 3: - moves[index] = move; + move_instances[index].type = move; + move_instances[index].pp = moves[move].pp; break; default: - moves[0] = moves[1]; - moves[1] = moves[2]; - moves[2] = moves[3]; - moves[3] = move; + { + int32_t ix = 0; + while (move_instances[ix].type != move_t::no_move) { + if (move_instances[ix].type == move) return; // do not double-learn move_instances + if (++ix == 4) { + move_instances[0] = move_instances[1]; + move_instances[1] = move_instances[2]; + move_instances[2] = move_instances[3]; + ix--; + break; + } + } + move_instances[ix].type = move; + move_instances[ix].pp = moves[move].pp; + } break; } } + +void pokemon_instance_t::learn_all_moves() +{ + move_instances[0].type = move_t::no_move; + move_instances[1].type = move_t::no_move; + move_instances[2].type = move_t::no_move; + move_instances[3].type = move_t::no_move; + + const level_move_t * level_moves = pokemon[species].by_level.moves; + for (int32_t ix = 0; ix < pokemon[species].by_level.length; ix++) { + if (level_moves[ix].level <= level) + learn_move(level_moves[ix].move, -1); + else + break; + } +} diff --git a/pokemon_instance.hpp b/pokemon_instance.hpp index 999f5d3..39b9c34 100644 --- a/pokemon_instance.hpp +++ b/pokemon_instance.hpp @@ -64,6 +64,11 @@ struct stat_values_t { static_assert((sizeof (stat_values_t)) == 10); +struct move_instance_t { + enum move_t::move type; + uint8_t pp; +}; + struct pokemon_instance_t { uint8_t nickname[12]; enum pokemon_t::pokemon species; @@ -72,7 +77,7 @@ struct pokemon_instance_t { determinant_values_t determinant_values; stat_values_t stat_values; stat_experience_t stat_experience; - enum move_t::move moves[4]; + move_instance_t move_instances[4]; uint16_t current_hit_points; enum ailment_t::ailment ailment; @@ -84,4 +89,5 @@ struct pokemon_instance_t { void determine_stats(); void learn_move(enum move_t::move move, int32_t index); + void learn_all_moves(); }; diff --git a/tools/generate/pokemon/moves.py b/tools/generate/pokemon/moves.py index bbdc976..dd9e29d 100644 --- a/tools/generate/pokemon/moves.py +++ b/tools/generate/pokemon/moves.py @@ -51,7 +51,7 @@ def struct_move_t(): _move_constants_str = (f"{s.lower()}," for s in _move_constants) return [ "struct move_t {", - "const uint8_t * name;" + "const uint8_t * name;", "uint8_t animation;", "uint8_t effect;", "uint8_t power;",