use sfml::system::Vector2; use sfml::graphics::IntRect; use engine::texture::Texture; use engine::renderer::Drawable; use engine::math::Matrix; /// This struct represents the different type of tiles that can exist in our maps. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum CollisionTile { /// A tile that contains nothing. Empty, /// A solid tile. Solid } impl CollisionTile { /// Creates a tile from a u8. pub fn from_u8(id: u8) -> Option { match id { 0 => Some(CollisionTile::Empty), 1 => Some(CollisionTile::Solid), _ => None, } } } /// This struct represents a renderable tile linking to its part in the tileset texture. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum GraphicTile { /// There is nothing to draw. Hidden, /// Top left corner of a solid tile. TopLeft, /// Top of a solid tile. Top, /// Top right corner of a solid tile. TopRight, /// Left of a solid tile. Left, /// Center of a solid tile. Center, /// Right of a solid tile. Right, /// Bottom left corner of a solid tile. BottomLeft, /// Bottom of a solid tile. Bottom, /// Bottom right corner of a solid tile. BottomRight, } impl GraphicTile { /// Checks if a graphic tile has a top border. pub fn is_top(&self) -> bool { match *self { GraphicTile::TopLeft | GraphicTile::Top | GraphicTile::TopRight => true, _ => false, } } /// Checks if a graphic tile has a left border. pub fn is_left(&self) -> bool { match *self { GraphicTile::TopLeft | GraphicTile::Left | GraphicTile::BottomLeft => true, _ => false, } } /// Checks if a graphic tile has a right border. pub fn is_right(&self) -> bool { match *self { GraphicTile::TopRight | GraphicTile::Right | GraphicTile::BottomRight => true, _ => false, } } /// Checks if a graphic tile has a bottom border. pub fn is_bottom(&self) -> bool { match *self { GraphicTile::BottomLeft | GraphicTile::Bottom | GraphicTile::BottomRight => true, _ => false, } } /// Creates a vec containing all the non hidden graphic tiles. pub fn all() -> Vec { vec![ GraphicTile::TopLeft, GraphicTile::Top, GraphicTile::TopRight, GraphicTile::Left, GraphicTile::Center, GraphicTile::Right, GraphicTile::BottomLeft, GraphicTile::Bottom, GraphicTile::BottomRight, ] } /// Creates the correct graphic tile depending on the neighbours. /// /// A none will be considered solid. pub fn from_neighbour_options( top: Option, left: Option, right: Option, bottom: Option) -> GraphicTile { GraphicTile::from_neighbours( top.unwrap_or(CollisionTile::Solid), left.unwrap_or(CollisionTile::Solid), right.unwrap_or(CollisionTile::Solid), bottom.unwrap_or(CollisionTile::Solid), ) } /// Creates the correct graphic tile depending on the neighbours. pub fn from_neighbours( top: CollisionTile, left: CollisionTile, right: CollisionTile, bottom: CollisionTile) -> GraphicTile { let mut all = GraphicTile::all() .into_iter() .map(|x| (x, true)) .collect::>(); for (ref mut tile, ref mut possible) in &mut all { if tile.is_top() == (top == CollisionTile::Solid) { *possible = false; } if tile.is_left() == (left == CollisionTile::Solid) { *possible = false; } if tile.is_right() == (right == CollisionTile::Solid) { *possible = false; } if tile.is_bottom() == (bottom == CollisionTile::Solid) { *possible = false; } } for (tile, possible) in all { if possible { return tile; } } panic!("Did not find a tile, implementation error"); } /// Returns the offset to the corresponding graphic tile in the texture. pub fn offset(&self) -> (i32, i32) { let vertical = match *self { GraphicTile::TopLeft | GraphicTile::Top | GraphicTile::TopRight => 0, GraphicTile::Left | GraphicTile::Center | GraphicTile::Right => 16, GraphicTile::BottomLeft | GraphicTile::Bottom | GraphicTile::BottomRight => 32, _ => 0, }; let horizontal = match *self { GraphicTile::TopLeft | GraphicTile::Left | GraphicTile::BottomLeft => 0, GraphicTile::Top | GraphicTile::Center | GraphicTile::Bottom => 16, GraphicTile::TopRight | GraphicTile::Right | GraphicTile::BottomRight => 32, _ => 0, }; // (horizontal, vertical) (vertical, horizontal) } } /// A tile and its position. pub struct PositionedTile { /// The graphic representation of the positioned tile. pub graphic: GraphicTile, /// The collision representation of the positioned tile. pub collision: CollisionTile, /// The position of the positioned tile. pub position: (f32, f32), } impl Drawable for PositionedTile { fn texture(&self) -> Texture { Texture::Overworld } fn texture_rect(&self) -> IntRect { let offset = self.graphic.offset(); IntRect::new(offset.0, offset.1, 16, 16) } fn position(&self) -> Vector2 { self.position.into() } } /// The map represents the tiles contained in a level. pub struct Map { /// The tiles contained in the level. tiles: Matrix<(CollisionTile, GraphicTile)>, } impl Map { /// Creates a map full of nothing, with a ground at the bottom. pub fn new(rows: usize, cols: usize) -> Map { let mut tiles = Matrix::from_size(rows, cols, CollisionTile::Empty); let rows = tiles.rows(); for i in 0 .. tiles.cols() { tiles[(rows - 1, i)] = CollisionTile::Solid; } tiles[(25, 12)] = CollisionTile::Solid; tiles[(25, 13)] = CollisionTile::Solid; tiles[(25, 14)] = CollisionTile::Solid; tiles[(25, 15)] = CollisionTile::Solid; Map::from_collision_tiles(tiles) } /// Creates a map from a txt file. pub fn from_str(text: &str) -> Result { let split = text.split('\n').collect::>(); // First two usize are the size of the map let size = split[0] .split_whitespace() .map(|x| x.parse::().unwrap()) .collect::>(); let mut tiles = Matrix::from_size(size[0], size[1], CollisionTile::Empty); for (row, line) in split.iter().skip(1).enumerate() { for (col, tile) in line.split_whitespace().enumerate() { tiles[(row, col)] = CollisionTile::from_u8(tile.parse::().unwrap()).unwrap(); } } Ok(Map::from_collision_tiles(tiles)) } /// Creates a map from its tiles. pub fn from_collision_tiles(tiles: Matrix) -> Map { let rows = tiles.rows(); let cols = tiles.cols(); let mut matrix = Matrix::from_size(rows, cols, (CollisionTile::Empty, GraphicTile::Hidden) ); for i in 0 .. rows { for j in 0 .. cols { let graphic = if tiles[(i, j)] == CollisionTile::Solid { // TODO This is uggly // If there is an overflow, we should give None instead let (i, j) = (i as isize, j as isize); GraphicTile::from_neighbour_options( tiles.get(((i ) as usize, (j-1) as usize)).map(|x| *x), tiles.get(((i-1) as usize, (j ) as usize)).map(|x| *x), tiles.get(((i+1) as usize, (j ) as usize)).map(|x| *x), tiles.get(((i ) as usize, (j+1) as usize)).map(|x| *x), ) } else { GraphicTile::Hidden }; matrix[(i, j)] = (tiles[(i, j)], graphic); } } Map { tiles: matrix, } } /// Returns an iterator to the positioned tiles. pub fn at(&self, row: usize, col: usize) -> PositionedTile { PositionedTile { collision: self.tiles[(row, col)].0, graphic: self.tiles[(row, col)].1, position: (col as f32 * 16.0, row as f32 * 16.0), } } /// Returns the number of rows of the map. pub fn rows(&self) -> usize { self.tiles.rows() } /// Returns the number of columns of the map. pub fn cols(&self) -> usize { self.tiles.cols() } /// Checks whether the vector (old, new) collides with an element of the map. /// /// Returns the height of the collision if any. pub fn collides(&self, old: Vector2, new: Vector2) -> Option { let height = new.y - old.y; let mut y = (old.y / 16.0).ceil() * 16.0; while y < new.y { let current_height = y - old.y; let x = old.x + (new.x - old.x) * current_height / height; // Find tile on x, y if x > 0.0 && y > 0.0 { let row = (y / 16.0) as usize + 2; let col = (x / 16.0) as usize + 1; if let Some((CollisionTile::Solid, _)) = self.tiles.get((row, col)) { return Some(y); } } y += 16.0; } None } }