diff --git a/Makefile b/Makefile index 95c7716..0b25637 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CFLAGS = -Isaturn -OPT ?= -O3 +OPT ?= -Og LIB = ./saturn GEN_PYTHON_SOURCE = $(wildcard tools/source/*.py) $(wildcard tools/generate/*.py) @@ -35,11 +35,12 @@ SRC += input.cpp SRC += vram.cpp SRC += font.cpp SRC += graphic.cpp -SRC += menu.cpp SRC += number.cpp SRC += pokemon_instance.cpp SRC += ailment.cpp -SRC += menu/stats.cpp +SRC += party.cpp +SRC += window/window.cpp +SRC += window/menu.cpp DEP = $(patsubst %.cpp,%.cpp.d,$(SRC)) diff --git a/battle.hpp b/battle.hpp new file mode 100644 index 0000000..e1b81d5 --- /dev/null +++ b/battle.hpp @@ -0,0 +1,6 @@ +enum battle_action { + fight, + swap_pokemon, + item, + run, +}; diff --git a/cell_offset.hpp b/cell_offset.hpp new file mode 100644 index 0000000..8690ba9 --- /dev/null +++ b/cell_offset.hpp @@ -0,0 +1,5 @@ +struct cell_offset +{ + static constexpr int32_t x = (320 - 160) / (2 * 8); + static constexpr int32_t y = (240 - 144) / (2 * 8); +}; diff --git a/derived/font.png b/derived/font.png index ac4957a..240d9ff 100644 Binary files a/derived/font.png and b/derived/font.png differ diff --git a/graphic.hpp b/graphic.hpp index 0b67d4a..8fe522c 100644 --- a/graphic.hpp +++ b/graphic.hpp @@ -4,7 +4,7 @@ #include "coordinates.hpp" #include "start_size.hpp" -#include "render_map.hpp" // for cell_offset +#include "cell_offset.hpp" // for cell_offset #include "vdp2.h" #include "pokemon_instance.hpp" diff --git a/input.hpp b/input.hpp index 4a077fb..14a55dc 100644 --- a/input.hpp +++ b/input.hpp @@ -57,4 +57,5 @@ struct event { static inline bool cursor_up() { return input_flopped(input.up ) >= 1; } static inline bool cursor_down() { return input_flopped(input.down ) >= 1; } static inline bool button_a() { return input_flopped(input.a ) == 1; } + static inline bool button_b() { return input_flopped(input.b ) == 1; } }; diff --git a/main.cpp b/main.cpp index 929dceb..66e631c 100644 --- a/main.cpp +++ b/main.cpp @@ -14,24 +14,17 @@ #include "vram.hpp" #include "font.hpp" -#include "gen/maps.hpp" +#include "gen/tilesets.hpp" #include "gen/sprites.hpp" -#include "map_objects.hpp" - -#include "coordinates.hpp" -#include "render_map.hpp" -#include "actor.hpp" -#include "ledge_tiles.hpp" - -#include "graphic.hpp" -#include "menu.hpp" - #include "pokemon.hpp" -#include "pokemon_instance.hpp" +#include "gen/maps.hpp" +#include "actor.hpp" +#include "player.hpp" -#include "menu/stats.hpp" +#include "cell_offset.hpp" -static int32_t pokemon_raw_index = 0; +#include "window/window.hpp" +#include "window/window_stack.hpp" struct draw_t { struct { @@ -46,10 +39,8 @@ struct draw_t { }; struct state_t { - enum map_t::map map; - enum map_t::map last_map; draw_t draw; - actor_t player; + player_state_t player[1]; }; static state_t state = { map_t::pallet_town, map_t::last_map, 0 }; @@ -203,6 +194,7 @@ void render_sprites(const offset_t& offset) { uint32_t ix = 2; + /* const uint32_t animation_frame = ((state.player.frame & 0b1000) != 0) * 3; render_sprite(ix, spritesheet_t::red, @@ -212,11 +204,6 @@ void render_sprites(const offset_t& offset) state.player.y_offset()); ix++; - render_pokemon(ix, static_cast(pokemon_raw_index), - {0, 0}, - true); // invert horizontally - 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]; @@ -229,6 +216,7 @@ void render_sprites(const offset_t& offset) -4); ix++; } + */ constexpr uint16_t top_x = 80 - 1; constexpr uint16_t top_y = 48 - 1; @@ -262,249 +250,39 @@ void render_sprites(const offset_t& offset) void render_map() { - const map_t& map = maps[state.map]; - const uint32_t base_pattern = state.draw.base_pattern.tilesets[map.tileset]; - vdp2.reg.PNCN0 = PNCN0__N0PNB__1WORD | PNCN0__N0CNSM | PNCN0__N0SCN((base_pattern >> 10) & 0x1f); + //const map_t& map = maps[state.map]; + //const uint32_t base_pattern = state.draw.base_pattern.tilesets[map.tileset]; + //vdp2.reg.PNCN0 = PNCN0__N0PNB__1WORD | PNCN0__N0CNSM | PNCN0__N0SCN((base_pattern >> 10) & 0x1f); for (int32_t y = (0 - 1); y < (9 + 2); y++) { for (int32_t x = (0 - 2); x < (10 + 2); x++) { + /* render_screen(base_pattern, map, state.player.world, {x, y} ); - } - } - - vdp2.reg.BGON = BGON__N0ON | BGON__N0TPON | BGON__N1ON; -} - -constexpr inline world_t direction_offset(const world_t& world, const enum actor_t::direction dir) -{ - switch (dir) { - default: [[fallthrough]]; - case actor_t::up: return {world.x , world.y - 1}; - case actor_t::down: return {world.x , world.y + 1}; - case actor_t::left: return {world.x - 1, world.y }; - case actor_t::right: return {world.x + 1, world.y }; - } -} - -constexpr inline uint8_t get_tile_ix(const map_t& map, const world_t& coord) -{ - // keep this synchronized with render_screen() - - const uint8_t block_ix = get_block(map, coord.to_block()); - const tileset_t& tileset = tilesets[map.tileset]; - const uint8_t * block_start = &(tileset.blockset.start)[block_ix * 4 * 4]; - const int32_t quadrant_x = 2 * (coord.x & 1); - const int32_t quadrant_y = 2 * (coord.y & 1); - - const int32_t block_row = 4 * (quadrant_y + 1); - const uint8_t tile_ix = block_start[block_row + quadrant_x]; - - return tile_ix; -} - -constexpr inline enum actor_t::collision collision(const map_t& map, - const world_t& world, - enum actor_t::direction direction) -{ - const world_t coord = direction_offset(world, direction); - uint8_t collision_tile_ix = get_tile_ix(map, coord); - - const tileset_t& tileset = tilesets[map.tileset]; - for (uint32_t i = 0; i < tileset.collision.size; i++) { - if (tileset.collision.start[i] == collision_tile_ix) - return actor_t::passable; - } - - // check ledge_tile pairs - - uint8_t actor_tile_ix = get_tile_ix(map, world); - - for (uint32_t i = 0; i < ledge_tiles_length; i++) { - const ledge_tile_t& lt = ledge_tiles[i]; - if (direction == lt.direction - && actor_tile_ix == lt.actor - && collision_tile_ix == lt.collision) { - return actor_t::jump; - } - } - - return actor_t::impassable; -} - -constexpr inline void collision_move(const map_t& map, actor_t::direction dir) -{ - const enum actor_t::collision c = collision(map, state.player.world, dir); - state.player.move(c, dir); -} - -void change_maps() -{ - const map_t& map = maps[state.map]; - -#define _has(_dir_) (map.connections[map_t::connection_t::_dir_].map != map_t::unconnected) -#define _get(_dir_) (maps[map.connections[map_t::connection_t::_dir_].map]) -#define _offset(_dir_) (map.connections[map_t::connection_t::_dir_].offset) - - const block_t block = state.player.world.to_block(); - if (block.y < 0) { - // north - if (_has(north)) { - const map_t& north_map = _get(north); - state.player.world.y = ((north_map.height + block.y) << 1) | (state.player.world.y & 1); - state.player.world.x = ((block.x - _offset(north)) << 1) | (state.player.world.x & 1); - state.map = map.connections[map_t::connection_t::north].map; - } - } else if (block.y >= static_cast(map.height)) { - // south - if (_has(south)) { - state.player.world.y = ((block.y - map.height) << 1) | (state.player.world.y & 1); - state.player.world.x = ((block.x - _offset(south)) << 1) | (state.player.world.x & 1); - state.map = map.connections[map_t::connection_t::south].map; - } - } else if (block.x < 0) { - // west - if (_has(west)) { - const map_t& west_map = _get(west); - state.player.world.x = ((west_map.width + block.x) << 1) | (state.player.world.x & 1); - state.player.world.y = ((block.y - _offset(west)) << 1) | (state.player.world.y & 1); - state.map = map.connections[map_t::connection_t::west].map; - } - } else if (block.x >= static_cast(map.width)) { - // east - if (_has(east)) { - state.player.world.x = ((block.x - map.width) << 1) | (state.player.world.x & 1); - state.player.world.y = ((block.y - _offset(east)) << 1) | (state.player.world.y & 1); - state.map = map.connections[map_t::connection_t::east].map; - } - } - -#undef _offset -#undef _get -#undef _has -} - -constexpr inline bool is_outside(const enum map_t::map& map_id) -{ - const map_t& map = maps[map_id]; - switch (map.tileset) { - case tileset_t::overworld: [[fallthrough]]; - case tileset_t::plateau: - return true; - default: - return false; - } -} - -struct collision_direction_t { - enum actor_t::collision collision; - enum actor_t::direction direction; -}; - -constexpr inline collision_direction_t find_direction(const map_t& map, const world_t& world, const enum actor_t::direction facing) -{ - // first, try `facing`, as this is typically correct in non- edge-cases - const enum actor_t::collision c_facing = collision(map, world, facing); - if (c_facing != actor_t::impassable) - return {c_facing, facing}; - - // otherwise try all other directions - // (this checks facing a second time for no reason) - constexpr enum actor_t::direction dirs[] = {actor_t::up, actor_t::down, actor_t::left, actor_t::right}; - for (const enum actor_t::direction& dir : dirs) { - const enum actor_t::collision c = collision(map, world, dir); - if (c != actor_t::impassable) - return {c, dir}; - } - - return {actor_t::impassable, facing}; -} - -void update_warp() -{ - if (state.player.moving) return; - world_t& coord = state.player.world; - - for (uint32_t j = 0; j < map_objects[state.map].warp_length; j++) { - const warp_event_t& warp = map_objects[state.map].warp_events[j]; - if (coord.x == warp.position.x && coord.y == warp.position.y) { - if (warp.destination.map == map_t::last_map) { - if (state.last_map == map_t::last_map) { - // use last_map as a sentinel value for "the player hasn't - // warped yet". This should never happen in normal gameplay. - continue; - } - - state.map = state.last_map; - } else { - // Only change last_map if the current map is an "outside" - // map. Warps that use last_map are designed to be used this - // way. - if (is_outside(state.map)) state.last_map = state.map; - - state.map = warp.destination.map; - } - // warp_index starts at 1 - const warp_event_t& dest = map_objects[state.map].warp_events[warp.destination.warp_index - 1]; - coord.x = dest.position.x; - coord.y = dest.position.y; - - // force the player to move off of the warp - - const collision_direction_t c_d = find_direction(maps[state.map], state.player.world, state.player.facing); - state.player.move(c_d.collision, c_d.direction); - - // must return: because map state.map changed, the rest of this - // loop is invalid - return; + */ } } } -void check_sign() -{ - const world_t coord = direction_offset(state.player.world, state.player.facing); - const map_t& map = maps[state.map]; - const object_t& obj = map_objects[state.map]; - - for (uint32_t i = 0; i < obj.bg_length; i++) { - const bg_event_t& event = obj.bg_events[i]; - 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]; - } - } -} - -static uint32_t stats_page = 1; -static uint8_t level = 70; - void update() { - state.player.tick(); - change_maps(); - update_warp(); + //state.player.tick(); - /* - 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); - else if (event::button_a() ) check_sign(); - */ + enum window_t::input_event window_input_event; - 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; + if (event::cursor_left() ) window_input_event = window_t::input_left; + else if (event::cursor_right()) window_input_event = window_t::input_right; + else if (event::cursor_up() ) window_input_event = window_t::input_up; + else if (event::cursor_down() ) window_input_event = window_t::input_down; + else if (event::button_a() ) window_input_event = window_t::input_a; + else if (event::button_b() ) window_input_event = window_t::input_b; + else return; + state.player[0].window_stack.update(window_input_event, + state.player[0].trainer); } static uint32_t frame = 0; @@ -513,7 +291,7 @@ void render() { frame++; - const offset_t offset = state.player.offset(); + const offset_t offset = state.player[0].actor.offset(); render_sprites(offset); @@ -524,33 +302,11 @@ void render() vdp2.reg.SCYIN0 = offset.y; vdp2.reg.SCYDN0 = 0; - render_map(); + //render_map(); + state.player[0].window_stack.draw(state.draw.base_pattern.font, + state.player[0].trainer); - 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); - - 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 = 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; - - 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; - } + vdp2.reg.BGON = BGON__N0ON | BGON__N0TPON | BGON__N1ON; } extern "C" @@ -580,9 +336,9 @@ void v_blank_in_int() smpc.reg.IREG[0].val = INTBACK__IREG0__STATUS_DISABLE; smpc.reg.IREG[1].val = ( INTBACK__IREG1__PERIPHERAL_DATA_ENABLE - | INTBACK__IREG1__PORT2_15BYTE - | INTBACK__IREG1__PORT1_15BYTE - ); + | INTBACK__IREG1__PORT2_15BYTE + | INTBACK__IREG1__PORT1_15BYTE + ); smpc.reg.IREG[2].val = INTBACK__IREG2__MAGIC; smpc.reg.COMREG = COMREG__INTBACK; @@ -700,12 +456,13 @@ void init_vdp2() void main() { - state.map = map_t::pallet_town; + //state.map = map_t::pallet_town; //state.map = map_t::pewter_gym; //state.map = map_t::viridian_forest; //state.map = map_t::route_2; - state.player.world.x = 6; - state.player.world.y = 6; + //state.player.world.x = 6; + //state.player.world.y = 6; + default_party(state.player[0].trainer.party); load_vram(); @@ -741,4 +498,9 @@ void main() scu.reg.IST = 0; scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); + + window_stack_t& window_stack = state.player[0].window_stack; + window_stack.ix = 0; + window_stack.stack[0].type = window_descriptor_t::fight; + window_stack.stack[0].window = { 0 }; } diff --git a/menu.cpp b/menu.cpp deleted file mode 100644 index 3acb6b6..0000000 --- a/menu.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "graphic.hpp" -#include "menu.hpp" -#include "control.hpp" - -#define POKE "\x94" -#define TRAINER "\x92" -#define PKMN "\x96" -#define S(v) reinterpret_cast(v) - -const menu_t start_menu = { - .top_left = {10, 0}, - .bottom_right = {19, 15}, - .width = 1, - .height = 7, - .h_advance = 0, - .v_advance = 2, - - .items = (menu_item_t[]){ - { S(POKE "DEX") }, - { S(POKE "MON") }, - { S("ITEM" ) }, - { S(TRAINER ) }, - { S("SAVE" ) }, - { S("OPTION" ) }, - { S("EXIT" ) }, - }, -}; - -const menu_t fight_menu = { - .top_left = {8, 12}, - .bottom_right = {19, 17}, - .width = 2, - .height = 2, - .h_advance = 6, - .v_advance = 2, - - .items = (menu_item_t[]) { - { S("FIGHT") }, { S(PKMN ) }, - { S("ITEM" ) }, { S("RUN") }, - }, -}; - -#undef S - -void draw_menu(const uint32_t base_pattern, - const menu_t& menu) -{ - draw_box_border(base_pattern, - menu.top_left, menu.bottom_right); - draw_box_background(base_pattern, - menu.top_left, menu.bottom_right); - - for (uint32_t y = 0; y < menu.height; y++) { - for (uint32_t x = 0; x < menu.width; x++) { - const menu_item_t& item = menu.items[menu.width * y + x]; - - const uint32_t cell_x = menu.top_left.x + 2 + (menu.h_advance * x); - const uint32_t cell_y = menu.top_left.y + menu.v_advance + (menu.v_advance * y); - - draw_text(base_pattern, item.label, cell_x, cell_y); - } - } -} - -void draw_menu_cursor(const uint32_t base_pattern, - const menu_t& menu, - const cursor_t& cursor) -{ - const uint32_t cell_x = menu.top_left.x + 1 + (menu.h_advance * cursor.x); - const uint32_t cell_y = menu.top_left.y + menu.v_advance + (menu.v_advance * cursor.y); - - put_char(base_pattern, cell_x, cell_y, interactive::l_arrow_solid); -} diff --git a/menu.hpp b/menu.hpp deleted file mode 100644 index 92abc93..0000000 --- a/menu.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "coordinates.hpp" - -struct menu_item_t { - const uint8_t * label; -}; - -struct menu_t { - screen_cell_t top_left; // in cells - screen_cell_t bottom_right; // in cells - uint8_t width; // in menu items - uint8_t height; // in menu items - uint8_t h_advance; // in cells - uint8_t v_advance; // in cells - - const menu_item_t * items; -}; - -struct cursor_t { - uint16_t x; // in menu items - uint16_t y; // in menu items -}; - -extern const menu_t start_menu; -extern const menu_t fight_menu; - -void draw_menu(const uint32_t base_pattern, - const menu_t& menu); - -void draw_menu_cursor(const uint32_t base_pattern, - const menu_t& menu, - const cursor_t& cursor); diff --git a/overworld/overworld.cpp b/overworld/overworld.cpp new file mode 100644 index 0000000..c207ed6 --- /dev/null +++ b/overworld/overworld.cpp @@ -0,0 +1,205 @@ +void overworld_update() +{ + change_maps(); + update_warp(); +} + +constexpr inline world_t direction_offset(const world_t& world, const enum actor_t::direction dir) +{ + switch (dir) { + default: [[fallthrough]]; + case actor_t::up: return {world.x , world.y - 1}; + case actor_t::down: return {world.x , world.y + 1}; + case actor_t::left: return {world.x - 1, world.y }; + case actor_t::right: return {world.x + 1, world.y }; + } +} + +constexpr inline uint8_t get_tile_ix(const map_t& map, const world_t& coord) +{ + // keep this synchronized with render_screen() + + const uint8_t block_ix = get_block(map, coord.to_block()); + const tileset_t& tileset = tilesets[map.tileset]; + const uint8_t * block_start = &(tileset.blockset.start)[block_ix * 4 * 4]; + const int32_t quadrant_x = 2 * (coord.x & 1); + const int32_t quadrant_y = 2 * (coord.y & 1); + + const int32_t block_row = 4 * (quadrant_y + 1); + const uint8_t tile_ix = block_start[block_row + quadrant_x]; + + return tile_ix; +} + +constexpr inline enum actor_t::collision collision(const map_t& map, + const world_t& world, + enum actor_t::direction direction) +{ + const world_t coord = direction_offset(world, direction); + uint8_t collision_tile_ix = get_tile_ix(map, coord); + + const tileset_t& tileset = tilesets[map.tileset]; + for (uint32_t i = 0; i < tileset.collision.size; i++) { + if (tileset.collision.start[i] == collision_tile_ix) + return actor_t::passable; + } + + // check ledge_tile pairs + + uint8_t actor_tile_ix = get_tile_ix(map, world); + + for (uint32_t i = 0; i < ledge_tiles_length; i++) { + const ledge_tile_t& lt = ledge_tiles[i]; + if (direction == lt.direction + && actor_tile_ix == lt.actor + && collision_tile_ix == lt.collision) { + return actor_t::jump; + } + } + + return actor_t::impassable; +} + +constexpr inline void collision_move(const map_t& map, actor_t::direction dir) +{ + const enum actor_t::collision c = collision(map, state.player.world, dir); + state.player.move(c, dir); +} + +void change_maps() +{ + const map_t& map = maps[state.map]; + +#define _has(_dir_) (map.connections[map_t::connection_t::_dir_].map != map_t::unconnected) +#define _get(_dir_) (maps[map.connections[map_t::connection_t::_dir_].map]) +#define _offset(_dir_) (map.connections[map_t::connection_t::_dir_].offset) + + const block_t block = state.player.world.to_block(); + if (block.y < 0) { + // north + if (_has(north)) { + const map_t& north_map = _get(north); + state.player.world.y = ((north_map.height + block.y) << 1) | (state.player.world.y & 1); + state.player.world.x = ((block.x - _offset(north)) << 1) | (state.player.world.x & 1); + state.map = map.connections[map_t::connection_t::north].map; + } + } else if (block.y >= static_cast(map.height)) { + // south + if (_has(south)) { + state.player.world.y = ((block.y - map.height) << 1) | (state.player.world.y & 1); + state.player.world.x = ((block.x - _offset(south)) << 1) | (state.player.world.x & 1); + state.map = map.connections[map_t::connection_t::south].map; + } + } else if (block.x < 0) { + // west + if (_has(west)) { + const map_t& west_map = _get(west); + state.player.world.x = ((west_map.width + block.x) << 1) | (state.player.world.x & 1); + state.player.world.y = ((block.y - _offset(west)) << 1) | (state.player.world.y & 1); + state.map = map.connections[map_t::connection_t::west].map; + } + } else if (block.x >= static_cast(map.width)) { + // east + if (_has(east)) { + state.player.world.x = ((block.x - map.width) << 1) | (state.player.world.x & 1); + state.player.world.y = ((block.y - _offset(east)) << 1) | (state.player.world.y & 1); + state.map = map.connections[map_t::connection_t::east].map; + } + } + +#undef _offset +#undef _get +#undef _has +} + +struct collision_direction_t { + enum actor_t::collision collision; + enum actor_t::direction direction; +}; + +void check_sign() +{ + const world_t coord = direction_offset(state.player.world, state.player.facing); + const map_t& map = maps[state.map]; + const object_t& obj = map_objects[state.map]; + + for (uint32_t i = 0; i < obj.bg_length; i++) { + const bg_event_t& event = obj.bg_events[i]; + 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]; + } + } +} + +void update_warp() +{ + if (state.player.moving) return; + world_t& coord = state.player.world; + + for (uint32_t j = 0; j < map_objects[state.map].warp_length; j++) { + const warp_event_t& warp = map_objects[state.map].warp_events[j]; + if (coord.x == warp.position.x && coord.y == warp.position.y) { + if (warp.destination.map == map_t::last_map) { + if (state.last_map == map_t::last_map) { + // use last_map as a sentinel value for "the player hasn't + // warped yet". This should never happen in normal gameplay. + continue; + } + + state.map = state.last_map; + } else { + // Only change last_map if the current map is an "outside" + // map. Warps that use last_map are designed to be used this + // way. + if (is_outside(state.map)) state.last_map = state.map; + + state.map = warp.destination.map; + } + // warp_index starts at 1 + const warp_event_t& dest = map_objects[state.map].warp_events[warp.destination.warp_index - 1]; + coord.x = dest.position.x; + coord.y = dest.position.y; + + // force the player to move off of the warp + + const collision_direction_t c_d = find_direction(maps[state.map], state.player.world, state.player.facing); + state.player.move(c_d.collision, c_d.direction); + + // must return: because map state.map changed, the rest of this + // loop is invalid + return; + } + } +} + +constexpr inline collision_direction_t find_direction(const map_t& map, const world_t& world, const enum actor_t::direction facing) +{ + // first, try `facing`, as this is typically correct in non- edge-cases + const enum actor_t::collision c_facing = collision(map, world, facing); + if (c_facing != actor_t::impassable) + return {c_facing, facing}; + + // otherwise try all other directions + // (this checks facing a second time for no reason) + constexpr enum actor_t::direction dirs[] = {actor_t::up, actor_t::down, actor_t::left, actor_t::right}; + for (const enum actor_t::direction& dir : dirs) { + const enum actor_t::collision c = collision(map, world, dir); + if (c != actor_t::impassable) + return {c, dir}; + } + + return {actor_t::impassable, facing}; +} + +constexpr inline bool is_outside(const enum map_t::map& map_id) +{ + const map_t& map = maps[map_id]; + switch (map.tileset) { + case tileset_t::overworld: [[fallthrough]]; + case tileset_t::plateau: + return true; + default: + return false; + } +} diff --git a/render_map.hpp b/overworld/render_map.hpp similarity index 95% rename from render_map.hpp rename to overworld/render_map.hpp index 28ab3c1..4b61e33 100644 --- a/render_map.hpp +++ b/overworld/render_map.hpp @@ -59,12 +59,6 @@ constexpr inline uint8_t get_block(const map_t& map, block_t block) #undef _has } -struct cell_offset -{ - static constexpr int32_t x = (320 - 160) / (2 * 8); - static constexpr int32_t y = (240 - 144) / (2 * 8); -}; - static inline void render_screen(const uint32_t base_pattern, const map_t& map, const world_t& origin, diff --git a/party.cpp b/party.cpp new file mode 100644 index 0000000..45baa6e --- /dev/null +++ b/party.cpp @@ -0,0 +1,9 @@ +#include "party.hpp" + +#include "pokemon.hpp" + +void default_party(party_t& party) +{ + party.size = 1; + party.pokemon_instances[0].init(pokemon_t::pikachu, 60); +} diff --git a/party.hpp b/party.hpp new file mode 100644 index 0000000..df95b68 --- /dev/null +++ b/party.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "pokemon_instance.hpp" + +struct party_t { + static constexpr uint8_t max_size = 6; + + pokemon_instance_t pokemon_instances[max_size]; + uint8_t active_ix; + uint8_t size; + + const pokemon_instance_t& active_pokemon() const + { + return pokemon_instances[active_ix]; + } +}; + +void default_party(party_t& party); diff --git a/player.hpp b/player.hpp new file mode 100644 index 0000000..2993f57 --- /dev/null +++ b/player.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "gen/maps.hpp" +#include "actor.hpp" +#include "trainer.hpp" +#include "window/window_stack.hpp" + +struct player_state_t { + enum map_t::map map; + enum map_t::map last_map; + window_stack_t window_stack; + actor_t actor; + trainer_state_t trainer; +}; diff --git a/pokemon_instance.cpp b/pokemon_instance.cpp index 13086b2..2c0e264 100644 --- a/pokemon_instance.cpp +++ b/pokemon_instance.cpp @@ -97,3 +97,19 @@ void pokemon_instance_t::learn_all_moves() break; } } + +void +pokemon_instance_t::init(const enum pokemon_t::pokemon species, + const uint8_t level) +{ + this->species = species; + this->level = level; + + stat_experience = {0, 0, 0, 0, 0}; + determinant_values.dvs = 0b1110'0101'1000'0110; + determine_stats(); + learn_all_moves(); + current_hit_points = stat_values.hit_points; + total_experience = 0; // fixme exp + ailment = ailment_t::ok; +} diff --git a/pokemon_instance.hpp b/pokemon_instance.hpp index 39b9c34..6ddee6b 100644 --- a/pokemon_instance.hpp +++ b/pokemon_instance.hpp @@ -70,6 +70,8 @@ struct move_instance_t { }; struct pokemon_instance_t { + static constexpr uint8_t maximum_moves = 4; + uint8_t nickname[12]; enum pokemon_t::pokemon species; uint8_t level; @@ -77,7 +79,7 @@ struct pokemon_instance_t { determinant_values_t determinant_values; stat_values_t stat_values; stat_experience_t stat_experience; - move_instance_t move_instances[4]; + move_instance_t move_instances[maximum_moves]; uint16_t current_hit_points; enum ailment_t::ailment ailment; @@ -86,8 +88,19 @@ struct pokemon_instance_t { // - id number // - original trainer + inline constexpr uint8_t moves_count() const + { + for (int32_t i = maximum_moves; i > 0; i--) { + if (move_instances[i - 1].type != move_t::no_move) + return i; + } + return 0; + } + void determine_stats(); void learn_move(enum move_t::move move, int32_t index); void learn_all_moves(); + void init(const enum pokemon_t::pokemon species, + const uint8_t level); }; diff --git a/stats.cpp b/stats.cpp new file mode 100644 index 0000000..278cb12 --- /dev/null +++ b/stats.cpp @@ -0,0 +1,31 @@ + 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); + + static pokemon_instance_t pokemon_instance; + 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; + + 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; + } + + + 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; + +} diff --git a/tools/generate/pokemon/moves.py b/tools/generate/pokemon/moves.py index dd9e29d..a095186 100644 --- a/tools/generate/pokemon/moves.py +++ b/tools/generate/pokemon/moves.py @@ -84,7 +84,7 @@ def move(constant_index, constant_name): ".animation = 0,", ".effect = 0,", f".power = {_move.power},", - ".type = 0,", + f".type = type_t::{_move.type.removesuffix('_TYPE').lower()},", f".accuracy = {_move.accuracy},", f".pp = {_move.pp},", "}," @@ -104,6 +104,7 @@ def includes_source(): yield '#include ' yield '' yield '#include "moves.hpp"' + yield '#include "types.hpp"' yield '' def generate_source(): diff --git a/trainer.hpp b/trainer.hpp new file mode 100644 index 0000000..5fd0d38 --- /dev/null +++ b/trainer.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "party.hpp" + +struct trainer_state_t { + party_t party; + // items +}; diff --git a/menu/dialog.cpp b/window/dialog.cpp similarity index 100% rename from menu/dialog.cpp rename to window/dialog.cpp diff --git a/window/menu.cpp b/window/menu.cpp new file mode 100644 index 0000000..de96856 --- /dev/null +++ b/window/menu.cpp @@ -0,0 +1,306 @@ +#include "menu.hpp" +#include "window.hpp" +#include "../graphic.hpp" +#include "../control.hpp" +#include "../trainer.hpp" +#include "../font.hpp" +#include "../number.hpp" +#include "../gen/pokemon/moves.hpp" +#include "../gen/pokemon/types.hpp" + +#define POKE "\x94" +#define TRAINER "\x92" +#define PKMN "\x96" + +#define S(v) reinterpret_cast(v) + +static void draw_cursor(const uint32_t base_pattern, + const menu_t& menu, + const cursor_t& cursor) +{ + const uint32_t cell_x = menu.top_left.x + 1 + (menu.h_advance * cursor.x); + const uint32_t cell_y = menu.top_left.y + menu.v_advance + (menu.v_advance * cursor.y); + + put_char(base_pattern, cell_x, cell_y, interactive::l_arrow_solid); +} + +static void draw_menu(const uint32_t base_pattern, + const menu_t& menu, + const trainer_state_t& trainer_state) +{ + draw_box_border(base_pattern, + menu.top_left, menu.bottom_right); + draw_box_background(base_pattern, + menu.top_left, menu.bottom_right); + + for (uint32_t y = 0; y < menu.height; y++) { + for (uint32_t x = 0; x < menu.width; x++) { + const uint32_t cell_x = menu.top_left.x + 2 + (menu.h_advance * x); + const uint32_t cell_y = menu.top_left.y + menu.v_advance + (menu.v_advance * y); + const uint32_t menu_entry_index = menu.width * y + x; + const menu_entry_t& entry = menu.entry; + + if (menu.entry_type == menu_t::labels) { + draw_text(base_pattern, + entry.labels[menu_entry_index], + cell_x, cell_y); + } else { + draw_text(base_pattern, + entry.label_func(menu_entry_index, trainer_state), + cell_x, cell_y); + } + } + } +} + +void +window__menu_t::draw(const uint32_t base_pattern, + const union window_descriptor_t::data& data, + const window_t& window, + const trainer_state_t& trainer_state) +{ + const menu_t& menu = *data.menu; + draw_menu(base_pattern, menu, trainer_state); + draw_cursor(base_pattern, menu, window.cursor); +} + +static void +_draw_move_types(const uint32_t base_pattern, + const uint8_t index, + const trainer_state_t& trainer_state) +{ + // this is fully custom, not worth making a data model for this + + constexpr screen_cell_t top_left = {0, 8}; + constexpr screen_cell_t bottom_right = {10, 12}; + + draw_box_border(base_pattern, top_left, bottom_right); + draw_box_background(base_pattern, + top_left, bottom_right); + + const pokemon_instance_t& pokemon_instance = trainer_state.party.active_pokemon(); + const move_instance_t& move_instance = pokemon_instance.move_instances[index]; + const move_t& move = moves[move_instance.type]; + + draw_text(base_pattern, + S("TYPE/"), + top_left.x + 1, top_left.y + 1); + + draw_text(base_pattern, + types[move.type].name, + top_left.x + 2, top_left.y + 2); + + draw_number_right_align(base_pattern, + {top_left.x + 5, top_left.y + 3}, + move_instance.pp, + 2, + ascii_to_font(' ')); + + put_char(base_pattern, + top_left.x + 7, top_left.y + 3, + ascii_to_font('/')); + + draw_number_right_align(base_pattern, + {top_left.x + 8, top_left.y + 3}, + move.pp, + 2, + ascii_to_font(' ')); +} + +void +window__menu_t::draw_fight_moves(const uint32_t base_pattern, + const union window_descriptor_t::data& data, + const window_t& window, + const trainer_state_t& trainer_state) +{ + window__menu_t::draw(base_pattern, + data, + window, + trainer_state); + + const menu_t& menu = *data.menu; + const cursor_t& cursor = window.cursor; + const uint32_t menu_entry_index = menu.width * cursor.y + cursor.x; + + _draw_move_types(base_pattern, + menu_entry_index, + trainer_state); +} + +static inline constexpr void +_move_cursor(const uint8_t width, + const uint8_t height, + const enum window_t::input_event event, + cursor_t& cursor) +{ + switch (event) { + case window_t::input_left: + if ((--cursor.x) < 0) cursor.x = width - 1; + break; + case window_t::input_right: + if ((++cursor.x) >= width) cursor.x = 0; + break; + case window_t::input_up: + if ((--cursor.y) < 0) cursor.y = height - 1; + break; + case window_t::input_down: + if ((++cursor.y) >= height) cursor.y = 0; + break; + default: break; + } +} + +window_descriptor_t::result +window__menu_t::update(const union window_descriptor_t::data& data, + window_t& window, + trainer_state_t& trainer_state, + const enum window_t::input_event event) +{ + const menu_t& menu = *data.menu; + + switch (event) { + case window_t::input_left: [[fallthrough]]; + case window_t::input_right: [[fallthrough]]; + case window_t::input_up: [[fallthrough]]; + case window_t::input_down: + _move_cursor(menu.width, menu.height, event, window.cursor); + break; + + case window_t::input_a: break; + case window_t::input_b: break; + default: break; + } + + return { window_t::no_op }; +} + +window_descriptor_t::result +window__menu_t::update_fight(const union window_descriptor_t::data& data, + window_t& window, + trainer_state_t& trainer_state, + const enum window_t::input_event event) +{ + const menu_t& menu = *data.menu; + + switch (event) { + case window_t::input_a: + { + const cursor_t& cursor = window.cursor; + const uint32_t menu_entry_index = menu.width * cursor.y + cursor.x; + switch (menu_entry_index) { + case 0: // FIGHT + return { window_t::spawn, window_descriptor_t::fight_moves }; + break; + case 1: // PKMN + break; + case 2: // ITEM + break; + case 3: // RUN + break; + default: + break; + } + } + break; + case window_t::input_b: + break; + default: + _move_cursor(menu.width, menu.width, event, window.cursor); + break; + } + + return { window_t::no_op }; +} + +static uint8_t const * const +_get_move_string(const uint32_t index, + const trainer_state_t& trainer_state) +{ + const pokemon_instance_t& pokemon_instance = trainer_state.party.active_pokemon(); + + if (index >= pokemon_instance_t::maximum_moves || + pokemon_instance.move_instances[index].type == move_t::no_move) + return S("-"); + else { + return moves[pokemon_instance.move_instances[index].type].name; + } +} + + +window_descriptor_t::result +window__menu_t::update_fight_moves(const union window_descriptor_t::data& data, + window_t& window, + trainer_state_t& trainer_state, + const enum window_t::input_event event) +{ + const menu_t& menu = *data.menu; + const uint8_t max_height = trainer_state.party.active_pokemon().moves_count(); + + switch (event) { + case window_t::input_a: + break; + case window_t::input_b: + return { window_t::dismiss }; + break; + default: + _move_cursor(menu.width, max_height, event, window.cursor); + break; + } + + return { window_t::no_op }; +} + +const menu_t menu_fight_moves = { + .top_left = {4, 12}, + .bottom_right = {19, 17}, + .width = 1, + .height = 4, + .h_advance = 0, + .v_advance = 1, + + .entry_type = menu_t::func, + .entry = { + { .label_func = _get_move_string } + } +}; + +const menu_t menu_start = { + .top_left = {10, 0}, + .bottom_right = {19, 15}, + .width = 1, + .height = 7, + .h_advance = 0, + .v_advance = 2, + + .entry_type = menu_t::labels, + .entry { + .labels = (menu_label_t[]){ + { S(POKE "DEX") }, + { S(POKE "MON") }, + { S("ITEM" ) }, + { S(TRAINER ) }, + { S("SAVE" ) }, + { S("OPTION" ) }, + { S("EXIT" ) }, + }, + } +}; + +const menu_t menu_fight = { + .top_left = {8, 12}, + .bottom_right = {19, 17}, + .width = 2, + .height = 2, + .h_advance = 6, + .v_advance = 2, + + .entry_type = menu_t::labels, + .entry = { + .labels = (menu_label_t[]) { + { S("FIGHT") }, { S(PKMN ) }, + { S("ITEM" ) }, { S("RUN") }, + } + }, +}; + +#undef S diff --git a/window/menu.hpp b/window/menu.hpp new file mode 100644 index 0000000..7078c5d --- /dev/null +++ b/window/menu.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "../coordinates.hpp" +#include "../trainer.hpp" + +typedef uint8_t const * const menu_label_t; + +struct menu_entry_t { + union { + menu_label_t * labels; + menu_label_t (* const label_func)(const uint32_t index, + const trainer_state_t& trainer); + }; +}; + +struct menu_t { + enum entry_type { + labels, + func, + }; + + const screen_cell_t top_left; // in cells + const screen_cell_t bottom_right; // in cells + const uint8_t width; // in menu items + const uint8_t height; // in menu items + const uint8_t h_advance; // in cells + const uint8_t v_advance; // in cells + + const enum entry_type entry_type; + const menu_entry_t entry; +}; + +extern const menu_t menu_start; +extern const menu_t menu_fight; +extern const menu_t menu_fight_moves; diff --git a/menu/stats.cpp b/window/stats.cpp similarity index 100% rename from menu/stats.cpp rename to window/stats.cpp diff --git a/menu/stats.hpp b/window/stats.hpp similarity index 100% rename from menu/stats.hpp rename to window/stats.hpp diff --git a/window/window.cpp b/window/window.cpp new file mode 100644 index 0000000..0f75069 --- /dev/null +++ b/window/window.cpp @@ -0,0 +1,21 @@ +#include "window.hpp" + +#include "menu.hpp" + +const window_descriptor_t window_descriptors[] = { + [window_descriptor_t::start] = { + .data = { .menu = &menu_start }, + .draw = window__menu_t::draw, + .update = window__menu_t::update, + }, + [window_descriptor_t::fight] = { + .data = { .menu = &menu_fight }, + .draw = window__menu_t::draw, + .update = window__menu_t::update_fight, + }, + [window_descriptor_t::fight_moves] = { + .data = { .menu = &menu_fight_moves }, + .draw = window__menu_t::draw_fight_moves, + .update = window__menu_t::update_fight_moves, + }, +}; diff --git a/window/window.hpp b/window/window.hpp new file mode 100644 index 0000000..27a3eaa --- /dev/null +++ b/window/window.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include + +#include "menu.hpp" +#include "../trainer.hpp" + +struct cursor_t { + int16_t x; // in menu items + int16_t y; // in menu items +}; + +struct window_t { + enum input_event { + input_up, + input_down, + input_left, + input_right, + input_a, + input_b, + }; + + enum window_event { + no_op, + spawn, + dismiss, + }; + + cursor_t cursor; +}; + +struct window_descriptor_t { + enum type { + start, + fight, + fight_moves, + }; + + union data { + menu_t const * const menu; + }; + + union data data; + + struct result { + enum window_t::window_event window_event; + union { + uint8_t no_op; + enum type type; + }; + + result(enum window_t::window_event window_event, + enum type type) + : window_event(window_event), type(type) + {} + + result(enum window_t::window_event window_event) + : window_event(window_event), no_op(0) + {} + }; + + void + (* const draw)(const uint32_t base_pattern, + const union data& data, + const window_t& window, + const trainer_state_t& trainer_state); + + result + (* const update)(const union data& data, + window_t& window, + trainer_state_t& trainer_state, + const enum window_t::input_event event + ); +}; + +extern const window_descriptor_t window_descriptors[]; + +// window types + +struct window__menu_t { + static void + draw(const uint32_t base_pattern, + const union window_descriptor_t::data& data, + const window_t& window, + const trainer_state_t& trainer_state); + + static void + draw_fight_moves(const uint32_t base_pattern, + const union window_descriptor_t::data& data, + const window_t& window, + const trainer_state_t& trainer_state); + + static window_descriptor_t::result + update(const union window_descriptor_t::data& data, + window_t& window, + trainer_state_t& trainer_state, + const enum window_t::input_event event + ); + + static window_descriptor_t::result + update_fight(const union window_descriptor_t::data& data, + window_t& window, + trainer_state_t& trainer_state, + const enum window_t::input_event event + ); + + static window_descriptor_t::result + update_fight_moves(const union window_descriptor_t::data& data, + window_t& window, + trainer_state_t& trainer_state, + const enum window_t::input_event event + ); +}; diff --git a/window/window_stack.hpp b/window/window_stack.hpp new file mode 100644 index 0000000..d5a0d6d --- /dev/null +++ b/window/window_stack.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include + +#include "window.hpp" +#include "../trainer.hpp" + +struct window_stack_t { + struct { + enum window_descriptor_t::type type; + window_t window; + } stack[16]; + int8_t ix; + + constexpr inline void + draw(const uint32_t base_pattern, + trainer_state_t& trainer_state); + + constexpr inline void + update(const enum window_t::input_event event, + trainer_state_t& trainer_state); +}; + +constexpr inline void +window_stack_t::draw(const uint32_t base_pattern, + trainer_state_t& trainer_state) +{ + if (ix < 0) return; + + const window_descriptor_t& descriptor = \ + window_descriptors[stack[ix].type]; + + descriptor.draw(base_pattern, + descriptor.data, + stack[ix].window, + trainer_state); +} + +constexpr inline void +window_stack_t::update(const enum window_t::input_event event, + trainer_state_t& trainer_state) +{ + if (ix < 0) return; + + const window_descriptor_t& descriptor = \ + window_descriptors[stack[ix].type]; + + const window_descriptor_t::result& result = \ + descriptor.update(descriptor.data, + stack[ix].window, + trainer_state, + event); + + switch (result.window_event) { + case window_t::spawn: + if (ix >= 15) return; + ix++; + stack[ix].type = result.type; + stack[ix].window.cursor = { 0 }; + break; + case window_t::dismiss: + if (ix < 0) return; + ix--; + default: + break; + } +}