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