initial menu/window system
This commit is contained in:
parent
3fffdab78d
commit
bb4f236b5b
7
Makefile
7
Makefile
@ -1,6 +1,6 @@
|
|||||||
CFLAGS = -Isaturn
|
CFLAGS = -Isaturn
|
||||||
|
|
||||||
OPT ?= -O3
|
OPT ?= -Og
|
||||||
LIB = ./saturn
|
LIB = ./saturn
|
||||||
|
|
||||||
GEN_PYTHON_SOURCE = $(wildcard tools/source/*.py) $(wildcard tools/generate/*.py)
|
GEN_PYTHON_SOURCE = $(wildcard tools/source/*.py) $(wildcard tools/generate/*.py)
|
||||||
@ -35,11 +35,12 @@ SRC += input.cpp
|
|||||||
SRC += vram.cpp
|
SRC += vram.cpp
|
||||||
SRC += font.cpp
|
SRC += font.cpp
|
||||||
SRC += graphic.cpp
|
SRC += graphic.cpp
|
||||||
SRC += menu.cpp
|
|
||||||
SRC += number.cpp
|
SRC += number.cpp
|
||||||
SRC += pokemon_instance.cpp
|
SRC += pokemon_instance.cpp
|
||||||
SRC += ailment.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))
|
DEP = $(patsubst %.cpp,%.cpp.d,$(SRC))
|
||||||
|
|
||||||
|
6
battle.hpp
Normal file
6
battle.hpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
enum battle_action {
|
||||||
|
fight,
|
||||||
|
swap_pokemon,
|
||||||
|
item,
|
||||||
|
run,
|
||||||
|
};
|
5
cell_offset.hpp
Normal file
5
cell_offset.hpp
Normal file
@ -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);
|
||||||
|
};
|
BIN
derived/font.png
BIN
derived/font.png
Binary file not shown.
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "coordinates.hpp"
|
#include "coordinates.hpp"
|
||||||
#include "start_size.hpp"
|
#include "start_size.hpp"
|
||||||
#include "render_map.hpp" // for cell_offset
|
#include "cell_offset.hpp" // for cell_offset
|
||||||
#include "vdp2.h"
|
#include "vdp2.h"
|
||||||
|
|
||||||
#include "pokemon_instance.hpp"
|
#include "pokemon_instance.hpp"
|
||||||
|
@ -57,4 +57,5 @@ struct event {
|
|||||||
static inline bool cursor_up() { return input_flopped(input.up ) >= 1; }
|
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 cursor_down() { return input_flopped(input.down ) >= 1; }
|
||||||
static inline bool button_a() { return input_flopped(input.a ) == 1; }
|
static inline bool button_a() { return input_flopped(input.a ) == 1; }
|
||||||
|
static inline bool button_b() { return input_flopped(input.b ) == 1; }
|
||||||
};
|
};
|
||||||
|
318
main.cpp
318
main.cpp
@ -14,24 +14,17 @@
|
|||||||
#include "vram.hpp"
|
#include "vram.hpp"
|
||||||
#include "font.hpp"
|
#include "font.hpp"
|
||||||
|
|
||||||
#include "gen/maps.hpp"
|
#include "gen/tilesets.hpp"
|
||||||
#include "gen/sprites.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.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 draw_t {
|
||||||
struct {
|
struct {
|
||||||
@ -46,10 +39,8 @@ struct draw_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct state_t {
|
struct state_t {
|
||||||
enum map_t::map map;
|
|
||||||
enum map_t::map last_map;
|
|
||||||
draw_t draw;
|
draw_t draw;
|
||||||
actor_t player;
|
player_state_t player[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
static state_t state = { map_t::pallet_town, map_t::last_map, 0 };
|
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;
|
uint32_t ix = 2;
|
||||||
|
|
||||||
|
/*
|
||||||
const uint32_t animation_frame = ((state.player.frame & 0b1000) != 0) * 3;
|
const uint32_t animation_frame = ((state.player.frame & 0b1000) != 0) * 3;
|
||||||
render_sprite(ix,
|
render_sprite(ix,
|
||||||
spritesheet_t::red,
|
spritesheet_t::red,
|
||||||
@ -212,11 +204,6 @@ void render_sprites(const offset_t& offset)
|
|||||||
state.player.y_offset());
|
state.player.y_offset());
|
||||||
ix++;
|
ix++;
|
||||||
|
|
||||||
render_pokemon(ix, static_cast<enum pokemon_t::pokemon>(pokemon_raw_index),
|
|
||||||
{0, 0},
|
|
||||||
true); // invert horizontally
|
|
||||||
ix++;
|
|
||||||
|
|
||||||
const object_t& obj = map_objects[state.map];
|
const object_t& obj = map_objects[state.map];
|
||||||
for (uint32_t i = 0; i < obj.object_length; i++) {
|
for (uint32_t i = 0; i < obj.object_length; i++) {
|
||||||
const object_event_t& event = obj.object_events[i];
|
const object_event_t& event = obj.object_events[i];
|
||||||
@ -229,6 +216,7 @@ void render_sprites(const offset_t& offset)
|
|||||||
-4);
|
-4);
|
||||||
ix++;
|
ix++;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
constexpr uint16_t top_x = 80 - 1;
|
constexpr uint16_t top_x = 80 - 1;
|
||||||
constexpr uint16_t top_y = 48 - 1;
|
constexpr uint16_t top_y = 48 - 1;
|
||||||
@ -262,249 +250,39 @@ void render_sprites(const offset_t& offset)
|
|||||||
|
|
||||||
void render_map()
|
void render_map()
|
||||||
{
|
{
|
||||||
const map_t& map = maps[state.map];
|
//const map_t& map = maps[state.map];
|
||||||
const uint32_t base_pattern = state.draw.base_pattern.tilesets[map.tileset];
|
//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);
|
//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 y = (0 - 1); y < (9 + 2); y++) {
|
||||||
for (int32_t x = (0 - 2); x < (10 + 2); x++) {
|
for (int32_t x = (0 - 2); x < (10 + 2); x++) {
|
||||||
|
/*
|
||||||
render_screen(base_pattern,
|
render_screen(base_pattern,
|
||||||
map,
|
map,
|
||||||
state.player.world,
|
state.player.world,
|
||||||
{x, y}
|
{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<int32_t>(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<int32_t>(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()
|
void update()
|
||||||
{
|
{
|
||||||
state.player.tick();
|
//state.player.tick();
|
||||||
change_maps();
|
|
||||||
update_warp();
|
|
||||||
|
|
||||||
/*
|
enum window_t::input_event window_input_event;
|
||||||
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();
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (event::cursor_left() ) pokemon_raw_index = (pokemon_raw_index - 1);
|
if (event::cursor_left() ) window_input_event = window_t::input_left;
|
||||||
else if (event::cursor_right()) pokemon_raw_index = (pokemon_raw_index + 1);
|
else if (event::cursor_right()) window_input_event = window_t::input_right;
|
||||||
else if (event::cursor_up() ) level++;
|
else if (event::cursor_up() ) window_input_event = window_t::input_up;
|
||||||
else if (event::cursor_down() ) level--;
|
else if (event::cursor_down() ) window_input_event = window_t::input_down;
|
||||||
else if (event::button_a() ) stats_page = !stats_page;
|
else if (event::button_a() ) window_input_event = window_t::input_a;
|
||||||
|
else if (event::button_b() ) window_input_event = window_t::input_b;
|
||||||
if (pokemon_raw_index < 0) pokemon_raw_index = pokemon_t::count - 1;
|
else return;
|
||||||
else if (pokemon_raw_index >= pokemon_t::count) pokemon_raw_index = 0;
|
|
||||||
|
|
||||||
|
state.player[0].window_stack.update(window_input_event,
|
||||||
|
state.player[0].trainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t frame = 0;
|
static uint32_t frame = 0;
|
||||||
@ -513,7 +291,7 @@ void render()
|
|||||||
{
|
{
|
||||||
frame++;
|
frame++;
|
||||||
|
|
||||||
const offset_t offset = state.player.offset();
|
const offset_t offset = state.player[0].actor.offset();
|
||||||
|
|
||||||
render_sprites(offset);
|
render_sprites(offset);
|
||||||
|
|
||||||
@ -524,33 +302,11 @@ void render()
|
|||||||
vdp2.reg.SCYIN0 = offset.y;
|
vdp2.reg.SCYIN0 = offset.y;
|
||||||
vdp2.reg.SCYDN0 = 0;
|
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);
|
vdp2.reg.BGON = BGON__N0ON | BGON__N0TPON | BGON__N1ON;
|
||||||
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<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 = 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
@ -700,12 +456,13 @@ void init_vdp2()
|
|||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
state.map = map_t::pallet_town;
|
//state.map = map_t::pallet_town;
|
||||||
//state.map = map_t::pewter_gym;
|
//state.map = map_t::pewter_gym;
|
||||||
//state.map = map_t::viridian_forest;
|
//state.map = map_t::viridian_forest;
|
||||||
//state.map = map_t::route_2;
|
//state.map = map_t::route_2;
|
||||||
state.player.world.x = 6;
|
//state.player.world.x = 6;
|
||||||
state.player.world.y = 6;
|
//state.player.world.y = 6;
|
||||||
|
default_party(state.player[0].trainer.party);
|
||||||
|
|
||||||
load_vram();
|
load_vram();
|
||||||
|
|
||||||
@ -741,4 +498,9 @@ void main()
|
|||||||
|
|
||||||
scu.reg.IST = 0;
|
scu.reg.IST = 0;
|
||||||
scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN);
|
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 };
|
||||||
}
|
}
|
||||||
|
73
menu.cpp
73
menu.cpp
@ -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<const uint8_t *>(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);
|
|
||||||
}
|
|
31
menu.hpp
31
menu.hpp
@ -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);
|
|
205
overworld/overworld.cpp
Normal file
205
overworld/overworld.cpp
Normal file
@ -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<int32_t>(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<int32_t>(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;
|
||||||
|
}
|
||||||
|
}
|
@ -59,12 +59,6 @@ constexpr inline uint8_t get_block(const map_t& map, block_t block)
|
|||||||
#undef _has
|
#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,
|
static inline void render_screen(const uint32_t base_pattern,
|
||||||
const map_t& map,
|
const map_t& map,
|
||||||
const world_t& origin,
|
const world_t& origin,
|
9
party.cpp
Normal file
9
party.cpp
Normal file
@ -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);
|
||||||
|
}
|
20
party.hpp
Normal file
20
party.hpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#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);
|
14
player.hpp
Normal file
14
player.hpp
Normal file
@ -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;
|
||||||
|
};
|
@ -97,3 +97,19 @@ void pokemon_instance_t::learn_all_moves()
|
|||||||
break;
|
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;
|
||||||
|
}
|
||||||
|
@ -70,6 +70,8 @@ struct move_instance_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct pokemon_instance_t {
|
struct pokemon_instance_t {
|
||||||
|
static constexpr uint8_t maximum_moves = 4;
|
||||||
|
|
||||||
uint8_t nickname[12];
|
uint8_t nickname[12];
|
||||||
enum pokemon_t::pokemon species;
|
enum pokemon_t::pokemon species;
|
||||||
uint8_t level;
|
uint8_t level;
|
||||||
@ -77,7 +79,7 @@ struct pokemon_instance_t {
|
|||||||
determinant_values_t determinant_values;
|
determinant_values_t determinant_values;
|
||||||
stat_values_t stat_values;
|
stat_values_t stat_values;
|
||||||
stat_experience_t stat_experience;
|
stat_experience_t stat_experience;
|
||||||
move_instance_t move_instances[4];
|
move_instance_t move_instances[maximum_moves];
|
||||||
uint16_t current_hit_points;
|
uint16_t current_hit_points;
|
||||||
enum ailment_t::ailment ailment;
|
enum ailment_t::ailment ailment;
|
||||||
|
|
||||||
@ -86,8 +88,19 @@ struct pokemon_instance_t {
|
|||||||
// - id number
|
// - id number
|
||||||
// - original trainer
|
// - 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 determine_stats();
|
||||||
|
|
||||||
void learn_move(enum move_t::move move, int32_t index);
|
void learn_move(enum move_t::move move, int32_t index);
|
||||||
void learn_all_moves();
|
void learn_all_moves();
|
||||||
|
void init(const enum pokemon_t::pokemon species,
|
||||||
|
const uint8_t level);
|
||||||
};
|
};
|
||||||
|
31
stats.cpp
Normal file
31
stats.cpp
Normal file
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -84,7 +84,7 @@ def move(constant_index, constant_name):
|
|||||||
".animation = 0,",
|
".animation = 0,",
|
||||||
".effect = 0,",
|
".effect = 0,",
|
||||||
f".power = {_move.power},",
|
f".power = {_move.power},",
|
||||||
".type = 0,",
|
f".type = type_t::{_move.type.removesuffix('_TYPE').lower()},",
|
||||||
f".accuracy = {_move.accuracy},",
|
f".accuracy = {_move.accuracy},",
|
||||||
f".pp = {_move.pp},",
|
f".pp = {_move.pp},",
|
||||||
"},"
|
"},"
|
||||||
@ -104,6 +104,7 @@ def includes_source():
|
|||||||
yield '#include <cstdint>'
|
yield '#include <cstdint>'
|
||||||
yield ''
|
yield ''
|
||||||
yield '#include "moves.hpp"'
|
yield '#include "moves.hpp"'
|
||||||
|
yield '#include "types.hpp"'
|
||||||
yield ''
|
yield ''
|
||||||
|
|
||||||
def generate_source():
|
def generate_source():
|
||||||
|
8
trainer.hpp
Normal file
8
trainer.hpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "party.hpp"
|
||||||
|
|
||||||
|
struct trainer_state_t {
|
||||||
|
party_t party;
|
||||||
|
// items
|
||||||
|
};
|
306
window/menu.cpp
Normal file
306
window/menu.cpp
Normal file
@ -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<const uint8_t *>(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
|
35
window/menu.hpp
Normal file
35
window/menu.hpp
Normal file
@ -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;
|
21
window/window.cpp
Normal file
21
window/window.cpp
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
};
|
113
window/window.hpp
Normal file
113
window/window.hpp
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#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
|
||||||
|
);
|
||||||
|
};
|
67
window/window_stack.hpp
Normal file
67
window/window_stack.hpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user