diff --git a/Makefile b/Makefile index e867e42..66a736e 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,8 @@ SRC += vram.cpp SRC += font.cpp SRC += graphic.cpp SRC += menu.cpp +SRC += number.cpp +SRC += pokemon_instance.cpp DEP = $(patsubst %.cpp,%.cpp.d,$(SRC)) diff --git a/derived/font.png b/derived/font.png index 3e60100..56efba3 100644 Binary files a/derived/font.png and b/derived/font.png differ diff --git a/graphic.cpp b/graphic.cpp index 0ac2d09..db737e9 100644 --- a/graphic.cpp +++ b/graphic.cpp @@ -5,6 +5,9 @@ #include "font.hpp" #include "start_size.hpp" #include "control.hpp" +#include "number.hpp" + +#include "pokemon_instance.hpp" struct dialog_border { static constexpr uint8_t corner_top_left = 105; @@ -44,6 +47,12 @@ struct hp_bar { static constexpr uint8_t end_cap_bar = 0x66; }; +struct stats { + static constexpr uint8_t id = 0x70; // ID + static constexpr uint8_t no = 0x71; // No + static constexpr uint8_t no_dot = 0x4e; // . +}; + #define S reinterpret_cast void draw_text(const uint32_t base_pattern, @@ -184,23 +193,95 @@ void draw_hp_bar(const uint32_t base_pattern, put_char(base_pattern, top_left.x + 8, top_left.y, end_cap); } -static uint32_t hp = 0; - -void draw_stats1(const uint32_t base_pattern) +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}); // top status battle box - draw_battle_border(base_pattern, {19, 1}, {8, 7}); - draw_hp_bar(base_pattern, {11, 4}, hp_bar::end_cap_bar, (hp >> 3) % 48); - hp++; + { + draw_battle_border(base_pattern, {19, 1}, {8, 7}); - // bottom border - draw_battle_border(base_pattern, {19, 9}, {12, 17}); + // 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); - // bottom left dialog box - draw_box_border(base_pattern, {0, 8}, {9, 17}); + 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 + +#define S reinterpret_cast + draw_text(base_pattern, S("STATUS/___"), 9, 6); + } + + // bottom right border + { + draw_battle_border(base_pattern, {19, 9}, {12, 17}); + + for (int32_t ix = 0; ix < 2; ix++) { + draw_text(base_pattern, S("TYPE"), 10, 9 + (2 * ix)); + put_char(base_pattern, 15, 9 + (2 * ix), ascii_to_font('1' + ix)); + put_char(base_pattern, 15, 9 + (2 * ix), ascii_to_font('/')); + } + } + + { // 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); +#undef S + + 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, diff --git a/graphic.hpp b/graphic.hpp index e9c65bf..f9f5a7e 100644 --- a/graphic.hpp +++ b/graphic.hpp @@ -5,6 +5,8 @@ #include "render_map.hpp" // for cell_offset #include "vdp2.h" +#include "pokemon_instance.hpp" + static inline void put_char(const uint32_t base_pattern, const int32_t x, const int32_t y, // in cells const uint8_t c) @@ -27,11 +29,12 @@ 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); +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 top_left = { 0, 12}; static constexpr screen_cell_t bottom_right = {19, 17}; static void draw(const uint32_t base_pattern, diff --git a/main.cpp b/main.cpp index 90c1479..aad9bbb 100644 --- a/main.cpp +++ b/main.cpp @@ -27,6 +27,7 @@ #include "menu.hpp" #include "pokemon.hpp" +#include "pokemon_instance.hpp" static int32_t pokemon_raw_index = 0; @@ -171,6 +172,15 @@ void render_pokemon(const uint32_t ix, const enum pokemon_t::pokemon pokemon_id, const uint32_t character_address = (base_pattern * 16) / 8; const uint32_t dimension = pokemon_sprite_dimension(pokemon[pokemon_id].pic.front.size); + int32_t x_offset; + int32_t y_offset; + switch (dimension) { + default: + case 40: x_offset = 2; y_offset = 2; break; + case 48: x_offset = 1; y_offset = 1; break; + case 56: x_offset = 1; y_offset = 0; break; + } + vdp1.vram.cmd[ix].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE; vdp1.vram.cmd[ix].LINK = 0; // The "end code" is 0xf, which is being used in the mai sprite palette. If @@ -181,8 +191,8 @@ void render_pokemon(const uint32_t ix, const enum pokemon_t::pokemon pokemon_id, | COLR__COLOR_BANK__TYPE0__PR(0); vdp1.vram.cmd[ix].SRCA = character_address; vdp1.vram.cmd[ix].SIZE = SIZE__X(dimension) | SIZE__Y(dimension); - vdp1.vram.cmd[ix].XA = (cell_offset::x * 8) + screen_cell.x * 8; - vdp1.vram.cmd[ix].YA = (cell_offset::y * 8) + screen_cell.y * 8; + vdp1.vram.cmd[ix].XA = (cell_offset::x * 8) + (screen_cell.x + x_offset) * 8; + vdp1.vram.cmd[ix].YA = (cell_offset::y * 8) + (screen_cell.y + y_offset) * 8; } void render_sprites(const offset_t& offset) @@ -486,8 +496,12 @@ void update() //else if (event::button_a() ) check_sign(); } +static uint32_t frame = 0; + void render() { + frame++; + const offset_t offset = state.player.offset(); render_sprites(offset); @@ -505,7 +519,18 @@ void render() cursor_t cursor = { 1, 1 }; draw_menu_cursor(state.draw.base_pattern.font, fight_menu, cursor); - draw_stats1(state.draw.base_pattern.font); + static pokemon_instance_t pokemon_instance; + 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.determine_stats(); + 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); } extern "C" diff --git a/number.cpp b/number.cpp new file mode 100644 index 0000000..04ecf7e --- /dev/null +++ b/number.cpp @@ -0,0 +1,68 @@ +#include + +#include "graphic.hpp" +#include "font.hpp" + +// left-align (with fill) +// right-align + +struct bcd_t { + uint32_t digits; + int32_t length; + + auto operator<=>(const bcd_t&) const = default; +}; + +static inline constexpr bcd_t int_to_bcd(int32_t n) +{ + if (n < 0) n = 0; // are there negative numbers in pokemon? + if (n == 0) return { 0, 1 }; + uint32_t bcd = 0; + + int32_t index = 0; + while (n != 0) { + bcd |= (n % 10) << (4 * index); + n /= 10; + index += 1; + } + return { bcd, index }; +} + +static_assert(int_to_bcd(0) == (bcd_t){0x0, 1}); +static_assert(int_to_bcd(9) == (bcd_t){0x9, 1}); +static_assert(int_to_bcd(20) == (bcd_t){0x20, 2}); +static_assert(int_to_bcd(512) == (bcd_t){0x512, 3}); + +void draw_number_right_align(const uint32_t base_pattern, + const screen_cell_t& pos, + const int32_t n, + const int32_t width, + const uint8_t fill) +{ + const bcd_t bcd = int_to_bcd(n); + + int32_t index = 0; + while (index < width) { + const uint8_t digit = (index < bcd.length) + ? ascii_to_font('0' + ((bcd.digits >> (4 * index)) & 0b1111)) + : fill; + index++; + put_char(base_pattern, (pos.x + width) - index, pos.y, digit); + } +} + +void draw_number_left_align(const uint32_t base_pattern, + const screen_cell_t& pos, + const int32_t n, + const int32_t width) +{ + const bcd_t bcd = int_to_bcd(n); + + int32_t index = 0; + int32_t length = width < bcd.length ? width : bcd.length; + while (index < length) { + const uint8_t digit = ascii_to_font('0' + ((bcd.digits >> (4 * index)) & 0b1111)); + index++; + put_char(base_pattern, pos.x + (length - index), pos.y, digit); + } +} diff --git a/number.hpp b/number.hpp new file mode 100644 index 0000000..184d92c --- /dev/null +++ b/number.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "coordinates.hpp" + +void draw_number_right_align(const uint32_t base_pattern, + const screen_cell_t& pos, + const int32_t n, + const int32_t width, + const uint8_t fill); + +void draw_number_left_align(const uint32_t base_pattern, + const screen_cell_t& pos, + const int32_t n, + const int32_t width); diff --git a/pokemon.hpp b/pokemon.hpp index 0cdbbd9..a81ce56 100644 --- a/pokemon.hpp +++ b/pokemon.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include "gen/pokemon/moves.hpp" diff --git a/pokemon_instance.cpp b/pokemon_instance.cpp index cadc9da..94b169b 100644 --- a/pokemon_instance.cpp +++ b/pokemon_instance.cpp @@ -1,9 +1,9 @@ #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) +static constexpr 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(stat_experience) / 4; @@ -17,45 +17,41 @@ 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) +void +pokemon_instance_t::determine_stats() { - 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; - } + stat_values.hit_points = _determine_stat(pokemon[species].base_stat_values.hit_points, + determinant_values.hit_points(), + stat_experience.hit_points, + level + ) + 10 + level; + + stat_values.attack = _determine_stat(pokemon[species].base_stat_values.attack, + determinant_values.attack(), + stat_experience.attack, + level + ) + 5; + + stat_values.defense = _determine_stat(pokemon[species].base_stat_values.defense, + determinant_values.defense(), + stat_experience.defense, + level + ) + 5; + + stat_values.speed = _determine_stat(pokemon[species].base_stat_values.speed, + determinant_values.speed(), + stat_experience.speed, + level + ) + 5; + + stat_values.special = _determine_stat(pokemon[species].base_stat_values.special, + determinant_values.special(), + stat_experience.special, + level + ) + 5; } -constexpr inline uint16_t +void pokemon_instance_t::learn_move(enum move_t::move move, int32_t index) { switch (index) { diff --git a/pokemon_instance.hpp b/pokemon_instance.hpp index 00772c7..45a79a3 100644 --- a/pokemon_instance.hpp +++ b/pokemon_instance.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include "sqrt.hpp" @@ -77,7 +79,7 @@ struct pokemon_instance_t { // - id number // - original trainer - constexpr inline uint16_t determine_stat(enum stat_t stat); + void determine_stats(); - constexpr inline void learn_move(enum move_t::move move, uint32_t index); + void learn_move(enum move_t::move move, int32_t index); };