pokemon/overworld/overworld.cpp

206 lines
6.7 KiB
C++

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;
}
}