From 2dc420bef98fc1edc5b7d40480b8cb97c24596db Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Fri, 24 Dec 2021 10:20:29 -0500 Subject: [PATCH] Create a rough town map generator, starting section 5.4 --- src/camera.rs | 27 ++++--- src/main.rs | 8 +-- src/map.rs | 41 ++++++----- src/map/tiletype.rs | 40 +++++++++++ src/map_builders.rs | 17 ++++- src/map_builders/area_starting_points.rs | 4 +- src/map_builders/town.rs | 89 ++++++++++++++++++++++++ src/raws/spawn_table_structs.rs | 2 - 8 files changed, 185 insertions(+), 43 deletions(-) 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 acc5939..ecf7639 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,5 +1,5 @@ -use rltk::{Point, Rltk, RGB}; -use specs::prelude::*; +use ::rltk::{Point, Rltk, RGB}; +use ::specs::prelude::*; use crate::{Hidden, Map, Position, Renderable, TileType}; @@ -127,25 +127,22 @@ pub fn render_debug_map(map: &Map, ctx: &mut Rltk) { } fn get_tile_glyph(idx: usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) { - let glyph; - let mut fg; let mut bg = RGB::from_f32(0., 0., 0.); - match map.tiles[idx] { - TileType::Floor => { - glyph = rltk::to_cp437('.'); - fg = RGB::from_f32(0., 0.5, 0.5); - } + let (glyph, mut fg) = match map.tiles[idx] { + TileType::Floor => (rltk::to_cp437('.'), RGB::from_f32(0., 0.5, 0.5)), + TileType::WoodFloor => (rltk::to_cp437('.'), RGB::named(rltk::CHOCOLATE)), TileType::Wall => { let x = idx as i32 % map.width; let y = idx as i32 / map.width; - glyph = wall_glyph(&*map, x, y); - fg = RGB::from_f32(0., 1.0, 0.); - } - TileType::DownStairs => { - glyph = rltk::to_cp437('>'); - fg = RGB::from_f32(0., 1.0, 1.0); + (wall_glyph(&*map, x, y), RGB::from_f32(0., 1.0, 0.)) } + TileType::DownStairs => (rltk::to_cp437('>'), RGB::from_f32(0., 1.0, 1.0)), + TileType::Bridge => (rltk::to_cp437('.'), RGB::named(rltk::CHOCOLATE)), + TileType::Road => (rltk::to_cp437('~'), RGB::named(rltk::GRAY)), + TileType::Grass => (rltk::to_cp437('"'), RGB::named(rltk::GREEN)), + TileType::ShallowWater => (rltk::to_cp437('≈'), RGB::named(rltk::CYAN)), + TileType::DeepWater => (rltk::to_cp437('≈'), RGB::named(rltk::NAVY_BLUE)), }; if map.bloodstains.contains(&idx) { diff --git a/src/main.rs b/src/main.rs index 54f374e..f3990e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,9 @@ mod visibility_system; #[macro_use] extern crate lazy_static; +use ::rltk::{GameState, Point, RandomNumberGenerator, Rltk}; +use ::specs::prelude::*; +use ::specs::saveload::{SimpleMarker, SimpleMarkerAllocator}; use components::*; use damage_system::DamageSystem; pub use game_log::GameLog; @@ -34,9 +37,6 @@ use melee_combat_system::MeleeCombatSystem; use monster_ai_system::MonsterAI; use player::*; pub use rect::Rect; -use rltk::{GameState, Point, RandomNumberGenerator, Rltk}; -use specs::prelude::*; -use specs::saveload::{SimpleMarker, SimpleMarkerAllocator}; use visibility_system::VisibilitySystem; /// Cut down on the amount of syntax to register components @@ -450,7 +450,7 @@ impl State { self.mapgen_history.clear(); let mut rng = self.ecs.write_resource::(); - let mut builder = map_builders::random_builder(new_depth, &mut rng, 80, 50); + let mut builder = map_builders::level_builder(new_depth, &mut rng, 80, 50); builder.build_map(&mut rng); std::mem::drop(rng); diff --git a/src/map.rs b/src/map.rs index fe063d3..9f90d0f 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,15 +1,13 @@ +mod tiletype; + use std::collections::HashSet; -use rltk::{Algorithm2D, BaseMap, Point, SmallVec}; -use serde::{Deserialize, Serialize}; -use specs::prelude::*; +use ::rltk::{Algorithm2D, BaseMap, Point, SmallVec}; +use ::serde::{Deserialize, Serialize}; +use ::specs::prelude::*; +pub use tiletype::{tile_opaque, tile_walkable, TileType}; -#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] -pub enum TileType { - Wall, - Floor, - DownStairs, -} +use crate::map::tiletype::tile_cost; #[derive(Default, Serialize, Deserialize, Clone)] pub struct Map { @@ -45,7 +43,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); } } @@ -76,7 +74,11 @@ impl Map { impl BaseMap for Map { fn is_opaque(&self, idx: usize) -> bool { - self.tiles[idx] == TileType::Wall || self.view_blocked.contains(&idx) + if idx > 0 && idx < self.tiles.len() { + tile_opaque(self.tiles[idx]) || self.view_blocked.contains(&idx) + } else { + true + } } fn get_available_exits(&self, idx: usize) -> SmallVec<[(usize, f32); 10]> { @@ -84,33 +86,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]; // 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..41fc8e5 --- /dev/null +++ b/src/map/tiletype.rs @@ -0,0 +1,40 @@ +use serde::{Deserialize, Serialize}; + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] +pub enum TileType { + Wall, + Floor, + DownStairs, + Road, + Grass, + ShallowWater, + DeepWater, + WoodFloor, + Bridge, +} + +pub fn tile_walkable(tt: TileType) -> bool { + matches!( + tt, + TileType::Floor + | TileType::DownStairs + | TileType::Road + | TileType::Grass + | TileType::ShallowWater + | TileType::WoodFloor + | TileType::Bridge + ) +} + +pub fn tile_opaque(tt: TileType) -> bool { + matches!(tt, TileType::Wall) +} + +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.rs b/src/map_builders.rs index 6f7b653..308f3b0 100644 --- a/src/map_builders.rs +++ b/src/map_builders.rs @@ -23,6 +23,7 @@ mod rooms_corridors_dogleg; mod rooms_corridors_lines; mod rooms_corridors_nearest; mod simple_map; +mod town; mod voronoi; mod voronoi_spawning; mod waveform_collapse; @@ -53,6 +54,7 @@ use rooms_corridors_lines::StraightLineCorridors; use rooms_corridors_nearest::NearestCorridors; use simple_map::SimpleMapBuilder; use specs::prelude::*; +use town::town_builder; use voronoi::VoronoiCellBuilder; use voronoi_spawning::VoronoiSpawning; use waveform_collapse::WaveformCollapseBuilder; @@ -175,7 +177,7 @@ pub trait MetaMapBuilder { fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap); } -fn random_start_position(rng: &mut rltk::RandomNumberGenerator) -> (XStart, YStart) { +pub fn random_start_position(rng: &mut rltk::RandomNumberGenerator) -> (XStart, YStart) { let x = match rng.roll_dice(1, 3) { 1 => XStart::Left, 2 => XStart::Center, @@ -330,3 +332,16 @@ pub fn random_builder( builder } + +pub fn level_builder( + new_depth: i32, + rng: &mut RandomNumberGenerator, + width: i32, + height: i32, +) -> BuilderChain { + rltk::console::log(format!("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/area_starting_points.rs b/src/map_builders/area_starting_points.rs index a34a2fd..4c43c0a 100644 --- a/src/map_builders/area_starting_points.rs +++ b/src/map_builders/area_starting_points.rs @@ -1,7 +1,7 @@ use rltk::RandomNumberGenerator; use crate::map_builders::{BuilderMap, MetaMapBuilder}; -use crate::{Position, TileType}; +use crate::{map, Position}; #[allow(dead_code)] pub enum XStart { @@ -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 map::tile_walkable(*tiletype) { available_floors.push(( idx, rltk::DistanceAlg::PythagorasSquared.distance2d( diff --git a/src/map_builders/town.rs b/src/map_builders/town.rs new file mode 100644 index 0000000..b7157a8 --- /dev/null +++ b/src/map_builders/town.rs @@ -0,0 +1,89 @@ +use rltk::RandomNumberGenerator; + +use super::{BuilderChain, BuilderMap, InitialMapBuilder}; +use crate::map_builders::area_starting_points::AreaStartingPosition; +use crate::map_builders::distant_exit::DistantExit; +use crate::TileType; + +pub fn town_builder( + new_depth: i32, + rng: &mut RandomNumberGenerator, + width: i32, + height: i32, +) -> BuilderChain { + let (start_x, start_y) = super::random_start_position(rng); + let mut chain = BuilderChain::new(new_depth, width, height); + + chain + .start_with(TownBuilder::new()) + .with(AreaStartingPosition::new(start_x, start_y)) + .with(DistantExit::new()); + + chain +} + +pub struct TownBuilder {} + +impl InitialMapBuilder for TownBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build_rooms(rng, build_data); + } +} + +impl TownBuilder { + pub fn new() -> Box { + Box::new(TownBuilder {}) + } + + pub fn build_rooms(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + self.grass_layer(build_data); + self.water_and_piers(rng, build_data); + + // Make visible for screenshot + for t in build_data.map.visible_tiles.iter_mut() { + *t = true; + } + build_data.take_snapshot(); + } + + fn grass_layer(&mut self, build_data: &mut BuilderMap) { + // We'll start with a nice layer of grass + for t in build_data.map.tiles.iter_mut() { + *t = TileType::Grass + } + build_data.take_snapshot(); + } + + fn water_and_piers(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + let mut n = (rng.roll_dice(1, 65535) as f32) / 65535_f32; + let mut water_width: Vec = Vec::new(); + + for y in 0..build_data.height { + let n_water = (f32::sin(n) * 10.0) as i32 + 14 + rng.roll_dice(1, 6); + 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 + 3 { + let idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[idx] = TileType::ShallowWater; + } + } + build_data.take_snapshot(); + + // Add piers + for _i in 0..rng.roll_dice(1, 4) + 6 { + let y = rng.roll_dice(1, build_data.height) - 1; + for x in 2 + rng.roll_dice(1, 6)..water_width[y as usize] + 4 { + let idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[idx] = TileType::WoodFloor; + } + } + build_data.take_snapshot(); + } +} diff --git a/src/raws/spawn_table_structs.rs b/src/raws/spawn_table_structs.rs index 88f89e2..40102b6 100644 --- a/src/raws/spawn_table_structs.rs +++ b/src/raws/spawn_table_structs.rs @@ -1,7 +1,5 @@ use serde::Deserialize; -use super::Renderable; - #[derive(Deserialize, Debug)] pub struct SpawnTableEntry { pub name: String,