From f2fabafe57f082b2be4e6c257f473128ded6ae92 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Wed, 26 Jul 2023 01:35:02 +0100 Subject: [PATCH] starting in town --- src/camera.rs | 88 +++++- src/main.rs | 4 +- src/{map.rs => map/mod.rs} | 64 ++-- src/map/tiletype.rs | 52 ++++ src/map_builders/area_starting_points.rs | 2 +- src/map_builders/mod.rs | 10 + src/map_builders/town.rs | 372 +++++++++++++++++++++++ src/player.rs | 16 +- src/spawner.rs | 4 +- 9 files changed, 551 insertions(+), 61 deletions(-) rename src/{map.rs => map/mod.rs} (70%) create mode 100644 src/map/tiletype.rs create mode 100644 src/map_builders/town.rs diff --git a/src/camera.rs b/src/camera.rs index 1b0ad1e..2ff126e 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -66,10 +66,10 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) { let entity_offset_y = pos.y - min_y; if entity_offset_x > 0 && entity_offset_x < map_width && entity_offset_y > 0 && entity_offset_y < map_height { - let offsets = RGB::from_u8(map.red_offset[idx], map.green_offset[idx], map.blue_offset[idx]); + let offsets = map.colour_offset[idx]; let mut draw = false; let mut fg = render.fg; - let mut bg = render.bg.add(RGB::from_u8(26, 45, 45)).add(offsets); + let mut bg = offset(render.bg, offsets); // Get bloodstain colours if map.bloodstains.contains(&idx) { bg = bg.add(RGB::from_f32(0.6, 0., 0.)); @@ -104,34 +104,96 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) { } } +fn offset(rgb: rltk::RGB, offsets: (f32, f32, f32)) -> RGB { + let r = rgb.r * offsets.0; + let g = rgb.g * offsets.1; + let b = rgb.b * offsets.2; + + return rltk::RGB::from_f32(r, g, b); +} + fn get_tile_glyph(idx: usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) { - let offsets = RGB::from_u8(map.red_offset[idx], map.green_offset[idx], map.blue_offset[idx]); - let glyph; - let mut fg = offsets.mul(2.0); - let mut bg = offsets.add(RGB::from_u8(26, 45, 45)); + let offsets = map.colour_offset[idx]; + let glyph: rltk::FontCharType; + let mut fg: RGB = RGB::new(); + let mut bg: RGB; + + let default_bg: RGB = RGB::from_u8(26, 45, 45); match map.tiles[idx] { TileType::Floor => { glyph = rltk::to_cp437('.'); - fg = fg.add(RGB::from_f32(0.1, 0.8, 0.5)); + fg = RGB::from_f32(0.1, 0.8, 0.5); + bg = default_bg; + } + TileType::WoodFloor => { + glyph = rltk::to_cp437('.'); + bg = RGB::from_u8(47, 36, 28); + } + TileType::Fence => { + glyph = rltk::to_cp437('='); + fg = RGB::from_u8(66, 32, 6); + bg = RGB::from_u8(44, 34, 26); } TileType::Wall => { let x = idx as i32 % map.width; let y = idx as i32 / map.width; glyph = wall_glyph(&*map, x, y); - fg = fg.add(RGB::from_f32(0.6, 0.5, 0.25)); + fg = RGB::from_f32(0.6, 0.5, 0.25); + bg = default_bg; } TileType::DownStair => { glyph = rltk::to_cp437('>'); fg = RGB::from_f32(0., 1., 1.); + bg = default_bg; + } + TileType::Bridge => { + glyph = rltk::to_cp437('.'); + fg = default_bg; + bg = default_bg; + } + TileType::Gravel => { + glyph = rltk::to_cp437(';'); + bg = RGB::from_u8(26, 26, 36); + } + TileType::Road => { + glyph = rltk::to_cp437('~'); + //fg = RGB::from_u8(112, 105, 94); + bg = default_bg; + } + TileType::Grass => { + glyph = rltk::to_cp437('"'); + bg = RGB::from_u8(26, 45, 26); + } + TileType::Sand => { + glyph = rltk::to_cp437('.'); + bg = RGB::from_u8(54, 54, 28); + } + TileType::ShallowWater => { + glyph = rltk::to_cp437('~'); + bg = RGB::from_u8(34, 42, 62); + } + TileType::DeepWater => { + glyph = rltk::to_cp437('~'); + bg = RGB::from_u8(24, 30, 42); } } if map.bloodstains.contains(&idx) { bg = bg.add(RGB::from_f32(0.6, 0., 0.)); } + + // If the foreground hasn't been changed, just add + // the bg to it. Otherwise, leave it as is. + if fg == RGB::new() { + fg = fg.add(bg).add(map.additional_fg_offset); + } + + fg = offset(fg, offsets); + bg = offset(bg, offsets); + if !map.visible_tiles[idx] { - fg = fg.mul(0.6); - bg = bg.mul(0.6); + fg = fg.mul(0.75); + bg = bg.mul(0.75); } return (glyph, fg, bg); @@ -291,14 +353,14 @@ pub fn render_debug_map(map: &Map, ctx: &mut Rltk) { let min_y = player_pos.y - center_y; let max_y = min_y + y_chars as i32; - let map_width = map.width - 1; - let map_height = map.height - 1; + let map_width = map.width; + let map_height = map.height; let mut y = 0; for ty in min_y..max_y { let mut x = 0; for tx in min_x..max_x { - if tx > 0 && tx < map_width && ty > 0 && ty < map_height { + if tx >= 0 && tx < map_width && ty >= 0 && ty < map_height { let idx = map.xy_idx(tx, ty); if map.revealed_tiles[idx] { let (glyph, fg, bg) = get_tile_glyph(idx, &*map); diff --git a/src/main.rs b/src/main.rs index ff1837c..30306c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,7 +45,7 @@ rltk::embedded_resource!(SCANLINESFS, "../resources/scanlines.fs"); rltk::embedded_resource!(SCANLINESVS, "../resources/scanlines.vs"); //Consts -pub const SHOW_MAPGEN: bool = false; +pub const SHOW_MAPGEN: bool = true; #[derive(PartialEq, Copy, Clone)] pub enum RunState { @@ -82,7 +82,7 @@ impl State { self.mapgen_timer = 0.0; self.mapgen_history.clear(); let mut rng = self.ecs.write_resource::(); - let mut builder = map_builders::random_builder(new_depth, &mut rng, 64, 64); + let mut builder = map_builders::level_builder(new_depth, &mut rng, 80, 50); builder.build_map(&mut rng); std::mem::drop(rng); self.mapgen_history = builder.build_data.history.clone(); diff --git a/src/map.rs b/src/map/mod.rs similarity index 70% rename from src/map.rs rename to src/map/mod.rs index 0ff42de..50f2aab 100644 --- a/src/map.rs +++ b/src/map/mod.rs @@ -2,13 +2,8 @@ use rltk::{Algorithm2D, BaseMap, Point}; use serde::{Deserialize, Serialize}; use specs::prelude::*; use std::collections::HashSet; - -#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] -pub enum TileType { - Wall, - Floor, - DownStair, -} +mod tiletype; +pub use tiletype::{tile_cost, tile_opaque, tile_walkable, TileType}; // FIXME: If the map size gets too small, entities stop being rendered starting from the right. // i.e. on a map size of 40*40, only entities to the left of the player are rendered. @@ -24,9 +19,8 @@ pub struct Map { pub lit_tiles: Vec, pub telepath_tiles: Vec, // Combine these offsets into one Vec<(u8, u8, u8)> - pub red_offset: Vec, - pub green_offset: Vec, - pub blue_offset: Vec, + pub colour_offset: Vec<(f32, f32, f32)>, + pub additional_fg_offset: rltk::RGB, pub blocked: Vec, pub depth: i32, pub bloodstains: HashSet, @@ -52,9 +46,8 @@ impl Map { visible_tiles: vec![false; map_tile_count], lit_tiles: vec![true; map_tile_count], // NYI: Light sources. Once those exist, we can set this to false. telepath_tiles: vec![false; map_tile_count], - red_offset: vec![0; map_tile_count], - green_offset: vec![0; map_tile_count], - blue_offset: vec![0; map_tile_count], + colour_offset: vec![(1.0, 1.0, 1.0); map_tile_count], + additional_fg_offset: rltk::RGB::from_u8(HALF_OFFSET, HALF_OFFSET, HALF_OFFSET), blocked: vec![false; map_tile_count], depth: new_depth, bloodstains: HashSet::new(), @@ -62,20 +55,16 @@ impl Map { tile_content: vec![Vec::new(); map_tile_count], }; - const MAX_OFFSET: u8 = 32; + const HALF_OFFSET: u8 = 5; + const OFFSET_PERCENT: i32 = 10; + const TWICE_OFFSET: i32 = OFFSET_PERCENT * 2; let mut rng = rltk::RandomNumberGenerator::new(); - for idx in 0..map.red_offset.len() { - let roll = rng.roll_dice(1, MAX_OFFSET as i32); - map.red_offset[idx] = roll as u8; - } - for idx in 0..map.green_offset.len() { - let roll = rng.roll_dice(1, MAX_OFFSET as i32); - map.green_offset[idx] = roll as u8; - } - for idx in 0..map.blue_offset.len() { - let roll = rng.roll_dice(1, MAX_OFFSET as i32); - map.blue_offset[idx] = roll as u8; + for idx in 0..map.colour_offset.len() { + let red_roll: f32 = (rng.roll_dice(1, TWICE_OFFSET - 1) + 1 - OFFSET_PERCENT) as f32 / 100f32 + 1.0; + let green_roll: f32 = (rng.roll_dice(1, TWICE_OFFSET - 1) + 1 - OFFSET_PERCENT) as f32 / 100f32 + 1.0; + let blue_roll: f32 = (rng.roll_dice(1, TWICE_OFFSET - 1) + 1 - OFFSET_PERCENT) as f32 / 100f32 + 1.0; + map.colour_offset[idx] = (red_roll, green_roll, blue_roll); } return map; @@ -92,7 +81,7 @@ impl Map { pub fn populate_blocked(&mut self) { for (i, tile) in self.tiles.iter_mut().enumerate() { - self.blocked[i] = *tile == TileType::Wall; + self.blocked[i] = !tile_walkable(*tile); } } @@ -112,7 +101,11 @@ impl Algorithm2D for Map { impl BaseMap for Map { fn is_opaque(&self, idx: usize) -> bool { let idx_u = idx as usize; - return self.tiles[idx_u] == TileType::Wall || self.view_blocked.contains(&idx_u); + if idx_u > 0 && idx_u < self.tiles.len() { + return tile_opaque(self.tiles[idx_u]) || self.view_blocked.contains(&idx_u); + } else { + return true; + } } fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 { @@ -128,33 +121,34 @@ impl BaseMap for Map { let x = (idx as i32) % self.width; let y = (idx as i32) / self.width; let w = self.width as usize; + let tt = self.tiles[idx as usize]; // Cardinal directions if self.is_exit_valid(x - 1, y) { - exits.push((idx - 1, 1.0)); + exits.push((idx - 1, tile_cost(tt))); } if self.is_exit_valid(x + 1, y) { - exits.push((idx + 1, 1.0)); + exits.push((idx + 1, tile_cost(tt))); } if self.is_exit_valid(x, y - 1) { - exits.push((idx - w, 1.0)); + exits.push((idx - w, tile_cost(tt))); } if self.is_exit_valid(x, y + 1) { - exits.push((idx + w, 1.0)); + exits.push((idx + w, tile_cost(tt))); } // Diagonals if self.is_exit_valid(x - 1, y - 1) { - exits.push((idx - w - 1, 1.45)); + exits.push((idx - w - 1, tile_cost(tt) * 1.45)); } if self.is_exit_valid(x + 1, y - 1) { - exits.push((idx - w + 1, 1.45)); + exits.push((idx - w + 1, tile_cost(tt) * 1.45)); } if self.is_exit_valid(x - 1, y + 1) { - exits.push((idx + w - 1, 1.45)); + exits.push((idx + w - 1, tile_cost(tt) * 1.45)); } if self.is_exit_valid(x + 1, y + 1) { - exits.push((idx + w + 1, 1.45)); + exits.push((idx + w + 1, tile_cost(tt) * 1.45)); } exits diff --git a/src/map/tiletype.rs b/src/map/tiletype.rs new file mode 100644 index 0000000..8f19c9a --- /dev/null +++ b/src/map/tiletype.rs @@ -0,0 +1,52 @@ +use serde::{Deserialize, Serialize}; + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] +pub enum TileType { + // Walls (opaque) + Wall, + // Impassable (transparent) + DeepWater, + Fence, + // Floors (walkable) + Floor, + WoodFloor, + Gravel, + Road, + Grass, + Sand, + ShallowWater, + Bridge, + // Stairs (changes floor) + DownStair, +} + +pub fn tile_walkable(tt: TileType) -> bool { + match tt { + TileType::Floor + | TileType::WoodFloor + | TileType::Gravel + | TileType::Road + | TileType::Grass + | TileType::Sand + | TileType::ShallowWater + | TileType::Bridge + | TileType::DownStair => true, + _ => false, + } +} + +pub fn tile_opaque(tt: TileType) -> bool { + match tt { + TileType::Wall => true, + _ => false, + } +} + +pub fn tile_cost(tt: TileType) -> f32 { + match tt { + TileType::Road => 0.8, + TileType::Grass => 1.1, + TileType::ShallowWater => 1.2, + _ => 1.0, + } +} diff --git a/src/map_builders/area_starting_points.rs b/src/map_builders/area_starting_points.rs index f890577..2d209d0 100644 --- a/src/map_builders/area_starting_points.rs +++ b/src/map_builders/area_starting_points.rs @@ -49,7 +49,7 @@ impl AreaStartingPosition { let mut available_floors: Vec<(usize, f32)> = Vec::new(); for (idx, tiletype) in build_data.map.tiles.iter().enumerate() { - if *tiletype == TileType::Floor { + if crate::tile_walkable(*tiletype) { available_floors.push(( idx, rltk::DistanceAlg::PythagorasSquared.distance2d( diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index a492d37..48a396f 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -58,6 +58,8 @@ mod door_placement; use door_placement::DoorPlacement; mod fill_edges; use fill_edges::FillEdges; +mod town; +use town::town_builder; // Shared data to be passed around build chain pub struct BuilderMap { @@ -314,3 +316,11 @@ pub fn random_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator, wid builder } + +pub fn level_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator, width: i32, height: i32) -> BuilderChain { + rltk::console::log(format!("DEBUGINFO: Depth: {}", new_depth)); + match new_depth { + 1 => town_builder(new_depth, rng, width, height), + _ => random_builder(new_depth, rng, width, height), + } +} diff --git a/src/map_builders/town.rs b/src/map_builders/town.rs new file mode 100644 index 0000000..65adb5b --- /dev/null +++ b/src/map_builders/town.rs @@ -0,0 +1,372 @@ +use super::{BuilderChain, BuilderMap, InitialMapBuilder, Position, TileType}; +use std::collections::HashSet; + +pub fn town_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator, width: i32, height: i32) -> BuilderChain { + let mut chain = BuilderChain::new(new_depth, width, height); + chain.start_with(TownBuilder::new()); + + return chain; +} + +pub struct TownBuilder {} + +impl InitialMapBuilder for TownBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build_rooms(rng, build_data); + } +} + +impl TownBuilder { + pub fn new() -> Box { + return Box::new(TownBuilder {}); + } + + pub fn build_rooms(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + // Make visible for snapshot + for t in build_data.map.visible_tiles.iter_mut() { + *t = true; + } + + self.grass_layer(build_data); + rltk::console::log("Placed grass."); + let piers = self.water_and_piers(rng, build_data); + rltk::console::log("Placed water and piers."); + let (mut available_building_tiles, wall_gap_y) = self.town_walls(rng, build_data); + rltk::console::log("Placed walls."); + let mut buildings = self.buildings(rng, build_data, &mut available_building_tiles); + rltk::console::log("Placed buildings."); + let doors = self.add_doors(rng, build_data, &mut buildings, wall_gap_y); + rltk::console::log("Placed doors."); + self.path_from_tiles_to_nearest_tiletype(build_data, &doors, TileType::Road, TileType::Road, true); + rltk::console::log("Placed path from doors to road."); + self.path_from_tiles_to_nearest_tiletype(build_data, &piers, TileType::Road, TileType::Road, false); + rltk::console::log("Placed path from piers to road."); + + build_data.take_snapshot(); + + // Sort buildings by size + let mut building_size: Vec<(usize, i32)> = Vec::new(); + for (i, building) in buildings.iter().enumerate() { + building_size.push((i, building.2 * building.3)); + } + building_size.sort_by(|a, b| b.1.cmp(&a.1)); + + // Largest building as start position + let tavern = &buildings[building_size[0].0]; + build_data.starting_position = Some(Position { x: tavern.0 + (tavern.2 / 2), y: tavern.1 + (tavern.3 / 2) }); + + // Smallest building as mine entrance + let mine = &buildings[building_size[building_size.len() - 1].0]; + let exit_idx = build_data.map.xy_idx(mine.0 + (mine.2 / 2), mine.1 + (mine.3 / 2)); + build_data.map.tiles[exit_idx] = TileType::DownStair; + } + + fn grass_layer(&mut self, build_data: &mut BuilderMap) { + // Grass everywhere + for t in build_data.map.tiles.iter_mut() { + *t = TileType::Grass; + } + build_data.take_snapshot(); + } + + fn water_and_piers(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) -> Vec { + let mut n = (rng.roll_dice(1, 65535) as f32) / 65535f32; + let mut water_width: Vec = Vec::new(); + let variance = 5; + let minimum_width = variance + 10; + let shallow_width = 6; + let sand_width = shallow_width + 4; + + for y in 0..build_data.height { + let n_water = (f32::sin(n) * variance as f32) as i32 + minimum_width + rng.roll_dice(1, 2); + water_width.push(n_water); + n += 0.1; + for x in 0..n_water { + let idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[idx] = TileType::DeepWater; + } + for x in n_water..n_water + shallow_width { + let idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[idx] = TileType::ShallowWater; + } + for x in n_water + shallow_width..n_water + sand_width { + let idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[idx] = TileType::Sand; + } + } + build_data.take_snapshot(); + + // Add piers + let mut placed_piers: Vec = Vec::new(); + let pier_width = 4; + let mut pier_idxs: Vec = Vec::new(); + + for _i in 0..rng.roll_dice(1, 3) + 2 { + let mut y; + loop { + y = rng.roll_dice(1, build_data.height - 3) - 1; + if !(placed_piers.contains(&y) || placed_piers.contains(&(y + pier_width))) { + rltk::console::log(format!("Placing pier at y {}-{}", y, y + 3)); + break; + } + } + + for i in 0..=pier_width { + placed_piers.push(y + i); + } + + let start_roll = rng.roll_dice(1, 4); + let largest_water_width; + if water_width[y as usize] > water_width[y as usize + 1] { + largest_water_width = water_width[y as usize]; + } else { + largest_water_width = water_width[y as usize + 1]; + } + + // Make pier length + for x in 2 + start_roll..largest_water_width + sand_width { + let idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[idx] = TileType::Fence; + let idx = build_data.map.xy_idx(x, y + 1); + build_data.map.tiles[idx] = TileType::WoodFloor; + let idx = build_data.map.xy_idx(x, y + 2); + build_data.map.tiles[idx] = TileType::WoodFloor; + let idx = build_data.map.xy_idx(x, y + 3); + build_data.map.tiles[idx] = TileType::Fence; + } + + // Set end of pier to fences + for y in y + 1..y + pier_width - 1 { + let idx = build_data.map.xy_idx(2 + start_roll, y); + build_data.map.tiles[idx] = TileType::Fence; + } + build_data.take_snapshot(); + + pier_idxs.push(build_data.map.xy_idx(largest_water_width + sand_width, y + 1)); + pier_idxs.push(build_data.map.xy_idx(largest_water_width + sand_width, y + 2)); + } + + return pier_idxs; + } + + fn town_walls( + &mut self, + rng: &mut rltk::RandomNumberGenerator, + build_data: &mut BuilderMap, + ) -> (HashSet, i32) { + let mut available_building_tiles: HashSet = HashSet::new(); + + const BORDER: i32 = 4; + const OFFSET_FROM_LEFT: i32 = 30 + BORDER; + const PATH_OFFSET_FROM_CENTRE: i32 = 4; + const HALF_PATH_THICKNESS: i32 = 3; + + let wall_gap_y = + (build_data.height / 2) + rng.roll_dice(1, PATH_OFFSET_FROM_CENTRE * 2) - 1 - PATH_OFFSET_FROM_CENTRE; + rltk::console::log(format!("Placing road centred at y {}", wall_gap_y)); + + for y in BORDER..build_data.height - BORDER { + if !(y > wall_gap_y - HALF_PATH_THICKNESS && y < wall_gap_y + HALF_PATH_THICKNESS) { + let idx = build_data.map.xy_idx(OFFSET_FROM_LEFT, y); + build_data.map.tiles[idx] = TileType::Wall; + + let idx_right = build_data.map.xy_idx(build_data.width - BORDER, y); + build_data.map.tiles[idx_right] = TileType::Wall; + + for x in OFFSET_FROM_LEFT + 1..build_data.width - BORDER { + let gravel_idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[gravel_idx] = TileType::Sand; + if y > BORDER + 1 + && y < build_data.height - BORDER - 1 + && x > OFFSET_FROM_LEFT + 2 + && x < build_data.width - BORDER - 1 + { + available_building_tiles.insert(gravel_idx); + } + } + } else { + for x in OFFSET_FROM_LEFT - 1..build_data.width { + let road_idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[road_idx] = TileType::Road; + } + } + } + build_data.take_snapshot(); + + for x in OFFSET_FROM_LEFT..build_data.width - BORDER + 1 { + let idx_top = build_data.map.xy_idx(x, BORDER - 1); + build_data.map.tiles[idx_top] = TileType::Wall; + let idx_bottom = build_data.map.xy_idx(x, build_data.height - BORDER); + build_data.map.tiles[idx_bottom] = TileType::Wall; + } + build_data.take_snapshot(); + + (available_building_tiles, wall_gap_y) + } + + fn buildings( + &mut self, + rng: &mut rltk::RandomNumberGenerator, + build_data: &mut BuilderMap, + available_building_tiles: &mut HashSet, + ) -> Vec<(i32, i32, i32, i32)> { + let mut buildings: Vec<(i32, i32, i32, i32)> = Vec::new(); + let mut n_buildings = 0; + + const BORDER: i32 = 2; + const REQUIRED_BUILDINGS: i32 = 8; + const OFFSET_FROM_LEFT: i32 = 30; + const MIN_BUILDING_SIZE: i32 = 4; + const MAX_BUILDING_SIZE: i32 = 10; + + while n_buildings < REQUIRED_BUILDINGS { + let bx = rng.roll_dice(1, build_data.map.width - OFFSET_FROM_LEFT - BORDER) + OFFSET_FROM_LEFT; + let by = rng.roll_dice(1, build_data.map.height) - BORDER; + let bw = rng.roll_dice(1, MAX_BUILDING_SIZE - MIN_BUILDING_SIZE) + MIN_BUILDING_SIZE; + let bh = rng.roll_dice(1, MAX_BUILDING_SIZE - MIN_BUILDING_SIZE) + MIN_BUILDING_SIZE; + let mut possible = true; + for y in by..by + bh { + for x in bx..bx + bw { + if x < 0 || x > build_data.width - 1 || y < 0 || y > build_data.height - 1 { + possible = false; + } else { + let idx = build_data.map.xy_idx(x, y); + if !available_building_tiles.contains(&idx) { + possible = false; + } + } + } + } + if possible { + n_buildings += 1; + buildings.push((bx, by, bw, bh)); + for y in by..by + bh { + for x in bx..bx + bw { + let idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[idx] = TileType::WoodFloor; + available_building_tiles.remove(&idx); + available_building_tiles.remove(&(idx + 1)); + available_building_tiles.remove(&(idx + build_data.width as usize)); + available_building_tiles.remove(&(idx - 1)); + available_building_tiles.remove(&(idx - build_data.width as usize)); + } + } + build_data.take_snapshot(); + rltk::console::log(format!("Placed building {}", n_buildings)); + } + } + + // Outlines + let mut mapclone = build_data.map.clone(); + for y in BORDER..build_data.height - BORDER { + for x in OFFSET_FROM_LEFT + BORDER..build_data.width - BORDER { + let idx = build_data.map.xy_idx(x, y); + if build_data.map.tiles[idx] == TileType::WoodFloor { + let mut neighbours = 0; + if build_data.map.tiles[idx - 1] != TileType::WoodFloor { + neighbours += 1; + } + if build_data.map.tiles[idx + 1] != TileType::WoodFloor { + neighbours += 1; + } + if build_data.map.tiles[idx - build_data.width as usize] != TileType::WoodFloor { + neighbours += 1; + } + if build_data.map.tiles[idx + build_data.width as usize] != TileType::WoodFloor { + neighbours += 1; + } + if neighbours > 0 { + mapclone.tiles[idx] = TileType::Wall; + } + } + } + } + build_data.map = mapclone; + build_data.take_snapshot(); + + buildings + } + + fn add_doors( + &mut self, + rng: &mut rltk::RandomNumberGenerator, + build_data: &mut BuilderMap, + buildings: &mut Vec<(i32, i32, i32, i32)>, + wall_gap_y: i32, + ) -> Vec { + let mut doors = Vec::new(); + for building in buildings.iter() { + let door_x = building.0 + 1 + rng.roll_dice(1, building.2 - 3); + let cy = building.1 + (building.3 / 2); + let idx = if cy > wall_gap_y { + // Door on north wall + build_data.map.xy_idx(door_x, building.1) + } else { + build_data.map.xy_idx(door_x, building.1 + building.3 - 1) + }; + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, "door".to_string())); + doors.push(idx); + } + build_data.take_snapshot(); + doors + } + + fn path_from_tiles_to_nearest_tiletype( + &mut self, + build_data: &mut BuilderMap, + tiles: &[usize], + tiletype: TileType, + new_road_tiletype: TileType, + include_new_tiles: bool, + ) { + let mut roads = self.find_tiletype(build_data, tiletype); + + build_data.map.populate_blocked(); + for tile_idx in tiles.iter() { + let mut nearest_tiletype: Vec<(usize, f32)> = Vec::new(); + let tile_pt = rltk::Point::new( + *tile_idx as i32 % build_data.map.width as i32, + *tile_idx as i32 / build_data.map.width as i32, + ); + for r in roads.iter() { + nearest_tiletype.push(( + *r, + rltk::DistanceAlg::PythagorasSquared.distance2d( + tile_pt, + rltk::Point::new(*r as i32 % build_data.map.width, *r as i32 / build_data.map.width), + ), + )); + } + nearest_tiletype.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + + let destination = nearest_tiletype[0].0; + let path = rltk::a_star_search(*tile_idx, destination, &mut build_data.map); + if path.success { + for step in path.steps.iter() { + let idx = *step as usize; + build_data.map.tiles[idx] = new_road_tiletype; + if include_new_tiles { + roads.push(idx); + } + } + } + build_data.take_snapshot(); + } + } + + fn find_tiletype(&mut self, build_data: &mut BuilderMap, tile: TileType) -> Vec { + let mut found_tiles = Vec::new(); + for y in 0..build_data.height { + for x in 0..build_data.width { + let idx = build_data.map.xy_idx(x, y); + if build_data.map.tiles[idx] == tile { + found_tiles.push(idx); + } + } + } + + found_tiles + } +} diff --git a/src/player.rs b/src/player.rs index d992b78..73f1f84 100644 --- a/src/player.rs +++ b/src/player.rs @@ -28,9 +28,9 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState { let delta_x = i; let delta_y = j; - if !(pos.x + delta_x < 1 + if !(pos.x + delta_x < 0 || pos.x + delta_x > map.width - 1 - || pos.y + delta_y < 1 + || pos.y + delta_y < 0 || pos.y + delta_y > map.height - 1) { let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); @@ -108,9 +108,9 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState { let delta_x = i; let delta_y = j; - if !(pos.x + delta_x < 1 + if !(pos.x + delta_x < 0 || pos.x + delta_x > map.width - 1 - || pos.y + delta_y < 1 + || pos.y + delta_y < 0 || pos.y + delta_y > map.height - 1) { let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); @@ -177,9 +177,9 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState { let delta_x = i; let delta_y = j; - if !(pos.x + delta_x < 1 + if !(pos.x + delta_x < 0 || pos.x + delta_x > map.width - 1 - || pos.y + delta_y < 1 + || pos.y + delta_y < 0 || pos.y + delta_y > map.height - 1) { let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); @@ -281,9 +281,9 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { let names = ecs.read_storage::(); for (entity, _player, pos, viewshed) in (&entities, &mut players, &mut positions, &mut viewsheds).join() { - if pos.x + delta_x < 1 + if pos.x + delta_x < 0 || pos.x + delta_x > map.width - 1 - || pos.y + delta_y < 1 + || pos.y + delta_y < 0 || pos.y + delta_y > map.height - 1 { return false; diff --git a/src/spawner.rs b/src/spawner.rs index cd2328c..b2be765 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -20,7 +20,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { }) .with(Player {}) .with(Viewshed { visible_tiles: Vec::new(), range: 12, dirty: true }) - .with(Name { name: "wanderer".to_string(), plural: "wanderers".to_string() }) + .with(Name { name: "you".to_string(), plural: "you".to_string() }) .with(CombatStats { max_hp: 12, hp: 12, defence: 0, power: 4 }) .with(HungerClock { state: HungerState::Satiated, duration: 50 }) .with(Attributes { @@ -36,7 +36,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { } // Consts -const MAX_ENTITIES: i32 = 4; +const MAX_ENTITIES: i32 = 3; /// Fills a room with stuff! pub fn spawn_room(