atomises rooms and corridors

- room sorter
- rounding room corners
- dogleg and bsp corridors
- room exploder
This commit is contained in:
Llywelwyn 2023-07-23 16:44:14 +01:00
parent b7b2061228
commit 2ceb20a822
9 changed files with 411 additions and 59 deletions

View file

@ -50,14 +50,14 @@ pub fn delete_the_dead(ecs: &mut World) {
.append("The")
.npc_name(&victim_name.name)
.colour(rltk::WHITE)
.append("was destroyed!")
.append("is destroyed!")
.log();
} else {
gamelog::Logger::new()
.append("The")
.npc_name(&victim_name.name)
.colour(rltk::WHITE)
.append("died!")
.append("dies!")
.log();
}
}

View file

@ -42,20 +42,6 @@ impl BspDungeonBuilder {
n_rooms += 1;
}
// Now we sort the rooms
rooms.sort_by(|a, b| a.x1.cmp(&b.x1));
// Now we want corridors
for i in 0..rooms.len() - 1 {
let room = rooms[i];
let next_room = rooms[i + 1];
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y);
build_data.take_snapshot();
}
build_data.rooms = Some(rooms);
}

View file

@ -13,6 +13,7 @@ use drunkard::DrunkardsWalkBuilder;
mod maze;
use maze::MazeBuilder;
mod simple_map;
use simple_map::SimpleMapBuilder;
mod voronoi;
use voronoi::VoronoiBuilder;
mod prefab_builder;
@ -35,6 +36,16 @@ use common::*;
use specs::prelude::*;
use voronoi_spawning::VoronoiSpawning;
use wfc::WaveFunctionCollapseBuilder;
mod room_exploder;
use room_exploder::RoomExploder;
mod room_corner_rounding;
use room_corner_rounding::RoomCornerRounder;
mod rooms_corridors_dogleg;
use rooms_corridors_dogleg::DoglegCorridors;
mod rooms_corridors_bsp;
use rooms_corridors_bsp::BspCorridors;
mod room_sorter;
use room_sorter::{RoomSort, RoomSorter};
// Shared data to be passed around build chain
pub struct BuilderMap {
@ -119,38 +130,139 @@ pub trait MetaMapBuilder {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap);
}
fn random_initial_builder(rng: &mut rltk::RandomNumberGenerator) -> (Box<dyn InitialMapBuilder>, bool) {
let builder = rng.roll_dice(1, 17);
let result: (Box<dyn InitialMapBuilder>, bool);
match builder {
1 => result = (BspDungeonBuilder::new(), true),
2 => result = (BspInteriorBuilder::new(), true),
3 => result = (CellularAutomataBuilder::new(), false),
4 => result = (DrunkardsWalkBuilder::open_area(), false),
5 => result = (DrunkardsWalkBuilder::open_halls(), false),
6 => result = (DrunkardsWalkBuilder::winding_passages(), false),
7 => result = (DrunkardsWalkBuilder::fat_passages(), false),
8 => result = (DrunkardsWalkBuilder::fearful_symmetry(), false),
9 => result = (MazeBuilder::new(), false),
10 => result = (DLABuilder::walk_inwards(), false),
11 => result = (DLABuilder::walk_outwards(), false),
12 => result = (DLABuilder::central_attractor(), false),
13 => result = (DLABuilder::insectoid(), false),
14 => result = (VoronoiBuilder::pythagoras(), false),
15 => result = (VoronoiBuilder::manhattan(), false),
16 => result = (PrefabBuilder::constant(prefab_builder::prefab_levels::WFC_POPULATED), false),
_ => result = (simple_map::SimpleMapBuilder::new(), true),
fn random_start_position(rng: &mut rltk::RandomNumberGenerator) -> (XStart, YStart) {
let x;
let xroll = rng.roll_dice(1, 3);
match xroll {
1 => x = XStart::LEFT,
2 => x = XStart::CENTRE,
_ => x = XStart::RIGHT,
}
result
let y;
let yroll = rng.roll_dice(1, 3);
match yroll {
1 => y = YStart::BOTTOM,
2 => y = YStart::CENTRE,
_ => y = YStart::TOP,
}
(x, y)
}
fn random_room_builder(rng: &mut rltk::RandomNumberGenerator, builder: &mut BuilderChain) {
let build_roll = rng.roll_dice(1, 3);
// Start with a room builder.
match build_roll {
1 => builder.start_with(SimpleMapBuilder::new()),
2 => builder.start_with(BspDungeonBuilder::new()),
_ => builder.start_with(BspInteriorBuilder::new()),
}
// BspInterior makes its own doorways. If we're not using that one,
// select a sorting method, a type of corridor, and modifiers.
if build_roll != 3 {
// Sort by one of the 5 available algorithms
let sort_roll = rng.roll_dice(1, 5);
match sort_roll {
1 => builder.with(RoomSorter::new(RoomSort::LEFTMOST)),
2 => builder.with(RoomSorter::new(RoomSort::RIGHTMOST)),
3 => builder.with(RoomSorter::new(RoomSort::TOPMOST)),
4 => builder.with(RoomSorter::new(RoomSort::BOTTOMMOST)),
_ => builder.with(RoomSorter::new(RoomSort::CENTRAL)),
}
let corridor_roll = rng.roll_dice(1, 2);
match corridor_roll {
1 => builder.with(DoglegCorridors::new()),
_ => builder.with(BspCorridors::new()),
}
let modifier_roll = rng.roll_dice(1, 6);
match modifier_roll {
1 => builder.with(RoomExploder::new()),
2 => builder.with(RoomCornerRounder::new()),
_ => {}
}
}
// Pick a starting position, in a room or elsewhere.
let start_roll = rng.roll_dice(1, 2);
match start_roll {
1 => builder.with(RoomBasedStartingPosition::new()),
_ => {
let (start_x, start_y) = random_start_position(rng);
builder.with(AreaStartingPosition::new(start_x, start_y));
}
}
// Decide where to put the exit - in a room or far away, anywhere.
let exit_roll = rng.roll_dice(1, 2);
match exit_roll {
1 => builder.with(RoomBasedStairs::new()),
_ => builder.with(DistantExit::new()),
}
// Decide whether to spawn entities only in rooms, or with voronoi noise.
let spawn_roll = rng.roll_dice(1, 2);
match spawn_roll {
1 => builder.with(RoomBasedSpawner::new()),
_ => builder.with(VoronoiSpawning::new()),
}
}
fn random_shape_builder(rng: &mut rltk::RandomNumberGenerator, builder: &mut BuilderChain) {
// Pick an initial builder
let builder_roll = rng.roll_dice(1, 16);
match builder_roll {
1 => builder.start_with(CellularAutomataBuilder::new()),
2 => builder.start_with(DrunkardsWalkBuilder::open_area()),
3 => builder.start_with(DrunkardsWalkBuilder::open_halls()),
4 => builder.start_with(DrunkardsWalkBuilder::winding_passages()),
5 => builder.start_with(DrunkardsWalkBuilder::fat_passages()),
6 => builder.start_with(DrunkardsWalkBuilder::fearful_symmetry()),
7 => builder.start_with(MazeBuilder::new()),
8 => builder.start_with(DLABuilder::walk_inwards()),
9 => builder.start_with(DLABuilder::walk_outwards()),
10 => builder.start_with(DLABuilder::central_attractor()),
11 => builder.start_with(DLABuilder::insectoid()),
12 => builder.start_with(VoronoiBuilder::pythagoras()),
13 => builder.start_with(VoronoiBuilder::manhattan()),
_ => builder.start_with(PrefabBuilder::constant(prefab_builder::prefab_levels::WFC_POPULATED)),
}
// 'Select' the centre by placing a starting position, and cull everywhere unreachable.
builder.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE));
builder.with(CullUnreachable::new());
// Now set the start to a random spot in our remaining area.
let (start_x, start_y) = random_start_position(rng);
builder.with(AreaStartingPosition::new(start_x, start_y));
// Place the exit and spawn mobs
builder.with(VoronoiSpawning::new());
builder.with(DistantExit::new());
}
pub fn random_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator) -> BuilderChain {
let mut builder = BuilderChain::new(new_depth);
builder.start_with(simple_map::SimpleMapBuilder::new());
builder.with(DLABuilder::heavy_erosion());
builder.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE));
builder.with(CullUnreachable::new());
builder.with(VoronoiSpawning::new());
builder.with(DistantExit::new());
let type_roll = rng.roll_dice(1, 2);
match type_roll {
1 => random_room_builder(rng, &mut builder),
_ => random_shape_builder(rng, &mut builder),
}
/* WFC needs some fixes.
if rng.roll_dice(1, 3) == 1 {
builder.with(WaveFunctionCollapseBuilder::new());
}
*/
if rng.roll_dice(1, 20) == 1 {
builder.with(PrefabBuilder::sectional(prefab_builder::prefab_sections::UNDERGROUND_FORT));
}
builder.with(PrefabBuilder::vaults());
builder
}

View file

@ -0,0 +1,57 @@
use super::{BuilderMap, MetaMapBuilder, Rect, TileType};
use rltk::RandomNumberGenerator;
pub struct RoomCornerRounder {}
impl MetaMapBuilder for RoomCornerRounder {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl RoomCornerRounder {
#[allow(dead_code)]
pub fn new() -> Box<RoomCornerRounder> {
return Box::new(RoomCornerRounder {});
}
fn fill_if_corner(&mut self, x: i32, y: i32, build_data: &mut BuilderMap) {
let w = build_data.map.width;
let h = build_data.map.height;
let idx = build_data.map.xy_idx(x, y);
let mut neighbour_walls = 0;
if x > 0 && build_data.map.tiles[idx - 1] == TileType::Wall {
neighbour_walls += 1;
}
if y > 0 && build_data.map.tiles[idx - w as usize] == TileType::Wall {
neighbour_walls += 1;
}
if x < w - 2 && build_data.map.tiles[idx + 1] == TileType::Wall {
neighbour_walls += 1;
}
if y < h - 2 && build_data.map.tiles[idx + w as usize] == TileType::Wall {
neighbour_walls += 1;
}
if neighbour_walls == 2 {
build_data.map.tiles[idx] = TileType::Wall;
}
}
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let rooms: Vec<Rect>;
if let Some(rooms_builder) = &build_data.rooms {
rooms = rooms_builder.clone();
} else {
panic!("RoomCornerRounding requires a builder with rooms.");
}
for room in rooms.iter() {
self.fill_if_corner(room.x1 + 1, room.y1 + 1, build_data);
self.fill_if_corner(room.x2, room.y1 + 1, build_data);
self.fill_if_corner(room.x1 + 1, room.y2, build_data);
self.fill_if_corner(room.x2, room.y2, build_data);
build_data.take_snapshot();
}
}
}

View file

@ -0,0 +1,82 @@
use super::{paint, BuilderMap, MetaMapBuilder, Rect, Symmetry, TileType};
use rltk::RandomNumberGenerator;
pub struct RoomExploder {}
impl MetaMapBuilder for RoomExploder {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl RoomExploder {
#[allow(dead_code)]
pub fn new() -> Box<RoomExploder> {
return Box::new(RoomExploder {});
}
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let rooms: Vec<Rect>;
if let Some(rooms_builder) = &build_data.rooms {
rooms = rooms_builder.clone();
} else {
panic!("RoomExploder requires a builder with rooms.");
}
for room in rooms.iter() {
let start = room.centre();
let n_diggers = rng.roll_dice(1, 20) - 5;
if n_diggers > 0 {
for _i in 0..n_diggers {
let mut drunk_x = start.0;
let mut drunk_y = start.1;
let mut drunk_life = 20;
let mut did_something = false;
while drunk_life > 0 {
let drunk_idx = build_data.map.xy_idx(drunk_x, drunk_y);
if build_data.map.tiles[drunk_idx] == TileType::Wall {
did_something = true;
}
paint(&mut build_data.map, Symmetry::None, 1, drunk_x, drunk_y);
build_data.map.tiles[drunk_idx] = TileType::DownStair;
let stagger_direction = rng.roll_dice(1, 4);
match stagger_direction {
1 => {
if drunk_x > 2 {
drunk_x -= 1;
}
}
2 => {
if drunk_x < build_data.map.width - 2 {
drunk_x += 1;
}
}
3 => {
if drunk_y > 2 {
drunk_y -= 1;
}
}
_ => {
if drunk_y < build_data.map.height - 2 {
drunk_y += 1;
}
}
}
drunk_life -= 1;
}
if did_something {
build_data.take_snapshot();
}
for t in build_data.map.tiles.iter_mut() {
if *t == TileType::DownStair {
*t = TileType::Floor;
}
}
}
}
}
}
}

View file

@ -0,0 +1,47 @@
use super::{BuilderMap, MetaMapBuilder, Rect};
use rltk::RandomNumberGenerator;
pub enum RoomSort {
LEFTMOST,
RIGHTMOST,
TOPMOST,
BOTTOMMOST,
CENTRAL,
}
pub struct RoomSorter {
sort_by: RoomSort,
}
impl MetaMapBuilder for RoomSorter {
#[allow(dead_code)]
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.sorter(rng, build_data);
}
}
impl RoomSorter {
#[allow(dead_code)]
pub fn new(sort_by: RoomSort) -> Box<RoomSorter> {
return Box::new(RoomSorter { sort_by });
}
fn sorter(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
match self.sort_by {
RoomSort::LEFTMOST => build_data.rooms.as_mut().unwrap().sort_by(|a, b| a.x1.cmp(&b.x1)),
RoomSort::RIGHTMOST => build_data.rooms.as_mut().unwrap().sort_by(|a, b| b.x2.cmp(&a.x2)),
RoomSort::TOPMOST => build_data.rooms.as_mut().unwrap().sort_by(|a, b| a.y1.cmp(&b.y1)),
RoomSort::BOTTOMMOST => build_data.rooms.as_mut().unwrap().sort_by(|a, b| b.y2.cmp(&a.y2)),
RoomSort::CENTRAL => {
let map_centre = rltk::Point::new(build_data.map.width / 2, build_data.map.height / 2);
build_data.rooms.as_mut().unwrap().sort_by(|a: &Rect, b: &Rect| {
let a_centre_pt = rltk::Point::new(a.centre().0, a.centre().1);
let b_centre_pt = rltk::Point::new(b.centre().0, b.centre().1);
let distance_a = rltk::DistanceAlg::Pythagoras.distance2d(a_centre_pt, map_centre);
let distance_b = rltk::DistanceAlg::Pythagoras.distance2d(b_centre_pt, map_centre);
return distance_a.partial_cmp(&distance_b).unwrap();
})
}
}
}
}

View file

@ -0,0 +1,38 @@
use super::{draw_corridor, BuilderMap, MetaMapBuilder, Rect};
use rltk::RandomNumberGenerator;
pub struct BspCorridors {}
impl MetaMapBuilder for BspCorridors {
#[allow(dead_code)]
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.corridors(rng, build_data);
}
}
impl BspCorridors {
#[allow(dead_code)]
pub fn new() -> Box<BspCorridors> {
Box::new(BspCorridors {})
}
fn corridors(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let rooms: Vec<Rect>;
if let Some(rooms_builder) = &build_data.rooms {
rooms = rooms_builder.clone();
} else {
panic!("BSP Corridors require a builder with room structures");
}
for i in 0..rooms.len() - 1 {
let room = rooms[i];
let next_room = rooms[i + 1];
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y);
build_data.take_snapshot();
}
}
}

View file

@ -0,0 +1,42 @@
use super::{apply_horizontal_tunnel, apply_vertical_tunnel, BuilderMap, MetaMapBuilder, Rect};
use rltk::RandomNumberGenerator;
pub struct DoglegCorridors {}
impl MetaMapBuilder for DoglegCorridors {
#[allow(dead_code)]
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.corridors(rng, build_data);
}
}
impl DoglegCorridors {
#[allow(dead_code)]
pub fn new() -> Box<DoglegCorridors> {
Box::new(DoglegCorridors {})
}
fn corridors(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let rooms: Vec<Rect>;
if let Some(rooms_builder) = &build_data.rooms {
rooms = rooms_builder.clone();
} else {
panic!("DoglegCorridors require a builder with rooms.");
}
for (i, room) in rooms.iter().enumerate() {
if i > 0 {
let (new_x, new_y) = room.centre();
let (prev_x, prev_y) = rooms[i as usize - 1].centre();
if rng.range(0, 2) == 1 {
apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, prev_y);
apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, new_x);
} else {
apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, prev_x);
apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, new_y);
}
build_data.take_snapshot();
}
}
}
}

View file

@ -1,4 +1,4 @@
use super::{apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, BuilderMap, InitialMapBuilder, Rect};
use super::{apply_room_to_map, apply_vertical_tunnel, BuilderMap, InitialMapBuilder, Rect};
use rltk::RandomNumberGenerator;
pub struct SimpleMapBuilder {}
@ -6,7 +6,7 @@ pub struct SimpleMapBuilder {}
impl InitialMapBuilder for SimpleMapBuilder {
#[allow(dead_code)]
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.rooms_and_corridors(rng, build_data);
self.build_rooms(rng, build_data);
}
}
@ -16,7 +16,7 @@ impl SimpleMapBuilder {
Box::new(SimpleMapBuilder {})
}
fn rooms_and_corridors(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
fn build_rooms(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
const MAX_ROOMS: i32 = 30;
const MIN_SIZE: i32 = 6;
const MAX_SIZE: i32 = 10;
@ -38,18 +38,6 @@ impl SimpleMapBuilder {
apply_room_to_map(&mut build_data.map, &new_room);
build_data.take_snapshot();
if !rooms.is_empty() {
let (new_x, new_y) = new_room.centre();
let (prev_x, prev_y) = rooms[rooms.len() - 1].centre();
if rng.range(0, 2) == 1 {
apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, prev_y);
apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, new_x);
} else {
apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, prev_x);
apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, new_y);
}
}
rooms.push(new_room);
build_data.take_snapshot();
}