From 3d8110ca1cf49ba4fe798271e7365de73dec75f1 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Thu, 3 Aug 2023 22:19:01 +0000 Subject: [PATCH] graphic: add functions to draw stats screen borders This also draws pokemon sprites on the screen, sequentially. --- Makefile | 20 +++++- coordinates.hpp | 4 +- graphic.cpp | 114 +++++++++++++++++++++++++++--- graphic.hpp | 7 +- input.hpp | 4 +- main.cpp | 84 ++++++++++++++++++++-- tools/generate/pokemon/pokemon.py | 5 +- tools/png_to_nbpp_sprite.py | 6 +- vram.cpp | 4 +- 9 files changed, 218 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index 4559600..e867e42 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,17 @@ LIB = ./saturn GEN_PYTHON_SOURCE = $(wildcard tools/source/*.py) $(wildcard tools/generate/*.py) +GEN_HEADERS = +GEN_HEADERS += gen/maps.hpp +GEN_HEADERS += gen/tilesets.hpp +GEN_HEADERS += gen/sprites.hpp +GEN_HEADERS += gen/collision_tile_ids.hpp +GEN_HEADERS += gen/text.hpp +GEN_HEADERS += gen/text_pointers.hpp +GEN_HEADERS += gen/pokemon/moves.hpp +GEN_HEADERS += gen/pokemon/types.hpp +GEN_HEADERS += gen/pokemon/pokemon_enum.inc.hpp + GEN_SRC = GEN_SRC += gen/maps.cpp GEN_SRC += gen/map_objects.cpp @@ -13,6 +24,8 @@ GEN_SRC += gen/tilesets.cpp GEN_SRC += gen/collision_tile_ids.cpp GEN_SRC += gen/text.cpp GEN_SRC += gen/text_pointers.cpp +GEN_SRC += gen/pokemon/moves.cpp +GEN_SRC += gen/pokemon/pokemon.cpp SRC = SRC += $(GEN_SRC) @@ -35,9 +48,10 @@ GFX_POKEMON = $(call res_png,2bpp,pokered/gfx/pokemon/front/) $(call res_png,2bp MAPS_BLOCKS = $(call res,blk,pokered/maps/) FONTS = res/font.2bpp.o -GENERATED = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(GFX_SPRITES) $(GFX_POKEMON) $(MAPS_BLOCKS) $(GEN_SRC) $(FONTS) +GENERATED_SRC = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(GFX_SPRITES) $(GFX_POKEMON) $(MAPS_BLOCKS) $(GEN_SRC) $(FONTS) +GENERATED = $(GENERATED_SRC) $(GEN_HEADERS) -OBJ = $(patsubst %.cpp,%.o,$(SRC) $(GENERATED)) +OBJ = $(patsubst %.cpp,%.o,$(SRC) $(GENERATED_SRC)) all: main.cue @@ -57,7 +71,7 @@ endef gen/%.hpp: $(GEN_PYTHON_SOURCE) $(RUN_GENERATE) $@ -gen/%.cpp: gen/%.hpp $(GEN_PYTHON_SOURCE) +gen/%.cpp: $(GEN_PYTHON_SOURCE) $(RUN_GENERATE) $@ define PNG_TO_2BPP diff --git a/coordinates.hpp b/coordinates.hpp index 5bfec53..0349d47 100644 --- a/coordinates.hpp +++ b/coordinates.hpp @@ -22,8 +22,8 @@ struct block_t { }; struct screen_cell_t { - uint32_t x; - uint32_t y; + int32_t x; + int32_t y; }; struct screen_t { diff --git a/graphic.cpp b/graphic.cpp index 57f5008..0ac2d09 100644 --- a/graphic.cpp +++ b/graphic.cpp @@ -9,7 +9,7 @@ struct dialog_border { static constexpr uint8_t corner_top_left = 105; static constexpr uint8_t corner_top_right = 106; - static constexpr uint8_t corner_bot_left = 107; + static constexpr uint8_t corner_bottom_left = 107; static constexpr uint8_t corner_bottom_right = 108; static constexpr uint8_t vertical_end_cap = 109; static constexpr uint8_t vertical = 110; @@ -21,18 +21,34 @@ struct battle_border { static constexpr uint8_t corner_bottom_right = 97; static constexpr uint8_t arrow_right = 98; static constexpr uint8_t vertical = 99; - static constexpr uint8_t corner_bot_left = 100; + static constexpr uint8_t corner_bottom_left = 100; static constexpr uint8_t three_dots = 101; static constexpr uint8_t vertical_end_cap = 102; static constexpr uint8_t level = 103; static constexpr uint8_t arrow_left = 104; }; +struct hp_bar { + static constexpr uint8_t hp = 0x50; + static constexpr uint8_t start_cap = 0x51; + static constexpr uint8_t seg0 = 0x52; + static constexpr uint8_t seg1 = 0x53; + static constexpr uint8_t seg2 = 0x54; + static constexpr uint8_t seg3 = 0x55; + static constexpr uint8_t seg4 = 0x56; + static constexpr uint8_t seg5 = 0x57; + static constexpr uint8_t seg6 = 0x58; + static constexpr uint8_t seg7 = 0x59; + static constexpr uint8_t seg8 = 0x5a; + static constexpr uint8_t end_cap_nobar = 0x5b; + static constexpr uint8_t end_cap_bar = 0x66; +}; + #define S reinterpret_cast void draw_text(const uint32_t base_pattern, const uint8_t * text, - const uint32_t x, const uint32_t y) + const int32_t x, const int32_t y) { uint8_t i = 0, x_o = 0; uint8_t c; @@ -80,9 +96,9 @@ void draw_box_border(const uint32_t base_pattern, put_char(base_pattern, top_left.x, top_left.y, dialog_border::corner_top_left); put_char(base_pattern, bottom_right.x, top_left.y, dialog_border::corner_top_right); put_char(base_pattern, bottom_right.x, bottom_right.y, dialog_border::corner_bottom_right); - put_char(base_pattern, top_left.x, bottom_right.y, dialog_border::corner_bot_left); + put_char(base_pattern, top_left.x, bottom_right.y, dialog_border::corner_bottom_left); - for (uint32_t y = top_left.y + 1; y < bottom_right.y; y++) { + for (int32_t y = top_left.y + 1; y < bottom_right.y; y++) { // left vertical bar put_char(base_pattern, top_left.x, y, dialog_border::vertical); // right vertical bar @@ -90,7 +106,7 @@ void draw_box_border(const uint32_t base_pattern, } - for (uint32_t x = top_left.x + 1; x < bottom_right.x; x++) { + for (int32_t x = top_left.x + 1; x < bottom_right.x; x++) { // top horizontal bar put_char(base_pattern, x, top_left.y, dialog_border::horizontal); @@ -102,13 +118,91 @@ void draw_box_border(const uint32_t base_pattern, void draw_box_background(const uint32_t base_pattern, const screen_cell_t& top_left, const screen_cell_t& bottom_right) { - for (uint32_t x = top_left.x + 1; x < bottom_right.x; x++) { - for (uint32_t y = top_left.y + 1; y < bottom_right.y; y++) { + for (int32_t x = top_left.x + 1; x < bottom_right.x; x++) { + for (int32_t y = top_left.y + 1; y < bottom_right.y; y++) { put_char(base_pattern, x, y, ascii_to_font(' ')); } } } +void draw_battle_border(const uint32_t base_pattern, + const screen_cell_t& top_corner, const screen_cell_t& bottom_corner) +{ + // draw the vertical line + for (int32_t y = top_corner.y; y < bottom_corner.y; y++) + put_char(base_pattern, top_corner.x, y, battle_border::vertical); + + // decide which direction we are drawing + const int32_t increment = bottom_corner.x <= top_corner.x ? 1 : -1; + + // draw the horizontal line + for (int32_t x = bottom_corner.x + increment; x != top_corner.x; x += increment) + put_char(base_pattern, x, bottom_corner.y, battle_border::horizontal); + + // draw the end cap and corner piece + const uint8_t end_cap = increment == 1 ? battle_border::arrow_left + : battle_border::arrow_right; + const uint8_t corner = increment == 1 ? battle_border::corner_bottom_right + : battle_border::corner_bottom_left; + put_char(base_pattern, bottom_corner.x, bottom_corner.y, end_cap); + put_char(base_pattern, top_corner.x , bottom_corner.y, corner ); +} + +constexpr inline uint8_t hp_seg(const int32_t hp_48, const int32_t ix) +{ + const int32_t hp_ix = hp_48 - (ix * 8); + + if (hp_ix < 0) return hp_bar::seg0; + + switch (hp_ix) { + default: [[fallthrough]]; + case 8: return hp_bar::seg8; + case 7: return hp_bar::seg7; + case 6: return hp_bar::seg6; + case 5: return hp_bar::seg5; + case 4: return hp_bar::seg4; + case 3: return hp_bar::seg3; + case 2: return hp_bar::seg2; + case 1: return hp_bar::seg1; + case 0: return hp_bar::seg0; + } +} + +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); + + for (int32_t ix = 0; ix < 6; ix++) { + const uint8_t seg = hp_seg(hp_48, ix); + put_char(base_pattern, top_left.x + 2 + ix, top_left.y, seg); + } + + 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) +{ + // 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++; + + // bottom border + draw_battle_border(base_pattern, {19, 9}, {12, 17}); + + // bottom left dialog box + draw_box_border(base_pattern, {0, 8}, {9, 17}); +} + void dialog_t::draw(const uint32_t base_pattern, const start_size_t& text) { @@ -116,8 +210,8 @@ void dialog_t::draw(const uint32_t base_pattern, draw_box_background(base_pattern, top_left, bottom_right); uint32_t ix = 0; - uint32_t x = top_left.x + 1; - uint32_t y = top_left.y + 2; + 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]; diff --git a/graphic.hpp b/graphic.hpp index d86a74c..e9c65bf 100644 --- a/graphic.hpp +++ b/graphic.hpp @@ -16,7 +16,7 @@ static inline void put_char(const uint32_t base_pattern, void draw_text(const uint32_t base_pattern, const uint8_t * text, - const uint32_t x, const uint32_t y); + const int32_t x, const int32_t y); void draw_box_border(const uint32_t base_pattern, const screen_cell_t& top_left, const screen_cell_t& bottom_right); @@ -24,6 +24,11 @@ void draw_box_border(const uint32_t base_pattern, void draw_box_background(const uint32_t base_pattern, const screen_cell_t& top_left, const screen_cell_t& bottom_right); +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); + struct dialog_t { static constexpr screen_cell_t top_left = { 0, 12}; diff --git a/input.hpp b/input.hpp index fb1ee62..4a077fb 100644 --- a/input.hpp +++ b/input.hpp @@ -29,8 +29,8 @@ void digital_callback(uint8_t fsm_state, uint8_t data); extern input_t input; -constexpr int input_arr = 1; -constexpr int input_das = 1; +constexpr int input_arr = 10; +constexpr int input_das = 20; constexpr int input_debounce = 2; static constexpr inline int32_t diff --git a/main.cpp b/main.cpp index 9460b8a..ce9e202 100644 --- a/main.cpp +++ b/main.cpp @@ -26,11 +26,19 @@ #include "graphic.hpp" #include "menu.hpp" +#include "pokemon.hpp" + +static int32_t pokemon_raw_index = 0; + struct draw_t { struct { uint16_t font; // div 32 uint16_t tilesets[tileset_t::count]; // div 32 uint16_t spritesheets[spritesheet_t::count]; // div 128 + struct { + uint16_t front; + uint16_t back; + } pokemon[pokemon_t::count]; // div 16 } base_pattern; }; @@ -45,21 +53,31 @@ static state_t state = { map_t::pallet_town, map_t::last_map, 0 }; uint32_t load_tileset(uint32_t top, enum tileset_t::tileset tileset) { - uint32_t base_address = top = cell_data(tilesets[tileset].tileset, top); + const uint32_t base_address = top = cell_data(tilesets[tileset].tileset, top); state.draw.base_pattern.tilesets[tileset] = base_address / 32; return top; } -uint32_t load_sprite(uint32_t top, enum spritesheet_t::spritesheet spritesheet) +uint32_t load_spritesheet(uint32_t top, enum spritesheet_t::spritesheet spritesheet) { const spritesheet_t& s = spritesheets[spritesheet]; - uint32_t base_address = top = character_pattern_table(s.spritesheet, top); + const uint32_t base_address = top = character_pattern_table(s.spritesheet, top); state.draw.base_pattern.spritesheets[spritesheet] = base_address / 128; return top; } +uint32_t load_pokemon(uint32_t top, enum pokemon_t::pokemon _pokemon) +{ + const pokemon_t& p = pokemon[_pokemon]; + const uint32_t base_address_front = top = character_pattern_table(p.pic.front, top); + const uint32_t base_address_back = top = character_pattern_table(p.pic.back, top); + state.draw.base_pattern.pokemon[_pokemon].front = base_address_front / 16; + state.draw.base_pattern.pokemon[_pokemon].back = base_address_back / 16; + return top; +} + void load_vram() { vdp2.reg.CYCA0 = 0xeeee'eeee; @@ -81,7 +99,10 @@ void load_vram() uint32_t vdp1_top = (sizeof (union vdp1_vram)); for (uint32_t i = 0; i < spritesheet_t::count; i++) - vdp1_top = load_sprite(vdp1_top, static_cast(i)); + vdp1_top = load_spritesheet(vdp1_top, static_cast(i)); + + for (uint32_t i = 0; i < pokemon_t::count; i++) + vdp1_top = load_pokemon(vdp1_top, static_cast(i)); } static inline uint32_t facing_offset(const actor_t::direction facing) @@ -109,7 +130,7 @@ static inline uint32_t facing_inverted(const actor_t::direction facing, const ui } } -void render_sprite(const uint32_t ix, const uint32_t sprite_id, +void render_sprite(const uint32_t ix, const enum spritesheet_t::spritesheet sprite_id, const enum actor_t::direction facing, const uint32_t animation_frame, const uint32_t animation_cycle, const screen_t& screen, const offset_t& offset, int32_t y_offset) @@ -135,6 +156,41 @@ void render_sprite(const uint32_t ix, const uint32_t sprite_id, vdp1.vram.cmd[ix].YA = (cell_offset::y * 8) + screen.y * 16 + y_offset - offset.y; } +uint32_t pokemon_sprite_dimension(const uint32_t size) +{ + switch (size) { + default: [[fallthrough]]; + case 256: return 32; + case 400: return 40; + case 576: return 48; + case 784: return 56; + } +} + +void render_pokemon(const uint32_t ix, const enum pokemon_t::pokemon pokemon_id, + const screen_cell_t& screen_cell) +{ + constexpr uint32_t color_address = 0; + const uint32_t base_pattern = state.draw.base_pattern.pokemon[pokemon_id].front; + const uint32_t character_address = (base_pattern * 16) / 8; + const uint32_t dimension = pokemon_sprite_dimension(pokemon[pokemon_id].pic.front.size); + + 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 + // both transparency and end codes are enabled, it seems there are only 14 + // usable colors in the 4-bit color mode. + vdp1.vram.cmd[ix].PMOD = PMOD__ECD | PMOD__COLOR_MODE__COLOR_BANK_16; + // It appears Kronos does not correctly calculate the color address in the + // VDP1 debugger. Kronos will report FFFC when the actual color table address + // in this example is 7FFE0. + vdp1.vram.cmd[ix].COLR = color_address; // non-palettized (rgb15) color data + 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; +} + void render_sprites(const offset_t& offset) { uint32_t ix = 2; @@ -148,6 +204,10 @@ void render_sprites(const offset_t& offset) state.player.y_offset()); ix++; + render_pokemon(ix, static_cast(pokemon_raw_index), + {0, 0}); + ix++; + const object_t& obj = map_objects[state.map]; for (uint32_t i = 0; i < obj.object_length; i++) { const object_event_t& event = obj.object_events[i]; @@ -417,10 +477,18 @@ void update() change_maps(); update_warp(); + /* if (event::cursor_left() ) collision_move(maps[state.map], actor_t::left); 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); + */ + + if (event::cursor_left() ) pokemon_raw_index = (pokemon_raw_index - 1); + else if (event::cursor_right()) pokemon_raw_index = (pokemon_raw_index + 1); + 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(); } @@ -442,6 +510,8 @@ void render() draw_menu(state.draw.base_pattern.font, fight_menu); cursor_t cursor = { 1, 1 }; draw_menu_cursor(state.draw.base_pattern.font, fight_menu, cursor); + + draw_stats1(state.draw.base_pattern.font); } extern "C" @@ -529,9 +599,9 @@ void init_vdp1() void init_vdp2() { - vdp2.reg.PRISA = PRISA__S0PRIN(6); // Sprite register 0 PRIority Number + vdp2.reg.PRISA = PRISA__S0PRIN(7); // Sprite register 0 PRIority Number vdp2.reg.PRINA = PRINA__N0PRIN(5) - | PRINA__N1PRIN(7); + | PRINA__N1PRIN(6); // DISP: Please make sure to change this bit from 0 to 1 during V blank. vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE diff --git a/tools/generate/pokemon/pokemon.py b/tools/generate/pokemon/pokemon.py index 4629119..afe66eb 100644 --- a/tools/generate/pokemon/pokemon.py +++ b/tools/generate/pokemon/pokemon.py @@ -127,11 +127,14 @@ def dex_constant_to_enum_name(dex_constant): return name.lower() def enum_inc(): + dex_constants = parse.pokemon_dex_constants_list() yield "enum pokemon {" - for dex_constant in parse.pokemon_dex_constants_list(): + for dex_constant in dex_constants: name = dex_constant_to_enum_name(dex_constant) yield f"{name.lower()}," yield "};" + yield "" + yield f"static constexpr int32_t count = {len(dex_constants)};" def generate_enum_inc(): render, out = renderer() diff --git a/tools/png_to_nbpp_sprite.py b/tools/png_to_nbpp_sprite.py index 7c4846f..0f270c6 100644 --- a/tools/png_to_nbpp_sprite.py +++ b/tools/png_to_nbpp_sprite.py @@ -5,8 +5,8 @@ from PIL import Image from palette import intensity_to_index -cell_width = 16 -cell_height = 16 +cell_width = None +cell_height = None def convert(image, bpp): assert bpp in {8, 4, 2}, bpp @@ -64,6 +64,8 @@ assert len(sys.argv) >= 4, sys.argv with open(out_path, 'wb') as f: for in_path in in_paths: im = Image.open(in_path) + cell_width, cell_height = im.size + assert cell_width in {16, 32, 40, 48, 56}, cell_width buf = convert(im, bpp) if 'NBPP_DEBUG' in os.environ: debug(buf, bpp) diff --git a/vram.cpp b/vram.cpp index 1b821f8..7f34d31 100644 --- a/vram.cpp +++ b/vram.cpp @@ -40,8 +40,8 @@ static void _2bpp_4bpp_vram_copy(uint32_t * vram, const start_size_t& buf) uint32_t character_pattern_table(const start_size_t& buf, const uint32_t top) { - // round to nearest multiple of 32 - const uint32_t table_size = ((buf.size * 2) + 0x20 - 1) & (-0x20); + // round to nearest multiple of 16 + const uint32_t table_size = ((buf.size * 2) + 0x10 - 1) & (-0x10); const uint32_t base_address = top - table_size; uint32_t * vram = &vdp1.vram.u32[(base_address / 4)];