From 2ceb20a822da3f3dbd9e508b3b0d85370f9a3d47 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sun, 23 Jul 2023 16:44:14 +0100 Subject: [PATCH] atomises rooms and corridors - room sorter - rounding room corners - dogleg and bsp corridors - room exploder --- src/damage_system.rs | 4 +- src/map_builders/bsp_dungeon.rs | 14 -- src/map_builders/mod.rs | 168 +++++++++++++++++---- src/map_builders/room_corner_rounding.rs | 57 +++++++ src/map_builders/room_exploder.rs | 82 ++++++++++ src/map_builders/room_sorter.rs | 47 ++++++ src/map_builders/rooms_corridors_bsp.rs | 38 +++++ src/map_builders/rooms_corridors_dogleg.rs | 42 ++++++ src/map_builders/simple_map.rs | 18 +-- 9 files changed, 411 insertions(+), 59 deletions(-) create mode 100644 src/map_builders/room_corner_rounding.rs create mode 100644 src/map_builders/room_exploder.rs create mode 100644 src/map_builders/room_sorter.rs create mode 100644 src/map_builders/rooms_corridors_bsp.rs create mode 100644 src/map_builders/rooms_corridors_dogleg.rs diff --git a/src/damage_system.rs b/src/damage_system.rs index 8330e13..2bf4a8e 100644 --- a/src/damage_system.rs +++ b/src/damage_system.rs @@ -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(); } } diff --git a/src/map_builders/bsp_dungeon.rs b/src/map_builders/bsp_dungeon.rs index a251ccb..27f9ca9 100644 --- a/src/map_builders/bsp_dungeon.rs +++ b/src/map_builders/bsp_dungeon.rs @@ -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); } diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index f855fb7..0ddd797 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -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, bool) { - let builder = rng.roll_dice(1, 17); - let result: (Box, 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 } diff --git a/src/map_builders/room_corner_rounding.rs b/src/map_builders/room_corner_rounding.rs new file mode 100644 index 0000000..e87a465 --- /dev/null +++ b/src/map_builders/room_corner_rounding.rs @@ -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 { + 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; + 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(); + } + } +} diff --git a/src/map_builders/room_exploder.rs b/src/map_builders/room_exploder.rs new file mode 100644 index 0000000..18c997b --- /dev/null +++ b/src/map_builders/room_exploder.rs @@ -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 { + return Box::new(RoomExploder {}); + } + + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + let rooms: Vec; + 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; + } + } + } + } + } + } +} diff --git a/src/map_builders/room_sorter.rs b/src/map_builders/room_sorter.rs new file mode 100644 index 0000000..0fd5eba --- /dev/null +++ b/src/map_builders/room_sorter.rs @@ -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 { + 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(); + }) + } + } + } +} diff --git a/src/map_builders/rooms_corridors_bsp.rs b/src/map_builders/rooms_corridors_bsp.rs new file mode 100644 index 0000000..688ab9b --- /dev/null +++ b/src/map_builders/rooms_corridors_bsp.rs @@ -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 { + Box::new(BspCorridors {}) + } + + fn corridors(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + let rooms: Vec; + 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(); + } + } +} diff --git a/src/map_builders/rooms_corridors_dogleg.rs b/src/map_builders/rooms_corridors_dogleg.rs new file mode 100644 index 0000000..2d5c721 --- /dev/null +++ b/src/map_builders/rooms_corridors_dogleg.rs @@ -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 { + Box::new(DoglegCorridors {}) + } + + fn corridors(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + let rooms: Vec; + 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(); + } + } + } +} diff --git a/src/map_builders/simple_map.rs b/src/map_builders/simple_map.rs index 1e4dcf0..40d0605 100644 --- a/src/map_builders/simple_map.rs +++ b/src/map_builders/simple_map.rs @@ -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(); }