Finish generating the empty town map, completing section 5.4

This commit is contained in:
Timothy Warren 2021-12-24 11:30:52 -05:00
parent b115f43d5d
commit a4e9c27c8f
3 changed files with 227 additions and 11 deletions

View File

@ -143,6 +143,7 @@ fn get_tile_glyph(idx: usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) {
TileType::Grass => (rltk::to_cp437('"'), RGB::named(rltk::GREEN)), TileType::Grass => (rltk::to_cp437('"'), RGB::named(rltk::GREEN)),
TileType::ShallowWater => (rltk::to_cp437('≈'), RGB::named(rltk::CYAN)), TileType::ShallowWater => (rltk::to_cp437('≈'), RGB::named(rltk::CYAN)),
TileType::DeepWater => (rltk::to_cp437('≈'), RGB::named(rltk::NAVY_BLUE)), TileType::DeepWater => (rltk::to_cp437('≈'), RGB::named(rltk::NAVY_BLUE)),
TileType::Gravel => (rltk::to_cp437(';'), RGB::named(rltk::GRAY)),
}; };
if map.bloodstains.contains(&idx) { if map.bloodstains.contains(&idx) {

View File

@ -11,6 +11,7 @@ pub enum TileType {
DeepWater, DeepWater,
WoodFloor, WoodFloor,
Bridge, Bridge,
Gravel,
} }
pub fn tile_walkable(tt: TileType) -> bool { pub fn tile_walkable(tt: TileType) -> bool {
@ -23,6 +24,7 @@ pub fn tile_walkable(tt: TileType) -> bool {
| TileType::ShallowWater | TileType::ShallowWater
| TileType::WoodFloor | TileType::WoodFloor
| TileType::Bridge | TileType::Bridge
| TileType::Gravel
) )
} }

View File

@ -1,23 +1,18 @@
use ::rltk::RandomNumberGenerator; use std::collections::HashSet;
use ::rltk::{Point, RandomNumberGenerator};
use super::{BuilderChain, BuilderMap, InitialMapBuilder}; use super::{BuilderChain, BuilderMap, InitialMapBuilder};
use crate::map_builders::area_starting_points::AreaStartingPosition; use crate::{Position, TileType};
use crate::map_builders::distant_exit::DistantExit;
use crate::TileType;
pub fn town_builder( pub fn town_builder(
new_depth: i32, new_depth: i32,
rng: &mut RandomNumberGenerator, _rng: &mut RandomNumberGenerator,
width: i32, width: i32,
height: i32, height: i32,
) -> BuilderChain { ) -> BuilderChain {
let (start_x, start_y) = super::random_start_position(rng);
let mut chain = BuilderChain::new(new_depth, width, height); let mut chain = BuilderChain::new(new_depth, width, height);
chain.start_with(TownBuilder::new());
chain
.start_with(TownBuilder::new())
.with(AreaStartingPosition::new(start_x, start_y))
.with(DistantExit::new());
chain chain
} }
@ -45,6 +40,29 @@ impl TownBuilder {
*t = true; *t = true;
} }
build_data.take_snapshot(); build_data.take_snapshot();
let (mut available_building_tiles, wall_gap_y) = self.town_walls(rng, build_data);
let mut buildings = self.buildings(rng, build_data, &mut available_building_tiles);
let doors = self.add_doors(rng, build_data, &mut buildings, wall_gap_y);
self.add_paths(build_data, &doors);
let exit_idx = build_data.map.xy_idx(build_data.width - 5, wall_gap_y);
build_data.map.tiles[exit_idx] = TileType::DownStairs;
// 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));
// Start in the pub
let the_pub = &buildings[building_size[0].0];
build_data.starting_position = Some(Position {
x: the_pub.0 + (the_pub.2 / 2),
y: the_pub.1 + (the_pub.3 / 2),
})
} }
fn grass_layer(&mut self, build_data: &mut BuilderMap) { fn grass_layer(&mut self, build_data: &mut BuilderMap) {
@ -86,4 +104,199 @@ impl TownBuilder {
} }
build_data.take_snapshot(); build_data.take_snapshot();
} }
fn town_walls(
&mut self,
rng: &mut RandomNumberGenerator,
build_data: &mut BuilderMap,
) -> (HashSet<usize>, i32) {
let mut available_building_tiles: HashSet<usize> = HashSet::new();
let wall_gap_y = rng.roll_dice(1, build_data.height - 9) + 5;
for y in 1..build_data.height - 2 {
if !(y > wall_gap_y - 4 && y < wall_gap_y + 4) {
let idx = build_data.map.xy_idx(30, y);
build_data.map.tiles[idx] = TileType::Wall;
build_data.map.tiles[idx - 1] = TileType::Floor;
let idx_right = build_data.map.xy_idx(build_data.width - 2, y);
build_data.map.tiles[idx_right] = TileType::Wall;
for x in 31..build_data.width - 2 {
let gravel_idx = build_data.map.xy_idx(x, y);
build_data.map.tiles[gravel_idx] = TileType::Gravel;
if y > 2 && y < build_data.height - 1 {
available_building_tiles.insert(gravel_idx);
}
}
} else {
for x in 30..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 30..build_data.width - 1 {
let idx_top = build_data.map.xy_idx(x, 1);
let idx_bot = build_data.map.xy_idx(x, build_data.height - 2);
build_data.map.tiles[idx_top] = TileType::Wall;
build_data.map.tiles[idx_bot] = TileType::Wall;
}
build_data.take_snapshot();
(available_building_tiles, wall_gap_y)
}
fn buildings(
&mut self,
rng: &mut RandomNumberGenerator,
build_data: &mut BuilderMap,
available_building_tiles: &mut HashSet<usize>,
) -> Vec<(i32, i32, i32, i32)> {
let mut buildings = Vec::new();
let mut n_buildings = 0;
while n_buildings < 12 {
let bx = rng.roll_dice(1, build_data.map.width - 32) + 30;
let by = rng.roll_dice(1, build_data.map.height) - 2;
let bw = rng.roll_dice(1, 8) + 4;
let bh = rng.roll_dice(1, 8) + 4;
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();
}
}
// Outline buildings
let mut mapclone = build_data.map.clone();
for y in 2..build_data.height - 2 {
for x in 32..build_data.width - 2 {
let idx = build_data.map.xy_idx(x, y);
if build_data.map.tiles[idx] == TileType::WoodFloor {
let mut neighbors = 0;
if build_data.map.tiles[idx - 1] != TileType::WoodFloor {
neighbors += 1;
}
if build_data.map.tiles[idx + 1] != TileType::WoodFloor {
neighbors += 1;
}
if build_data.map.tiles[idx - build_data.width as usize] != TileType::WoodFloor
{
neighbors += 1;
}
if build_data.map.tiles[idx + build_data.width as usize] != TileType::WoodFloor
{
neighbors += 1;
}
if neighbors > 0 {
mapclone.tiles[idx] = TileType::Wall;
}
}
}
}
build_data.map = mapclone;
build_data.take_snapshot();
buildings
}
fn add_doors(
&mut self,
rng: &mut RandomNumberGenerator,
build_data: &mut BuilderMap,
buildings: &mut Vec<(i32, i32, i32, i32)>,
wall_gap_y: i32,
) -> Vec<usize> {
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 the 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 add_paths(&mut self, build_data: &mut BuilderMap, doors: &[usize]) {
let mut roads = 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] == TileType::Road {
roads.push(idx);
}
}
}
build_data.map.populate_blocked();
for door_idx in doors.iter() {
let mut nearest_roads: Vec<(usize, f32)> = Vec::new();
let door_pt = Point::new(
*door_idx as i32 % build_data.map.width as i32,
*door_idx as i32 / build_data.map.width as i32,
);
for r in roads.iter() {
nearest_roads.push((
*r,
::rltk::DistanceAlg::PythagorasSquared.distance2d(
door_pt,
Point::new(
*r as i32 % build_data.map.width,
*r as i32 / build_data.map.width as i32,
),
),
));
}
nearest_roads.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
let destination = nearest_roads[0].0;
let path = ::rltk::a_star_search(*door_idx, destination, &build_data.map);
if path.success {
for step in path.steps.iter() {
let idx = *step as usize;
build_data.map.tiles[idx] = TileType::Road;
roads.push(idx);
}
}
build_data.take_snapshot();
}
}
} }