refactors mapgen into chained builders

This commit is contained in:
Llywelwyn 2023-07-21 18:34:08 +01:00
parent 8a5600267c
commit dd367dc39b
22 changed files with 1381 additions and 1480 deletions

View file

@ -78,19 +78,19 @@ impl State {
self.mapgen_index = 0; self.mapgen_index = 0;
self.mapgen_timer = 0.0; self.mapgen_timer = 0.0;
self.mapgen_history.clear(); self.mapgen_history.clear();
let mut rng = self.ecs.write_resource::<rltk::RandomNumberGenerator>();
let mut builder = map_builders::random_builder(new_depth, &mut rng);
builder.build_map(&mut rng);
std::mem::drop(rng);
self.mapgen_history = builder.build_data.history.clone();
let player_start; let player_start;
// Scope for borrow checker
let mut builder = map_builders::random_builder(new_depth);
{ {
// Build a new map using RNG (to retain seed)
let mut rng = self.ecs.write_resource::<RandomNumberGenerator>();
builder.build_map(&mut rng);
self.mapgen_history = builder.get_snapshot_history();
let mut worldmap_resource = self.ecs.write_resource::<Map>(); let mut worldmap_resource = self.ecs.write_resource::<Map>();
*worldmap_resource = builder.get_map(); *worldmap_resource = builder.build_data.map.clone();
player_start = builder.get_starting_pos(); // Unwrap so we get a CTD if there's no starting pos.
// Spawn entities player_start = builder.build_data.starting_position.as_mut().unwrap().clone();
} }
// Spawn entities
builder.spawn_entities(&mut self.ecs); builder.spawn_entities(&mut self.ecs);
// Place player and update resources // Place player and update resources

View file

@ -0,0 +1,73 @@
use super::{BuilderMap, MetaMapBuilder, Position, TileType};
use rltk::RandomNumberGenerator;
#[allow(dead_code)]
pub enum XStart {
LEFT,
CENTRE,
RIGHT,
}
#[allow(dead_code)]
pub enum YStart {
TOP,
CENTRE,
BOTTOM,
}
pub struct AreaStartingPosition {
x: XStart,
y: YStart,
}
impl MetaMapBuilder for AreaStartingPosition {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl AreaStartingPosition {
#[allow(dead_code)]
pub fn new(x: XStart, y: YStart) -> Box<AreaStartingPosition> {
Box::new(AreaStartingPosition { x, y })
}
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let seed_x;
let seed_y;
match self.x {
XStart::LEFT => seed_x = 1,
XStart::CENTRE => seed_x = build_data.map.width / 2,
XStart::RIGHT => seed_x = build_data.map.width - 2,
}
match self.y {
YStart::TOP => seed_y = 1,
YStart::CENTRE => seed_y = build_data.map.height / 2,
YStart::BOTTOM => seed_y = build_data.map.height - 2,
}
let mut available_floors: Vec<(usize, f32)> = Vec::new();
for (idx, tiletype) in build_data.map.tiles.iter().enumerate() {
if *tiletype == TileType::Floor {
available_floors.push((
idx,
rltk::DistanceAlg::PythagorasSquared.distance2d(
rltk::Point::new(idx as i32 % build_data.map.width, idx as i32 / build_data.map.width),
rltk::Point::new(seed_x, seed_y),
),
));
}
}
if available_floors.is_empty() {
panic!("No valid floors to start on");
}
available_floors.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
let start_x = available_floors[0].0 as i32 % build_data.map.width;
let start_y = available_floors[0].0 as i32 / build_data.map.width;
build_data.starting_position = Some(Position { x: start_x, y: start_y });
}
}

View file

@ -1,119 +1,74 @@
use super::{apply_room_to_map, spawner, Map, MapBuilder, Position, Rect, TileType, SHOW_MAPGEN}; use super::{apply_room_to_map, draw_corridor, BuilderMap, InitialMapBuilder, Map, Rect, TileType};
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
pub struct BspDungeonBuilder { pub struct BspDungeonBuilder {
map: Map,
starting_position: Position,
depth: i32,
rooms: Vec<Rect>,
history: Vec<Map>,
rects: Vec<Rect>, rects: Vec<Rect>,
spawn_list: Vec<(usize, String)>,
} }
impl MapBuilder for BspDungeonBuilder { impl InitialMapBuilder for BspDungeonBuilder {
fn build_map(&mut self, rng: &mut RandomNumberGenerator) { #[allow(dead_code)]
return self.build(rng); fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
} self.build(rng, build_data);
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
return &self.spawn_list;
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
} }
} }
impl BspDungeonBuilder { impl BspDungeonBuilder {
#[allow(dead_code)] #[allow(dead_code)]
pub fn new(new_depth: i32) -> BspDungeonBuilder { pub fn new() -> Box<BspDungeonBuilder> {
BspDungeonBuilder { Box::new(BspDungeonBuilder { rects: Vec::new() })
map: Map::new(new_depth),
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
rooms: Vec::new(),
history: Vec::new(),
rects: Vec::new(),
spawn_list: Vec::new(),
}
} }
fn build(&mut self, mut rng: &mut RandomNumberGenerator) { fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let mut rooms: Vec<Rect> = Vec::new();
self.rects.clear(); self.rects.clear();
self.rects.push(Rect::new(2, 2, self.map.width - 5, self.map.height - 5)); self.rects.push(Rect::new(2, 2, build_data.map.width - 5, build_data.map.height - 5)); // Start with a single map-sized rectangle
let first_room = self.rects[0]; let first_room = self.rects[0];
self.add_subrects(first_room); // Divide first room self.add_subrects(first_room); // Divide the first room
// Up to 240 times, get a random rect and divide it. If it's possible // Up to 240 times, we get a random rectangle and divide it. If its possible to squeeze a
// to place a room in there, place it and add it to the rooms list. // room in there, we place it and add it to the rooms list.
let mut n_rooms = 0; let mut n_rooms = 0;
while n_rooms < 240 { while n_rooms < 240 {
let rect = self.get_random_rect(&mut rng); let rect = self.get_random_rect(rng);
let candidate = self.get_random_subrect(rect, &mut rng); let candidate = self.get_random_sub_rect(rect, rng);
if self.is_possible(candidate) { if self.is_possible(candidate, &build_data.map) {
apply_room_to_map(&mut self.map, &candidate); apply_room_to_map(&mut build_data.map, &candidate);
self.rooms.push(candidate); rooms.push(candidate);
self.add_subrects(rect); self.add_subrects(rect);
self.take_snapshot(); build_data.take_snapshot();
} }
n_rooms += 1; n_rooms += 1;
} }
let start = self.rooms[0].centre();
self.starting_position = Position { x: start.0, y: start.1 };
// Sort rooms by left co-ordinate. Optional, but helps to make connected rooms line up. // Now we sort the rooms
self.rooms.sort_by(|a, b| a.x1.cmp(&b.x1)); rooms.sort_by(|a, b| a.x1.cmp(&b.x1));
// Corridors // Now we want corridors
for i in 0..self.rooms.len() - 1 { for i in 0..rooms.len() - 1 {
let room = self.rooms[i]; let room = rooms[i];
let next_room = self.rooms[i + 1]; let next_room = rooms[i + 1];
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 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 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_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); let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
self.draw_corridor(start_x, start_y, end_x, end_y); draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y);
self.take_snapshot(); build_data.take_snapshot();
}
// Stairs
let stairs = self.rooms[self.rooms.len() - 1].centre();
let stairs_idx = self.map.xy_idx(stairs.0, stairs.1);
self.map.tiles[stairs_idx] = TileType::DownStair;
// Spawn entities
for room in self.rooms.iter().skip(1) {
spawner::spawn_room(&self.map, rng, room, self.depth, &mut self.spawn_list);
} }
build_data.rooms = Some(rooms);
} }
fn add_subrects(&mut self, rect: Rect) { fn add_subrects(&mut self, rect: Rect) {
let w = i32::abs(rect.x1 - rect.x2); let width = i32::abs(rect.x1 - rect.x2);
let h = i32::abs(rect.y1 - rect.y2); let height = i32::abs(rect.y1 - rect.y2);
let half_w = i32::max(w / 2, 1); let half_width = i32::max(width / 2, 1);
let half_h = i32::max(h / 2, 1); let half_height = i32::max(height / 2, 1);
self.rects.push(Rect::new(rect.x1, rect.y1, half_w, half_h)); self.rects.push(Rect::new(rect.x1, rect.y1, half_width, half_height));
self.rects.push(Rect::new(rect.x1, rect.y1 + half_h, half_w, half_h)); self.rects.push(Rect::new(rect.x1, rect.y1 + half_height, half_width, half_height));
self.rects.push(Rect::new(rect.x1 + half_w, rect.y1, half_w, half_h)); self.rects.push(Rect::new(rect.x1 + half_width, rect.y1, half_width, half_height));
self.rects.push(Rect::new(rect.x1 + half_w, rect.y1 + half_h, half_w, half_h)); self.rects.push(Rect::new(rect.x1 + half_width, rect.y1 + half_height, half_width, half_height));
} }
fn get_random_rect(&mut self, rng: &mut RandomNumberGenerator) -> Rect { fn get_random_rect(&mut self, rng: &mut RandomNumberGenerator) -> Rect {
@ -121,26 +76,26 @@ impl BspDungeonBuilder {
return self.rects[0]; return self.rects[0];
} }
let idx = (rng.roll_dice(1, self.rects.len() as i32) - 1) as usize; let idx = (rng.roll_dice(1, self.rects.len() as i32) - 1) as usize;
return self.rects[idx]; self.rects[idx]
} }
fn get_random_subrect(&self, rect: Rect, rng: &mut RandomNumberGenerator) -> Rect { fn get_random_sub_rect(&self, rect: Rect, rng: &mut RandomNumberGenerator) -> Rect {
let mut result = rect; let mut result = rect;
let rect_width = i32::abs(rect.x1 - rect.x2); let rect_width = i32::abs(rect.x1 - rect.x2);
let rect_height = i32::abs(rect.y1 - rect.y2); let rect_height = i32::abs(rect.y1 - rect.y2);
let w = i32::max(3, rng.roll_dice(1, i32::min(rect_width, 14)) - 1) + 1; let w = i32::max(3, rng.roll_dice(1, i32::min(rect_width, 10)) - 1) + 1;
let h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 14)) - 1) + 1; let h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 10)) - 1) + 1;
result.x1 += rng.roll_dice(1, 6) - 1; result.x1 += rng.roll_dice(1, 6) - 1;
result.y1 += rng.roll_dice(1, 6) - 1; result.y1 += rng.roll_dice(1, 6) - 1;
result.x2 = result.x1 + w; result.x2 = result.x1 + w;
result.y2 = result.y1 + h; result.y2 = result.y1 + h;
return result; result
} }
fn is_possible(&self, rect: Rect) -> bool { fn is_possible(&self, rect: Rect, map: &Map) -> bool {
let mut expanded = rect; let mut expanded = rect;
expanded.x1 -= 2; expanded.x1 -= 2;
expanded.x2 += 2; expanded.x2 += 2;
@ -151,10 +106,10 @@ impl BspDungeonBuilder {
for y in expanded.y1..=expanded.y2 { for y in expanded.y1..=expanded.y2 {
for x in expanded.x1..=expanded.x2 { for x in expanded.x1..=expanded.x2 {
if x > self.map.width - 2 { if x > map.width - 2 {
can_build = false; can_build = false;
} }
if y > self.map.height - 2 { if y > map.height - 2 {
can_build = false; can_build = false;
} }
if x < 1 { if x < 1 {
@ -164,34 +119,14 @@ impl BspDungeonBuilder {
can_build = false; can_build = false;
} }
if can_build { if can_build {
let idx = self.map.xy_idx(x, y); let idx = map.xy_idx(x, y);
if self.map.tiles[idx] != TileType::Wall { if map.tiles[idx] != TileType::Wall {
can_build = false; can_build = false;
} }
} }
} }
} }
return can_build; can_build
}
fn draw_corridor(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
let mut x = x1;
let mut y = y1;
while x != x2 || y != y2 {
if x < x2 {
x += 1;
} else if x > x2 {
x -= 1;
} else if y < y2 {
y += 1;
} else if y > y2 {
y -= 1;
}
let idx = self.map.xy_idx(x, y);
self.map.tiles[idx] = TileType::Floor;
}
} }
} }

View file

@ -1,165 +1,102 @@
use super::{spawner, Map, MapBuilder, Position, Rect, TileType, SHOW_MAPGEN}; use super::{draw_corridor, BuilderMap, InitialMapBuilder, Rect, TileType};
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
const MIN_ROOM_SIZE: i32 = 8;
pub struct BspInteriorBuilder { pub struct BspInteriorBuilder {
map: Map,
starting_position: Position,
depth: i32,
rooms: Vec<Rect>,
history: Vec<Map>,
rects: Vec<Rect>, rects: Vec<Rect>,
spawn_list: Vec<(usize, String)>,
} }
impl MapBuilder for BspInteriorBuilder { impl InitialMapBuilder for BspInteriorBuilder {
fn build_map(&mut self, rng: &mut RandomNumberGenerator) { #[allow(dead_code)]
return self.build(rng); fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
} self.build(rng, build_data);
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
return &self.spawn_list;
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
} }
} }
impl BspInteriorBuilder { impl BspInteriorBuilder {
#[allow(dead_code)] #[allow(dead_code)]
pub fn new(new_depth: i32) -> BspInteriorBuilder { pub fn new() -> Box<BspInteriorBuilder> {
BspInteriorBuilder { Box::new(BspInteriorBuilder { rects: Vec::new() })
map: Map::new(new_depth),
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
rooms: Vec::new(),
history: Vec::new(),
rects: Vec::new(),
spawn_list: Vec::new(),
}
} }
fn build(&mut self, rng: &mut RandomNumberGenerator) { fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let mut rooms: Vec<Rect> = Vec::new();
self.rects.clear(); self.rects.clear();
self.rects.push(Rect::new(1, 1, self.map.width - 2, self.map.height - 2)); // Start with a single map-sized rectangle self.rects.push(Rect::new(1, 1, build_data.map.width - 2, build_data.map.height - 2)); // Start with a single map-sized rectangle
let first_room = self.rects[0]; let first_room = self.rects[0];
self.add_subrects(first_room, rng); // Divide the first room self.add_subrects(first_room, rng); // Divide the first room
let rooms = self.rects.clone(); let rooms_copy = self.rects.clone();
for r in rooms.iter() { for r in rooms_copy.iter() {
let room = *r; let room = *r;
self.rooms.push(room); //room.x2 -= 1;
//room.y2 -= 1;
rooms.push(room);
for y in room.y1..room.y2 { for y in room.y1..room.y2 {
for x in room.x1..room.x2 { for x in room.x1..room.x2 {
let idx = self.map.xy_idx(x, y); let idx = build_data.map.xy_idx(x, y);
if idx > 0 && idx < ((self.map.width * self.map.height) - 1) as usize { if idx > 0 && idx < ((build_data.map.width * build_data.map.height) - 1) as usize {
self.map.tiles[idx] = TileType::Floor; build_data.map.tiles[idx] = TileType::Floor;
} }
} }
} }
self.take_snapshot(); build_data.take_snapshot();
} }
let start = self.rooms[0].centre();
self.starting_position = Position { x: start.0, y: start.1 };
// Now we want corridors // Now we want corridors
for i in 0..self.rooms.len() - 1 { for i in 0..rooms.len() - 1 {
let room = self.rooms[i]; let room = rooms[i];
let next_room = self.rooms[i + 1]; let next_room = rooms[i + 1];
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 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 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_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); let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
self.draw_corridor(start_x, start_y, end_x, end_y); draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y);
self.take_snapshot(); build_data.take_snapshot();
} }
// Don't forget the stairs build_data.rooms = Some(rooms);
let stairs = self.rooms[self.rooms.len() - 1].centre();
let stairs_idx = self.map.xy_idx(stairs.0, stairs.1);
self.map.tiles[stairs_idx] = TileType::DownStair;
// Spawn entities
for room in self.rooms.iter().skip(1) {
spawner::spawn_room(&self.map, rng, room, self.depth, &mut self.spawn_list);
}
} }
fn add_subrects(&mut self, rect: Rect, rng: &mut RandomNumberGenerator) { fn add_subrects(&mut self, rect: Rect, rng: &mut RandomNumberGenerator) {
const MIN_ROOM_SIZE: i32 = 6; // Remove the last rect from the list
// Remove last rect
if !self.rects.is_empty() { if !self.rects.is_empty() {
self.rects.remove(self.rects.len() - 1); self.rects.remove(self.rects.len() - 1);
} }
// Calc bounds // Calculate boundaries
let w = rect.x2 - rect.x1; let width = rect.x2 - rect.x1;
let h = rect.y2 - rect.y1; let height = rect.y2 - rect.y1;
let half_w = w / 2; let half_width = width / 2;
let half_h = h / 2; let half_height = height / 2;
let split = rng.roll_dice(1, 4); let split = rng.roll_dice(1, 4);
if split <= 2 { if split <= 2 {
// Horizontal split // Horizontal split
let h1 = Rect::new(rect.x1, rect.y1, half_w - 1, h); let h1 = Rect::new(rect.x1, rect.y1, half_width - 1, height);
self.rects.push(h1); self.rects.push(h1);
if half_w > MIN_ROOM_SIZE { if half_width > MIN_ROOM_SIZE {
self.add_subrects(h1, rng); self.add_subrects(h1, rng);
} }
let h2 = Rect::new(rect.x1 + half_w, rect.y1, half_w, h); let h2 = Rect::new(rect.x1 + half_width, rect.y1, half_width, height);
self.rects.push(h2); self.rects.push(h2);
if half_w > MIN_ROOM_SIZE { if half_width > MIN_ROOM_SIZE {
self.add_subrects(h2, rng); self.add_subrects(h2, rng);
} }
} else { } else {
// Vertical split // Vertical split
let v1 = Rect::new(rect.x1, rect.y1, w, half_h - 1); let v1 = Rect::new(rect.x1, rect.y1, width, half_height - 1);
self.rects.push(v1); self.rects.push(v1);
if half_h > MIN_ROOM_SIZE { if half_height > MIN_ROOM_SIZE {
self.add_subrects(v1, rng); self.add_subrects(v1, rng);
} }
let v2 = Rect::new(rect.x1, rect.y1 + half_h, w, half_h); let v2 = Rect::new(rect.x1, rect.y1 + half_height, width, half_height);
self.rects.push(v2); self.rects.push(v2);
if half_h > MIN_ROOM_SIZE { if half_height > MIN_ROOM_SIZE {
self.add_subrects(v2, rng); self.add_subrects(v2, rng);
} }
} }
} }
fn draw_corridor(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
let mut x = x1;
let mut y = y1;
while x != x2 || y != y2 {
if x < x2 {
x += 1;
} else if x > x2 {
x -= 1;
} else if y < y2 {
y += 1;
} else if y > y2 {
y -= 1;
}
let idx = self.map.xy_idx(x, y);
self.map.tiles[idx] = TileType::Floor;
}
}
} }

View file

@ -1,143 +1,80 @@
use super::{ use super::{BuilderMap, InitialMapBuilder, TileType};
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
Position, TileType, SHOW_MAPGEN,
};
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
use std::collections::HashMap;
const PASSES: i32 = 15; pub struct CellularAutomataBuilder {}
pub struct CellularAutomataBuilder { impl InitialMapBuilder for CellularAutomataBuilder {
map: Map, #[allow(dead_code)]
starting_position: Position, fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
depth: i32, self.build(rng, build_data);
history: Vec<Map>,
noise_areas: HashMap<i32, Vec<usize>>,
spawn_list: Vec<(usize, String)>,
}
impl MapBuilder for CellularAutomataBuilder {
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
return self.build(rng);
}
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
return &self.spawn_list;
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
} }
} }
impl CellularAutomataBuilder { impl CellularAutomataBuilder {
pub fn new(new_depth: i32) -> CellularAutomataBuilder { #[allow(dead_code)]
CellularAutomataBuilder { pub fn new() -> Box<CellularAutomataBuilder> {
map: Map::new(new_depth), Box::new(CellularAutomataBuilder {})
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
spawn_list: Vec::new(),
}
} }
fn build(&mut self, rng: &mut RandomNumberGenerator) { #[allow(clippy::map_entry)]
// Set 55% of map to floor fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
for y in 1..self.map.height - 1 { // First we completely randomize the map, setting 55% of it to be floor.
for x in 1..self.map.width - 1 { for y in 1..build_data.map.height - 1 {
for x in 1..build_data.map.width - 1 {
let roll = rng.roll_dice(1, 100); let roll = rng.roll_dice(1, 100);
let idx = self.map.xy_idx(x, y); let idx = build_data.map.xy_idx(x, y);
if roll > 55 { if roll > 55 {
self.map.tiles[idx] = TileType::Floor build_data.map.tiles[idx] = TileType::Floor
} else { } else {
self.map.tiles[idx] = TileType::Wall build_data.map.tiles[idx] = TileType::Wall
} }
} }
} }
self.take_snapshot(); build_data.take_snapshot();
// Iteratively apply cellular automata rules // Now we iteratively apply cellular automata rules
for _i in 0..PASSES { for _i in 0..15 {
let mut newtiles = self.map.tiles.clone(); let mut newtiles = build_data.map.tiles.clone();
for y in 1..self.map.height - 1 { for y in 1..build_data.map.height - 1 {
for x in 1..self.map.width - 1 { for x in 1..build_data.map.width - 1 {
let idx = self.map.xy_idx(x, y); let idx = build_data.map.xy_idx(x, y);
let mut neighbours = 0; let mut neighbors = 0;
if self.map.tiles[idx - 1] == TileType::Wall { if build_data.map.tiles[idx - 1] == TileType::Wall {
neighbours += 1; neighbors += 1;
} }
if self.map.tiles[idx + 1] == TileType::Wall { if build_data.map.tiles[idx + 1] == TileType::Wall {
neighbours += 1; neighbors += 1;
} }
if self.map.tiles[idx - self.map.width as usize] == TileType::Wall { if build_data.map.tiles[idx - build_data.map.width as usize] == TileType::Wall {
neighbours += 1; neighbors += 1;
} }
if self.map.tiles[idx + self.map.width as usize] == TileType::Wall { if build_data.map.tiles[idx + build_data.map.width as usize] == TileType::Wall {
neighbours += 1; neighbors += 1;
} }
if self.map.tiles[idx - (self.map.width as usize - 1)] == TileType::Wall { if build_data.map.tiles[idx - (build_data.map.width as usize - 1)] == TileType::Wall {
neighbours += 1; neighbors += 1;
} }
if self.map.tiles[idx - (self.map.width as usize + 1)] == TileType::Wall { if build_data.map.tiles[idx - (build_data.map.width as usize + 1)] == TileType::Wall {
neighbours += 1; neighbors += 1;
} }
if self.map.tiles[idx + (self.map.width as usize - 1)] == TileType::Wall { if build_data.map.tiles[idx + (build_data.map.width as usize - 1)] == TileType::Wall {
neighbours += 1; neighbors += 1;
} }
if self.map.tiles[idx + (self.map.width as usize + 1)] == TileType::Wall { if build_data.map.tiles[idx + (build_data.map.width as usize + 1)] == TileType::Wall {
neighbours += 1; neighbors += 1;
} }
if neighbours > 4 || neighbours == 0 { if neighbors > 4 || neighbors == 0 {
newtiles[idx] = TileType::Wall; newtiles[idx] = TileType::Wall;
} else { } else {
newtiles[idx] = TileType::Floor; newtiles[idx] = TileType::Floor;
} }
} }
} }
self.map.tiles = newtiles.clone();
self.take_snapshot();
}
// Find a starting point; start at the middle and walk left until we find an open tile build_data.map.tiles = newtiles.clone();
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 }; build_data.take_snapshot();
let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
while self.map.tiles[start_idx] != TileType::Floor {
self.starting_position.x -= 1;
start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
}
// Find all tiles reachable from starting pos
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
self.take_snapshot();
// Place stairs
self.map.tiles[exit_tile] = TileType::DownStair;
self.take_snapshot();
// Noise map for spawning entities
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
// Spawn the entities
for area in self.noise_areas.iter() {
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
} }
} }
} }

View file

@ -1,6 +1,5 @@
use super::{Map, Rect, TileType}; use super::{Map, Rect, TileType};
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::collections::HashMap;
pub fn apply_room_to_map(map: &mut Map, room: &Rect) { pub fn apply_room_to_map(map: &mut Map, room: &Rect) {
for y in room.y1 + 1..=room.y2 { for y in room.y1 + 1..=room.y2 {
@ -29,53 +28,24 @@ pub fn apply_vertical_tunnel(map: &mut Map, y1: i32, y2: i32, x: i32) {
} }
} }
pub fn remove_unreachable_areas_returning_most_distant(map: &mut Map, start_idx: usize) -> usize { pub fn draw_corridor(map: &mut Map, x1: i32, y1: i32, x2: i32, y2: i32) {
map.populate_blocked(); let mut x = x1;
let map_starts: Vec<usize> = vec![start_idx]; let mut y = y1;
let dijkstra_map = rltk::DijkstraMap::new(map.width as usize, map.height as usize, &map_starts, map, 200.0);
let mut exit_tile = (0, 0.0f32); while x != x2 || y != y2 {
for (i, tile) in map.tiles.iter_mut().enumerate() { if x < x2 {
if *tile == TileType::Floor { x += 1;
let distance_to_start = dijkstra_map.map[i]; } else if x > x2 {
// We can't get to this tile - so we'll make it a wall x -= 1;
if distance_to_start == std::f32::MAX { } else if y < y2 {
*tile = TileType::Wall; y += 1;
} else { } else if y > y2 {
// If it is further away than our current exit candidate, move the exit y -= 1;
if distance_to_start > exit_tile.1 {
exit_tile.0 = i;
exit_tile.1 = distance_to_start;
}
}
} }
let idx = map.xy_idx(x, y);
map.tiles[idx] = TileType::Floor;
} }
return exit_tile.0;
}
#[allow(clippy::map_entry)]
pub fn generate_voronoi_spawn_regions(map: &Map, rng: &mut rltk::RandomNumberGenerator) -> HashMap<i32, Vec<usize>> {
let mut noise_areas: HashMap<i32, Vec<usize>> = HashMap::new();
let mut noise = rltk::FastNoise::seeded(rng.roll_dice(1, 65536) as u64);
noise.set_noise_type(rltk::NoiseType::Cellular);
noise.set_frequency(0.08);
noise.set_cellular_distance_function(rltk::CellularDistanceFunction::Manhattan);
for y in 1..map.height - 1 {
for x in 1..map.width - 1 {
let idx = map.xy_idx(x, y);
if map.tiles[idx] == TileType::Floor {
let cell_value_f = noise.get_noise(x as f32, y as f32) * 10240.0;
let cell_value = cell_value_f as i32;
if noise_areas.contains_key(&cell_value) {
noise_areas.get_mut(&cell_value).unwrap().push(idx);
} else {
noise_areas.insert(cell_value, vec![idx]);
}
}
}
}
return noise_areas;
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -0,0 +1,40 @@
use super::{BuilderMap, MetaMapBuilder, TileType};
use rltk::RandomNumberGenerator;
pub struct CullUnreachable {}
impl MetaMapBuilder for CullUnreachable {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl CullUnreachable {
#[allow(dead_code)]
pub fn new() -> Box<CullUnreachable> {
Box::new(CullUnreachable {})
}
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let starting_pos = build_data.starting_position.as_ref().unwrap().clone();
let start_idx = build_data.map.xy_idx(starting_pos.x, starting_pos.y);
build_data.map.populate_blocked();
let map_starts: Vec<usize> = vec![start_idx];
let dijkstra_map = rltk::DijkstraMap::new(
build_data.map.width as usize,
build_data.map.height as usize,
&map_starts,
&build_data.map,
1000.0,
);
for (i, tile) in build_data.map.tiles.iter_mut().enumerate() {
if *tile == TileType::Floor {
let distance_to_start = dijkstra_map.map[i];
// We can't get to this tile - so we'll make it a wall
if distance_to_start == std::f32::MAX {
*tile = TileType::Wall;
}
}
}
}
}

View file

@ -0,0 +1,49 @@
use super::{BuilderMap, MetaMapBuilder, TileType};
use rltk::RandomNumberGenerator;
pub struct DistantExit {}
impl MetaMapBuilder for DistantExit {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl DistantExit {
#[allow(dead_code)]
pub fn new() -> Box<DistantExit> {
Box::new(DistantExit {})
}
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let starting_pos = build_data.starting_position.as_ref().unwrap().clone();
let start_idx = build_data.map.xy_idx(starting_pos.x, starting_pos.y);
build_data.map.populate_blocked();
let map_starts: Vec<usize> = vec![start_idx];
let dijkstra_map = rltk::DijkstraMap::new(
build_data.map.width as usize,
build_data.map.height as usize,
&map_starts,
&build_data.map,
1000.0,
);
let mut exit_tile = (0, 0.0f32);
for (i, tile) in build_data.map.tiles.iter_mut().enumerate() {
if *tile == TileType::Floor {
let distance_to_start = dijkstra_map.map[i];
if distance_to_start != std::f32::MAX {
// If it is further away than our current exit candidate, move the exit
if distance_to_start > exit_tile.1 {
exit_tile.0 = i;
exit_tile.1 = distance_to_start;
}
}
}
}
// Place a staircase
let stairs_idx = exit_tile.0;
build_data.map.tiles[stairs_idx] = TileType::DownStair;
build_data.take_snapshot();
}
}

View file

@ -1,12 +1,8 @@
use super::{ use super::{paint, BuilderMap, InitialMapBuilder, Position, Symmetry, TileType};
common::Symmetry, generate_voronoi_spawn_regions, paint, remove_unreachable_areas_returning_most_distant, spawner,
Map, MapBuilder, Position, TileType, SHOW_MAPGEN,
};
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
use std::collections::HashMap;
#[allow(dead_code)]
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
#[allow(dead_code)]
pub enum DLAAlgorithm { pub enum DLAAlgorithm {
WalkInwards, WalkInwards,
WalkOutwards, WalkOutwards,
@ -14,134 +10,95 @@ pub enum DLAAlgorithm {
} }
pub struct DLABuilder { pub struct DLABuilder {
map: Map,
starting_position: Position,
depth: i32,
history: Vec<Map>,
noise_areas: HashMap<i32, Vec<usize>>,
algorithm: DLAAlgorithm, algorithm: DLAAlgorithm,
brush_size: i32, brush_size: i32,
symmetry: Symmetry, symmetry: Symmetry,
floor_percent: f32, floor_percent: f32,
spawn_list: Vec<(usize, String)>,
} }
impl MapBuilder for DLABuilder { impl InitialMapBuilder for DLABuilder {
fn build_map(&mut self, rng: &mut RandomNumberGenerator) { #[allow(dead_code)]
return self.build(rng); fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
} self.build(rng, build_data);
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
return &self.spawn_list;
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
} }
} }
#[allow(dead_code)]
impl DLABuilder { impl DLABuilder {
pub fn walk_inwards(new_depth: i32) -> DLABuilder { #[allow(dead_code)]
DLABuilder { pub fn new() -> Box<DLABuilder> {
map: Map::new(new_depth), Box::new(DLABuilder {
starting_position: Position { x: 0, y: 0 }, algorithm: DLAAlgorithm::WalkInwards,
depth: new_depth, brush_size: 2,
history: Vec::new(), symmetry: Symmetry::None,
noise_areas: HashMap::new(), floor_percent: 0.25,
})
}
#[allow(dead_code)]
pub fn walk_inwards() -> Box<DLABuilder> {
Box::new(DLABuilder {
algorithm: DLAAlgorithm::WalkInwards, algorithm: DLAAlgorithm::WalkInwards,
brush_size: 1, brush_size: 1,
symmetry: Symmetry::None, symmetry: Symmetry::None,
floor_percent: 0.25, floor_percent: 0.25,
spawn_list: Vec::new(), })
}
} }
pub fn walk_outwards(new_depth: i32) -> DLABuilder { #[allow(dead_code)]
DLABuilder { pub fn walk_outwards() -> Box<DLABuilder> {
map: Map::new(new_depth), Box::new(DLABuilder {
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
algorithm: DLAAlgorithm::WalkOutwards, algorithm: DLAAlgorithm::WalkOutwards,
brush_size: 2, brush_size: 2,
symmetry: Symmetry::None, symmetry: Symmetry::None,
floor_percent: 0.25, floor_percent: 0.25,
spawn_list: Vec::new(), })
}
} }
pub fn central_attractor(new_depth: i32) -> DLABuilder { #[allow(dead_code)]
DLABuilder { pub fn central_attractor() -> Box<DLABuilder> {
map: Map::new(new_depth), Box::new(DLABuilder {
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
algorithm: DLAAlgorithm::CentralAttractor, algorithm: DLAAlgorithm::CentralAttractor,
brush_size: 2, brush_size: 2,
symmetry: Symmetry::None, symmetry: Symmetry::None,
floor_percent: 0.25, floor_percent: 0.25,
spawn_list: Vec::new(), })
}
} }
pub fn insectoid(new_depth: i32) -> DLABuilder { #[allow(dead_code)]
DLABuilder { pub fn insectoid() -> Box<DLABuilder> {
map: Map::new(new_depth), Box::new(DLABuilder {
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
algorithm: DLAAlgorithm::CentralAttractor, algorithm: DLAAlgorithm::CentralAttractor,
brush_size: 2, brush_size: 2,
symmetry: Symmetry::Horizontal, symmetry: Symmetry::Horizontal,
floor_percent: 0.25, floor_percent: 0.25,
spawn_list: Vec::new(), })
}
} }
#[allow(clippy::map_entry)] #[allow(clippy::map_entry)]
fn build(&mut self, rng: &mut RandomNumberGenerator) { fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
// Carve starting seed // Carve a starting seed
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 }; let starting_position = Position { x: build_data.map.width / 2, y: build_data.map.height / 2 };
let start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y); let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y);
self.take_snapshot(); build_data.take_snapshot();
self.map.tiles[start_idx] = TileType::Floor; build_data.map.tiles[start_idx] = TileType::Floor;
self.map.tiles[start_idx - 1] = TileType::Floor; build_data.map.tiles[start_idx - 1] = TileType::Floor;
self.map.tiles[start_idx + 1] = TileType::Floor; build_data.map.tiles[start_idx + 1] = TileType::Floor;
self.map.tiles[start_idx - self.map.width as usize] = TileType::Floor; build_data.map.tiles[start_idx - build_data.map.width as usize] = TileType::Floor;
self.map.tiles[start_idx + self.map.width as usize] = TileType::Floor; build_data.map.tiles[start_idx + build_data.map.width as usize] = TileType::Floor;
// Random walker // Random walker
let total_tiles = self.map.width * self.map.height; let total_tiles = build_data.map.width * build_data.map.height;
let desired_floor_tiles = (self.floor_percent * total_tiles as f32) as usize; let desired_floor_tiles = (self.floor_percent * total_tiles as f32) as usize;
let mut floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); let mut floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
while floor_tile_count < desired_floor_tiles { while floor_tile_count < desired_floor_tiles {
match self.algorithm { match self.algorithm {
DLAAlgorithm::WalkInwards => { DLAAlgorithm::WalkInwards => {
let mut digger_x = rng.roll_dice(1, self.map.width - 3) + 1; let mut digger_x = rng.roll_dice(1, build_data.map.width - 3) + 1;
let mut digger_y = rng.roll_dice(1, self.map.height - 3) + 1; let mut digger_y = rng.roll_dice(1, build_data.map.height - 3) + 1;
let mut prev_x = digger_x; let mut prev_x = digger_x;
let mut prev_y = digger_y; let mut prev_y = digger_y;
let mut digger_idx = self.map.xy_idx(digger_x, digger_y); let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
while self.map.tiles[digger_idx] == TileType::Wall { while build_data.map.tiles[digger_idx] == TileType::Wall {
prev_x = digger_x; prev_x = digger_x;
prev_y = digger_y; prev_y = digger_y;
let stagger_direction = rng.roll_dice(1, 4); let stagger_direction = rng.roll_dice(1, 4);
@ -152,7 +109,7 @@ impl DLABuilder {
} }
} }
2 => { 2 => {
if digger_x < self.map.width - 2 { if digger_x < build_data.map.width - 2 {
digger_x += 1; digger_x += 1;
} }
} }
@ -162,20 +119,21 @@ impl DLABuilder {
} }
} }
_ => { _ => {
if digger_y < self.map.height - 2 { if digger_y < build_data.map.height - 2 {
digger_y += 1; digger_y += 1;
} }
} }
} }
digger_idx = self.map.xy_idx(digger_x, digger_y); digger_idx = build_data.map.xy_idx(digger_x, digger_y);
} }
paint(&mut self.map, self.symmetry, self.brush_size, prev_x, prev_y); paint(&mut build_data.map, self.symmetry, self.brush_size, prev_x, prev_y);
} }
DLAAlgorithm::WalkOutwards => { DLAAlgorithm::WalkOutwards => {
let mut digger_x = self.starting_position.x; let mut digger_x = starting_position.x;
let mut digger_y = self.starting_position.y; let mut digger_y = starting_position.y;
let mut digger_idx = self.map.xy_idx(digger_x, digger_y); let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
while self.map.tiles[digger_idx] == TileType::Floor { while build_data.map.tiles[digger_idx] == TileType::Floor {
let stagger_direction = rng.roll_dice(1, 4); let stagger_direction = rng.roll_dice(1, 4);
match stagger_direction { match stagger_direction {
1 => { 1 => {
@ -184,7 +142,7 @@ impl DLABuilder {
} }
} }
2 => { 2 => {
if digger_x < self.map.width - 2 { if digger_x < build_data.map.width - 2 {
digger_x += 1; digger_x += 1;
} }
} }
@ -194,56 +152,44 @@ impl DLABuilder {
} }
} }
_ => { _ => {
if digger_y < self.map.height - 2 { if digger_y < build_data.map.height - 2 {
digger_y += 1; digger_y += 1;
} }
} }
} }
digger_idx = self.map.xy_idx(digger_x, digger_y); digger_idx = build_data.map.xy_idx(digger_x, digger_y);
} }
paint(&mut self.map, self.symmetry, self.brush_size, digger_x, digger_y); paint(&mut build_data.map, self.symmetry, self.brush_size, digger_x, digger_y);
} }
DLAAlgorithm::CentralAttractor => { DLAAlgorithm::CentralAttractor => {
let mut digger_x = rng.roll_dice(1, self.map.width - 3) + 1; let mut digger_x = rng.roll_dice(1, build_data.map.width - 3) + 1;
let mut digger_y = rng.roll_dice(1, self.map.height - 3) + 1; let mut digger_y = rng.roll_dice(1, build_data.map.height - 3) + 1;
let mut prev_x = digger_x; let mut prev_x = digger_x;
let mut prev_y = digger_y; let mut prev_y = digger_y;
let mut digger_idx = self.map.xy_idx(digger_x, digger_y); let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
let mut path = rltk::line2d( let mut path = rltk::line2d(
rltk::LineAlg::Bresenham, rltk::LineAlg::Bresenham,
rltk::Point::new(digger_x, digger_y), rltk::Point::new(digger_x, digger_y),
rltk::Point::new(self.starting_position.x, self.starting_position.y), rltk::Point::new(starting_position.x, starting_position.y),
); );
while self.map.tiles[digger_idx] == TileType::Wall && !path.is_empty() { while build_data.map.tiles[digger_idx] == TileType::Wall && !path.is_empty() {
prev_x = digger_x; prev_x = digger_x;
prev_y = digger_y; prev_y = digger_y;
digger_x = path[0].x; digger_x = path[0].x;
digger_y = path[0].y; digger_y = path[0].y;
path.remove(0); path.remove(0);
digger_idx = self.map.xy_idx(digger_x, digger_y); digger_idx = build_data.map.xy_idx(digger_x, digger_y);
} }
paint(&mut self.map, self.symmetry, self.brush_size, prev_x, prev_y); paint(&mut build_data.map, self.symmetry, self.brush_size, prev_x, prev_y);
} }
} }
self.take_snapshot();
floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
}
// Find all tiles we can reach from the starting point
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
self.take_snapshot();
// Place the stairs build_data.take_snapshot();
self.map.tiles[exit_tile] = TileType::DownStair;
self.take_snapshot();
// Now we build a noise map for use in spawning entities later floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
// Spawn the entities
for area in self.noise_areas.iter() {
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
} }
} }
} }

View file

@ -1,11 +1,8 @@
use super::{ use super::{paint, BuilderMap, InitialMapBuilder, Position, Symmetry, TileType};
generate_voronoi_spawn_regions, paint, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
Position, Symmetry, TileType, SHOW_MAPGEN,
};
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
use std::collections::HashMap;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
#[allow(dead_code)]
pub enum DrunkSpawnMode { pub enum DrunkSpawnMode {
StartingPoint, StartingPoint,
Random, Random,
@ -20,53 +17,25 @@ pub struct DrunkardSettings {
} }
pub struct DrunkardsWalkBuilder { pub struct DrunkardsWalkBuilder {
map: Map,
starting_position: Position,
depth: i32,
history: Vec<Map>,
noise_areas: HashMap<i32, Vec<usize>>,
settings: DrunkardSettings, settings: DrunkardSettings,
spawn_list: Vec<(usize, String)>,
} }
impl MapBuilder for DrunkardsWalkBuilder { impl InitialMapBuilder for DrunkardsWalkBuilder {
fn build_map(&mut self, rng: &mut RandomNumberGenerator) { #[allow(dead_code)]
return self.build(rng); fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
} self.build(rng, build_data);
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
return &self.spawn_list;
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
} }
} }
#[allow(dead_code)]
impl DrunkardsWalkBuilder { impl DrunkardsWalkBuilder {
pub fn open_area(new_depth: i32) -> DrunkardsWalkBuilder { #[allow(dead_code)]
DrunkardsWalkBuilder { pub fn new(settings: DrunkardSettings) -> DrunkardsWalkBuilder {
map: Map::new(new_depth), DrunkardsWalkBuilder { settings }
starting_position: Position { x: 0, y: 0 }, }
depth: new_depth,
history: Vec::new(), #[allow(dead_code)]
noise_areas: HashMap::new(), pub fn open_area() -> Box<DrunkardsWalkBuilder> {
Box::new(DrunkardsWalkBuilder {
settings: DrunkardSettings { settings: DrunkardSettings {
spawn_mode: DrunkSpawnMode::StartingPoint, spawn_mode: DrunkSpawnMode::StartingPoint,
drunken_lifetime: 400, drunken_lifetime: 400,
@ -74,17 +43,12 @@ impl DrunkardsWalkBuilder {
brush_size: 1, brush_size: 1,
symmetry: Symmetry::None, symmetry: Symmetry::None,
}, },
spawn_list: Vec::new(), })
}
} }
pub fn open_halls(new_depth: i32) -> DrunkardsWalkBuilder { #[allow(dead_code)]
DrunkardsWalkBuilder { pub fn open_halls() -> Box<DrunkardsWalkBuilder> {
map: Map::new(new_depth), Box::new(DrunkardsWalkBuilder {
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
settings: DrunkardSettings { settings: DrunkardSettings {
spawn_mode: DrunkSpawnMode::Random, spawn_mode: DrunkSpawnMode::Random,
drunken_lifetime: 400, drunken_lifetime: 400,
@ -92,17 +56,12 @@ impl DrunkardsWalkBuilder {
brush_size: 1, brush_size: 1,
symmetry: Symmetry::None, symmetry: Symmetry::None,
}, },
spawn_list: Vec::new(), })
}
} }
pub fn winding_passages(new_depth: i32) -> DrunkardsWalkBuilder { #[allow(dead_code)]
DrunkardsWalkBuilder { pub fn winding_passages() -> Box<DrunkardsWalkBuilder> {
map: Map::new(new_depth), Box::new(DrunkardsWalkBuilder {
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
settings: DrunkardSettings { settings: DrunkardSettings {
spawn_mode: DrunkSpawnMode::Random, spawn_mode: DrunkSpawnMode::Random,
drunken_lifetime: 100, drunken_lifetime: 100,
@ -110,17 +69,12 @@ impl DrunkardsWalkBuilder {
brush_size: 1, brush_size: 1,
symmetry: Symmetry::None, symmetry: Symmetry::None,
}, },
spawn_list: Vec::new(), })
}
} }
pub fn fat_passages(new_depth: i32) -> DrunkardsWalkBuilder { #[allow(dead_code)]
DrunkardsWalkBuilder { pub fn fat_passages() -> Box<DrunkardsWalkBuilder> {
map: Map::new(new_depth), Box::new(DrunkardsWalkBuilder {
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
settings: DrunkardSettings { settings: DrunkardSettings {
spawn_mode: DrunkSpawnMode::Random, spawn_mode: DrunkSpawnMode::Random,
drunken_lifetime: 100, drunken_lifetime: 100,
@ -128,17 +82,12 @@ impl DrunkardsWalkBuilder {
brush_size: 2, brush_size: 2,
symmetry: Symmetry::None, symmetry: Symmetry::None,
}, },
spawn_list: Vec::new(), })
}
} }
pub fn fearful_symmetry(new_depth: i32) -> DrunkardsWalkBuilder { #[allow(dead_code)]
DrunkardsWalkBuilder { pub fn fearful_symmetry() -> Box<DrunkardsWalkBuilder> {
map: Map::new(new_depth), Box::new(DrunkardsWalkBuilder {
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
settings: DrunkardSettings { settings: DrunkardSettings {
spawn_mode: DrunkSpawnMode::Random, spawn_mode: DrunkSpawnMode::Random,
drunken_lifetime: 100, drunken_lifetime: 100,
@ -146,51 +95,47 @@ impl DrunkardsWalkBuilder {
brush_size: 1, brush_size: 1,
symmetry: Symmetry::Both, symmetry: Symmetry::Both,
}, },
spawn_list: Vec::new(), })
}
} }
#[allow(clippy::map_entry)] fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
fn build(&mut self, rng: &mut RandomNumberGenerator) { // Set a central starting point
// Central starting pos let starting_position = Position { x: build_data.map.width / 2, y: build_data.map.height / 2 };
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 }; let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y);
let start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y); build_data.map.tiles[start_idx] = TileType::Floor;
self.map.tiles[start_idx] = TileType::Floor;
let total_tiles = self.map.width * self.map.height; let total_tiles = build_data.map.width * build_data.map.height;
let desired_floor_tiles = (self.settings.floor_percent * total_tiles as f32) as usize; let desired_floor_tiles = (self.settings.floor_percent * total_tiles as f32) as usize;
let mut floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); let mut floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
let mut digger_count = 0; let mut digger_count = 0;
let mut active_digger_count = 0;
while floor_tile_count < desired_floor_tiles { while floor_tile_count < desired_floor_tiles {
let mut did_something = false; let mut did_something = false;
let mut drunk_x; let mut drunk_x;
let mut drunk_y; let mut drunk_y;
match self.settings.spawn_mode { match self.settings.spawn_mode {
DrunkSpawnMode::StartingPoint => { DrunkSpawnMode::StartingPoint => {
drunk_x = self.starting_position.x; drunk_x = starting_position.x;
drunk_y = self.starting_position.y; drunk_y = starting_position.y;
} }
DrunkSpawnMode::Random => { DrunkSpawnMode::Random => {
if digger_count == 0 { if digger_count == 0 {
drunk_x = self.starting_position.x; drunk_x = starting_position.x;
drunk_y = self.starting_position.y; drunk_y = starting_position.y;
} else { } else {
drunk_x = rng.roll_dice(1, self.map.width - 3) + 1; drunk_x = rng.roll_dice(1, build_data.map.width - 3) + 1;
drunk_y = rng.roll_dice(1, self.map.height - 3) + 1; drunk_y = rng.roll_dice(1, build_data.map.height - 3) + 1;
} }
} }
} }
let mut drunk_life = self.settings.drunken_lifetime; let mut drunk_life = self.settings.drunken_lifetime;
while drunk_life > 0 { while drunk_life > 0 {
let drunk_idx = self.map.xy_idx(drunk_x, drunk_y); let drunk_idx = build_data.map.xy_idx(drunk_x, drunk_y);
if self.map.tiles[drunk_idx] == TileType::Wall { if build_data.map.tiles[drunk_idx] == TileType::Wall {
did_something = true; did_something = true;
} }
paint(&mut self.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y); paint(&mut build_data.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y);
self.map.tiles[drunk_idx] = TileType::DownStair; build_data.map.tiles[drunk_idx] = TileType::DownStair;
let stagger_direction = rng.roll_dice(1, 4); let stagger_direction = rng.roll_dice(1, 4);
match stagger_direction { match stagger_direction {
@ -200,7 +145,7 @@ impl DrunkardsWalkBuilder {
} }
} }
2 => { 2 => {
if drunk_x < self.map.width - 2 { if drunk_x < build_data.map.width - 2 {
drunk_x += 1; drunk_x += 1;
} }
} }
@ -210,46 +155,25 @@ impl DrunkardsWalkBuilder {
} }
} }
_ => { _ => {
if drunk_y < self.map.height - 2 { if drunk_y < build_data.map.height - 2 {
drunk_y += 1; drunk_y += 1;
} }
} }
} }
drunk_life -= 1; drunk_life -= 1;
} }
if did_something { if did_something {
self.take_snapshot(); build_data.take_snapshot();
active_digger_count += 1;
} }
digger_count += 1; digger_count += 1;
for t in self.map.tiles.iter_mut() { for t in build_data.map.tiles.iter_mut() {
if *t == TileType::DownStair { if *t == TileType::DownStair {
*t = TileType::Floor; *t = TileType::Floor;
} }
} }
floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
}
rltk::console::log(format!(
"{} dwarves gave up their sobriety, of whom {} actually found a wall.",
digger_count, active_digger_count
));
// Find all tiles reachable from starting pos
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
self.take_snapshot();
// Place stairs
self.map.tiles[exit_tile] = TileType::DownStair;
self.take_snapshot();
// Noise map for spawning entities
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
// Spawn the entities
for area in self.noise_areas.iter() {
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
} }
} }
} }

View file

@ -1,209 +1,30 @@
use super::{ use super::{BuilderMap, InitialMapBuilder, Map, TileType};
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
Position, TileType, SHOW_MAPGEN,
};
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
use std::collections::HashMap;
pub struct MazeBuilder { pub struct MazeBuilder {}
map: Map,
starting_position: Position,
depth: i32,
history: Vec<Map>,
noise_areas: HashMap<i32, Vec<usize>>,
spawn_list: Vec<(usize, String)>,
}
impl MapBuilder for MazeBuilder { impl InitialMapBuilder for MazeBuilder {
fn build_map(&mut self, rng: &mut RandomNumberGenerator) { #[allow(dead_code)]
return self.build(rng); fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
} self.build(rng, build_data);
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
return &self.spawn_list;
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
} }
} }
impl MazeBuilder { impl MazeBuilder {
#[allow(dead_code)] #[allow(dead_code)]
pub fn new(new_depth: i32) -> MazeBuilder { pub fn new() -> Box<MazeBuilder> {
MazeBuilder { Box::new(MazeBuilder {})
map: Map::new(new_depth),
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
spawn_list: Vec::new(),
}
} }
#[allow(clippy::map_entry)] #[allow(clippy::map_entry)]
fn build(&mut self, rng: &mut RandomNumberGenerator) { fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
// Maze gen // Maze gen
let mut maze = Grid::new((self.map.width / 2) - 2, (self.map.height / 2) - 2, rng); let mut maze = Grid::new((build_data.map.width / 2) - 2, (build_data.map.height / 2) - 2, rng);
maze.generate_maze(self); maze.generate_maze(build_data);
// Starting point @ top left
self.starting_position = Position { x: 2, y: 2 };
let start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
self.take_snapshot();
// Find all tiles we can reach from the starting point
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
self.take_snapshot();
// Place the stairs
self.map.tiles[exit_tile] = TileType::DownStair;
self.take_snapshot();
// Now we build a noise map for use in spawning entities later
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
// Spawn the entities
for area in self.noise_areas.iter() {
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
}
} }
} }
struct Grid<'a> { /* Maze code taken under MIT from https://github.com/cyucelen/mazeGenerator/ */
width: i32,
height: i32,
cells: Vec<Cell>,
backtrace: Vec<usize>,
current: usize,
rng: &'a mut RandomNumberGenerator,
}
impl<'a> Grid<'a> {
fn new(width: i32, height: i32, rng: &mut RandomNumberGenerator) -> Grid {
let mut grid = Grid { width, height, cells: Vec::new(), backtrace: Vec::new(), current: 0, rng };
for row in 0..height {
for column in 0..width {
grid.cells.push(Cell::new(row, column))
}
}
return grid;
}
fn calculate_index(&self, row: i32, column: i32) -> i32 {
if row < 0 || column < 0 || column > self.width - 1 || row > self.height - 1 {
-1
} else {
column + (row * self.width)
}
}
fn get_available_neighbours(&self) -> Vec<usize> {
let mut neighbours: Vec<usize> = Vec::new();
let current_row = self.cells[self.current].row;
let current_column = self.cells[self.current].column;
let neighbour_indices: [i32; 4] = [
self.calculate_index(current_row - 1, current_column),
self.calculate_index(current_row, current_column + 1),
self.calculate_index(current_row + 1, current_column),
self.calculate_index(current_row, current_column - 1),
];
for i in neighbour_indices.iter() {
if *i != -1 && !self.cells[*i as usize].visited {
neighbours.push(*i as usize);
}
}
return neighbours;
}
fn find_next_cell(&mut self) -> Option<usize> {
let neighbours = self.get_available_neighbours();
if neighbours.is_empty() {
return None;
}
if neighbours.len() == 1 {
return Some(neighbours[0]);
}
return Some(neighbours[(self.rng.roll_dice(1, neighbours.len() as i32) - 1) as usize]);
}
fn generate_maze(&mut self, generator: &mut MazeBuilder) {
let mut i = 0;
loop {
self.cells[self.current].visited = true;
let next = self.find_next_cell();
match next {
Some(next) => {
self.cells[next].visited = true;
self.backtrace.push(self.current);
let (lower_part, higher_part) = self.cells.split_at_mut(std::cmp::max(self.current, next));
let cell1 = &mut lower_part[std::cmp::min(self.current, next)];
let cell2 = &mut higher_part[0];
cell1.remove_walls(cell2);
self.current = next;
}
None => {
if self.backtrace.is_empty() {
break;
}
self.current = self.backtrace[0];
self.backtrace.remove(0);
}
}
if i % 50 == 0 {
self.copy_to_map(&mut generator.map);
generator.take_snapshot();
}
i += 1;
}
}
fn copy_to_map(&self, map: &mut Map) {
// Clear map
for i in map.tiles.iter_mut() {
*i = TileType::Wall;
}
for cell in self.cells.iter() {
let x = cell.column + 1;
let y = cell.row + 1;
let idx = map.xy_idx(x * 2, y * 2);
map.tiles[idx] = TileType::Floor;
if !cell.walls[TOP] {
map.tiles[idx - map.width as usize] = TileType::Floor
}
if !cell.walls[RIGHT] {
map.tiles[idx + 1] = TileType::Floor
}
if !cell.walls[BOTTOM] {
map.tiles[idx + map.width as usize] = TileType::Floor
}
if !cell.walls[LEFT] {
map.tiles[idx - 1] = TileType::Floor
}
}
}
}
const TOP: usize = 0; const TOP: usize = 0;
const RIGHT: usize = 1; const RIGHT: usize = 1;
@ -235,10 +56,139 @@ impl Cell {
next.walls[LEFT] = false; next.walls[LEFT] = false;
} else if y == 1 { } else if y == 1 {
self.walls[TOP] = false; self.walls[TOP] = false;
next.walls[BOTTOM] = false next.walls[BOTTOM] = false;
} else if y == -1 { } else if y == -1 {
self.walls[BOTTOM] = false; self.walls[BOTTOM] = false;
next.walls[TOP] = false; next.walls[TOP] = false;
} }
} }
} }
struct Grid<'a> {
width: i32,
height: i32,
cells: Vec<Cell>,
backtrace: Vec<usize>,
current: usize,
rng: &'a mut RandomNumberGenerator,
}
impl<'a> Grid<'a> {
fn new(width: i32, height: i32, rng: &mut RandomNumberGenerator) -> Grid {
let mut grid = Grid { width, height, cells: Vec::new(), backtrace: Vec::new(), current: 0, rng };
for row in 0..height {
for column in 0..width {
grid.cells.push(Cell::new(row, column));
}
}
grid
}
fn calculate_index(&self, row: i32, column: i32) -> i32 {
if row < 0 || column < 0 || column > self.width - 1 || row > self.height - 1 {
-1
} else {
column + (row * self.width)
}
}
fn get_available_neighbors(&self) -> Vec<usize> {
let mut neighbors: Vec<usize> = Vec::new();
let current_row = self.cells[self.current].row;
let current_column = self.cells[self.current].column;
let neighbor_indices: [i32; 4] = [
self.calculate_index(current_row - 1, current_column),
self.calculate_index(current_row, current_column + 1),
self.calculate_index(current_row + 1, current_column),
self.calculate_index(current_row, current_column - 1),
];
for i in neighbor_indices.iter() {
if *i != -1 && !self.cells[*i as usize].visited {
neighbors.push(*i as usize);
}
}
neighbors
}
fn find_next_cell(&mut self) -> Option<usize> {
let neighbors = self.get_available_neighbors();
if !neighbors.is_empty() {
if neighbors.len() == 1 {
return Some(neighbors[0]);
} else {
return Some(neighbors[(self.rng.roll_dice(1, neighbors.len() as i32) - 1) as usize]);
}
}
None
}
fn generate_maze(&mut self, build_data: &mut BuilderMap) {
let mut i = 0;
loop {
self.cells[self.current].visited = true;
let next = self.find_next_cell();
match next {
Some(next) => {
self.cells[next].visited = true;
self.backtrace.push(self.current);
// __lower_part__ __higher_part_
// / \ / \
// --------cell1------ | cell2-----------
let (lower_part, higher_part) = self.cells.split_at_mut(std::cmp::max(self.current, next));
let cell1 = &mut lower_part[std::cmp::min(self.current, next)];
let cell2 = &mut higher_part[0];
cell1.remove_walls(cell2);
self.current = next;
}
None => {
if !self.backtrace.is_empty() {
self.current = self.backtrace[0];
self.backtrace.remove(0);
} else {
break;
}
}
}
if i % 50 == 0 {
self.copy_to_map(&mut build_data.map);
build_data.take_snapshot();
}
i += 1;
}
}
fn copy_to_map(&self, map: &mut Map) {
// Clear the map
for i in map.tiles.iter_mut() {
*i = TileType::Wall;
}
for cell in self.cells.iter() {
let x = cell.column + 1;
let y = cell.row + 1;
let idx = map.xy_idx(x * 2, y * 2);
map.tiles[idx] = TileType::Floor;
if !cell.walls[TOP] {
map.tiles[idx - map.width as usize] = TileType::Floor
}
if !cell.walls[RIGHT] {
map.tiles[idx + 1] = TileType::Floor
}
if !cell.walls[BOTTOM] {
map.tiles[idx + map.width as usize] = TileType::Floor
}
if !cell.walls[LEFT] {
map.tiles[idx - 1] = TileType::Floor
}
}
}
}

View file

@ -17,63 +17,157 @@ mod voronoi;
use voronoi::VoronoiBuilder; use voronoi::VoronoiBuilder;
mod prefab_builder; mod prefab_builder;
use prefab_builder::PrefabBuilder; use prefab_builder::PrefabBuilder;
mod room_based_spawner;
mod wfc; mod wfc;
use room_based_spawner::*;
mod room_based_stairs;
use room_based_stairs::*;
mod room_based_starting_position;
use room_based_starting_position::*;
mod area_starting_points;
use area_starting_points::{AreaStartingPosition, XStart, YStart};
mod cull_unreachable;
use cull_unreachable::CullUnreachable;
mod distant_exit;
use distant_exit::DistantExit;
mod voronoi_spawning;
use common::*; use common::*;
use rltk::RandomNumberGenerator;
use specs::prelude::*; use specs::prelude::*;
use voronoi_spawning::VoronoiSpawning;
use wfc::WaveFunctionCollapseBuilder; use wfc::WaveFunctionCollapseBuilder;
pub trait MapBuilder { // Shared data to be passed around build chain
fn build_map(&mut self, rng: &mut RandomNumberGenerator); pub struct BuilderMap {
fn spawn_entities(&mut self, ecs: &mut World) { pub spawn_list: Vec<(usize, String)>,
for entity in self.get_spawn_list().iter() { pub map: Map,
pub starting_position: Option<Position>,
pub rooms: Option<Vec<Rect>>,
pub history: Vec<Map>,
}
impl BuilderMap {
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
}
}
pub struct BuilderChain {
starter: Option<Box<dyn InitialMapBuilder>>,
builders: Vec<Box<dyn MetaMapBuilder>>,
pub build_data: BuilderMap,
}
impl BuilderChain {
pub fn new(new_depth: i32) -> BuilderChain {
BuilderChain {
starter: None,
builders: Vec::new(),
build_data: BuilderMap {
spawn_list: Vec::new(),
map: Map::new(new_depth),
starting_position: None,
rooms: None,
history: Vec::new(),
},
}
}
pub fn start_with(&mut self, starter: Box<dyn InitialMapBuilder>) {
match self.starter {
None => self.starter = Some(starter),
Some(_) => panic!("You can only have one starting builder."),
};
}
pub fn with(&mut self, metabuilder: Box<dyn MetaMapBuilder>) {
self.builders.push(metabuilder);
}
pub fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator) {
match &mut self.starter {
None => panic!("Cannot run a map builder chain without a starting build system"),
Some(starter) => {
// Build the starting map
starter.build_map(rng, &mut self.build_data);
}
}
// Build additional layers in turn
for metabuilder in self.builders.iter_mut() {
metabuilder.build_map(rng, &mut self.build_data);
}
}
pub fn spawn_entities(&mut self, ecs: &mut World) {
for entity in self.build_data.spawn_list.iter() {
spawner::spawn_entity(ecs, &(&entity.0, &entity.1)); spawner::spawn_entity(ecs, &(&entity.0, &entity.1));
} }
} }
fn get_map(&mut self) -> Map;
fn get_starting_pos(&mut self) -> Position;
fn get_spawn_list(&self) -> &Vec<(usize, String)>;
fn get_snapshot_history(&self) -> Vec<Map>;
fn take_snapshot(&mut self);
} }
#[rustfmt::skip] pub trait InitialMapBuilder {
pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> { fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap);
let mut rng = rltk::RandomNumberGenerator::new(); }
let builder = rng.roll_dice(1, 17);
let mut result : Box<dyn MapBuilder>; pub trait MetaMapBuilder {
rltk::console::log("Picking procgen type ->"); fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap);
match builder { }
1 => { result = Box::new(BspDungeonBuilder::new(new_depth)); }
2 => { result = Box::new(BspInteriorBuilder::new(new_depth)); } fn random_initial_builder(rng: &mut rltk::RandomNumberGenerator) -> (Box<dyn InitialMapBuilder>, bool) {
3 => { result = Box::new(CellularAutomataBuilder::new(new_depth)); } let builder = rng.roll_dice(1, 17);
4 => { result = Box::new(DrunkardsWalkBuilder::open_area(new_depth)); } let result: (Box<dyn InitialMapBuilder>, bool);
5 => { result = Box::new(DrunkardsWalkBuilder::open_halls(new_depth)); } match builder {
6 => { result = Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)); } 1 => result = (BspDungeonBuilder::new(), true),
7 => { result = Box::new(DrunkardsWalkBuilder::fat_passages(new_depth)); } 2 => result = (BspInteriorBuilder::new(), true),
8 => { result = Box::new(DrunkardsWalkBuilder::fearful_symmetry(new_depth)); } 3 => result = (CellularAutomataBuilder::new(), false),
9 => { result = Box::new(MazeBuilder::new(new_depth)); } 4 => result = (DrunkardsWalkBuilder::open_area(), false),
10 => { result = Box::new(DLABuilder::walk_inwards(new_depth)); } 5 => result = (DrunkardsWalkBuilder::open_halls(), false),
11 => { result = Box::new(DLABuilder::walk_outwards(new_depth)); } 6 => result = (DrunkardsWalkBuilder::winding_passages(), false),
12 => { result = Box::new(DLABuilder::central_attractor(new_depth)); } 7 => result = (DrunkardsWalkBuilder::fat_passages(), false),
13 => { result = Box::new(DLABuilder::insectoid(new_depth)); } 8 => result = (DrunkardsWalkBuilder::fearful_symmetry(), false),
14 => { result = Box::new(VoronoiBuilder::pythagoras(new_depth)); } 9 => result = (MazeBuilder::new(), false),
15 => { result = Box::new(VoronoiBuilder::manhattan(new_depth)); } 10 => result = (DLABuilder::walk_inwards(), false),
16 => { result = Box::new(PrefabBuilder::constant(new_depth, prefab_builder::prefab_levels::WFC_POPULATED)) }, 11 => result = (DLABuilder::walk_outwards(), false),
_ => { result = Box::new(simple_map::SimpleMapBuilder::new(new_depth)); } 12 => result = (DLABuilder::central_attractor(), false),
} 13 => result = (DLABuilder::insectoid(), false),
14 => result = (VoronoiBuilder::pythagoras(), false),
if rng.roll_dice(1, 3)==1 { 15 => result = (VoronoiBuilder::manhattan(), false),
result = Box::new(WaveFunctionCollapseBuilder::derived_map(new_depth, result)); 16 => result = (PrefabBuilder::constant(prefab_builder::prefab_levels::WFC_POPULATED), false),
rltk::console::log("-> wfc"); _ => result = (simple_map::SimpleMapBuilder::new(), true),
}
if rng.roll_dice(1, 20)==1 {
result = Box::new(PrefabBuilder::sectional(new_depth, prefab_builder::prefab_sections::UNDERGROUND_FORT ,result));
rltk::console::log("-> sectional");
}
result = Box::new(PrefabBuilder::vaults(new_depth, result));
result
} }
result
}
pub fn random_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator) -> BuilderChain {
let mut builder = BuilderChain::new(new_depth);
let (random_starter, has_rooms) = random_initial_builder(rng);
builder.start_with(random_starter);
if has_rooms {
builder.with(RoomBasedSpawner::new());
builder.with(RoomBasedStairs::new());
builder.with(RoomBasedStartingPosition::new());
} else {
builder.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE));
builder.with(CullUnreachable::new());
builder.with(VoronoiSpawning::new());
builder.with(DistantExit::new());
}
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

@ -1,193 +1,290 @@
use super::{remove_unreachable_areas_returning_most_distant, Map, MapBuilder, Position, TileType, SHOW_MAPGEN}; use super::{BuilderMap, InitialMapBuilder, MetaMapBuilder, Position, TileType};
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
pub mod prefab_levels; pub mod prefab_levels;
pub mod prefab_sections; pub mod prefab_sections;
pub mod prefab_vaults; pub mod prefab_vaults;
use std::collections::HashSet; use std::collections::HashSet;
#[derive(PartialEq, Copy, Clone)]
#[allow(dead_code)] #[allow(dead_code)]
#[derive(PartialEq, Clone)]
pub enum PrefabMode { pub enum PrefabMode {
RexLevel { template: &'static str }, RexLevel { template: &'static str },
Constant { level: prefab_levels::PrefabLevel }, Constant { level: prefab_levels::PrefabLevel },
Sectional { section: prefab_sections::PrefabSection }, Sectional { section: prefab_sections::PrefabSection },
Vaults, RoomVaults,
}
pub struct PrefabBuilder {
map: Map,
starting_position: Position,
depth: i32,
history: Vec<Map>,
mode: PrefabMode,
spawn_list: Vec<(usize, String)>,
previous_builder: Option<Box<dyn MapBuilder>>,
}
impl MapBuilder for PrefabBuilder {
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
return self.build(rng);
}
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
return &self.spawn_list;
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
}
} }
#[allow(dead_code)] #[allow(dead_code)]
pub struct PrefabBuilder {
mode: PrefabMode,
}
impl MetaMapBuilder for PrefabBuilder {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl InitialMapBuilder for PrefabBuilder {
#[allow(dead_code)]
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl PrefabBuilder { impl PrefabBuilder {
pub fn rex_level(new_depth: i32, template: &'static str) -> PrefabBuilder { #[allow(dead_code)]
PrefabBuilder { pub fn new() -> Box<PrefabBuilder> {
map: Map::new(new_depth), Box::new(PrefabBuilder { mode: PrefabMode::RoomVaults })
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
mode: PrefabMode::RexLevel { template },
spawn_list: Vec::new(),
previous_builder: None,
}
}
pub fn constant(new_depth: i32, level: prefab_levels::PrefabLevel) -> PrefabBuilder {
PrefabBuilder {
map: Map::new(new_depth),
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
mode: PrefabMode::Constant { level },
spawn_list: Vec::new(),
previous_builder: None,
}
}
pub fn sectional(
new_depth: i32,
section: prefab_sections::PrefabSection,
previous_builder: Box<dyn MapBuilder>,
) -> PrefabBuilder {
PrefabBuilder {
map: Map::new(new_depth),
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
mode: PrefabMode::Sectional { section },
spawn_list: Vec::new(),
previous_builder: Some(previous_builder),
}
}
pub fn vaults(new_depth: i32, previous_builder: Box<dyn MapBuilder>) -> PrefabBuilder {
PrefabBuilder {
map: Map::new(new_depth),
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
mode: PrefabMode::Vaults,
spawn_list: Vec::new(),
previous_builder: Some(previous_builder),
}
} }
fn build(&mut self, rng: &mut RandomNumberGenerator) { #[allow(dead_code)]
pub fn rex_level(template: &'static str) -> Box<PrefabBuilder> {
Box::new(PrefabBuilder { mode: PrefabMode::RexLevel { template } })
}
#[allow(dead_code)]
pub fn constant(level: prefab_levels::PrefabLevel) -> Box<PrefabBuilder> {
Box::new(PrefabBuilder { mode: PrefabMode::Constant { level } })
}
#[allow(dead_code)]
pub fn sectional(section: prefab_sections::PrefabSection) -> Box<PrefabBuilder> {
Box::new(PrefabBuilder { mode: PrefabMode::Sectional { section } })
}
#[allow(dead_code)]
pub fn vaults() -> Box<PrefabBuilder> {
Box::new(PrefabBuilder { mode: PrefabMode::RoomVaults })
}
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
match self.mode { match self.mode {
PrefabMode::RexLevel { template } => self.load_rex_map(&template), PrefabMode::RexLevel { template } => self.load_rex_map(&template, build_data),
PrefabMode::Constant { level } => self.load_ascii_map(&level), PrefabMode::Constant { level } => self.load_ascii_map(&level, build_data),
PrefabMode::Sectional { section } => self.apply_sectional(&section, rng), PrefabMode::Sectional { section } => self.apply_sectional(&section, rng, build_data),
PrefabMode::Vaults => self.apply_room_vaults(rng), PrefabMode::RoomVaults => self.apply_room_vaults(rng, build_data),
} }
self.take_snapshot(); build_data.take_snapshot();
}
// Find starting pos by starting at middle and walking left until finding a floor tile fn char_to_map(&mut self, ch: char, idx: usize, build_data: &mut BuilderMap) {
if self.starting_position.x == 0 { match ch {
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 }; ' ' => build_data.map.tiles[idx] = TileType::Floor,
let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y); '#' => build_data.map.tiles[idx] = TileType::Wall,
while self.map.tiles[start_idx] != TileType::Floor { '>' => build_data.map.tiles[idx] = TileType::DownStair,
self.starting_position.x -= 1; '≈' => build_data.map.tiles[idx] = TileType::Floor, // Placeholder for vines/brush
start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y); '@' => {
let x = idx as i32 % build_data.map.width;
let y = idx as i32 / build_data.map.width;
build_data.map.tiles[idx] = TileType::Floor;
build_data.starting_position = Some(Position { x: x as i32, y: y as i32 });
}
'g' => {
build_data.map.tiles[idx] = TileType::Floor;
build_data.spawn_list.push((idx, "goblin".to_string()));
}
'G' => {
build_data.map.tiles[idx] = TileType::Floor;
build_data.spawn_list.push((idx, "goblin chieftain".to_string()));
}
'o' => {
build_data.map.tiles[idx] = TileType::Floor;
build_data.spawn_list.push((idx, "orc".to_string()));
}
'^' => {
build_data.map.tiles[idx] = TileType::Floor;
build_data.spawn_list.push((idx, "bear trap".to_string()));
}
'%' => {
build_data.map.tiles[idx] = TileType::Floor;
build_data.spawn_list.push((idx, "rations".to_string()));
}
'!' => {
build_data.map.tiles[idx] = TileType::Floor;
build_data.spawn_list.push((idx, "health potion".to_string()));
}
'/' => {
build_data.map.tiles[idx] = TileType::Floor;
build_data.spawn_list.push((idx, "magic missile wand".to_string()));
// Placeholder for wand spawn
}
'?' => {
build_data.map.tiles[idx] = TileType::Floor;
build_data.spawn_list.push((idx, "fireball scroll".to_string()));
// Placeholder for scroll spawn
}
_ => {
rltk::console::log(format!("Unknown glyph '{}' when loading prefab", (ch as u8) as char));
} }
self.take_snapshot();
// Find all tiles we can reach from the starting point
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
self.take_snapshot();
// Place the stairs
self.map.tiles[exit_tile] = TileType::DownStair;
self.take_snapshot();
} }
} }
fn apply_previous_iteration<F>(&mut self, rng: &mut RandomNumberGenerator, mut filter: F) #[allow(dead_code)]
where fn load_rex_map(&mut self, path: &str, build_data: &mut BuilderMap) {
F: FnMut(i32, i32, &(usize, String)) -> bool, let xp_file = rltk::rex::XpFile::from_resource(path).unwrap();
for layer in &xp_file.layers {
for y in 0..layer.height {
for x in 0..layer.width {
let cell = layer.get(x, y).unwrap();
if x < build_data.map.width as usize && y < build_data.map.height as usize {
let idx = build_data.map.xy_idx(x as i32, y as i32);
// We're doing some nasty casting to make it easier to type things like '#' in the match
self.char_to_map(cell.ch as u8 as char, idx, build_data);
}
}
}
}
}
fn read_ascii_to_vec(template: &str) -> Vec<char> {
let mut string_vec: Vec<char> = template.chars().filter(|a| *a != '\r' && *a != '\n').collect();
for c in string_vec.iter_mut() {
if *c as u8 == 160u8 {
*c = ' ';
}
}
string_vec
}
#[allow(dead_code)]
fn load_ascii_map(&mut self, level: &prefab_levels::PrefabLevel, build_data: &mut BuilderMap) {
let string_vec = PrefabBuilder::read_ascii_to_vec(level.template);
let mut i = 0;
for ty in 0..level.height {
for tx in 0..level.width {
if tx < build_data.map.width as usize && ty < build_data.map.height as usize {
let idx = build_data.map.xy_idx(tx as i32, ty as i32);
if i < string_vec.len() {
self.char_to_map(string_vec[i], idx, build_data);
}
}
i += 1;
}
}
}
fn apply_previous_iteration<F>(
&mut self,
mut filter: F,
_rng: &mut RandomNumberGenerator,
build_data: &mut BuilderMap,
) where
F: FnMut(i32, i32) -> bool,
{ {
// Build the map let width = build_data.map.width;
let prev_builder = self.previous_builder.as_mut().unwrap(); build_data.spawn_list.retain(|(idx, _name)| {
prev_builder.build_map(rng); let x = *idx as i32 % width;
self.starting_position = prev_builder.get_starting_pos(); let y = *idx as i32 / width;
self.map = prev_builder.get_map().clone(); filter(x, y)
self.history = prev_builder.get_snapshot_history(); });
for e in prev_builder.get_spawn_list().iter() { build_data.take_snapshot();
let idx = e.0;
let x = idx as i32 % self.map.width;
let y = idx as i32 / self.map.width;
if filter(x, y, e) {
self.spawn_list.push((idx, e.1.to_string()))
}
}
self.take_snapshot();
} }
fn apply_room_vaults(&mut self, rng: &mut RandomNumberGenerator) { #[allow(dead_code)]
fn apply_sectional(
&mut self,
section: &prefab_sections::PrefabSection,
rng: &mut RandomNumberGenerator,
build_data: &mut BuilderMap,
) {
use prefab_sections::*;
let string_vec = PrefabBuilder::read_ascii_to_vec(section.template);
// Place the new section
let chunk_x;
match section.placement.0 {
HorizontalPlacement::Left => chunk_x = 0,
HorizontalPlacement::Center => chunk_x = (build_data.map.width / 2) - (section.width as i32 / 2),
HorizontalPlacement::Right => chunk_x = (build_data.map.width - 1) - section.width as i32,
}
let chunk_y;
match section.placement.1 {
VerticalPlacement::Top => chunk_y = 0,
VerticalPlacement::Center => chunk_y = (build_data.map.height / 2) - (section.height as i32 / 2),
VerticalPlacement::Bottom => chunk_y = (build_data.map.height - 1) - section.height as i32,
}
// Build the map
self.apply_previous_iteration(
|x, y| {
x < chunk_x
|| x > (chunk_x + section.width as i32)
|| y < chunk_y
|| y > (chunk_y + section.height as i32)
},
rng,
build_data,
);
let mut i = 0;
for ty in 0..section.height {
for tx in 0..section.width {
if tx > 0 && tx < build_data.map.width as usize - 1 && ty < build_data.map.height as usize - 1 && ty > 0
{
let idx = build_data.map.xy_idx(tx as i32 + chunk_x, ty as i32 + chunk_y);
if i < string_vec.len() {
self.char_to_map(string_vec[i], idx, build_data);
}
}
i += 1;
}
}
build_data.take_snapshot();
}
fn apply_room_vaults(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
use prefab_vaults::*; use prefab_vaults::*;
// Apply prev builder, and keep all entities // Apply the previous builder, and keep all entities it spawns (for now)
self.apply_previous_iteration(rng, |_x, _y, _e| true); self.apply_previous_iteration(|_x, _y| true, rng, build_data);
// Roll for a vault // Do we want a vault at all?
let vault_roll = rng.roll_dice(1, 6); let vault_roll = rng.roll_dice(1, 6) + build_data.map.depth;
if vault_roll < 4 { if vault_roll < 4 {
return; return;
} }
// Get all vaults // Note that this is a place-holder and will be moved out of this function
let master_vault_list = vec![GOBLINS_4X4, GOBLINS2_4X4, CLASSIC_TRAP_5X5]; let master_vault_list = vec![
// Filter out vaults from outside the current depth CLASSIC_TRAP_5X5,
let mut possible_vaults: Vec<&PrefabVault> = CLASSIC_TRAP_DIAGONALGAP_5X5,
master_vault_list.iter().filter(|v| self.depth >= v.first_depth && self.depth <= v.last_depth).collect(); CLASSIC_TRAP_CARDINALGAP_5X5,
// Return if there's no possible vaults GOBLINS_4X4,
GOBLINS2_4X4,
GOBLINS_5X5,
GOBLINS_6X6,
FLUFF_6X3,
FLUFF2_6X3,
HOUSE_NOTRAP_7X7,
HOUSE_TRAP_7X7,
ORC_HOUSE_8X8,
];
// Filter the vault list down to ones that are applicable to the current depth
let mut possible_vaults: Vec<&PrefabVault> = master_vault_list
.iter()
.filter(|v| build_data.map.depth >= v.first_depth && build_data.map.depth <= v.last_depth)
.collect();
if possible_vaults.is_empty() { if possible_vaults.is_empty() {
return; return;
} } // Bail out if there's nothing to build
// Pick number of vaults
let n_vaults = i32::min(rng.roll_dice(1, 3), possible_vaults.len() as i32); let n_vaults = i32::min(rng.roll_dice(1, 3), possible_vaults.len() as i32);
let mut used_tiles: HashSet<usize> = HashSet::new(); let mut used_tiles: HashSet<usize> = HashSet::new();
for _i in 0..n_vaults { for _i in 0..n_vaults {
// Select a vault let vault_index = if possible_vaults.len() == 1 {
let vault_idx = if possible_vaults.len() == 1 {
0 0
} else { } else {
(rng.roll_dice(1, possible_vaults.len() as i32) - 1) as usize (rng.roll_dice(1, possible_vaults.len() as i32) - 1) as usize
}; };
let vault = possible_vaults[vault_idx]; let vault = possible_vaults[vault_index];
// Decide if we want to flip the vault // Decide if we want to flip the vault
let mut flip_x: bool = false; let mut flip_x: bool = false;
let mut flip_y: bool = false; let mut flip_y: bool = false;
@ -214,23 +311,25 @@ impl PrefabBuilder {
} }
} }
// Make a list of all places the vault can fit // We'll make a list of places in which the vault could fit
let mut vault_positions: Vec<Position> = Vec::new(); let mut vault_positions: Vec<Position> = Vec::new();
let mut idx = 0usize; let mut idx = 0usize;
loop { loop {
let x = (idx % self.map.width as usize) as i32; let x = (idx % build_data.map.width as usize) as i32;
let y = (idx / self.map.width as usize) as i32; let y = (idx / build_data.map.width as usize) as i32;
// Check for overflow
// Check that we won't overflow the map
if x > 1 if x > 1
&& (x + vault.width as i32) < self.map.width - 2 && (x + vault.width as i32) < build_data.map.width - 2
&& y > 1 && y > 1
&& (y + vault.height as i32) < self.map.height - 2 && (y + vault.height as i32) < build_data.map.height - 2
{ {
let mut possible = true; let mut possible = true;
for tile_y in 0..vault.height as i32 { for ty in 0..vault.height as i32 {
for tile_x in 0..vault.width as i32 { for tx in 0..vault.width as i32 {
let idx = self.map.xy_idx(tile_x + x, tile_y + y); let idx = build_data.map.xy_idx(tx + x, ty + y);
if self.map.tiles[idx] != TileType::Floor { if build_data.map.tiles[idx] != TileType::Floor {
possible = false; possible = false;
} }
if used_tiles.contains(&idx) { if used_tiles.contains(&idx) {
@ -238,19 +337,19 @@ impl PrefabBuilder {
} }
} }
} }
// If we find a position that works, push it
if possible { if possible {
vault_positions.push(Position { x, y }); vault_positions.push(Position { x, y });
break;
} }
} }
// Once we reach the end of the map, break
idx += 1; idx += 1;
if idx >= self.map.tiles.len() - 1 { if idx >= build_data.map.tiles.len() - 1 {
break; break;
} }
} }
// If we have a position, make the vault
if !vault_positions.is_empty() { if !vault_positions.is_empty() {
let pos_idx = if vault_positions.len() == 1 { let pos_idx = if vault_positions.len() == 1 {
0 0
@ -258,17 +357,19 @@ impl PrefabBuilder {
(rng.roll_dice(1, vault_positions.len() as i32) - 1) as usize (rng.roll_dice(1, vault_positions.len() as i32) - 1) as usize
}; };
let pos = &vault_positions[pos_idx]; let pos = &vault_positions[pos_idx];
let chunk_x = pos.x; let chunk_x = pos.x;
let chunk_y = pos.y; let chunk_y = pos.y;
// Filter out entities from our spawn list that would have spawned inside this vault
let width = self.map.width; // For borrow checker. let width = build_data.map.width; // The borrow checker really doesn't like it
let height = self.map.height; // As above. let height = build_data.map.height; // when we access `self` inside the `retain`
self.spawn_list.retain(|e| { build_data.spawn_list.retain(|e| {
let idx = e.0 as i32; let idx = e.0 as i32;
let x = idx % width; let x = idx % width;
let y = idx / height; let y = idx / height;
x < chunk_x || x > chunk_x + vault.width as i32 || y < chunk_y || y > chunk_y + vault.height as i32 x < chunk_x || x > chunk_x + vault.width as i32 || y < chunk_y || y > chunk_y + vault.height as i32
}); });
let string_vec = PrefabBuilder::read_ascii_to_vec(vault.template); let string_vec = PrefabBuilder::read_ascii_to_vec(vault.template);
let mut i = 0; let mut i = 0;
for tile_y in 0..vault.height { for tile_y in 0..vault.height {
@ -282,140 +383,17 @@ impl PrefabBuilder {
if flip_y { if flip_y {
y_ = vault.width as i32 - 1 - y_; y_ = vault.width as i32 - 1 - y_;
} }
self.map.xy_idx(x_ + chunk_x, y_ as i32 + chunk_y); let idx = build_data.map.xy_idx(x_ + chunk_x, y_ + chunk_y);
self.char_to_map(string_vec[i], idx); if i < string_vec.len() {
self.char_to_map(string_vec[i], idx, build_data);
}
used_tiles.insert(idx); used_tiles.insert(idx);
i += 1; i += 1;
} }
} }
rltk::console::log("-> adding vault"); build_data.take_snapshot();
self.take_snapshot();
possible_vaults.remove(vault_idx);
}
}
}
pub fn apply_sectional(&mut self, section: &prefab_sections::PrefabSection, rng: &mut RandomNumberGenerator) { possible_vaults.remove(vault_index);
use prefab_sections::*;
let string_vec = PrefabBuilder::read_ascii_to_vec(section.template);
// Place the new section
let chunk_x;
match section.placement.0 {
HorizontalPlacement::Left => chunk_x = 0,
HorizontalPlacement::Center => chunk_x = (self.map.width / 2) - (section.width as i32 / 2),
HorizontalPlacement::Right => chunk_x = (self.map.width - 1) - section.width as i32,
}
let chunk_y;
match section.placement.1 {
VerticalPlacement::Top => chunk_y = 0,
VerticalPlacement::Center => chunk_y = (self.map.height / 2) - (section.height as i32 / 2),
VerticalPlacement::Bottom => chunk_y = (self.map.height - 1) - section.height as i32,
}
// Build the map
self.apply_previous_iteration(rng, |x, y, _e| {
x < chunk_x || x > (chunk_x + section.width as i32) || y < chunk_y || y > (chunk_y + section.height as i32)
});
let mut i = 0;
for ty in 0..section.height {
for tx in 0..section.width {
if tx < self.map.width as usize && ty < self.map.height as usize {
let idx = self.map.xy_idx(tx as i32 + chunk_x, ty as i32 + chunk_y);
self.char_to_map(string_vec[i], idx);
}
i += 1;
}
}
self.take_snapshot();
}
fn char_to_map(&mut self, ch: char, idx: usize) {
match ch {
' ' => self.map.tiles[idx] = TileType::Floor,
'#' => self.map.tiles[idx] = TileType::Wall,
'>' => self.map.tiles[idx] = TileType::DownStair,
'@' => {
let x = idx as i32 % self.map.width;
let y = idx as i32 / self.map.width;
self.map.tiles[idx] = TileType::Floor;
self.starting_position = Position { x: x as i32, y: y as i32 };
}
'g' => {
self.map.tiles[idx] = TileType::Floor;
self.spawn_list.push((idx, "goblin".to_string()));
}
'G' => {
self.map.tiles[idx] = TileType::Floor;
self.spawn_list.push((idx, "goblin chieftain".to_string()));
}
'o' => {
self.map.tiles[idx] = TileType::Floor;
self.spawn_list.push((idx, "orc".to_string()));
}
'^' => {
self.map.tiles[idx] = TileType::Floor;
self.spawn_list.push((idx, "bear trap".to_string()));
}
'%' => {
self.map.tiles[idx] = TileType::Floor;
self.spawn_list.push((idx, "rations".to_string()));
}
'!' => {
self.map.tiles[idx] = TileType::Floor;
self.spawn_list.push((idx, "health potion".to_string()));
}
_ => {
rltk::console::log(format!("Unknown glyph loading map: {}", (ch as u8) as char));
}
}
}
#[allow(dead_code)]
fn load_rex_map(&mut self, path: &str) {
let xp_file = rltk::rex::XpFile::from_resource(path).unwrap();
for layer in &xp_file.layers {
for y in 0..layer.height {
for x in 0..layer.width {
let cell = layer.get(x, y).unwrap();
if x < self.map.width as usize && y < self.map.height as usize {
// Saving these for later, for flipping the pref horizontally/vertically/both.
// let flipped_x = (self.map.width - 1) - x as i32;
// let flipped_y = (self.map.height - 1) - y as i32;
let idx = self.map.xy_idx(x as i32, y as i32);
// We're doing some nasty casting to make it easier to type things like '#' in the match
self.char_to_map(cell.ch as u8 as char, idx);
}
}
}
}
}
fn read_ascii_to_vec(template: &str) -> Vec<char> {
let mut string_vec: Vec<char> = template.chars().filter(|a| *a != '\r' && *a != '\n').collect();
for c in string_vec.iter_mut() {
if *c as u8 == 160u8 {
*c = ' ';
}
}
return string_vec;
}
#[allow(dead_code)]
fn load_ascii_map(&mut self, level: &prefab_levels::PrefabLevel) {
let string_vec = PrefabBuilder::read_ascii_to_vec(level.template);
let mut i = 0;
for y in 0..level.height {
for x in 0..level.width {
if x < self.map.width as usize && y < self.map.height as usize {
let idx = self.map.xy_idx(x as i32, y as i32);
self.char_to_map(string_vec[i], idx);
}
i += 1;
} }
} }
} }

View file

@ -17,7 +17,6 @@ pub struct PrefabVault {
pub can_flip: Flipping, pub can_flip: Flipping,
} }
#[allow(dead_code)]
pub const CLASSIC_TRAP_5X5: PrefabVault = PrefabVault { pub const CLASSIC_TRAP_5X5: PrefabVault = PrefabVault {
template: CLASSIC_TRAP_5X5_V, template: CLASSIC_TRAP_5X5_V,
width: 5, width: 5,
@ -26,7 +25,6 @@ pub const CLASSIC_TRAP_5X5: PrefabVault = PrefabVault {
last_depth: 100, last_depth: 100,
can_flip: Flipping::None, can_flip: Flipping::None,
}; };
#[allow(dead_code)]
const CLASSIC_TRAP_5X5_V: &str = " const CLASSIC_TRAP_5X5_V: &str = "
^^^ ^^^
@ -35,7 +33,38 @@ const CLASSIC_TRAP_5X5_V: &str = "
"; ";
#[allow(dead_code)] pub const CLASSIC_TRAP_CARDINALGAP_5X5: PrefabVault = PrefabVault {
template: CLASSIC_TRAP_CARDINALGAP_5X5_V,
width: 5,
height: 5,
first_depth: 0,
last_depth: 100,
can_flip: Flipping::Both,
};
const CLASSIC_TRAP_CARDINALGAP_5X5_V: &str = "
     
 ^ ^ 
 ^!^ 
 ^^^ 
     
";
pub const CLASSIC_TRAP_DIAGONALGAP_5X5: PrefabVault = PrefabVault {
template: CLASSIC_TRAP_DIAGONALGAP_5X5_V,
width: 5,
height: 5,
first_depth: 0,
last_depth: 100,
can_flip: Flipping::Both,
};
const CLASSIC_TRAP_DIAGONALGAP_5X5_V: &str = "
     
 ^^  
 ^!^ 
 ^^^ 
     
";
pub const GOBLINS_4X4: PrefabVault = PrefabVault { pub const GOBLINS_4X4: PrefabVault = PrefabVault {
template: GOBLINS_4X4_V, template: GOBLINS_4X4_V,
width: 4, width: 4,
@ -51,7 +80,6 @@ const GOBLINS_4X4_V: &str = "
^g^ ^g^
"; ";
#[allow(dead_code)]
pub const GOBLINS2_4X4: PrefabVault = PrefabVault { pub const GOBLINS2_4X4: PrefabVault = PrefabVault {
template: GOBLINS2_4X4_V, template: GOBLINS2_4X4_V,
width: 4, width: 4,
@ -66,3 +94,119 @@ G# #
g#  g# 
# g^ # g^
"; ";
pub const GOBLINS_5X5: PrefabVault = PrefabVault {
template: GOBLINS_5X5_V,
width: 5,
height: 5,
first_depth: 0,
last_depth: 100,
can_flip: Flipping::Both,
};
const GOBLINS_5X5_V: &str = "
 ^#g 
G#?#^
/g g#
## ^#
^# # 
";
pub const GOBLINS_6X6: PrefabVault = PrefabVault {
template: GOBLINS_6X6_V,
width: 6,
height: 6,
first_depth: 0,
last_depth: 100,
can_flip: Flipping::Both,
};
const GOBLINS_6X6_V: &str = "
   #  
 #^#g 
#G#$#^
 /gGg#
g##$^
 ^ # ^
";
pub const FLUFF_6X3: PrefabVault = PrefabVault {
template: FLUFF_6X3_V,
width: 6,
height: 3,
first_depth: 0,
last_depth: 100,
can_flip: Flipping::Both,
};
const FLUFF_6X3_V: &str = "
###^ 
 ^ #
 ## 
";
pub const FLUFF2_6X3: PrefabVault = PrefabVault {
template: FLUFF2_6X3_V,
width: 6,
height: 3,
first_depth: 0,
last_depth: 100,
can_flip: Flipping::Both,
};
const FLUFF2_6X3_V: &str = "
 ^###
# ^ 
 ## 
";
pub const HOUSE_NOTRAP_7X7: PrefabVault = PrefabVault {
template: HOUSE_NOTRAP_7X7_V,
width: 7,
height: 7,
first_depth: 0,
last_depth: 100,
can_flip: Flipping::Both,
};
const HOUSE_NOTRAP_7X7_V: &str = "
#   #
#   g #
  # #  
   ?   
  # #  
# g   #
#   #
";
pub const HOUSE_TRAP_7X7: PrefabVault = PrefabVault {
template: HOUSE_TRAP_7X7_V,
width: 7,
height: 7,
first_depth: 0,
last_depth: 100,
can_flip: Flipping::Both,
};
const HOUSE_TRAP_7X7_V: &str = "
#   #
#     #
  #^#  
  ^? g 
 g#^#  
#     #
#   #
";
pub const ORC_HOUSE_8X8: PrefabVault = PrefabVault {
template: ORC_HOUSE_8X8_V,
width: 8,
height: 8,
first_depth: 0,
last_depth: 100,
can_flip: Flipping::Both,
};
const ORC_HOUSE_8X8_V: &str = "
######
#o g #
#   %#
# %o #
#   ?#
##+###
";

View file

@ -0,0 +1,27 @@
use super::{spawner, BuilderMap, MetaMapBuilder};
use rltk::RandomNumberGenerator;
pub struct RoomBasedSpawner {}
impl MetaMapBuilder for RoomBasedSpawner {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl RoomBasedSpawner {
#[allow(dead_code)]
pub fn new() -> Box<RoomBasedSpawner> {
Box::new(RoomBasedSpawner {})
}
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
if let Some(rooms) = &build_data.rooms {
for room in rooms.iter().skip(1) {
spawner::spawn_room(&build_data.map, rng, room, build_data.map.depth, &mut build_data.spawn_list);
}
} else {
panic!("RoomBasedSpawner only works after rooms have been created");
}
}
}

View file

@ -0,0 +1,28 @@
use super::{BuilderMap, MetaMapBuilder, TileType};
use rltk::RandomNumberGenerator;
pub struct RoomBasedStairs {}
impl MetaMapBuilder for RoomBasedStairs {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl RoomBasedStairs {
#[allow(dead_code)]
pub fn new() -> Box<RoomBasedStairs> {
Box::new(RoomBasedStairs {})
}
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
if let Some(rooms) = &build_data.rooms {
let stairs_position = rooms[rooms.len() - 1].centre();
let stairs_idx = build_data.map.xy_idx(stairs_position.0, stairs_position.1);
build_data.map.tiles[stairs_idx] = TileType::DownStair;
build_data.take_snapshot();
} else {
panic!("RoomBasedStairs only works after rooms have been created");
}
}
}

View file

@ -0,0 +1,26 @@
use super::{BuilderMap, MetaMapBuilder, Position};
use rltk::RandomNumberGenerator;
pub struct RoomBasedStartingPosition {}
impl MetaMapBuilder for RoomBasedStartingPosition {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl RoomBasedStartingPosition {
#[allow(dead_code)]
pub fn new() -> Box<RoomBasedStartingPosition> {
Box::new(RoomBasedStartingPosition {})
}
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
if let Some(rooms) = &build_data.rooms {
let start_pos = rooms[0].centre();
build_data.starting_position = Some(Position { x: start_pos.0, y: start_pos.1 });
} else {
panic!("RoomBasedStartingPosition only works after rooms have been created");
}
}
}

View file

@ -1,107 +1,59 @@
use super::{ use super::{apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, BuilderMap, InitialMapBuilder, Rect};
apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, spawner, Map, MapBuilder, Position, Rect,
TileType, SHOW_MAPGEN,
};
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
pub struct SimpleMapBuilder { pub struct SimpleMapBuilder {}
map: Map,
starting_position: Position,
depth: i32,
rooms: Vec<Rect>,
history: Vec<Map>,
spawn_list: Vec<(usize, String)>,
}
impl MapBuilder for SimpleMapBuilder { impl InitialMapBuilder for SimpleMapBuilder {
fn build_map(&mut self, rng: &mut RandomNumberGenerator) { #[allow(dead_code)]
return self.rooms_and_corridors(rng); fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
} self.rooms_and_corridors(rng, build_data);
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
return &self.spawn_list;
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
} }
} }
impl SimpleMapBuilder { impl SimpleMapBuilder {
#[allow(dead_code)] #[allow(dead_code)]
pub fn new(new_depth: i32) -> SimpleMapBuilder { pub fn new() -> Box<SimpleMapBuilder> {
SimpleMapBuilder { Box::new(SimpleMapBuilder {})
map: Map::new(new_depth),
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
rooms: Vec::new(),
history: Vec::new(),
spawn_list: Vec::new(),
}
} }
fn rooms_and_corridors(&mut self, rng: &mut RandomNumberGenerator) { fn rooms_and_corridors(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
const MAX_ROOMS: i32 = 30; const MAX_ROOMS: i32 = 30;
const MIN_SIZE: i32 = 6; const MIN_SIZE: i32 = 6;
const MAX_SIZE: i32 = 10; const MAX_SIZE: i32 = 10;
let mut rooms: Vec<Rect> = Vec::new();
for _i in 0..MAX_ROOMS { for _i in 0..MAX_ROOMS {
let w = rng.range(MIN_SIZE, MAX_SIZE); let w = rng.range(MIN_SIZE, MAX_SIZE);
let h = rng.range(MIN_SIZE, MAX_SIZE); let h = rng.range(MIN_SIZE, MAX_SIZE);
let x = rng.roll_dice(1, self.map.width - w - 1) - 1; let x = rng.roll_dice(1, build_data.map.width - w - 1) - 1;
let y = rng.roll_dice(1, self.map.height - h - 1) - 1; let y = rng.roll_dice(1, build_data.map.height - h - 1) - 1;
let new_room = Rect::new(x, y, w, h); let new_room = Rect::new(x, y, w, h);
let mut ok = true; let mut ok = true;
for other_room in self.rooms.iter() { for other_room in rooms.iter() {
if new_room.intersect(other_room) { if new_room.intersect(other_room) {
ok = false ok = false
} }
} }
if ok { if ok {
apply_room_to_map(&mut self.map, &new_room); apply_room_to_map(&mut build_data.map, &new_room);
build_data.take_snapshot();
if !self.rooms.is_empty() { if !rooms.is_empty() {
let (new_x, new_y) = new_room.centre(); let (new_x, new_y) = new_room.centre();
let (prev_x, prev_y) = self.rooms[self.rooms.len() - 1].centre(); let (prev_x, prev_y) = rooms[rooms.len() - 1].centre();
if rng.range(0, 2) == 1 { if rng.range(0, 2) == 1 {
apply_horizontal_tunnel(&mut self.map, prev_x, new_x, prev_y); apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, prev_y);
apply_vertical_tunnel(&mut self.map, prev_y, new_y, new_x); apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, new_x);
} else { } else {
apply_vertical_tunnel(&mut self.map, prev_y, new_y, prev_x); apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, prev_x);
apply_horizontal_tunnel(&mut self.map, prev_x, new_x, new_y); apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, new_y);
} }
} }
self.rooms.push(new_room); rooms.push(new_room);
self.take_snapshot(); build_data.take_snapshot();
} }
} }
build_data.rooms = Some(rooms);
let stairs_position = self.rooms[self.rooms.len() - 1].centre();
let stairs_idx = self.map.xy_idx(stairs_position.0, stairs_position.1);
self.map.tiles[stairs_idx] = TileType::DownStair;
let start_pos = self.rooms[0].centre();
self.starting_position = Position { x: start_pos.0, y: start_pos.1 };
// Spawn entities
for room in self.rooms.iter().skip(1) {
spawner::spawn_room(&self.map, rng, room, self.depth, &mut self.spawn_list);
}
} }
} }

View file

@ -1,12 +1,8 @@
use super::{ use super::{BuilderMap, InitialMapBuilder, TileType};
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
Position, TileType, SHOW_MAPGEN,
};
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
use std::collections::HashMap;
#[allow(dead_code)]
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
#[allow(dead_code)]
pub enum DistanceAlgorithm { pub enum DistanceAlgorithm {
Pythagoras, Pythagoras,
Manhattan, Manhattan,
@ -14,94 +10,42 @@ pub enum DistanceAlgorithm {
} }
pub struct VoronoiBuilder { pub struct VoronoiBuilder {
map: Map,
starting_position: Position,
depth: i32,
history: Vec<Map>,
noise_areas: HashMap<i32, Vec<usize>>,
n_seeds: usize, n_seeds: usize,
distance_algorithm: DistanceAlgorithm, distance_algorithm: DistanceAlgorithm,
spawn_list: Vec<(usize, String)>,
} }
impl MapBuilder for VoronoiBuilder { impl InitialMapBuilder for VoronoiBuilder {
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
return self.build(rng);
}
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
return &self.spawn_list;
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
}
}
#[allow(dead_code)]
impl VoronoiBuilder {
pub fn pythagoras(new_depth: i32) -> VoronoiBuilder {
VoronoiBuilder {
map: Map::new(new_depth),
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
n_seeds: 64,
distance_algorithm: DistanceAlgorithm::Pythagoras,
spawn_list: Vec::new(),
}
}
pub fn manhattan(new_depth: i32) -> VoronoiBuilder {
VoronoiBuilder {
map: Map::new(new_depth),
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
n_seeds: 64,
distance_algorithm: DistanceAlgorithm::Manhattan,
spawn_list: Vec::new(),
}
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn chebyshev(new_depth: i32) -> VoronoiBuilder { fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
VoronoiBuilder { self.build(rng, build_data);
map: Map::new(new_depth), }
starting_position: Position { x: 0, y: 0 }, }
depth: new_depth,
history: Vec::new(), impl VoronoiBuilder {
noise_areas: HashMap::new(), #[allow(dead_code)]
n_seeds: 64, pub fn new() -> Box<VoronoiBuilder> {
distance_algorithm: DistanceAlgorithm::Chebyshev, Box::new(VoronoiBuilder { n_seeds: 64, distance_algorithm: DistanceAlgorithm::Pythagoras })
spawn_list: Vec::new(), }
}
#[allow(dead_code)]
pub fn pythagoras() -> Box<VoronoiBuilder> {
Box::new(VoronoiBuilder { n_seeds: 64, distance_algorithm: DistanceAlgorithm::Pythagoras })
}
#[allow(dead_code)]
pub fn manhattan() -> Box<VoronoiBuilder> {
Box::new(VoronoiBuilder { n_seeds: 64, distance_algorithm: DistanceAlgorithm::Manhattan })
} }
#[allow(clippy::map_entry)] #[allow(clippy::map_entry)]
fn build(&mut self, rng: &mut RandomNumberGenerator) { fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
// Make a Voronoi diagram. We'll do this the hard way to learn about the technique! // Make a Voronoi diagram. We'll do this the hard way to learn about the technique!
let mut voronoi_seeds: Vec<(usize, rltk::Point)> = Vec::new(); let mut voronoi_seeds: Vec<(usize, rltk::Point)> = Vec::new();
while voronoi_seeds.len() < self.n_seeds { while voronoi_seeds.len() < self.n_seeds {
let vx = rng.roll_dice(1, self.map.width - 1); let vx = rng.roll_dice(1, build_data.map.width - 1);
let vy = rng.roll_dice(1, self.map.height - 1); let vy = rng.roll_dice(1, build_data.map.height - 1);
let vidx = self.map.xy_idx(vx, vy); let vidx = build_data.map.xy_idx(vx, vy);
let candidate = (vidx, rltk::Point::new(vx, vy)); let candidate = (vidx, rltk::Point::new(vx, vy));
if !voronoi_seeds.contains(&candidate) { if !voronoi_seeds.contains(&candidate) {
voronoi_seeds.push(candidate); voronoi_seeds.push(candidate);
@ -109,10 +53,10 @@ impl VoronoiBuilder {
} }
let mut voronoi_distance = vec![(0, 0.0f32); self.n_seeds]; let mut voronoi_distance = vec![(0, 0.0f32); self.n_seeds];
let mut voronoi_membership: Vec<i32> = vec![0; self.map.width as usize * self.map.height as usize]; let mut voronoi_membership: Vec<i32> = vec![0; build_data.map.width as usize * build_data.map.height as usize];
for (i, vid) in voronoi_membership.iter_mut().enumerate() { for (i, vid) in voronoi_membership.iter_mut().enumerate() {
let x = i as i32 % self.map.width; let x = i as i32 % build_data.map.width;
let y = i as i32 / self.map.width; let y = i as i32 / build_data.map.width;
for (seed, pos) in voronoi_seeds.iter().enumerate() { for (seed, pos) in voronoi_seeds.iter().enumerate() {
let distance; let distance;
@ -135,52 +79,29 @@ impl VoronoiBuilder {
*vid = voronoi_distance[0].0 as i32; *vid = voronoi_distance[0].0 as i32;
} }
for y in 1..self.map.height - 1 { for y in 1..build_data.map.height - 1 {
for x in 1..self.map.width - 1 { for x in 1..build_data.map.width - 1 {
let mut neighbors = 0; let mut neighbors = 0;
let my_idx = self.map.xy_idx(x, y); let my_idx = build_data.map.xy_idx(x, y);
let my_seed = voronoi_membership[my_idx]; let my_seed = voronoi_membership[my_idx];
if voronoi_membership[self.map.xy_idx(x - 1, y)] != my_seed { if voronoi_membership[build_data.map.xy_idx(x - 1, y)] != my_seed {
neighbors += 1; neighbors += 1;
} }
if voronoi_membership[self.map.xy_idx(x + 1, y)] != my_seed { if voronoi_membership[build_data.map.xy_idx(x + 1, y)] != my_seed {
neighbors += 1; neighbors += 1;
} }
if voronoi_membership[self.map.xy_idx(x, y - 1)] != my_seed { if voronoi_membership[build_data.map.xy_idx(x, y - 1)] != my_seed {
neighbors += 1; neighbors += 1;
} }
if voronoi_membership[self.map.xy_idx(x, y + 1)] != my_seed { if voronoi_membership[build_data.map.xy_idx(x, y + 1)] != my_seed {
neighbors += 1; neighbors += 1;
} }
if neighbors < 2 { if neighbors < 2 {
self.map.tiles[my_idx] = TileType::Floor; build_data.map.tiles[my_idx] = TileType::Floor;
} }
} }
self.take_snapshot(); build_data.take_snapshot();
}
// Find a starting point; start at the middle and walk left until we find an open tile
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 };
let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
while self.map.tiles[start_idx] != TileType::Floor {
self.starting_position.x -= 1;
start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
}
self.take_snapshot();
// Find all tiles we can reach from the starting point
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
self.take_snapshot();
// Place the stairs
self.map.tiles[exit_tile] = TileType::DownStair;
self.take_snapshot();
// Now we build a noise map for use in spawning entities later
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
// Spawn the entities
for area in self.noise_areas.iter() {
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
} }
} }
} }

View file

@ -0,0 +1,48 @@
use super::{spawner, BuilderMap, MetaMapBuilder, TileType};
use rltk::RandomNumberGenerator;
use std::collections::HashMap;
pub struct VoronoiSpawning {}
impl MetaMapBuilder for VoronoiSpawning {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl VoronoiSpawning {
#[allow(dead_code)]
pub fn new() -> Box<VoronoiSpawning> {
Box::new(VoronoiSpawning {})
}
#[allow(clippy::map_entry)]
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let mut noise_areas: HashMap<i32, Vec<usize>> = HashMap::new();
let mut noise = rltk::FastNoise::seeded(rng.roll_dice(1, 65536) as u64);
noise.set_noise_type(rltk::NoiseType::Cellular);
noise.set_frequency(0.08);
noise.set_cellular_distance_function(rltk::CellularDistanceFunction::Manhattan);
for y in 1..build_data.map.height - 1 {
for x in 1..build_data.map.width - 1 {
let idx = build_data.map.xy_idx(x, y);
if build_data.map.tiles[idx] == TileType::Floor {
let cell_value_f = noise.get_noise(x as f32, y as f32) * 10240.0;
let cell_value = cell_value_f as i32;
if noise_areas.contains_key(&cell_value) {
noise_areas.get_mut(&cell_value).unwrap().push(idx);
} else {
noise_areas.insert(cell_value, vec![idx]);
}
}
}
}
// Spawn the entities
for area in noise_areas.iter() {
spawner::spawn_region(&build_data.map, rng, area.1, build_data.map.depth, &mut build_data.spawn_list);
}
}
}

View file

@ -1,154 +1,76 @@
use super::{ use super::{BuilderMap, Map, MetaMapBuilder, TileType};
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
Position, TileType, SHOW_MAPGEN,
};
mod common;
use common::MapChunk;
mod constraints;
mod solver;
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
use solver::Solver; mod common;
use std::collections::HashMap; use common::*;
mod constraints;
use constraints::*;
mod solver;
use solver::*;
pub struct WaveFunctionCollapseBuilder { /// Provides a map builder using the Wave Function Collapse algorithm.
map: Map, pub struct WaveFunctionCollapseBuilder {}
starting_position: Position,
depth: i32,
history: Vec<Map>,
noise_areas: HashMap<i32, Vec<usize>>,
derive_from: Option<Box<dyn MapBuilder>>,
spawn_list: Vec<(usize, String)>,
}
impl MapBuilder for WaveFunctionCollapseBuilder { impl MetaMapBuilder for WaveFunctionCollapseBuilder {
fn build_map(&mut self, rng: &mut RandomNumberGenerator) { fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
return self.build(rng); self.build(rng, build_data);
}
// Getters
fn get_map(&mut self) -> Map {
return self.map.clone();
}
fn get_starting_pos(&mut self) -> Position {
return self.starting_position.clone();
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
return &self.spawn_list;
}
// Mapgen visualisation stuff
fn get_snapshot_history(&self) -> Vec<Map> {
return self.history.clone();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
} }
} }
#[allow(dead_code)]
impl WaveFunctionCollapseBuilder { impl WaveFunctionCollapseBuilder {
pub fn new(new_depth: i32, derive_from: Option<Box<dyn MapBuilder>>) -> WaveFunctionCollapseBuilder { /// Constructor for wfc.
WaveFunctionCollapseBuilder { #[allow(dead_code)]
map: Map::new(new_depth), pub fn new() -> Box<WaveFunctionCollapseBuilder> {
starting_position: Position { x: 0, y: 0 }, Box::new(WaveFunctionCollapseBuilder {})
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
derive_from,
spawn_list: Vec::new(),
}
} }
pub fn derived_map(new_depth: i32, builder: Box<dyn MapBuilder>) -> WaveFunctionCollapseBuilder { fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
WaveFunctionCollapseBuilder::new(new_depth, Some(builder))
}
fn build(&mut self, rng: &mut RandomNumberGenerator) {
const CHUNK_SIZE: i32 = 8; const CHUNK_SIZE: i32 = 8;
build_data.take_snapshot();
let prebuilder = &mut self.derive_from.as_mut().unwrap(); let patterns = build_patterns(&build_data.map, CHUNK_SIZE, true, true);
prebuilder.build_map(rng); let constraints = patterns_to_constraints(patterns, CHUNK_SIZE);
self.map = prebuilder.get_map(); self.render_tile_gallery(&constraints, CHUNK_SIZE, build_data);
self.history = prebuilder.get_snapshot_history();
for t in self.map.tiles.iter_mut() {
if *t == TileType::DownStair {
*t = TileType::Floor;
}
}
self.take_snapshot();
let patterns = constraints::build_patterns(&self.map, CHUNK_SIZE, true, true); build_data.map = Map::new(build_data.map.depth);
let constraints = common::patterns_to_constraints(patterns, CHUNK_SIZE);
self.render_tile_gallery(&constraints, CHUNK_SIZE);
// Call solver
self.map = Map::new(self.depth);
loop { loop {
let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &self.map); let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &build_data.map);
while !solver.iteration(&mut self.map, rng) { while !solver.iteration(&mut build_data.map, rng) {
self.take_snapshot(); build_data.take_snapshot();
} }
self.take_snapshot(); build_data.take_snapshot();
if solver.possible { if solver.possible {
break; break;
} } // If it has hit an impossible condition, try again
}
// Find a starting point; start at the middle and walk left until we find an open tile
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 };
let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
while self.map.tiles[start_idx] != TileType::Floor {
self.starting_position.x -= 1;
start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
}
self.take_snapshot();
// Find all tiles we can reach from the starting point
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
self.take_snapshot();
// Place the stairs
self.map.tiles[exit_tile] = TileType::DownStair;
self.take_snapshot();
// Now we build a noise map for use in spawning entities later
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
// Spawn the entities
for area in self.noise_areas.iter() {
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
} }
build_data.spawn_list.clear();
} }
fn render_tile_gallery(&mut self, constraints: &Vec<MapChunk>, chunk_size: i32) { fn render_tile_gallery(&mut self, constraints: &[MapChunk], chunk_size: i32, build_data: &mut BuilderMap) {
self.map = Map::new(0); build_data.map = Map::new(0);
let mut counter = 0; let mut counter = 0;
let mut x = 1; let mut x = 1;
let mut y = 1; let mut y = 1;
while counter < constraints.len() { while counter < constraints.len() {
constraints::render_pattern_to_map(&mut self.map, &constraints[counter], chunk_size, x, y); render_pattern_to_map(&mut build_data.map, &constraints[counter], chunk_size, x, y);
x += chunk_size + 1; x += chunk_size + 1;
if x + chunk_size > self.map.width { if x + chunk_size > build_data.map.width {
// Next row // Move to the next row
x = 1; x = 1;
y += chunk_size + 1; y += chunk_size + 1;
self.take_snapshot();
if y + chunk_size > self.map.height { if y + chunk_size > build_data.map.height {
// Next page // Move to the next page
self.take_snapshot(); build_data.take_snapshot();
self.map = Map::new(0); build_data.map = Map::new(0);
x = 1; x = 1;
y = 1; y = 1;
} }
} }
counter += 1; counter += 1;
} }
self.take_snapshot(); build_data.take_snapshot();
} }
} }

View file

@ -170,7 +170,7 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
// 20 mobs : 6 items : 2 food : 1 trap // 20 mobs : 6 items : 2 food : 1 trap
fn category_table() -> RandomTable { fn category_table() -> RandomTable {
return RandomTable::new().add("mob", 20).add("item", 6).add("food", 2).add("trap", 1); return RandomTable::new().add("mob", 12).add("item", 6).add("food", 2).add("trap", 1);
} }
fn debug_table() -> RandomTable { fn debug_table() -> RandomTable {