use crate::map::MapTrait;
use crate::tileset::TilesetTrait;
use super::collision::{
check_movement_collision, check_warp_at_position, is_facing_map_edge, CollisionProvider,
CollisionResult, SpritePosition,
};
use super::types::{
Direction, MapData, MovementState, OverworldState as GenericOverworldState, TransportMode,
};
pub const WALK_COUNTER_INIT: u8 = 8;
#[derive(Debug, Clone, Copy, Default)]
pub struct InputState {
pub up: bool,
pub down: bool,
pub left: bool,
pub right: bool,
pub a_button: bool,
pub b_button: bool,
pub start: bool,
pub select: bool,
}
impl InputState {
pub fn direction_pressed(&self) -> Option<Direction> {
if self.down {
Some(Direction::Down)
} else if self.up {
Some(Direction::Up)
} else if self.left {
Some(Direction::Left)
} else if self.right {
Some(Direction::Right)
} else {
None
}
}
pub fn to_pad_bits(&self) -> u8 {
let mut bits = 0u8;
if self.down {
bits |= super::collision::PAD_DOWN;
}
if self.up {
bits |= super::collision::PAD_UP;
}
if self.left {
bits |= super::collision::PAD_LEFT;
}
if self.right {
bits |= super::collision::PAD_RIGHT;
}
bits
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MoveResult {
Walking,
LedgeJump,
TurnedOnly,
Blocked(CollisionResult),
ReachedMapEdge,
Warped { warp_index: usize },
StillMoving,
NoInput,
}
pub fn try_move<M: MapTrait, T: TilesetTrait>(
state: &mut GenericOverworldState<M>,
direction: Direction,
tileset: T,
map_width_blocks: u8,
map_height_blocks: u8,
standing_tile: u8,
target_tile: u8,
npc_positions: &[SpritePosition],
held_input: u8,
provider: &impl CollisionProvider<T>,
) -> MoveResult {
if state.player.movement_state != MovementState::Idle {
return MoveResult::StillMoving;
}
let was_facing = state.player.facing;
state.player.facing = direction;
let result = check_movement_collision(
state.player.x,
state.player.y,
direction,
tileset,
map_width_blocks,
map_height_blocks,
standing_tile,
target_tile,
state.player.transport,
npc_positions,
held_input,
provider,
);
match result {
CollisionResult::Passable | CollisionResult::StopSurfing => {
if result == CollisionResult::StopSurfing {
state.player.transport = TransportMode::Walking;
}
state.standing_on_warp = false;
state.player.movement_state = MovementState::Walking;
state.walk_counter = WALK_COUNTER_INIT;
MoveResult::Walking
}
CollisionResult::LedgeJump => {
state.standing_on_warp = false;
state.player.movement_state = MovementState::Jumping;
state.walk_counter = WALK_COUNTER_INIT * 2;
MoveResult::LedgeJump
}
CollisionResult::MapEdge => MoveResult::ReachedMapEdge,
_ => {
if was_facing != direction {
MoveResult::TurnedOnly
} else {
MoveResult::Blocked(result)
}
}
}
}
pub fn advance_step<M: MapTrait>(state: &mut GenericOverworldState<M>) -> bool {
if state.walk_counter == 0 {
return true;
}
let decrement = if state.player.transport == TransportMode::Biking
&& state.player.bike_speedup_active
{
2
} else {
1
};
state.walk_counter = state.walk_counter.saturating_sub(decrement);
if state.walk_counter == 0 {
let (dx, dy) = direction_delta(state.player.facing);
if state.player.movement_state == MovementState::Jumping {
let new_x = (state.player.x as i32 + dx as i32 * 2) as u16;
let new_y = (state.player.y as i32 + dy as i32 * 2) as u16;
state.player.x = new_x;
state.player.y = new_y;
} else {
let new_x = (state.player.x as i32 + dx as i32) as u16;
let new_y = (state.player.y as i32 + dy as i32) as u16;
state.player.x = new_x;
state.player.y = new_y;
}
state.player.movement_state = MovementState::Idle;
if state.encounter_cooldown > 0 {
state.encounter_cooldown -= 1;
}
if state.repel_steps > 0 {
state.repel_steps -= 1;
}
return true;
}
false
}
pub fn direction_delta(dir: Direction) -> (i8, i8) {
match dir {
Direction::Down => (0, 1),
Direction::Up => (0, -1),
Direction::Left => (-1, 0),
Direction::Right => (1, 0),
}
}
pub fn opposite_direction(dir: Direction) -> Direction {
match dir {
Direction::Down => Direction::Up,
Direction::Up => Direction::Down,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
}
}
pub fn frames_per_step(transport: TransportMode) -> u8 {
match transport {
TransportMode::Walking => WALK_COUNTER_INIT,
TransportMode::Biking => WALK_COUNTER_INIT / 2,
TransportMode::Surfing => WALK_COUNTER_INIT,
}
}
pub fn direction_to_facing_index(dir: Direction) -> u8 {
match dir {
Direction::Down => 0,
Direction::Up => 1,
Direction::Left => 2,
Direction::Right => 3,
}
}
pub fn get_tile_at_position<M: MapTrait, T: TilesetTrait, Mus>(
map: &MapData<M, T, Mus>,
x: u16,
y: u16,
provider: &impl CollisionProvider<T>,
) -> u8 {
provider.get_tile_at_position(map.tileset, &map.blocks, map.width, x, y)
}
pub fn get_target_tile_for_direction<M: MapTrait, T: TilesetTrait, Mus>(
map: &MapData<M, T, Mus>,
x: u16,
y: u16,
dir: Direction,
provider: &impl CollisionProvider<T>,
) -> u8 {
let (dx, dy) = direction_delta(dir);
let target_x = ((x as i32) + dx as i32).max(0) as u16;
let target_y = ((y as i32) + dy as i32).max(0) as u16;
provider.get_tile_at_position(map.tileset, &map.blocks, map.width, target_x, target_y)
}
pub fn extra_warp_check<M: MapTrait, T: TilesetTrait, Mus>(
map: &MapData<M, T, Mus>,
player_x: u16,
player_y: u16,
facing: Direction,
provider: &impl CollisionProvider<T>,
) -> bool {
let tile_in_front = get_target_tile_for_direction(map, player_x, player_y, facing, provider);
if let Some(result) = provider.check_extra_warp_special(map.tileset, tile_in_front) {
return result;
}
if provider.uses_warp_tile_in_front_check(map.tileset) {
let facing_idx = direction_to_facing_index(facing);
provider.is_warp_carpet_tile_in_front(map.tileset, facing_idx, tile_in_front)
} else {
is_facing_map_edge(player_x, player_y, facing, map.width, map.height)
}
}
pub fn check_warps_no_collision<M: MapTrait, T: TilesetTrait, Mus>(
state: &mut GenericOverworldState<M>,
map: &MapData<M, T, Mus>,
standing_tile: u8,
direction_held: bool,
provider: &impl CollisionProvider<T>,
) -> Option<usize> {
let warp_idx = check_warp_at_position(state.player.x, state.player.y, map)?;
state.standing_on_warp = true;
if provider.is_door_tile(map.tileset, standing_tile) {
return Some(warp_idx);
}
if provider.is_warp_tile(map.tileset, standing_tile) {
state.standing_on_warp = false;
return Some(warp_idx);
}
if extra_warp_check(map, state.player.x, state.player.y, state.player.facing, provider) {
if direction_held {
return Some(warp_idx);
}
}
None
}
pub fn check_collision_warp<M: MapTrait, T: TilesetTrait, Mus>(
state: &mut GenericOverworldState<M>,
map: &MapData<M, T, Mus>,
move_result: MoveResult,
provider: &impl CollisionProvider<T>,
) -> MoveResult {
match move_result {
MoveResult::Blocked(_) | MoveResult::ReachedMapEdge => {
if state.standing_on_warp {
if extra_warp_check(
map,
state.player.x,
state.player.y,
state.player.facing,
provider,
) {
if let Some(warp_idx) =
check_warp_at_position(state.player.x, state.player.y, map)
{
return MoveResult::Warped {
warp_index: warp_idx,
};
}
}
}
move_result
}
_ => move_result,
}
}
pub fn process_frame<M: MapTrait, T: TilesetTrait, Mus>(
state: &mut GenericOverworldState<M>,
input: &InputState,
map: &MapData<M, T, Mus>,
standing_tile: u8,
target_tile: u8,
npc_positions: &[SpritePosition],
provider: &impl CollisionProvider<T>,
) -> MoveResult {
if state.player.movement_state != MovementState::Idle {
let step_done = advance_step(state);
if step_done {
let new_standing_tile = get_tile_at_position(map, state.player.x, state.player.y, provider);
let direction_held = input.direction_pressed().is_some();
if let Some(warp_idx) = check_warps_no_collision(
state,
map,
new_standing_tile,
direction_held,
provider,
) {
return MoveResult::Warped {
warp_index: warp_idx,
};
}
if let Some(direction) = input.direction_pressed() {
let held_input = input.to_pad_bits();
let new_target_tile = get_target_tile_for_direction(
map,
state.player.x,
state.player.y,
direction,
provider,
);
let move_result = try_move(
state,
direction,
map.tileset,
map.width,
map.height,
new_standing_tile,
new_target_tile,
npc_positions,
held_input,
provider,
);
return check_collision_warp(state, map, move_result, provider);
}
}
return MoveResult::StillMoving;
}
let direction = match input.direction_pressed() {
Some(dir) => dir,
None => return MoveResult::NoInput,
};
let held_input = input.to_pad_bits();
let move_result = try_move(
state,
direction,
map.tileset,
map.width,
map.height,
standing_tile,
target_tile,
npc_positions,
held_input,
provider,
);
check_collision_warp(state, map, move_result, provider)
}