use crate::tileset::TilesetTrait;
use super::types::{Direction, MapData, TransportMode};
pub const SPRITE_FACING_DOWN: u8 = 0x00;
pub const SPRITE_FACING_UP: u8 = 0x04;
pub const SPRITE_FACING_LEFT: u8 = 0x08;
pub const SPRITE_FACING_RIGHT: u8 = 0x0C;
pub const PAD_DOWN: u8 = 0x80;
pub const PAD_UP: u8 = 0x40;
pub const PAD_LEFT: u8 = 0x20;
pub const PAD_RIGHT: u8 = 0x10;
pub trait CollisionProvider<T: TilesetTrait> {
fn is_tile_passable(&self, tileset: T, tile_id: u8) -> bool;
fn check_tile_pair_collision(
&self,
tileset: T,
standing_tile: u8,
target_tile: u8,
on_water: bool,
) -> bool;
fn check_ledge_jump(
&self,
tileset: T,
sprite_facing: u8,
standing_tile: u8,
target_tile: u8,
held_input: u8,
) -> bool;
fn is_counter_tile(&self, tileset: T, tile_id: u8) -> bool;
fn get_tile_at_position(
&self,
tileset: T,
blocks: &[u8],
map_width: u8,
x: u16,
y: u16,
) -> u8;
fn is_door_tile(&self, tileset: T, tile_id: u8) -> bool;
fn is_warp_tile(&self, tileset: T, tile_id: u8) -> bool;
fn is_warp_carpet_tile_in_front(&self, tileset: T, facing_idx: u8, tile_id: u8) -> bool;
fn is_water_tile(&self, _tileset: T, _tile_id: u8) -> bool {
false
}
fn get_connection_edge_tile(
&self,
_tileset: T,
_map_width_blocks: u8,
_map_height_blocks: u8,
_x: u16,
_y: u16,
_direction: Direction,
) -> Option<u8> {
None
}
fn uses_warp_tile_in_front_check(&self, tileset: T) -> bool;
fn check_extra_warp_special(&self, tileset: T, tile_in_front: u8) -> Option<bool>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CollisionResult {
Passable,
TileBlocked,
TilePairBlocked,
SpriteBlocked,
MapEdge,
LedgeJump,
WaterBlocked,
CounterTile,
StopSurfing,
}
pub fn direction_to_sprite_facing(dir: Direction) -> u8 {
match dir {
Direction::Down => SPRITE_FACING_DOWN,
Direction::Up => SPRITE_FACING_UP,
Direction::Left => SPRITE_FACING_LEFT,
Direction::Right => SPRITE_FACING_RIGHT,
}
}
pub fn direction_to_pad_input(dir: Direction) -> u8 {
match dir {
Direction::Down => PAD_DOWN,
Direction::Up => PAD_UP,
Direction::Left => PAD_LEFT,
Direction::Right => PAD_RIGHT,
}
}
pub fn get_target_coords(
x: u16,
y: u16,
direction: Direction,
map_width_blocks: u8,
map_height_blocks: u8,
) -> Option<(u16, u16)> {
let max_x = (map_width_blocks as u16) * 2;
let max_y = (map_height_blocks as u16) * 2;
match direction {
Direction::Up => {
if y == 0 {
None
} else {
Some((x, y - 1))
}
}
Direction::Down => {
if y + 1 >= max_y {
None
} else {
Some((x, y + 1))
}
}
Direction::Left => {
if x == 0 {
None
} else {
Some((x - 1, y))
}
}
Direction::Right => {
if x + 1 >= max_x {
None
} else {
Some((x + 1, y))
}
}
}
}
pub fn get_block_at(x: u16, y: u16, map_width_blocks: u8, blocks: &[u8]) -> Option<u8> {
let bx = (x / 2) as usize;
let by = (y / 2) as usize;
let w = map_width_blocks as usize;
let idx = by * w + bx;
blocks.get(idx).copied()
}
#[derive(Debug, Clone, Copy)]
pub struct SpritePosition {
pub x: u16,
pub y: u16,
}
pub fn check_sprite_collision(
target_x: u16,
target_y: u16,
npc_positions: &[SpritePosition],
) -> bool {
npc_positions
.iter()
.any(|npc| npc.x == target_x && npc.y == target_y)
}
pub fn check_movement_collision<T: TilesetTrait>(
player_x: u16,
player_y: u16,
direction: Direction,
tileset: T,
map_width_blocks: u8,
map_height_blocks: u8,
standing_tile: u8,
target_tile: u8,
transport: TransportMode,
npc_positions: &[SpritePosition],
held_input: u8,
provider: &impl CollisionProvider<T>,
) -> CollisionResult {
let target_coords = get_target_coords(
player_x,
player_y,
direction,
map_width_blocks,
map_height_blocks,
);
if target_coords.is_none() {
if let Some(edge_tile) = provider.get_connection_edge_tile(
tileset,
map_width_blocks,
map_height_blocks,
player_x,
player_y,
direction,
) {
if transport == TransportMode::Surfing {
if provider.check_tile_pair_collision(tileset, standing_tile, edge_tile, true) {
return CollisionResult::TilePairBlocked;
}
if provider.is_water_tile(tileset, edge_tile) {
return CollisionResult::MapEdge;
}
if provider.is_tile_passable(tileset, edge_tile) {
return CollisionResult::MapEdge;
}
return CollisionResult::TileBlocked;
}
if provider.check_tile_pair_collision(tileset, standing_tile, edge_tile, false) {
return CollisionResult::TilePairBlocked;
}
if provider.is_counter_tile(tileset, edge_tile) {
return CollisionResult::CounterTile;
}
if !provider.is_tile_passable(tileset, edge_tile) {
return CollisionResult::TileBlocked;
}
}
return CollisionResult::MapEdge;
}
let (tx, ty) = target_coords.unwrap();
if transport == TransportMode::Walking || transport == TransportMode::Biking {
let sprite_facing = direction_to_sprite_facing(direction);
if provider.check_ledge_jump(tileset, sprite_facing, standing_tile, target_tile, held_input) {
return CollisionResult::LedgeJump;
}
}
if check_sprite_collision(tx, ty, npc_positions) {
return CollisionResult::SpriteBlocked;
}
if transport == TransportMode::Surfing {
if provider.check_tile_pair_collision(tileset, standing_tile, target_tile, true) {
return CollisionResult::TilePairBlocked;
}
if provider.is_water_tile(tileset, target_tile) {
return CollisionResult::Passable;
}
if provider.is_tile_passable(tileset, target_tile) {
return CollisionResult::StopSurfing;
}
return CollisionResult::TileBlocked;
}
let on_water = transport == TransportMode::Surfing;
if provider.check_tile_pair_collision(tileset, standing_tile, target_tile, on_water) {
return CollisionResult::TilePairBlocked;
}
if provider.is_counter_tile(tileset, target_tile) {
return CollisionResult::CounterTile;
}
if !provider.is_tile_passable(tileset, target_tile) {
return CollisionResult::TileBlocked;
}
CollisionResult::Passable
}
pub fn is_facing_map_edge(
player_x: u16,
player_y: u16,
direction: Direction,
map_width_blocks: u8,
map_height_blocks: u8,
) -> bool {
get_target_coords(
player_x,
player_y,
direction,
map_width_blocks,
map_height_blocks,
)
.is_none()
}
pub fn check_warp_at_position<M: crate::map::MapTrait, T: TilesetTrait, Mus>(
x: u16,
y: u16,
map: &MapData<M, T, Mus>,
) -> Option<usize> {
map.warps
.iter()
.position(|w| w.x as u16 == x && w.y as u16 == y)
}