use crate::map::MapTrait;
use crate::tileset::TilesetTrait;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::hash::Hash;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Direction {
Down,
Up,
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TransportMode {
Walking,
Biking,
Surfing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MovementState {
Idle,
Walking,
Jumping,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MapConnection<M: MapTrait> {
pub direction: Direction,
pub target_map: M,
pub offset: i8,
}
impl<M: MapTrait> MapConnection<M> {
pub fn new(direction: Direction, target_map: M, offset: i8) -> Self {
Self {
direction,
target_map,
offset,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MapConnections<M: MapTrait> {
pub north: Option<MapConnection<M>>,
pub south: Option<MapConnection<M>>,
pub west: Option<MapConnection<M>>,
pub east: Option<MapConnection<M>>,
}
impl<M: MapTrait> Default for MapConnections<M> {
fn default() -> Self {
Self {
north: None,
south: None,
west: None,
east: None,
}
}
}
impl<M: MapTrait> MapConnections<M> {
pub fn count(&self) -> usize {
self.north.is_some() as usize
+ self.south.is_some() as usize
+ self.west.is_some() as usize
+ self.east.is_some() as usize
}
pub fn get(&self, dir: Direction) -> Option<&MapConnection<M>> {
match dir {
Direction::Up => self.north.as_ref(),
Direction::Down => self.south.as_ref(),
Direction::Left => self.west.as_ref(),
Direction::Right => self.east.as_ref(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct WarpPoint<M: MapTrait> {
pub x: u8,
pub y: u8,
pub target_map: M,
pub target_warp_id: u8,
pub is_last_map: bool,
}
impl<M: MapTrait> WarpPoint<M> {
pub fn new(x: u8, y: u8, target_map: M, target_warp_id: u8) -> Self {
Self {
x,
y,
target_map,
target_warp_id,
is_last_map: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Sign {
pub x: u8,
pub y: u8,
pub text_id: u8,
}
impl Sign {
pub fn new(x: u8, y: u8, text_id: u8) -> Self {
Self { x, y, text_id }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NpcMovementType {
Stationary,
Wander,
FixedPath,
FacePlayer,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct NpcDefinition {
pub sprite_id: u8,
pub x: u8,
pub y: u8,
pub movement: NpcMovementType,
pub facing: Direction,
pub range: u8,
pub text_id: u8,
}
impl NpcDefinition {
#[allow(clippy::too_many_arguments)]
pub fn new(
sprite_id: u8,
x: u8,
y: u8,
movement: NpcMovementType,
facing: Direction,
range: u8,
text_id: u8,
) -> Self {
Self {
sprite_id,
x,
y,
movement,
facing,
range,
text_id,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MapData<M: MapTrait, T: TilesetTrait, Mus> {
pub id: M,
pub width: u8,
pub height: u8,
pub tileset: T,
pub music: Mus,
pub blocks: Vec<u8>,
pub warps: Vec<WarpPoint<M>>,
pub npcs: Vec<NpcDefinition>,
pub signs: Vec<Sign>,
pub connections: MapConnections<M>,
}
impl<M: MapTrait, T: TilesetTrait, Mus> MapData<M, T, Mus> {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: M,
width: u8,
height: u8,
tileset: T,
music: Mus,
blocks: Vec<u8>,
warps: Vec<WarpPoint<M>>,
npcs: Vec<NpcDefinition>,
signs: Vec<Sign>,
connections: MapConnections<M>,
) -> Self {
Self {
id,
width,
height,
tileset,
music,
blocks,
warps,
npcs,
signs,
connections,
}
}
pub fn set_block(&mut self, block_x: u8, block_y: u8, block_id: u8) -> bool {
let (w, h) = (self.width as usize, self.height as usize);
let (bx, by) = (block_x as usize, block_y as usize);
if bx >= w || by >= h {
return false;
}
let idx = by * w + bx;
if idx < self.blocks.len() {
self.blocks[idx] = block_id;
true
} else {
false
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PlayerState {
pub x: u16,
pub y: u16,
pub facing: Direction,
pub movement_state: MovementState,
pub transport: TransportMode,
#[serde(default = "default_bike_speedup")]
pub bike_speedup_active: bool,
}
fn default_bike_speedup() -> bool {
true
}
impl Default for PlayerState {
fn default() -> Self {
Self {
x: 0,
y: 0,
facing: Direction::Down,
movement_state: MovementState::Idle,
transport: TransportMode::Walking,
bike_speedup_active: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct OverworldState<M: MapTrait> {
pub current_map: M,
pub player: PlayerState,
pub walk_counter: u8,
pub encounter_cooldown: u8,
pub repel_steps: u16,
pub standing_on_warp: bool,
pub standing_on_door: bool,
pub exiting_door: bool,
}
impl<M: MapTrait> OverworldState<M> {
pub fn new(start_map: M) -> Self {
Self {
current_map: start_map,
player: PlayerState::default(),
walk_counter: 0,
encounter_cooldown: 0,
repel_steps: 0,
standing_on_warp: false,
standing_on_door: false,
exiting_door: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct OverworldInput {
pub up: bool,
pub down: bool,
pub left: bool,
pub right: bool,
pub a: bool,
pub b: bool,
pub start: bool,
pub select: bool,
}
impl OverworldInput {
pub fn new(
up: bool,
down: bool,
left: bool,
right: bool,
a: bool,
b: bool,
start: bool,
select: bool,
) -> Self {
Self {
up,
down,
left,
right,
a,
b,
start,
select,
}
}
pub fn none() -> Self {
Self {
up: false,
down: false,
left: false,
right: false,
a: false,
b: false,
start: false,
select: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
struct TestMap;
impl MapTrait for TestMap {}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
struct TestTileset;
impl TilesetTrait for TestTileset {
fn id(&self) -> u8 {
0
}
fn name(&self) -> &'static str {
"test"
}
}
fn make_map() -> MapData<TestMap, TestTileset, u8> {
MapData::new(
TestMap,
3,
2,
TestTileset,
0u8,
vec![0, 1, 2, 3, 4, 5],
Vec::new(),
Vec::new(),
Vec::new(),
MapConnections::default(),
)
}
#[test]
fn set_block_in_bounds_mutates_and_returns_true() {
let mut map = make_map();
assert!(map.set_block(2, 1, 99));
assert_eq!(map.blocks[5], 99);
assert_eq!(map.blocks, vec![0, 1, 2, 3, 4, 99]);
assert!(map.set_block(0, 0, 42));
assert_eq!(map.blocks[0], 42);
}
#[test]
fn set_block_out_of_bounds_returns_false_and_no_change() {
let mut map = make_map();
let before = map.blocks.clone();
assert!(!map.set_block(3, 0, 99));
assert!(!map.set_block(0, 2, 99));
assert!(!map.set_block(10, 10, 99));
assert_eq!(map.blocks, before, "out-of-bounds writes must not mutate");
}
}