graphic: display nearly-complete stats page #1

Type is not displayed because the type names have no
parsers/generators yet.
This commit is contained in:
Zack Buhman 2023-08-04 03:28:17 +00:00
parent e88135d4e3
commit abe7bde678
10 changed files with 252 additions and 57 deletions

View File

@ -35,6 +35,8 @@ SRC += vram.cpp
SRC += font.cpp SRC += font.cpp
SRC += graphic.cpp SRC += graphic.cpp
SRC += menu.cpp SRC += menu.cpp
SRC += number.cpp
SRC += pokemon_instance.cpp
DEP = $(patsubst %.cpp,%.cpp.d,$(SRC)) DEP = $(patsubst %.cpp,%.cpp.d,$(SRC))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -5,6 +5,9 @@
#include "font.hpp" #include "font.hpp"
#include "start_size.hpp" #include "start_size.hpp"
#include "control.hpp" #include "control.hpp"
#include "number.hpp"
#include "pokemon_instance.hpp"
struct dialog_border { struct dialog_border {
static constexpr uint8_t corner_top_left = 105; static constexpr uint8_t corner_top_left = 105;
@ -44,6 +47,12 @@ struct hp_bar {
static constexpr uint8_t end_cap_bar = 0x66; 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<const uint8_t *> #define S reinterpret_cast<const uint8_t *>
void draw_text(const uint32_t base_pattern, 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); 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,
const pokemon_instance_t& pokemon_instance)
void draw_stats1(const uint32_t base_pattern)
{ {
// white out the entire screen // white out the entire screen
draw_box_background(base_pattern, {-1, -1}, {20, 18}); draw_box_background(base_pattern, {-1, -1}, {20, 18});
// top status battle box // top status battle box
{
draw_battle_border(base_pattern, {19, 1}, {8, 7}); 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++;
// bottom border // 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
#define S reinterpret_cast<const uint8_t *>
draw_text(base_pattern, S("STATUS/___"), 9, 6);
}
// bottom right border
{
draw_battle_border(base_pattern, {19, 9}, {12, 17}); draw_battle_border(base_pattern, {19, 9}, {12, 17});
// bottom left dialog box 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_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<int32_t>(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, void dialog_t::draw(const uint32_t base_pattern,

View File

@ -5,6 +5,8 @@
#include "render_map.hpp" // for cell_offset #include "render_map.hpp" // for cell_offset
#include "vdp2.h" #include "vdp2.h"
#include "pokemon_instance.hpp"
static inline void put_char(const uint32_t base_pattern, static inline void put_char(const uint32_t base_pattern,
const int32_t x, const int32_t y, // in cells const int32_t x, const int32_t y, // in cells
const uint8_t c) const uint8_t c)
@ -27,7 +29,8 @@ void draw_box_background(const uint32_t base_pattern,
void draw_battle_border(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); 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 struct dialog_t
{ {

View File

@ -27,6 +27,7 @@
#include "menu.hpp" #include "menu.hpp"
#include "pokemon.hpp" #include "pokemon.hpp"
#include "pokemon_instance.hpp"
static int32_t pokemon_raw_index = 0; 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 character_address = (base_pattern * 16) / 8;
const uint32_t dimension = pokemon_sprite_dimension(pokemon[pokemon_id].pic.front.size); 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].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE;
vdp1.vram.cmd[ix].LINK = 0; vdp1.vram.cmd[ix].LINK = 0;
// The "end code" is 0xf, which is being used in the mai sprite palette. If // 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); | COLR__COLOR_BANK__TYPE0__PR(0);
vdp1.vram.cmd[ix].SRCA = character_address; vdp1.vram.cmd[ix].SRCA = character_address;
vdp1.vram.cmd[ix].SIZE = SIZE__X(dimension) | SIZE__Y(dimension); 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].XA = (cell_offset::x * 8) + (screen_cell.x + x_offset) * 8;
vdp1.vram.cmd[ix].YA = (cell_offset::y * 8) + screen_cell.y * 8; vdp1.vram.cmd[ix].YA = (cell_offset::y * 8) + (screen_cell.y + y_offset) * 8;
} }
void render_sprites(const offset_t& offset) void render_sprites(const offset_t& offset)
@ -486,8 +496,12 @@ void update()
//else if (event::button_a() ) check_sign(); //else if (event::button_a() ) check_sign();
} }
static uint32_t frame = 0;
void render() void render()
{ {
frame++;
const offset_t offset = state.player.offset(); const offset_t offset = state.player.offset();
render_sprites(offset); render_sprites(offset);
@ -505,7 +519,18 @@ void render()
cursor_t cursor = { 1, 1 }; cursor_t cursor = { 1, 1 };
draw_menu_cursor(state.draw.base_pattern.font, fight_menu, cursor); 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<enum pokemon_t::pokemon>(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" extern "C"

68
number.cpp Normal file
View File

@ -0,0 +1,68 @@
#include <cstdint>
#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);
}
}

16
number.hpp Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
#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);

View File

@ -1,3 +1,5 @@
#pragma once
#include <cstdint> #include <cstdint>
#include "gen/pokemon/moves.hpp" #include "gen/pokemon/moves.hpp"

View File

@ -1,6 +1,6 @@
#include "pokemon_instance.hpp" #include "pokemon_instance.hpp"
static uint16_t _determine_stat(const uint32_t base_stat_value, static constexpr uint16_t _determine_stat(const uint32_t base_stat_value,
const uint32_t determinant_value, const uint32_t determinant_value,
const uint32_t stat_experience, const uint32_t stat_experience,
const uint32_t level) const uint32_t level)
@ -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(130, 0b1000, 0, 70) == (198 - 5));
static_assert(_determine_stat(154, 0b0110, 0, 70) == (229 - 5)); static_assert(_determine_stat(154, 0b0110, 0, 70) == (229 - 5));
constexpr inline uint16_t void
pokemon_instance_t::determine_stat(enum stat_t stat) pokemon_instance_t::determine_stats()
{ {
switch (stat) { stat_values.hit_points = _determine_stat(pokemon[species].base_stat_values.hit_points,
default:
case stat_t::hit_points:
return _determine_stat(pokemon[species].base_stat_values.hit_points,
determinant_values.hit_points(), determinant_values.hit_points(),
stat_experience.hit_points, stat_experience.hit_points,
level) level
+ 10 + level; ) + 10 + level;
case stat_t::attack:
return _determine_stat(pokemon[species].base_stat_values.attack, stat_values.attack = _determine_stat(pokemon[species].base_stat_values.attack,
determinant_values.attack(), determinant_values.attack(),
stat_experience.attack, stat_experience.attack,
level) level
+ 5; ) + 5;
case stat_t::defense:
return _determine_stat(pokemon[species].base_stat_values.defense, stat_values.defense = _determine_stat(pokemon[species].base_stat_values.defense,
determinant_values.defense(), determinant_values.defense(),
stat_experience.defense, stat_experience.defense,
level) level
+ 5; ) + 5;
case stat_t::speed:
return _determine_stat(pokemon[species].base_stat_values.speed, stat_values.speed = _determine_stat(pokemon[species].base_stat_values.speed,
determinant_values.speed(), determinant_values.speed(),
stat_experience.speed, stat_experience.speed,
level) level
+ 5; ) + 5;
case stat_t::special:
return _determine_stat(pokemon[species].base_stat_values.special, stat_values.special = _determine_stat(pokemon[species].base_stat_values.special,
determinant_values.special(), determinant_values.special(),
stat_experience.special, stat_experience.special,
level) level
+ 5; ) + 5;
}
} }
constexpr inline uint16_t void
pokemon_instance_t::learn_move(enum move_t::move move, int32_t index) pokemon_instance_t::learn_move(enum move_t::move move, int32_t index)
{ {
switch (index) { switch (index) {

View File

@ -1,3 +1,5 @@
#pragma once
#include <cstdint> #include <cstdint>
#include "sqrt.hpp" #include "sqrt.hpp"
@ -77,7 +79,7 @@ struct pokemon_instance_t {
// - id number // - id number
// - original trainer // - 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);
}; };