main: add warp events

You can now walk from pallet town to the mount moon exit.
This commit is contained in:
Zack Buhman 2023-07-29 00:16:44 +00:00
parent e6cae1c242
commit e1a430abec
12 changed files with 424 additions and 122 deletions

View File

@ -9,6 +9,8 @@ GEN_SRC =
GEN_SRC += gen/maps.cpp
GEN_SRC += gen/map_objects.cpp
GEN_SRC += gen/sprites.cpp
GEN_SRC += gen/tilesets.cpp
GEN_SRC += gen/collision_tile_ids.cpp
SRC =
SRC += $(GEN_SRC)

View File

@ -14,10 +14,15 @@ struct actor_t
world_t world;
enum direction facing;
bool moving;
bool collision;
uint8_t frame;
uint8_t cycle;
constexpr inline offset_t offset()
{
if (collision) {
return {0, 0};
} else {
switch (facing) {
case left:
return {-frame, 0};
@ -31,15 +36,18 @@ struct actor_t
return {0, 0};
}
}
}
// call tick() before move()
constexpr inline void tick()
{
if (!moving) return;
if (++frame == 16) {
moving = false;
frame = frame + 1;
if (frame == 16) {
cycle += 1;
frame = 0;
if (!collision) {
switch (facing) {
case left: world.x--; break;
case right: world.x++; break;
@ -47,9 +55,12 @@ struct actor_t
case down: world.y++; break;
}
}
moving = false;
}
}
constexpr inline void move(enum direction dir)
constexpr inline void move(enum direction dir,
bool collision)
{
/*
m 1 . 2 . 3 . 4 .
@ -57,10 +68,11 @@ struct actor_t
*/
if (moving) return;
if (!(this->collision) && moving) return;
if (!collision)
frame = 0;
facing = dir;
moving = true;
this->collision = collision;
}
};

View File

@ -14,13 +14,13 @@ struct offset_t { // in pixels
int32_t y;
};
struct world_t;
struct block_t {
int32_t x;
int32_t y;
};
struct world_t;
struct screen_t {
int32_t x;
int32_t y;

210
main.cpp
View File

@ -88,11 +88,12 @@ struct draw_t {
struct state_t {
enum map_t::map map;
enum map_t::map last_map;
draw_t draw;
actor_t player;
};
} __attribute__ ((aligned (4)));
static state_t state = { map_t::pallet_town, 0 };
static state_t state = { map_t::pallet_town, map_t::last_map, 0 };
uint32_t load_tileset(uint32_t top, enum tileset_t::tileset tileset)
{
@ -132,12 +133,41 @@ void load_vram()
vdp1_top = load_sprite(vdp1_top, static_cast<enum spritesheet_t::spritesheet>(i));
}
void render_sprite(const uint32_t ix, const uint32_t sprite_id, const screen_t& screen, const offset_t& offset)
static inline uint32_t facing_offset(const actor_t::direction facing)
{
switch (facing) {
default: [[fallthrough]];
case actor_t::down: return 0;
case actor_t::up: return 1;
case actor_t::left: return 2;
case actor_t::right: return 2;
}
}
static inline uint32_t facing_inverted(const actor_t::direction facing, const uint32_t animation_cycle)
{
switch (facing) {
default: [[fallthrough]];
case actor_t::down: [[fallthrough]];
case actor_t::up:
return (animation_cycle & 1) ? CTRL__DIR__INVERTED_HORIZONTALLY : CTRL__DIR__NOT_INVERTED;
case actor_t::left:
return CTRL__DIR__NOT_INVERTED;
case actor_t::right:
return CTRL__DIR__INVERTED_HORIZONTALLY;
}
}
void render_sprite(const uint32_t ix, const uint32_t sprite_id,
const enum actor_t::direction facing, const uint32_t animation_frame, const uint32_t animation_cycle,
const screen_t& screen, const offset_t& offset)
{
constexpr uint32_t color_address = 0;
const uint32_t character_address = (state.draw.base_pattern.spritesheets[sprite_id] * 128) / 8;
const uint32_t sprite_offset = facing_offset(facing) + animation_frame;
const uint32_t base_pattern = state.draw.base_pattern.spritesheets[sprite_id];
const uint32_t character_address = ((base_pattern + sprite_offset) * 128) / 8;
vdp1.vram.cmd[ix].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE;
vdp1.vram.cmd[ix].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE | facing_inverted(facing, animation_cycle);
vdp1.vram.cmd[ix].LINK = 0;
// The "end code" is 0xf, which is being used in the mai sprite palette. If
// both transparency and end codes are enabled, it seems there are only 14
@ -157,7 +187,12 @@ void render_sprites(const offset_t& offset)
{
uint32_t ix = 2;
render_sprite(ix, spritesheet_t::red, {4, 4}, {0, 0});
const uint32_t animation_frame = ((state.player.frame & 0b1000) != 0) * 3;
render_sprite(ix,
spritesheet_t::red,
state.player.facing, animation_frame, state.player.cycle,
{4, 4},
{0, 0});
ix++;
const object_t& obj = map_objects[state.map];
@ -166,6 +201,7 @@ void render_sprites(const offset_t& offset)
const world_t world = { event.position.x, event.position.y };
render_sprite(ix,
event.sprite_id,
actor_t::down, 0, 0,
world.to_screen(state.player.world),
offset);
ix++;
@ -220,22 +256,153 @@ void render_map()
vdp2.reg.BGON = BGON__N0ON | BGON__N0TPON;
}
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 bool collision(const map_t& map,
const world_t& world,
enum actor_t::direction dir)
{
const world_t coord = direction_offset(world, dir);
// 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];
for (uint32_t i = 0; i < tileset.collision.size; i++) {
if (tileset.collision.start[i] == tile_ix)
return false;
}
return true;
}
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 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;
}
}
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;
state.player.move(state.player.facing, false);
return;
}
}
}
void update()
{
state.player.tick();
change_maps();
update_warp();
const map_t& map = maps[state.map];
if (event::cursor_left()) {
state.player.move(actor_t::left);
//state.player.world.x--;
const bool c = collision(map, state.player.world, actor_t::left);
state.player.move(actor_t::left, c);
} else if (event::cursor_right()) {
state.player.move(actor_t::right);
//state.player.world.x++;
const bool c = collision(map, state.player.world, actor_t::right);
state.player.move(actor_t::right, c);
} else if (event::cursor_up()) {
state.player.move(actor_t::up);
//state.player.world.y--;
const bool c = collision(map, state.player.world, actor_t::up);
state.player.move(actor_t::up, c);
} else if (event::cursor_down()) {
state.player.move(actor_t::down);
//state.player.world.y++;
const bool c = collision(map, state.player.world, actor_t::down);
state.player.move(actor_t::down, c);
}
}
@ -400,6 +567,10 @@ void init_vdp2()
void main()
{
state.map = map_t::pallet_town;
//state.map = map_t::viridian_forest;
//state.map = map_t::route_2;
state.player.world.x = 6;
state.player.world.y = 6;
load_vram();
@ -408,6 +579,17 @@ void main()
init_vdp1();
init_vdp2();
constexpr uint16_t top_x = 80 - 1;
constexpr uint16_t top_y = 48 - 1;
constexpr uint16_t bot_x = 239 + 1;
constexpr uint16_t bot_y = 191 + 1;
vdp2.reg.WCTLA = WCTLA__N0W0E | WCTLA__N0W0A__OUTSIDE;
vdp2.reg.WPSX0 = top_x << 1;
vdp2.reg.WPSY0 = top_y;
vdp2.reg.WPEX0 = bot_x << 1;
vdp2.reg.WPEY0 = bot_y;
// free-running timer
sh2.reg.TCR = TCR__CKS__INTERNAL_DIV128;
sh2.reg.FTCSR = 0;

View File

@ -5,7 +5,7 @@
#include "map_objects.hpp"
#include "vdp2.h"
static inline uint8_t get_block(const map_t& map, block_t block)
constexpr inline uint8_t get_block(const map_t& map, block_t block)
{
const uint8_t border_block = map_objects[map.id].border_block;
@ -15,10 +15,10 @@ static inline uint8_t get_block(const map_t& map, block_t block)
const bool y_gt = block.y >= 0;
const bool inside_map = x_lt && y_lt && x_gt && y_gt;
const bool north = x_lt && x_gt && !(y_gt);
const bool south = x_lt && x_gt && !(y_lt);
const bool west = y_lt && y_gt && !(x_gt);
const bool east = y_lt && y_gt && !(x_lt);
const bool north = x_lt && x_gt && !y_gt;
const bool south = x_lt && x_gt && !y_lt;
const bool west = y_lt && y_gt && !x_gt;
const bool east = y_lt && y_gt && !x_lt;
#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])
@ -27,11 +27,29 @@ static inline uint8_t get_block(const map_t& map, block_t block)
if (inside_map) {
return map.blocks.start[map.width * block.y + block.x];
} else if (north && _has(north)) {
map_t north_map = _get(north);
const map_t& north_map = _get(north);
return get_block(north_map, {
.x = block.x - _offset(north),
.y = static_cast<int32_t>(north_map.height + block.y),
});
} else if (south && _has(south)) {
const map_t& south_map = _get(south);
return get_block(south_map, {
.x = block.x - _offset(south),
.y = static_cast<int32_t>(block.y - map.height),
});
} else if (west && _has(west)) {
const map_t& west_map = _get(west);
return get_block(west_map, {
.x = static_cast<int32_t>(west_map.width + block.x),
.y = block.y - _offset(west),
});
} else if (east && _has(east)) {
const map_t& east_map = _get(east);
return get_block(east_map, {
.x = static_cast<int32_t>(block.x - map.width),
.y = block.y - _offset(east),
});
} else {
return border_block;
}
@ -53,11 +71,10 @@ static inline void render_screen(const uint32_t base_pattern,
const screen_t& screen)
{
const world_t world = screen.to_world(origin);
const block_t block = world.to_block();
const uint8_t block_ix = get_block(map, block);
const uint8_t * block_start = &(tilesets[map.tileset].blockset.start)[block_ix * 4 * 4];
// keep this synchronized with collision()
const uint8_t block_ix = get_block(map, world.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 * (world.x & 1);
const int32_t quadrant_y = 2 * (world.y & 1);

View File

@ -0,0 +1,48 @@
from parse import parse
from generate.generate import renderer
from generate.tilesets import sorted_tilesets_constants_list
def includes_header():
yield '#include <cstdint>'
yield ''
def extern_collision_tile_ids():
for name, index in sorted_tilesets_constants_list():
tileset_header = parse.tileset_headers_list[index]
coll_path = tileset_header.coll()
tile_ids = parse.collision_tile_ids_list[coll_path]
yield f"extern uint8_t {coll_path}[{len(tile_ids)}];"
def generate_header():
render, out = renderer()
render(includes_header())
render(extern_collision_tile_ids())
return out
def collision_array(name, index):
tileset_header = parse.tileset_headers_list[index]
coll_path = tileset_header.coll()
tile_ids = parse.collision_tile_ids_list[coll_path]
yield f"uint8_t {coll_path}[] = {{"
yield " ".join(
f"{tile_ix},"
for tile_ix in sorted(tile_ids)
)
yield "};"
def collision_tile_ids():
for name, index in sorted_tilesets_constants_list():
yield from collision_array(name, index)
def includes_source():
yield '#include <cstdint>'
yield ''
yield '#include "collision_tile_ids.hpp"'
yield ''
def generate_source():
render, out = renderer()
render(includes_source())
render(collision_tile_ids());
return out

View File

@ -1,11 +1,18 @@
from generate import maps
from generate import map_objects
from generate import sprites
from generate import tilesets
from generate import collision_tile_ids
files = [
(maps.generate_maps_header, "maps.hpp"),
(maps.generate_maps_source, "maps.cpp"),
(map_objects.generate_map_objects_source, "map_objects.cpp"),
(sprites.generate_sprites_header, "sprites.hpp"),
(sprites.generate_sprites_source, "sprites.cpp"),
(maps.generate_header, "maps.hpp"),
(maps.generate_source, "maps.cpp"),
(tilesets.generate_header, "tilesets.hpp"),
(tilesets.generate_source, "tilesets.cpp"),
# map_objects.hpp is not generated
(map_objects.generate_source, "map_objects.cpp"),
(sprites.generate_header, "sprites.hpp"),
(sprites.generate_source, "sprites.cpp"),
(collision_tile_ids.generate_header, "collision_tile_ids.hpp"),
(collision_tile_ids.generate_source, "collision_tile_ids.cpp"),
]

View File

@ -99,7 +99,7 @@ def map_objects():
yield from object(map_name, map_objects)
yield "};"
def generate_map_objects_source():
def generate_source():
render, out = renderer()
render(includes_source())
render(map_objects())

View File

@ -38,47 +38,17 @@ def sorted_map_headers():
# e.g CERULEAN_TRASHED_HOUSE_COPY has no map header
)
def sorted_tilesets_constants_list():
return sorted(parse.tileset_constants_list.items(), key=default_sort)
def includes_header():
yield "#pragma once"
yield ""
yield '#include "../start_size.hpp"'
yield '#include "tilesets.hpp"'
yield ""
for map_header in sorted_map_headers():
block_path = parse.maps_blocks_list[map_header.blocks()]
yield f'#include "../res/{block_path}.h"'
for tileset_name, _ in sorted_tilesets_constants_list():
tileset_index = parse.tileset_constants_list[tileset_name]
tileset_header = parse.tileset_headers_list[tileset_index]
blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()]
gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()]
yield f'#include "../res/{blockset_path}.h"'
yield f'#include "../res/{gfx_path}.h"'
yield ""
def struct_tileset_t():
_sorted_tilesets_constants_list = list(sorted_tilesets_constants_list())
tileset_names = (
f"{name.lower()},"
for name, _ in _sorted_tilesets_constants_list
)
return [
"struct tileset_t {",
"start_size_t blockset;",
"start_size_t tileset;",
"",
"enum tileset {",
*tileset_names,
"};",
"",
f"static constexpr int32_t count = {len(_sorted_tilesets_constants_list)};",
"};",
]
def struct_map_t():
_sorted_map_headers = list(sorted_map_headers())
map_names = (
@ -119,33 +89,6 @@ def struct_connection_t():
"};",
]
def blockset_tileset(name: str):
tileset_index = parse.tileset_constants_list[name]
tileset_header = parse.tileset_headers_list[tileset_index]
blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()]
gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()]
return [
f"[tileset_t::{name.lower()}] = {{",
".blockset = {",
*start_size_value(blockset_path),
"},",
".tileset = {",
*start_size_value(gfx_path),
"}",
"},"
]
def tilesets_header():
yield "extern const tileset_t tilesets[];"
def tilesets():
yield "const tileset_t tilesets[] = {"
for name, _ in sorted_tilesets_constants_list():
yield from blockset_tileset(name)
yield "};"
def connections(map_header):
cs = dict((c.name, c) for c in map_header.connections)
assert len(cs) == len(map_header.connections), map_header.connections
@ -183,7 +126,7 @@ def map(map_header):
"},",
]
def maps_header():
def extern_maps():
yield "extern const map_t maps[];"
def maps():
@ -192,13 +135,11 @@ def maps():
yield from map(map_header)
yield "};"
def generate_maps_header():
def generate_header():
render, out = renderer()
render(includes_header())
render(struct_tileset_t())
render(struct_map_t())
render(tilesets_header());
render(maps_header());
render(extern_maps());
return out
def includes_source():
@ -207,9 +148,8 @@ def includes_source():
yield '#include "maps.hpp"'
yield ''
def generate_maps_source():
def generate_source():
render, out = renderer()
render(includes_source())
render(tilesets())
render(maps())
return out

View File

@ -61,7 +61,7 @@ def struct_spritesheet_t():
def sprites_header():
yield "extern const spritesheet_t spritesheets[];"
def generate_sprites_header():
def generate_header():
render, out = renderer()
render(includes_header())
render(struct_spritesheet_t())
@ -94,7 +94,7 @@ def sprites():
yield from sprite(name, index)
yield "};"
def generate_sprites_source():
def generate_source():
render, out = renderer()
render(includes_source());
render(sprites())

View File

@ -0,0 +1,94 @@
from parse import parse
from generate.sort import default_sort
from generate.generate import renderer
from generate.binary import start_size_value
def sorted_tilesets_constants_list():
return sorted(parse.tileset_constants_list.items(), key=default_sort)
def includes_header():
yield "#pragma once"
yield ""
yield '#include "../start_size.hpp"'
yield ""
for tileset_name, _ in sorted_tilesets_constants_list():
tileset_index = parse.tileset_constants_list[tileset_name]
tileset_header = parse.tileset_headers_list[tileset_index]
blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()]
gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()]
yield f'#include "../res/{blockset_path}.h"'
yield f'#include "../res/{gfx_path}.h"'
def struct_tileset_t():
_sorted_tilesets_constants_list = list(sorted_tilesets_constants_list())
tileset_names = (
f"{name.lower()},"
for name, _ in _sorted_tilesets_constants_list
)
return [
"struct tileset_t {",
"start_size_t blockset;",
"start_size_t tileset;",
"start_size_t collision;",
"",
"enum tileset {",
*tileset_names,
"};",
"",
f"static constexpr int32_t count = {len(_sorted_tilesets_constants_list)};",
"};",
]
def extern_tilesets():
yield "extern const tileset_t tilesets[];"
def generate_header():
render, out = renderer()
render(includes_header())
render(struct_tileset_t())
render(extern_tilesets())
return out
def blockset_tileset(name, index):
tileset_header = parse.tileset_headers_list[index]
blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()]
gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()]
coll_path = tileset_header.coll()
return [
f"[tileset_t::{name.lower()}] = {{",
".blockset = {",
*start_size_value(blockset_path),
"},",
".tileset = {",
*start_size_value(gfx_path),
"},",
".collision = {",
f".start = &{coll_path}[0],",
f".size = (sizeof ({coll_path})),",
"}",
"},"
]
def tilesets():
yield "const tileset_t tilesets[] = {"
for name, index in sorted_tilesets_constants_list():
yield from blockset_tileset(name, index)
yield "};"
def includes_source():
yield '#include <cstdint>'
yield ''
yield '#include "tilesets.hpp"'
yield '#include "collision_tile_ids.hpp"'
yield ''
def generate_source():
render, out = renderer()
render(includes_source())
render(tilesets())
return out

View File

@ -1,5 +1,5 @@
from pprint import pprint
from parse import parse
for i in parse.map_objects_list.items():
for i in parse.collision_tile_ids_list.items():
pprint(i)