use crate::assets::ASSETS; use comfy::{IVec2, TextureHandle, UVec2}; use std::collections::HashMap; pub enum MovementCost { /// No movement possible - cost infinitely high. Infinite, /// There is a path for this movement - movement is cheap. Path, /// There is no path and no obstacle. Default, /// There is an obstacle (i.e. fence) - movement is expensive. Obstacle } #[derive(Clone, Copy, Debug)] pub enum Tile { Grass, Path { left: bool, right: bool, top: bool, bottom: bool }, House { texture: TextureHandle, door: bool } } impl Tile { pub fn textures(&self) -> Vec { match self { Self::Grass => vec![ASSETS.overworld.grass], Self::Path { left, right, top, bottom } => { let path_texture = match (left, right, top, bottom) { (true, true, false, false) => ASSETS.overworld.path_horiz, (false, false, true, true) => ASSETS.overworld.path_vert, (true, false, true, false) => ASSETS.overworld.path_top_left, (true, false, false, true) => ASSETS.overworld.path_bottom_left, (false, true, true, false) => ASSETS.overworld.path_top_right, (false, true, false, true) => ASSETS.overworld.path_bottom_right, (true, true, true, false) => ASSETS.overworld.path_top_left_right, (true, true, false, true) => ASSETS.overworld.path_bottom_left_right, (true, false, true, true) => ASSETS.overworld.path_top_left_bottom, (false, true, true, true) => ASSETS.overworld.path_top_right_bottom, (true, true, true, true) => ASSETS.overworld.path_crossing, (true, false, false, false) | (false, true, false, false) | (false, false, true, false) | (false, false, false, true) => panic!("We don't have no dead ends"), (false, false, false, false) => panic!("I think you meant grass?!?") }; vec![ASSETS.overworld.grass, path_texture] }, Self::House { texture, .. } => { vec![ASSETS.overworld.grass, *texture] } } } pub fn can_stand_inside(&self) -> bool { unimplemented!() } pub fn movement_cost_left(&self) -> MovementCost { unimplemented!() } pub fn movement_cost_right(&self) -> MovementCost { unimplemented!() } pub fn movement_cost_up(&self) -> MovementCost { unimplemented!() } pub fn movement_cost_down(&self) -> MovementCost { unimplemented!() } pub fn can_enter_house(&self) -> bool { unimplemented!() } } /// The size of a chunk (both width and height). This value squared gives the amount of /// tiles in the chunk. const CHUNK_SIZE: u32 = 100; /// Chunks #[derive(Debug)] #[allow(dead_code)] pub struct Chunk { /// All tiles within this chunk. tiles: [Tile; (CHUNK_SIZE * CHUNK_SIZE) as usize], /// All paths that leave this chunk on the left hand side. paths_left: Vec, /// All paths that leave this chunk on the right hand side. paths_right: Vec, /// All paths that leave this chunk on the bottom side. paths_bottom: Vec, /// All paths that leave this chunk on the top side. paths_top: Vec } impl Chunk { pub fn generate_chunk() -> Self { // TODO real worldgen // for the time being we just copy this pre-made house block into the chunk fn path(left: bool, right: bool, top: bool, bottom: bool) -> Tile { Tile::Path { left, right, top, bottom } } fn house(texture: TextureHandle) -> Tile { Tile::House { texture, door: false } } let path_horiz = path(true, true, false, false); let path_vert = path(false, false, true, true); let path_crossing = path(true, true, true, true); let grass = Tile::Grass; let block = [ [ path_crossing, path_horiz, path_horiz, path_horiz, path_horiz, path_horiz, path_horiz, path_horiz, path_horiz, path_horiz, path_crossing ], [ path_vert, grass, grass, grass, house(ASSETS.overworld.house_roof_top), grass, grass, grass, grass, grass, path_vert ], [ path_vert, grass, grass, house(ASSETS.overworld.house_roof_mid_left), house(ASSETS.overworld.house_mid_window), house(ASSETS.overworld.house_roof_mid_right), grass, grass, grass, grass, path_vert ], [ path_vert, grass, house(ASSETS.overworld.house_roof_left), house(ASSETS.overworld.house_mid_window), house(ASSETS.overworld.house_mid), house(ASSETS.overworld.house_mid_window), house(ASSETS.overworld.house_roof_right), grass, grass, grass, path_vert ], [ path_vert, grass, house(ASSETS.overworld.house_bottom_left), house(ASSETS.overworld.house_bottom_door), house(ASSETS.overworld.house_bottom_window), house(ASSETS.overworld.house_bottom_window), house(ASSETS.overworld.house_bottom_right), grass, grass, grass, path_vert ], [ path_vert, grass, grass, path(false, true, true, false), path_horiz, path_horiz, path_horiz, path_horiz, path_horiz, path_horiz, path(true, false, true, true) ], [ path_vert, grass, grass, grass, grass, grass, grass, grass, grass, grass, path_vert ], [ path_vert, grass, grass, grass, grass, grass, grass, grass, grass, grass, path_vert ] ]; // and then we copy this pre-made block into our chunk let mut tiles = [Tile::Grass; (CHUNK_SIZE * CHUNK_SIZE) as usize]; for y in 0 .. CHUNK_SIZE { for x in 0 .. CHUNK_SIZE { let row = block[y as usize % block.len()]; tiles[(y * CHUNK_SIZE + x) as usize] = row[x as usize % row.len()]; } } Self { tiles, paths_left: vec![], paths_right: vec![], paths_bottom: vec![], paths_top: vec![] } } pub fn get_tile(&self, local_chunk_coords: UVec2) -> Option<&Tile> { self.tiles .get((local_chunk_coords.y * CHUNK_SIZE + local_chunk_coords.x) as usize) } /// iterate over all tiles and its local chunk coords pub fn iter_tiles(&self) -> impl Iterator { self.tiles.iter().enumerate().map(|(i, tile)| { let i = i as u32; (UVec2::new(i % CHUNK_SIZE, i / CHUNK_SIZE), tile) }) } } #[derive(Debug, Default)] pub struct Overworld { chunks: HashMap } fn world_to_chunk_and_local_coords(world_coords: IVec2) -> (IVec2, UVec2) { let chunk_coords = IVec2 { x: world_coords.x.div_euclid(CHUNK_SIZE as _), y: world_coords.y.div_euclid(CHUNK_SIZE as _) }; let local_chunk_coords = UVec2 { x: world_coords.x.rem_euclid(CHUNK_SIZE as _) as _, y: world_coords.y.rem_euclid(CHUNK_SIZE as _) as _ }; (chunk_coords, local_chunk_coords) } impl Overworld { /// Return a [`Tile`] at the given world coordinates, or `None` if that tile has not /// been generated yet. pub fn get_tile(&self, world_coords: IVec2) -> Option<&Tile> { let (chunk_coords, local_chunk_coords) = world_to_chunk_and_local_coords(world_coords); let chunk = self.chunks.get(&chunk_coords)?; chunk.get_tile(local_chunk_coords) } fn get_or_generate_tile(&mut self, world_coords: IVec2) -> &Tile { let (chunk_coords, local_chunk_coords) = world_to_chunk_and_local_coords(world_coords); let chunk = self .chunks .entry(chunk_coords) .or_insert_with(Chunk::generate_chunk); chunk.get_tile(local_chunk_coords).unwrap() } /// iterate over all tiles and its global coords pub fn iter_tiles(&self) -> impl Iterator { self.chunks.iter().flat_map(|(chunk_coords, chunk)| { chunk.iter_tiles().map(|(local_coords, tile)| { // never fail because chunksize fits alswas into i32 let local_coords: IVec2 = local_coords.try_into().unwrap(); (local_coords + (*chunk_coords * CHUNK_SIZE as i32), tile) }) }) } }