refactors mapgen into chained builders
This commit is contained in:
parent
8a5600267c
commit
dd367dc39b
22 changed files with 1381 additions and 1480 deletions
18
src/main.rs
18
src/main.rs
|
|
@ -78,19 +78,19 @@ impl State {
|
|||
self.mapgen_index = 0;
|
||||
self.mapgen_timer = 0.0;
|
||||
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;
|
||||
// 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>();
|
||||
*worldmap_resource = builder.get_map();
|
||||
player_start = builder.get_starting_pos();
|
||||
// Spawn entities
|
||||
*worldmap_resource = builder.build_data.map.clone();
|
||||
// Unwrap so we get a CTD if there's no starting pos.
|
||||
player_start = builder.build_data.starting_position.as_mut().unwrap().clone();
|
||||
}
|
||||
// Spawn entities
|
||||
builder.spawn_entities(&mut self.ecs);
|
||||
|
||||
// Place player and update resources
|
||||
|
|
|
|||
73
src/map_builders/area_starting_points.rs
Normal file
73
src/map_builders/area_starting_points.rs
Normal 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 });
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
pub struct BspDungeonBuilder {
|
||||
map: Map,
|
||||
starting_position: Position,
|
||||
depth: i32,
|
||||
rooms: Vec<Rect>,
|
||||
history: Vec<Map>,
|
||||
rects: Vec<Rect>,
|
||||
spawn_list: Vec<(usize, String)>,
|
||||
}
|
||||
|
||||
impl MapBuilder for BspDungeonBuilder {
|
||||
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 InitialMapBuilder for BspDungeonBuilder {
|
||||
#[allow(dead_code)]
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
self.build(rng, build_data);
|
||||
}
|
||||
}
|
||||
|
||||
impl BspDungeonBuilder {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(new_depth: i32) -> BspDungeonBuilder {
|
||||
BspDungeonBuilder {
|
||||
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(),
|
||||
}
|
||||
pub fn new() -> Box<BspDungeonBuilder> {
|
||||
Box::new(BspDungeonBuilder { rects: 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.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];
|
||||
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
|
||||
// to place a room in there, place it and add it to the rooms list.
|
||||
// Up to 240 times, we get a random rectangle and divide it. If its possible to squeeze a
|
||||
// room in there, we place it and add it to the rooms list.
|
||||
let mut n_rooms = 0;
|
||||
while n_rooms < 240 {
|
||||
let rect = self.get_random_rect(&mut rng);
|
||||
let candidate = self.get_random_subrect(rect, &mut rng);
|
||||
let rect = self.get_random_rect(rng);
|
||||
let candidate = self.get_random_sub_rect(rect, rng);
|
||||
|
||||
if self.is_possible(candidate) {
|
||||
apply_room_to_map(&mut self.map, &candidate);
|
||||
self.rooms.push(candidate);
|
||||
if self.is_possible(candidate, &build_data.map) {
|
||||
apply_room_to_map(&mut build_data.map, &candidate);
|
||||
rooms.push(candidate);
|
||||
self.add_subrects(rect);
|
||||
self.take_snapshot();
|
||||
build_data.take_snapshot();
|
||||
}
|
||||
|
||||
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.
|
||||
self.rooms.sort_by(|a, b| a.x1.cmp(&b.x1));
|
||||
// Now we sort the rooms
|
||||
rooms.sort_by(|a, b| a.x1.cmp(&b.x1));
|
||||
|
||||
// Corridors
|
||||
for i in 0..self.rooms.len() - 1 {
|
||||
let room = self.rooms[i];
|
||||
let next_room = self.rooms[i + 1];
|
||||
// Now we want corridors
|
||||
for i in 0..rooms.len() - 1 {
|
||||
let room = rooms[i];
|
||||
let next_room = rooms[i + 1];
|
||||
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
|
||||
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
|
||||
let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
|
||||
let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
|
||||
self.draw_corridor(start_x, start_y, end_x, end_y);
|
||||
self.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);
|
||||
draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y);
|
||||
build_data.take_snapshot();
|
||||
}
|
||||
build_data.rooms = Some(rooms);
|
||||
}
|
||||
|
||||
fn add_subrects(&mut self, rect: Rect) {
|
||||
let w = i32::abs(rect.x1 - rect.x2);
|
||||
let h = i32::abs(rect.y1 - rect.y2);
|
||||
let half_w = i32::max(w / 2, 1);
|
||||
let half_h = i32::max(h / 2, 1);
|
||||
let width = i32::abs(rect.x1 - rect.x2);
|
||||
let height = i32::abs(rect.y1 - rect.y2);
|
||||
let half_width = i32::max(width / 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_h, half_w, half_h));
|
||||
self.rects.push(Rect::new(rect.x1 + half_w, rect.y1, half_w, half_h));
|
||||
self.rects.push(Rect::new(rect.x1 + half_w, rect.y1 + half_h, 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_height, half_width, half_height));
|
||||
self.rects.push(Rect::new(rect.x1 + half_width, rect.y1, half_width, half_height));
|
||||
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 {
|
||||
|
|
@ -121,26 +76,26 @@ impl BspDungeonBuilder {
|
|||
return self.rects[0];
|
||||
}
|
||||
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 rect_width = i32::abs(rect.x1 - rect.x2);
|
||||
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 h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 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, 10)) - 1) + 1;
|
||||
|
||||
result.x1 += rng.roll_dice(1, 6) - 1;
|
||||
result.y1 += rng.roll_dice(1, 6) - 1;
|
||||
result.x2 = result.x1 + w;
|
||||
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;
|
||||
expanded.x1 -= 2;
|
||||
expanded.x2 += 2;
|
||||
|
|
@ -151,10 +106,10 @@ impl BspDungeonBuilder {
|
|||
|
||||
for y in expanded.y1..=expanded.y2 {
|
||||
for x in expanded.x1..=expanded.x2 {
|
||||
if x > self.map.width - 2 {
|
||||
if x > map.width - 2 {
|
||||
can_build = false;
|
||||
}
|
||||
if y > self.map.height - 2 {
|
||||
if y > map.height - 2 {
|
||||
can_build = false;
|
||||
}
|
||||
if x < 1 {
|
||||
|
|
@ -164,34 +119,14 @@ impl BspDungeonBuilder {
|
|||
can_build = false;
|
||||
}
|
||||
if can_build {
|
||||
let idx = self.map.xy_idx(x, y);
|
||||
if self.map.tiles[idx] != TileType::Wall {
|
||||
let idx = map.xy_idx(x, y);
|
||||
if map.tiles[idx] != TileType::Wall {
|
||||
can_build = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 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;
|
||||
}
|
||||
can_build
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
const MIN_ROOM_SIZE: i32 = 8;
|
||||
|
||||
pub struct BspInteriorBuilder {
|
||||
map: Map,
|
||||
starting_position: Position,
|
||||
depth: i32,
|
||||
rooms: Vec<Rect>,
|
||||
history: Vec<Map>,
|
||||
rects: Vec<Rect>,
|
||||
spawn_list: Vec<(usize, String)>,
|
||||
}
|
||||
|
||||
impl MapBuilder for BspInteriorBuilder {
|
||||
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 InitialMapBuilder for BspInteriorBuilder {
|
||||
#[allow(dead_code)]
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
self.build(rng, build_data);
|
||||
}
|
||||
}
|
||||
|
||||
impl BspInteriorBuilder {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(new_depth: i32) -> BspInteriorBuilder {
|
||||
BspInteriorBuilder {
|
||||
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(),
|
||||
}
|
||||
pub fn new() -> Box<BspInteriorBuilder> {
|
||||
Box::new(BspInteriorBuilder { rects: 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.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];
|
||||
self.add_subrects(first_room, rng); // Divide the first room
|
||||
|
||||
let rooms = self.rects.clone();
|
||||
for r in rooms.iter() {
|
||||
let rooms_copy = self.rects.clone();
|
||||
for r in rooms_copy.iter() {
|
||||
let room = *r;
|
||||
self.rooms.push(room);
|
||||
//room.x2 -= 1;
|
||||
//room.y2 -= 1;
|
||||
rooms.push(room);
|
||||
for y in room.y1..room.y2 {
|
||||
for x in room.x1..room.x2 {
|
||||
let idx = self.map.xy_idx(x, y);
|
||||
if idx > 0 && idx < ((self.map.width * self.map.height) - 1) as usize {
|
||||
self.map.tiles[idx] = TileType::Floor;
|
||||
let idx = build_data.map.xy_idx(x, y);
|
||||
if idx > 0 && idx < ((build_data.map.width * build_data.map.height) - 1) as usize {
|
||||
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
|
||||
for i in 0..self.rooms.len() - 1 {
|
||||
let room = self.rooms[i];
|
||||
let next_room = self.rooms[i + 1];
|
||||
for i in 0..rooms.len() - 1 {
|
||||
let room = rooms[i];
|
||||
let next_room = rooms[i + 1];
|
||||
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
|
||||
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
|
||||
let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
|
||||
let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
|
||||
self.draw_corridor(start_x, start_y, end_x, end_y);
|
||||
self.take_snapshot();
|
||||
draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y);
|
||||
build_data.take_snapshot();
|
||||
}
|
||||
|
||||
// Don't forget the 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, rng: &mut RandomNumberGenerator) {
|
||||
const MIN_ROOM_SIZE: i32 = 6;
|
||||
// Remove last rect
|
||||
// Remove the last rect from the list
|
||||
if !self.rects.is_empty() {
|
||||
self.rects.remove(self.rects.len() - 1);
|
||||
}
|
||||
|
||||
// Calc bounds
|
||||
let w = rect.x2 - rect.x1;
|
||||
let h = rect.y2 - rect.y1;
|
||||
let half_w = w / 2;
|
||||
let half_h = h / 2;
|
||||
// Calculate boundaries
|
||||
let width = rect.x2 - rect.x1;
|
||||
let height = rect.y2 - rect.y1;
|
||||
let half_width = width / 2;
|
||||
let half_height = height / 2;
|
||||
|
||||
let split = rng.roll_dice(1, 4);
|
||||
|
||||
if split <= 2 {
|
||||
// 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);
|
||||
if half_w > MIN_ROOM_SIZE {
|
||||
if half_width > MIN_ROOM_SIZE {
|
||||
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);
|
||||
if half_w > MIN_ROOM_SIZE {
|
||||
if half_width > MIN_ROOM_SIZE {
|
||||
self.add_subrects(h2, rng);
|
||||
}
|
||||
} else {
|
||||
// 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);
|
||||
if half_h > MIN_ROOM_SIZE {
|
||||
if half_height > MIN_ROOM_SIZE {
|
||||
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);
|
||||
if half_h > MIN_ROOM_SIZE {
|
||||
if half_height > MIN_ROOM_SIZE {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,143 +1,80 @@
|
|||
use super::{
|
||||
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
|
||||
Position, TileType, SHOW_MAPGEN,
|
||||
};
|
||||
use super::{BuilderMap, InitialMapBuilder, TileType};
|
||||
use rltk::RandomNumberGenerator;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const PASSES: i32 = 15;
|
||||
pub struct CellularAutomataBuilder {}
|
||||
|
||||
pub struct CellularAutomataBuilder {
|
||||
map: Map,
|
||||
starting_position: Position,
|
||||
depth: i32,
|
||||
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 InitialMapBuilder for CellularAutomataBuilder {
|
||||
#[allow(dead_code)]
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
self.build(rng, build_data);
|
||||
}
|
||||
}
|
||||
|
||||
impl CellularAutomataBuilder {
|
||||
pub fn new(new_depth: i32) -> CellularAutomataBuilder {
|
||||
CellularAutomataBuilder {
|
||||
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(dead_code)]
|
||||
pub fn new() -> Box<CellularAutomataBuilder> {
|
||||
Box::new(CellularAutomataBuilder {})
|
||||
}
|
||||
|
||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
||||
// Set 55% of map to floor
|
||||
for y in 1..self.map.height - 1 {
|
||||
for x in 1..self.map.width - 1 {
|
||||
#[allow(clippy::map_entry)]
|
||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
// First we completely randomize the map, setting 55% of it to be floor.
|
||||
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 idx = self.map.xy_idx(x, y);
|
||||
let idx = build_data.map.xy_idx(x, y);
|
||||
if roll > 55 {
|
||||
self.map.tiles[idx] = TileType::Floor
|
||||
build_data.map.tiles[idx] = TileType::Floor
|
||||
} 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
|
||||
for _i in 0..PASSES {
|
||||
let mut newtiles = self.map.tiles.clone();
|
||||
// Now we iteratively apply cellular automata rules
|
||||
for _i in 0..15 {
|
||||
let mut newtiles = build_data.map.tiles.clone();
|
||||
|
||||
for y in 1..self.map.height - 1 {
|
||||
for x in 1..self.map.width - 1 {
|
||||
let idx = self.map.xy_idx(x, y);
|
||||
let mut neighbours = 0;
|
||||
if self.map.tiles[idx - 1] == TileType::Wall {
|
||||
neighbours += 1;
|
||||
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);
|
||||
let mut neighbors = 0;
|
||||
if build_data.map.tiles[idx - 1] == TileType::Wall {
|
||||
neighbors += 1;
|
||||
}
|
||||
if self.map.tiles[idx + 1] == TileType::Wall {
|
||||
neighbours += 1;
|
||||
if build_data.map.tiles[idx + 1] == TileType::Wall {
|
||||
neighbors += 1;
|
||||
}
|
||||
if self.map.tiles[idx - self.map.width as usize] == TileType::Wall {
|
||||
neighbours += 1;
|
||||
if build_data.map.tiles[idx - build_data.map.width as usize] == TileType::Wall {
|
||||
neighbors += 1;
|
||||
}
|
||||
if self.map.tiles[idx + self.map.width as usize] == TileType::Wall {
|
||||
neighbours += 1;
|
||||
if build_data.map.tiles[idx + build_data.map.width as usize] == TileType::Wall {
|
||||
neighbors += 1;
|
||||
}
|
||||
if self.map.tiles[idx - (self.map.width as usize - 1)] == TileType::Wall {
|
||||
neighbours += 1;
|
||||
if build_data.map.tiles[idx - (build_data.map.width as usize - 1)] == TileType::Wall {
|
||||
neighbors += 1;
|
||||
}
|
||||
if self.map.tiles[idx - (self.map.width as usize + 1)] == TileType::Wall {
|
||||
neighbours += 1;
|
||||
if build_data.map.tiles[idx - (build_data.map.width as usize + 1)] == TileType::Wall {
|
||||
neighbors += 1;
|
||||
}
|
||||
if self.map.tiles[idx + (self.map.width as usize - 1)] == TileType::Wall {
|
||||
neighbours += 1;
|
||||
if build_data.map.tiles[idx + (build_data.map.width as usize - 1)] == TileType::Wall {
|
||||
neighbors += 1;
|
||||
}
|
||||
if self.map.tiles[idx + (self.map.width as usize + 1)] == TileType::Wall {
|
||||
neighbours += 1;
|
||||
if build_data.map.tiles[idx + (build_data.map.width as usize + 1)] == TileType::Wall {
|
||||
neighbors += 1;
|
||||
}
|
||||
|
||||
if neighbours > 4 || neighbours == 0 {
|
||||
if neighbors > 4 || neighbors == 0 {
|
||||
newtiles[idx] = TileType::Wall;
|
||||
} else {
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
build_data.map.tiles = newtiles.clone();
|
||||
build_data.take_snapshot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use super::{Map, Rect, TileType};
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn apply_room_to_map(map: &mut Map, room: &Rect) {
|
||||
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 {
|
||||
map.populate_blocked();
|
||||
let map_starts: Vec<usize> = vec![start_idx];
|
||||
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);
|
||||
for (i, tile) in 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;
|
||||
} else {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
pub fn draw_corridor(map: &mut Map, 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 = 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)]
|
||||
|
|
|
|||
40
src/map_builders/cull_unreachable.rs
Normal file
40
src/map_builders/cull_unreachable.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/map_builders/distant_exit.rs
Normal file
49
src/map_builders/distant_exit.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,8 @@
|
|||
use super::{
|
||||
common::Symmetry, generate_voronoi_spawn_regions, paint, remove_unreachable_areas_returning_most_distant, spawner,
|
||||
Map, MapBuilder, Position, TileType, SHOW_MAPGEN,
|
||||
};
|
||||
use super::{paint, BuilderMap, InitialMapBuilder, Position, Symmetry, TileType};
|
||||
use rltk::RandomNumberGenerator;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum DLAAlgorithm {
|
||||
WalkInwards,
|
||||
WalkOutwards,
|
||||
|
|
@ -14,134 +10,95 @@ pub enum DLAAlgorithm {
|
|||
}
|
||||
|
||||
pub struct DLABuilder {
|
||||
map: Map,
|
||||
starting_position: Position,
|
||||
depth: i32,
|
||||
history: Vec<Map>,
|
||||
noise_areas: HashMap<i32, Vec<usize>>,
|
||||
algorithm: DLAAlgorithm,
|
||||
brush_size: i32,
|
||||
symmetry: Symmetry,
|
||||
floor_percent: f32,
|
||||
spawn_list: Vec<(usize, String)>,
|
||||
}
|
||||
|
||||
impl MapBuilder for DLABuilder {
|
||||
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 InitialMapBuilder for DLABuilder {
|
||||
#[allow(dead_code)]
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
self.build(rng, build_data);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl DLABuilder {
|
||||
pub fn walk_inwards(new_depth: i32) -> DLABuilder {
|
||||
DLABuilder {
|
||||
map: Map::new(new_depth),
|
||||
starting_position: Position { x: 0, y: 0 },
|
||||
depth: new_depth,
|
||||
history: Vec::new(),
|
||||
noise_areas: HashMap::new(),
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Box<DLABuilder> {
|
||||
Box::new(DLABuilder {
|
||||
algorithm: DLAAlgorithm::WalkInwards,
|
||||
brush_size: 2,
|
||||
symmetry: Symmetry::None,
|
||||
floor_percent: 0.25,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn walk_inwards() -> Box<DLABuilder> {
|
||||
Box::new(DLABuilder {
|
||||
algorithm: DLAAlgorithm::WalkInwards,
|
||||
brush_size: 1,
|
||||
symmetry: Symmetry::None,
|
||||
floor_percent: 0.25,
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn walk_outwards(new_depth: i32) -> DLABuilder {
|
||||
DLABuilder {
|
||||
map: Map::new(new_depth),
|
||||
starting_position: Position { x: 0, y: 0 },
|
||||
depth: new_depth,
|
||||
history: Vec::new(),
|
||||
noise_areas: HashMap::new(),
|
||||
#[allow(dead_code)]
|
||||
pub fn walk_outwards() -> Box<DLABuilder> {
|
||||
Box::new(DLABuilder {
|
||||
algorithm: DLAAlgorithm::WalkOutwards,
|
||||
brush_size: 2,
|
||||
symmetry: Symmetry::None,
|
||||
floor_percent: 0.25,
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn central_attractor(new_depth: i32) -> DLABuilder {
|
||||
DLABuilder {
|
||||
map: Map::new(new_depth),
|
||||
starting_position: Position { x: 0, y: 0 },
|
||||
depth: new_depth,
|
||||
history: Vec::new(),
|
||||
noise_areas: HashMap::new(),
|
||||
#[allow(dead_code)]
|
||||
pub fn central_attractor() -> Box<DLABuilder> {
|
||||
Box::new(DLABuilder {
|
||||
algorithm: DLAAlgorithm::CentralAttractor,
|
||||
brush_size: 2,
|
||||
symmetry: Symmetry::None,
|
||||
floor_percent: 0.25,
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insectoid(new_depth: i32) -> DLABuilder {
|
||||
DLABuilder {
|
||||
map: Map::new(new_depth),
|
||||
starting_position: Position { x: 0, y: 0 },
|
||||
depth: new_depth,
|
||||
history: Vec::new(),
|
||||
noise_areas: HashMap::new(),
|
||||
#[allow(dead_code)]
|
||||
pub fn insectoid() -> Box<DLABuilder> {
|
||||
Box::new(DLABuilder {
|
||||
algorithm: DLAAlgorithm::CentralAttractor,
|
||||
brush_size: 2,
|
||||
symmetry: Symmetry::Horizontal,
|
||||
floor_percent: 0.25,
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::map_entry)]
|
||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
||||
// Carve starting seed
|
||||
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 };
|
||||
let start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
||||
self.take_snapshot();
|
||||
self.map.tiles[start_idx] = TileType::Floor;
|
||||
self.map.tiles[start_idx - 1] = TileType::Floor;
|
||||
self.map.tiles[start_idx + 1] = TileType::Floor;
|
||||
self.map.tiles[start_idx - self.map.width as usize] = TileType::Floor;
|
||||
self.map.tiles[start_idx + self.map.width as usize] = TileType::Floor;
|
||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
// Carve a starting seed
|
||||
let starting_position = Position { x: build_data.map.width / 2, y: build_data.map.height / 2 };
|
||||
let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y);
|
||||
build_data.take_snapshot();
|
||||
build_data.map.tiles[start_idx] = TileType::Floor;
|
||||
build_data.map.tiles[start_idx - 1] = TileType::Floor;
|
||||
build_data.map.tiles[start_idx + 1] = TileType::Floor;
|
||||
build_data.map.tiles[start_idx - build_data.map.width as usize] = TileType::Floor;
|
||||
build_data.map.tiles[start_idx + build_data.map.width as usize] = TileType::Floor;
|
||||
|
||||
// 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 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 {
|
||||
match self.algorithm {
|
||||
DLAAlgorithm::WalkInwards => {
|
||||
let mut digger_x = rng.roll_dice(1, self.map.width - 3) + 1;
|
||||
let mut digger_y = rng.roll_dice(1, self.map.height - 3) + 1;
|
||||
let mut digger_x = rng.roll_dice(1, build_data.map.width - 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_y = digger_y;
|
||||
let mut digger_idx = self.map.xy_idx(digger_x, digger_y);
|
||||
while self.map.tiles[digger_idx] == TileType::Wall {
|
||||
let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
||||
while build_data.map.tiles[digger_idx] == TileType::Wall {
|
||||
prev_x = digger_x;
|
||||
prev_y = digger_y;
|
||||
let stagger_direction = rng.roll_dice(1, 4);
|
||||
|
|
@ -152,7 +109,7 @@ impl DLABuilder {
|
|||
}
|
||||
}
|
||||
2 => {
|
||||
if digger_x < self.map.width - 2 {
|
||||
if digger_x < build_data.map.width - 2 {
|
||||
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_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 => {
|
||||
let mut digger_x = self.starting_position.x;
|
||||
let mut digger_y = self.starting_position.y;
|
||||
let mut digger_idx = self.map.xy_idx(digger_x, digger_y);
|
||||
while self.map.tiles[digger_idx] == TileType::Floor {
|
||||
let mut digger_x = starting_position.x;
|
||||
let mut digger_y = starting_position.y;
|
||||
let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
||||
while build_data.map.tiles[digger_idx] == TileType::Floor {
|
||||
let stagger_direction = rng.roll_dice(1, 4);
|
||||
match stagger_direction {
|
||||
1 => {
|
||||
|
|
@ -184,7 +142,7 @@ impl DLABuilder {
|
|||
}
|
||||
}
|
||||
2 => {
|
||||
if digger_x < self.map.width - 2 {
|
||||
if digger_x < build_data.map.width - 2 {
|
||||
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_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 => {
|
||||
let mut digger_x = rng.roll_dice(1, self.map.width - 3) + 1;
|
||||
let mut digger_y = rng.roll_dice(1, self.map.height - 3) + 1;
|
||||
let mut digger_x = rng.roll_dice(1, build_data.map.width - 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_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(
|
||||
rltk::LineAlg::Bresenham,
|
||||
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_y = digger_y;
|
||||
digger_x = path[0].x;
|
||||
digger_y = path[0].y;
|
||||
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
|
||||
self.map.tiles[exit_tile] = TileType::DownStair;
|
||||
self.take_snapshot();
|
||||
build_data.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);
|
||||
floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
use super::{
|
||||
generate_voronoi_spawn_regions, paint, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
|
||||
Position, Symmetry, TileType, SHOW_MAPGEN,
|
||||
};
|
||||
use super::{paint, BuilderMap, InitialMapBuilder, Position, Symmetry, TileType};
|
||||
use rltk::RandomNumberGenerator;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum DrunkSpawnMode {
|
||||
StartingPoint,
|
||||
Random,
|
||||
|
|
@ -20,53 +17,25 @@ pub struct DrunkardSettings {
|
|||
}
|
||||
|
||||
pub struct DrunkardsWalkBuilder {
|
||||
map: Map,
|
||||
starting_position: Position,
|
||||
depth: i32,
|
||||
history: Vec<Map>,
|
||||
noise_areas: HashMap<i32, Vec<usize>>,
|
||||
settings: DrunkardSettings,
|
||||
spawn_list: Vec<(usize, String)>,
|
||||
}
|
||||
|
||||
impl MapBuilder for DrunkardsWalkBuilder {
|
||||
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 InitialMapBuilder for DrunkardsWalkBuilder {
|
||||
#[allow(dead_code)]
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
self.build(rng, build_data);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl DrunkardsWalkBuilder {
|
||||
pub fn open_area(new_depth: i32) -> DrunkardsWalkBuilder {
|
||||
DrunkardsWalkBuilder {
|
||||
map: Map::new(new_depth),
|
||||
starting_position: Position { x: 0, y: 0 },
|
||||
depth: new_depth,
|
||||
history: Vec::new(),
|
||||
noise_areas: HashMap::new(),
|
||||
#[allow(dead_code)]
|
||||
pub fn new(settings: DrunkardSettings) -> DrunkardsWalkBuilder {
|
||||
DrunkardsWalkBuilder { settings }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn open_area() -> Box<DrunkardsWalkBuilder> {
|
||||
Box::new(DrunkardsWalkBuilder {
|
||||
settings: DrunkardSettings {
|
||||
spawn_mode: DrunkSpawnMode::StartingPoint,
|
||||
drunken_lifetime: 400,
|
||||
|
|
@ -74,17 +43,12 @@ impl DrunkardsWalkBuilder {
|
|||
brush_size: 1,
|
||||
symmetry: Symmetry::None,
|
||||
},
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_halls(new_depth: i32) -> DrunkardsWalkBuilder {
|
||||
DrunkardsWalkBuilder {
|
||||
map: Map::new(new_depth),
|
||||
starting_position: Position { x: 0, y: 0 },
|
||||
depth: new_depth,
|
||||
history: Vec::new(),
|
||||
noise_areas: HashMap::new(),
|
||||
#[allow(dead_code)]
|
||||
pub fn open_halls() -> Box<DrunkardsWalkBuilder> {
|
||||
Box::new(DrunkardsWalkBuilder {
|
||||
settings: DrunkardSettings {
|
||||
spawn_mode: DrunkSpawnMode::Random,
|
||||
drunken_lifetime: 400,
|
||||
|
|
@ -92,17 +56,12 @@ impl DrunkardsWalkBuilder {
|
|||
brush_size: 1,
|
||||
symmetry: Symmetry::None,
|
||||
},
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn winding_passages(new_depth: i32) -> DrunkardsWalkBuilder {
|
||||
DrunkardsWalkBuilder {
|
||||
map: Map::new(new_depth),
|
||||
starting_position: Position { x: 0, y: 0 },
|
||||
depth: new_depth,
|
||||
history: Vec::new(),
|
||||
noise_areas: HashMap::new(),
|
||||
#[allow(dead_code)]
|
||||
pub fn winding_passages() -> Box<DrunkardsWalkBuilder> {
|
||||
Box::new(DrunkardsWalkBuilder {
|
||||
settings: DrunkardSettings {
|
||||
spawn_mode: DrunkSpawnMode::Random,
|
||||
drunken_lifetime: 100,
|
||||
|
|
@ -110,17 +69,12 @@ impl DrunkardsWalkBuilder {
|
|||
brush_size: 1,
|
||||
symmetry: Symmetry::None,
|
||||
},
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fat_passages(new_depth: i32) -> DrunkardsWalkBuilder {
|
||||
DrunkardsWalkBuilder {
|
||||
map: Map::new(new_depth),
|
||||
starting_position: Position { x: 0, y: 0 },
|
||||
depth: new_depth,
|
||||
history: Vec::new(),
|
||||
noise_areas: HashMap::new(),
|
||||
#[allow(dead_code)]
|
||||
pub fn fat_passages() -> Box<DrunkardsWalkBuilder> {
|
||||
Box::new(DrunkardsWalkBuilder {
|
||||
settings: DrunkardSettings {
|
||||
spawn_mode: DrunkSpawnMode::Random,
|
||||
drunken_lifetime: 100,
|
||||
|
|
@ -128,17 +82,12 @@ impl DrunkardsWalkBuilder {
|
|||
brush_size: 2,
|
||||
symmetry: Symmetry::None,
|
||||
},
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fearful_symmetry(new_depth: i32) -> DrunkardsWalkBuilder {
|
||||
DrunkardsWalkBuilder {
|
||||
map: Map::new(new_depth),
|
||||
starting_position: Position { x: 0, y: 0 },
|
||||
depth: new_depth,
|
||||
history: Vec::new(),
|
||||
noise_areas: HashMap::new(),
|
||||
#[allow(dead_code)]
|
||||
pub fn fearful_symmetry() -> Box<DrunkardsWalkBuilder> {
|
||||
Box::new(DrunkardsWalkBuilder {
|
||||
settings: DrunkardSettings {
|
||||
spawn_mode: DrunkSpawnMode::Random,
|
||||
drunken_lifetime: 100,
|
||||
|
|
@ -146,51 +95,47 @@ impl DrunkardsWalkBuilder {
|
|||
brush_size: 1,
|
||||
symmetry: Symmetry::Both,
|
||||
},
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::map_entry)]
|
||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
||||
// Central starting pos
|
||||
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 };
|
||||
let start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
||||
self.map.tiles[start_idx] = TileType::Floor;
|
||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
// Set a central starting point
|
||||
let starting_position = Position { x: build_data.map.width / 2, y: build_data.map.height / 2 };
|
||||
let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y);
|
||||
build_data.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 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 active_digger_count = 0;
|
||||
|
||||
while floor_tile_count < desired_floor_tiles {
|
||||
let mut did_something = false;
|
||||
let mut drunk_x;
|
||||
let mut drunk_y;
|
||||
match self.settings.spawn_mode {
|
||||
DrunkSpawnMode::StartingPoint => {
|
||||
drunk_x = self.starting_position.x;
|
||||
drunk_y = self.starting_position.y;
|
||||
drunk_x = starting_position.x;
|
||||
drunk_y = starting_position.y;
|
||||
}
|
||||
DrunkSpawnMode::Random => {
|
||||
if digger_count == 0 {
|
||||
drunk_x = self.starting_position.x;
|
||||
drunk_y = self.starting_position.y;
|
||||
drunk_x = starting_position.x;
|
||||
drunk_y = starting_position.y;
|
||||
} else {
|
||||
drunk_x = rng.roll_dice(1, self.map.width - 3) + 1;
|
||||
drunk_y = rng.roll_dice(1, self.map.height - 3) + 1;
|
||||
drunk_x = rng.roll_dice(1, build_data.map.width - 3) + 1;
|
||||
drunk_y = rng.roll_dice(1, build_data.map.height - 3) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut drunk_life = self.settings.drunken_lifetime;
|
||||
|
||||
while drunk_life > 0 {
|
||||
let drunk_idx = self.map.xy_idx(drunk_x, drunk_y);
|
||||
if self.map.tiles[drunk_idx] == TileType::Wall {
|
||||
let drunk_idx = build_data.map.xy_idx(drunk_x, drunk_y);
|
||||
if build_data.map.tiles[drunk_idx] == TileType::Wall {
|
||||
did_something = true;
|
||||
}
|
||||
paint(&mut self.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y);
|
||||
self.map.tiles[drunk_idx] = TileType::DownStair;
|
||||
paint(&mut build_data.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y);
|
||||
build_data.map.tiles[drunk_idx] = TileType::DownStair;
|
||||
|
||||
let stagger_direction = rng.roll_dice(1, 4);
|
||||
match stagger_direction {
|
||||
|
|
@ -200,7 +145,7 @@ impl DrunkardsWalkBuilder {
|
|||
}
|
||||
}
|
||||
2 => {
|
||||
if drunk_x < self.map.width - 2 {
|
||||
if drunk_x < build_data.map.width - 2 {
|
||||
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_life -= 1;
|
||||
}
|
||||
if did_something {
|
||||
self.take_snapshot();
|
||||
active_digger_count += 1;
|
||||
build_data.take_snapshot();
|
||||
}
|
||||
|
||||
digger_count += 1;
|
||||
for t in self.map.tiles.iter_mut() {
|
||||
for t in build_data.map.tiles.iter_mut() {
|
||||
if *t == TileType::DownStair {
|
||||
*t = TileType::Floor;
|
||||
}
|
||||
}
|
||||
floor_tile_count = self.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);
|
||||
floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,209 +1,30 @@
|
|||
use super::{
|
||||
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
|
||||
Position, TileType, SHOW_MAPGEN,
|
||||
};
|
||||
use super::{BuilderMap, InitialMapBuilder, Map, TileType};
|
||||
use rltk::RandomNumberGenerator;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct MazeBuilder {
|
||||
map: Map,
|
||||
starting_position: Position,
|
||||
depth: i32,
|
||||
history: Vec<Map>,
|
||||
noise_areas: HashMap<i32, Vec<usize>>,
|
||||
spawn_list: Vec<(usize, String)>,
|
||||
}
|
||||
pub struct MazeBuilder {}
|
||||
|
||||
impl MapBuilder for MazeBuilder {
|
||||
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 InitialMapBuilder for MazeBuilder {
|
||||
#[allow(dead_code)]
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
self.build(rng, build_data);
|
||||
}
|
||||
}
|
||||
|
||||
impl MazeBuilder {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(new_depth: i32) -> MazeBuilder {
|
||||
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(),
|
||||
}
|
||||
pub fn new() -> Box<MazeBuilder> {
|
||||
Box::new(MazeBuilder {})
|
||||
}
|
||||
|
||||
#[allow(clippy::map_entry)]
|
||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
// Maze gen
|
||||
let mut maze = Grid::new((self.map.width / 2) - 2, (self.map.height / 2) - 2, rng);
|
||||
maze.generate_maze(self);
|
||||
|
||||
// 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);
|
||||
}
|
||||
let mut maze = Grid::new((build_data.map.width / 2) - 2, (build_data.map.height / 2) - 2, rng);
|
||||
maze.generate_maze(build_data);
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Maze code taken under MIT from https://github.com/cyucelen/mazeGenerator/ */
|
||||
|
||||
const TOP: usize = 0;
|
||||
const RIGHT: usize = 1;
|
||||
|
|
@ -235,10 +56,139 @@ impl Cell {
|
|||
next.walls[LEFT] = false;
|
||||
} else if y == 1 {
|
||||
self.walls[TOP] = false;
|
||||
next.walls[BOTTOM] = false
|
||||
next.walls[BOTTOM] = false;
|
||||
} else if y == -1 {
|
||||
self.walls[BOTTOM] = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,63 +17,157 @@ mod voronoi;
|
|||
use voronoi::VoronoiBuilder;
|
||||
mod prefab_builder;
|
||||
use prefab_builder::PrefabBuilder;
|
||||
mod room_based_spawner;
|
||||
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 rltk::RandomNumberGenerator;
|
||||
use specs::prelude::*;
|
||||
use voronoi_spawning::VoronoiSpawning;
|
||||
use wfc::WaveFunctionCollapseBuilder;
|
||||
|
||||
pub trait MapBuilder {
|
||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator);
|
||||
fn spawn_entities(&mut self, ecs: &mut World) {
|
||||
for entity in self.get_spawn_list().iter() {
|
||||
// Shared data to be passed around build chain
|
||||
pub struct BuilderMap {
|
||||
pub spawn_list: Vec<(usize, String)>,
|
||||
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));
|
||||
}
|
||||
}
|
||||
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 fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
|
||||
let mut rng = rltk::RandomNumberGenerator::new();
|
||||
let builder = rng.roll_dice(1, 17);
|
||||
let mut result : Box<dyn MapBuilder>;
|
||||
rltk::console::log("Picking procgen type ->");
|
||||
match builder {
|
||||
1 => { result = Box::new(BspDungeonBuilder::new(new_depth)); }
|
||||
2 => { result = Box::new(BspInteriorBuilder::new(new_depth)); }
|
||||
3 => { result = Box::new(CellularAutomataBuilder::new(new_depth)); }
|
||||
4 => { result = Box::new(DrunkardsWalkBuilder::open_area(new_depth)); }
|
||||
5 => { result = Box::new(DrunkardsWalkBuilder::open_halls(new_depth)); }
|
||||
6 => { result = Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)); }
|
||||
7 => { result = Box::new(DrunkardsWalkBuilder::fat_passages(new_depth)); }
|
||||
8 => { result = Box::new(DrunkardsWalkBuilder::fearful_symmetry(new_depth)); }
|
||||
9 => { result = Box::new(MazeBuilder::new(new_depth)); }
|
||||
10 => { result = Box::new(DLABuilder::walk_inwards(new_depth)); }
|
||||
11 => { result = Box::new(DLABuilder::walk_outwards(new_depth)); }
|
||||
12 => { result = Box::new(DLABuilder::central_attractor(new_depth)); }
|
||||
13 => { result = Box::new(DLABuilder::insectoid(new_depth)); }
|
||||
14 => { result = Box::new(VoronoiBuilder::pythagoras(new_depth)); }
|
||||
15 => { result = Box::new(VoronoiBuilder::manhattan(new_depth)); }
|
||||
16 => { result = Box::new(PrefabBuilder::constant(new_depth, prefab_builder::prefab_levels::WFC_POPULATED)) },
|
||||
_ => { result = Box::new(simple_map::SimpleMapBuilder::new(new_depth)); }
|
||||
}
|
||||
|
||||
if rng.roll_dice(1, 3)==1 {
|
||||
result = Box::new(WaveFunctionCollapseBuilder::derived_map(new_depth, result));
|
||||
rltk::console::log("-> wfc");
|
||||
}
|
||||
|
||||
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
|
||||
pub trait InitialMapBuilder {
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap);
|
||||
}
|
||||
|
||||
pub trait MetaMapBuilder {
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap);
|
||||
}
|
||||
|
||||
fn random_initial_builder(rng: &mut rltk::RandomNumberGenerator) -> (Box<dyn InitialMapBuilder>, bool) {
|
||||
let builder = rng.roll_dice(1, 17);
|
||||
let result: (Box<dyn InitialMapBuilder>, bool);
|
||||
match builder {
|
||||
1 => result = (BspDungeonBuilder::new(), true),
|
||||
2 => result = (BspInteriorBuilder::new(), true),
|
||||
3 => result = (CellularAutomataBuilder::new(), false),
|
||||
4 => result = (DrunkardsWalkBuilder::open_area(), false),
|
||||
5 => result = (DrunkardsWalkBuilder::open_halls(), false),
|
||||
6 => result = (DrunkardsWalkBuilder::winding_passages(), false),
|
||||
7 => result = (DrunkardsWalkBuilder::fat_passages(), false),
|
||||
8 => result = (DrunkardsWalkBuilder::fearful_symmetry(), false),
|
||||
9 => result = (MazeBuilder::new(), false),
|
||||
10 => result = (DLABuilder::walk_inwards(), false),
|
||||
11 => result = (DLABuilder::walk_outwards(), false),
|
||||
12 => result = (DLABuilder::central_attractor(), false),
|
||||
13 => result = (DLABuilder::insectoid(), false),
|
||||
14 => result = (VoronoiBuilder::pythagoras(), false),
|
||||
15 => result = (VoronoiBuilder::manhattan(), false),
|
||||
16 => result = (PrefabBuilder::constant(prefab_builder::prefab_levels::WFC_POPULATED), false),
|
||||
_ => result = (simple_map::SimpleMapBuilder::new(), true),
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
pub mod prefab_levels;
|
||||
pub mod prefab_sections;
|
||||
pub mod prefab_vaults;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum PrefabMode {
|
||||
RexLevel { template: &'static str },
|
||||
Constant { level: prefab_levels::PrefabLevel },
|
||||
Sectional { section: prefab_sections::PrefabSection },
|
||||
Vaults,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
RoomVaults,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
pub fn rex_level(new_depth: i32, template: &'static str) -> PrefabBuilder {
|
||||
PrefabBuilder {
|
||||
map: Map::new(new_depth),
|
||||
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),
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Box<PrefabBuilder> {
|
||||
Box::new(PrefabBuilder { mode: PrefabMode::RoomVaults })
|
||||
}
|
||||
|
||||
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 {
|
||||
PrefabMode::RexLevel { template } => self.load_rex_map(&template),
|
||||
PrefabMode::Constant { level } => self.load_ascii_map(&level),
|
||||
PrefabMode::Sectional { section } => self.apply_sectional(§ion, rng),
|
||||
PrefabMode::Vaults => self.apply_room_vaults(rng),
|
||||
PrefabMode::RexLevel { template } => self.load_rex_map(&template, build_data),
|
||||
PrefabMode::Constant { level } => self.load_ascii_map(&level, build_data),
|
||||
PrefabMode::Sectional { section } => self.apply_sectional(§ion, rng, build_data),
|
||||
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
|
||||
if self.starting_position.x == 0 {
|
||||
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);
|
||||
fn char_to_map(&mut self, ch: char, idx: usize, build_data: &mut BuilderMap) {
|
||||
match ch {
|
||||
' ' => build_data.map.tiles[idx] = TileType::Floor,
|
||||
'#' => build_data.map.tiles[idx] = TileType::Wall,
|
||||
'>' => build_data.map.tiles[idx] = TileType::DownStair,
|
||||
'≈' => build_data.map.tiles[idx] = TileType::Floor, // Placeholder for vines/brush
|
||||
'@' => {
|
||||
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)
|
||||
where
|
||||
F: FnMut(i32, i32, &(usize, String)) -> bool,
|
||||
#[allow(dead_code)]
|
||||
fn load_rex_map(&mut self, path: &str, build_data: &mut BuilderMap) {
|
||||
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 prev_builder = self.previous_builder.as_mut().unwrap();
|
||||
prev_builder.build_map(rng);
|
||||
self.starting_position = prev_builder.get_starting_pos();
|
||||
self.map = prev_builder.get_map().clone();
|
||||
self.history = prev_builder.get_snapshot_history();
|
||||
for e in prev_builder.get_spawn_list().iter() {
|
||||
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();
|
||||
let width = build_data.map.width;
|
||||
build_data.spawn_list.retain(|(idx, _name)| {
|
||||
let x = *idx as i32 % width;
|
||||
let y = *idx as i32 / width;
|
||||
filter(x, y)
|
||||
});
|
||||
build_data.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::*;
|
||||
|
||||
// Apply prev builder, and keep all entities
|
||||
self.apply_previous_iteration(rng, |_x, _y, _e| true);
|
||||
// Apply the previous builder, and keep all entities it spawns (for now)
|
||||
self.apply_previous_iteration(|_x, _y| true, rng, build_data);
|
||||
|
||||
// Roll for a vault
|
||||
let vault_roll = rng.roll_dice(1, 6);
|
||||
// Do we want a vault at all?
|
||||
let vault_roll = rng.roll_dice(1, 6) + build_data.map.depth;
|
||||
if vault_roll < 4 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all vaults
|
||||
let master_vault_list = vec![GOBLINS_4X4, GOBLINS2_4X4, CLASSIC_TRAP_5X5];
|
||||
// Filter out vaults from outside the current depth
|
||||
let mut possible_vaults: Vec<&PrefabVault> =
|
||||
master_vault_list.iter().filter(|v| self.depth >= v.first_depth && self.depth <= v.last_depth).collect();
|
||||
// Return if there's no possible vaults
|
||||
// Note that this is a place-holder and will be moved out of this function
|
||||
let master_vault_list = vec![
|
||||
CLASSIC_TRAP_5X5,
|
||||
CLASSIC_TRAP_DIAGONALGAP_5X5,
|
||||
CLASSIC_TRAP_CARDINALGAP_5X5,
|
||||
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() {
|
||||
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 mut used_tiles: HashSet<usize> = HashSet::new();
|
||||
|
||||
for _i in 0..n_vaults {
|
||||
// Select a vault
|
||||
let vault_idx = if possible_vaults.len() == 1 {
|
||||
let vault_index = if possible_vaults.len() == 1 {
|
||||
0
|
||||
} else {
|
||||
(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
|
||||
let mut flip_x: 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 idx = 0usize;
|
||||
loop {
|
||||
let x = (idx % self.map.width as usize) as i32;
|
||||
let y = (idx / self.map.width as usize) as i32;
|
||||
// Check for overflow
|
||||
let x = (idx % build_data.map.width as usize) as i32;
|
||||
let y = (idx / build_data.map.width as usize) as i32;
|
||||
|
||||
// Check that we won't overflow the map
|
||||
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 + vault.height as i32) < self.map.height - 2
|
||||
&& (y + vault.height as i32) < build_data.map.height - 2
|
||||
{
|
||||
let mut possible = true;
|
||||
for tile_y in 0..vault.height as i32 {
|
||||
for tile_x in 0..vault.width as i32 {
|
||||
let idx = self.map.xy_idx(tile_x + x, tile_y + y);
|
||||
if self.map.tiles[idx] != TileType::Floor {
|
||||
for ty in 0..vault.height as i32 {
|
||||
for tx in 0..vault.width as i32 {
|
||||
let idx = build_data.map.xy_idx(tx + x, ty + y);
|
||||
if build_data.map.tiles[idx] != TileType::Floor {
|
||||
possible = false;
|
||||
}
|
||||
if used_tiles.contains(&idx) {
|
||||
|
|
@ -238,19 +337,19 @@ impl PrefabBuilder {
|
|||
}
|
||||
}
|
||||
}
|
||||
// If we find a position that works, push it
|
||||
|
||||
if possible {
|
||||
vault_positions.push(Position { x, y });
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Once we reach the end of the map, break
|
||||
|
||||
idx += 1;
|
||||
if idx >= self.map.tiles.len() - 1 {
|
||||
if idx >= build_data.map.tiles.len() - 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a position, make the vault
|
||||
if !vault_positions.is_empty() {
|
||||
let pos_idx = if vault_positions.len() == 1 {
|
||||
0
|
||||
|
|
@ -258,17 +357,19 @@ impl PrefabBuilder {
|
|||
(rng.roll_dice(1, vault_positions.len() as i32) - 1) as usize
|
||||
};
|
||||
let pos = &vault_positions[pos_idx];
|
||||
|
||||
let chunk_x = pos.x;
|
||||
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 height = self.map.height; // As above.
|
||||
self.spawn_list.retain(|e| {
|
||||
|
||||
let width = build_data.map.width; // The borrow checker really doesn't like it
|
||||
let height = build_data.map.height; // when we access `self` inside the `retain`
|
||||
build_data.spawn_list.retain(|e| {
|
||||
let idx = e.0 as i32;
|
||||
let x = idx % width;
|
||||
let y = idx / height;
|
||||
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 mut i = 0;
|
||||
for tile_y in 0..vault.height {
|
||||
|
|
@ -282,140 +383,17 @@ impl PrefabBuilder {
|
|||
if flip_y {
|
||||
y_ = vault.width as i32 - 1 - y_;
|
||||
}
|
||||
self.map.xy_idx(x_ + chunk_x, y_ as i32 + chunk_y);
|
||||
self.char_to_map(string_vec[i], idx);
|
||||
let idx = build_data.map.xy_idx(x_ + chunk_x, y_ + chunk_y);
|
||||
if i < string_vec.len() {
|
||||
self.char_to_map(string_vec[i], idx, build_data);
|
||||
}
|
||||
used_tiles.insert(idx);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
rltk::console::log("-> adding vault");
|
||||
self.take_snapshot();
|
||||
possible_vaults.remove(vault_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
build_data.take_snapshot();
|
||||
|
||||
pub fn apply_sectional(&mut self, section: &prefab_sections::PrefabSection, rng: &mut RandomNumberGenerator) {
|
||||
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;
|
||||
possible_vaults.remove(vault_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ pub struct PrefabVault {
|
|||
pub can_flip: Flipping,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const CLASSIC_TRAP_5X5: PrefabVault = PrefabVault {
|
||||
template: CLASSIC_TRAP_5X5_V,
|
||||
width: 5,
|
||||
|
|
@ -26,7 +25,6 @@ pub const CLASSIC_TRAP_5X5: PrefabVault = PrefabVault {
|
|||
last_depth: 100,
|
||||
can_flip: Flipping::None,
|
||||
};
|
||||
#[allow(dead_code)]
|
||||
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 {
|
||||
template: GOBLINS_4X4_V,
|
||||
width: 4,
|
||||
|
|
@ -51,7 +80,6 @@ const GOBLINS_4X4_V: &str = "
|
|||
^g^
|
||||
";
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const GOBLINS2_4X4: PrefabVault = PrefabVault {
|
||||
template: GOBLINS2_4X4_V,
|
||||
width: 4,
|
||||
|
|
@ -66,3 +94,119 @@ 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 #
|
||||
# ?#
|
||||
##+###
|
||||
|
||||
";
|
||||
|
|
|
|||
27
src/map_builders/room_based_spawner.rs
Normal file
27
src/map_builders/room_based_spawner.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/map_builders/room_based_stairs.rs
Normal file
28
src/map_builders/room_based_stairs.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/map_builders/room_based_starting_position.rs
Normal file
26
src/map_builders/room_based_starting_position.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,107 +1,59 @@
|
|||
use super::{
|
||||
apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, spawner, Map, MapBuilder, Position, Rect,
|
||||
TileType, SHOW_MAPGEN,
|
||||
};
|
||||
use super::{apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, BuilderMap, InitialMapBuilder, Rect};
|
||||
use rltk::RandomNumberGenerator;
|
||||
|
||||
pub struct SimpleMapBuilder {
|
||||
map: Map,
|
||||
starting_position: Position,
|
||||
depth: i32,
|
||||
rooms: Vec<Rect>,
|
||||
history: Vec<Map>,
|
||||
spawn_list: Vec<(usize, String)>,
|
||||
}
|
||||
pub struct SimpleMapBuilder {}
|
||||
|
||||
impl MapBuilder for SimpleMapBuilder {
|
||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
||||
return self.rooms_and_corridors(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 InitialMapBuilder for SimpleMapBuilder {
|
||||
#[allow(dead_code)]
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
self.rooms_and_corridors(rng, build_data);
|
||||
}
|
||||
}
|
||||
|
||||
impl SimpleMapBuilder {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(new_depth: i32) -> SimpleMapBuilder {
|
||||
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(),
|
||||
}
|
||||
pub fn new() -> Box<SimpleMapBuilder> {
|
||||
Box::new(SimpleMapBuilder {})
|
||||
}
|
||||
|
||||
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 MIN_SIZE: i32 = 6;
|
||||
const MAX_SIZE: i32 = 10;
|
||||
let mut rooms: Vec<Rect> = Vec::new();
|
||||
|
||||
for _i in 0..MAX_ROOMS {
|
||||
let w = 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 y = rng.roll_dice(1, self.map.height - h - 1) - 1;
|
||||
let x = rng.roll_dice(1, build_data.map.width - w - 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 mut ok = true;
|
||||
for other_room in self.rooms.iter() {
|
||||
for other_room in rooms.iter() {
|
||||
if new_room.intersect(other_room) {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
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 (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 {
|
||||
apply_horizontal_tunnel(&mut self.map, prev_x, new_x, prev_y);
|
||||
apply_vertical_tunnel(&mut self.map, prev_y, new_y, new_x);
|
||||
apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, prev_y);
|
||||
apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, new_x);
|
||||
} else {
|
||||
apply_vertical_tunnel(&mut self.map, prev_y, new_y, prev_x);
|
||||
apply_horizontal_tunnel(&mut self.map, prev_x, new_x, new_y);
|
||||
apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, prev_x);
|
||||
apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, new_y);
|
||||
}
|
||||
}
|
||||
|
||||
self.rooms.push(new_room);
|
||||
self.take_snapshot();
|
||||
rooms.push(new_room);
|
||||
build_data.take_snapshot();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
build_data.rooms = Some(rooms);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
use super::{
|
||||
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
|
||||
Position, TileType, SHOW_MAPGEN,
|
||||
};
|
||||
use super::{BuilderMap, InitialMapBuilder, TileType};
|
||||
use rltk::RandomNumberGenerator;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum DistanceAlgorithm {
|
||||
Pythagoras,
|
||||
Manhattan,
|
||||
|
|
@ -14,94 +10,42 @@ pub enum DistanceAlgorithm {
|
|||
}
|
||||
|
||||
pub struct VoronoiBuilder {
|
||||
map: Map,
|
||||
starting_position: Position,
|
||||
depth: i32,
|
||||
history: Vec<Map>,
|
||||
noise_areas: HashMap<i32, Vec<usize>>,
|
||||
n_seeds: usize,
|
||||
distance_algorithm: DistanceAlgorithm,
|
||||
spawn_list: Vec<(usize, String)>,
|
||||
}
|
||||
|
||||
impl MapBuilder 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(),
|
||||
}
|
||||
}
|
||||
impl InitialMapBuilder for VoronoiBuilder {
|
||||
#[allow(dead_code)]
|
||||
pub fn chebyshev(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::Chebyshev,
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
self.build(rng, build_data);
|
||||
}
|
||||
}
|
||||
|
||||
impl VoronoiBuilder {
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Box<VoronoiBuilder> {
|
||||
Box::new(VoronoiBuilder { n_seeds: 64, distance_algorithm: DistanceAlgorithm::Pythagoras })
|
||||
}
|
||||
|
||||
#[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)]
|
||||
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!
|
||||
let mut voronoi_seeds: Vec<(usize, rltk::Point)> = Vec::new();
|
||||
|
||||
while voronoi_seeds.len() < self.n_seeds {
|
||||
let vx = rng.roll_dice(1, self.map.width - 1);
|
||||
let vy = rng.roll_dice(1, self.map.height - 1);
|
||||
let vidx = self.map.xy_idx(vx, vy);
|
||||
let vx = rng.roll_dice(1, build_data.map.width - 1);
|
||||
let vy = rng.roll_dice(1, build_data.map.height - 1);
|
||||
let vidx = build_data.map.xy_idx(vx, vy);
|
||||
let candidate = (vidx, rltk::Point::new(vx, vy));
|
||||
if !voronoi_seeds.contains(&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_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() {
|
||||
let x = i as i32 % self.map.width;
|
||||
let y = i as i32 / self.map.width;
|
||||
let x = i as i32 % build_data.map.width;
|
||||
let y = i as i32 / build_data.map.width;
|
||||
|
||||
for (seed, pos) in voronoi_seeds.iter().enumerate() {
|
||||
let distance;
|
||||
|
|
@ -135,52 +79,29 @@ impl VoronoiBuilder {
|
|||
*vid = voronoi_distance[0].0 as i32;
|
||||
}
|
||||
|
||||
for y in 1..self.map.height - 1 {
|
||||
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 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];
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if neighbors < 2 {
|
||||
self.map.tiles[my_idx] = TileType::Floor;
|
||||
build_data.map.tiles[my_idx] = TileType::Floor;
|
||||
}
|
||||
}
|
||||
self.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);
|
||||
build_data.take_snapshot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
48
src/map_builders/voronoi_spawning.rs
Normal file
48
src/map_builders/voronoi_spawning.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,154 +1,76 @@
|
|||
use super::{
|
||||
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 super::{BuilderMap, Map, MetaMapBuilder, TileType};
|
||||
use rltk::RandomNumberGenerator;
|
||||
use solver::Solver;
|
||||
use std::collections::HashMap;
|
||||
mod common;
|
||||
use common::*;
|
||||
mod constraints;
|
||||
use constraints::*;
|
||||
mod solver;
|
||||
use solver::*;
|
||||
|
||||
pub struct WaveFunctionCollapseBuilder {
|
||||
map: Map,
|
||||
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)>,
|
||||
}
|
||||
/// Provides a map builder using the Wave Function Collapse algorithm.
|
||||
pub struct WaveFunctionCollapseBuilder {}
|
||||
|
||||
impl MapBuilder for WaveFunctionCollapseBuilder {
|
||||
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 MetaMapBuilder for WaveFunctionCollapseBuilder {
|
||||
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
self.build(rng, build_data);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl WaveFunctionCollapseBuilder {
|
||||
pub fn new(new_depth: i32, derive_from: Option<Box<dyn MapBuilder>>) -> WaveFunctionCollapseBuilder {
|
||||
WaveFunctionCollapseBuilder {
|
||||
map: Map::new(new_depth),
|
||||
starting_position: Position { x: 0, y: 0 },
|
||||
depth: new_depth,
|
||||
history: Vec::new(),
|
||||
noise_areas: HashMap::new(),
|
||||
derive_from,
|
||||
spawn_list: Vec::new(),
|
||||
}
|
||||
/// Constructor for wfc.
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Box<WaveFunctionCollapseBuilder> {
|
||||
Box::new(WaveFunctionCollapseBuilder {})
|
||||
}
|
||||
|
||||
pub fn derived_map(new_depth: i32, builder: Box<dyn MapBuilder>) -> WaveFunctionCollapseBuilder {
|
||||
WaveFunctionCollapseBuilder::new(new_depth, Some(builder))
|
||||
}
|
||||
|
||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||
const CHUNK_SIZE: i32 = 8;
|
||||
build_data.take_snapshot();
|
||||
|
||||
let prebuilder = &mut self.derive_from.as_mut().unwrap();
|
||||
prebuilder.build_map(rng);
|
||||
self.map = prebuilder.get_map();
|
||||
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 = build_patterns(&build_data.map, CHUNK_SIZE, true, true);
|
||||
let constraints = patterns_to_constraints(patterns, CHUNK_SIZE);
|
||||
self.render_tile_gallery(&constraints, CHUNK_SIZE, build_data);
|
||||
|
||||
let patterns = constraints::build_patterns(&self.map, CHUNK_SIZE, true, true);
|
||||
let constraints = common::patterns_to_constraints(patterns, CHUNK_SIZE);
|
||||
self.render_tile_gallery(&constraints, CHUNK_SIZE);
|
||||
|
||||
// Call solver
|
||||
self.map = Map::new(self.depth);
|
||||
build_data.map = Map::new(build_data.map.depth);
|
||||
loop {
|
||||
let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &self.map);
|
||||
while !solver.iteration(&mut self.map, rng) {
|
||||
self.take_snapshot();
|
||||
let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &build_data.map);
|
||||
while !solver.iteration(&mut build_data.map, rng) {
|
||||
build_data.take_snapshot();
|
||||
}
|
||||
self.take_snapshot();
|
||||
build_data.take_snapshot();
|
||||
if solver.possible {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
} // If it has hit an impossible condition, try again
|
||||
}
|
||||
build_data.spawn_list.clear();
|
||||
}
|
||||
|
||||
fn render_tile_gallery(&mut self, constraints: &Vec<MapChunk>, chunk_size: i32) {
|
||||
self.map = Map::new(0);
|
||||
fn render_tile_gallery(&mut self, constraints: &[MapChunk], chunk_size: i32, build_data: &mut BuilderMap) {
|
||||
build_data.map = Map::new(0);
|
||||
let mut counter = 0;
|
||||
let mut x = 1;
|
||||
let mut y = 1;
|
||||
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;
|
||||
if x + chunk_size > self.map.width {
|
||||
// Next row
|
||||
if x + chunk_size > build_data.map.width {
|
||||
// Move to the next row
|
||||
x = 1;
|
||||
y += chunk_size + 1;
|
||||
self.take_snapshot();
|
||||
|
||||
if y + chunk_size > self.map.height {
|
||||
// Next page
|
||||
self.take_snapshot();
|
||||
self.map = Map::new(0);
|
||||
if y + chunk_size > build_data.map.height {
|
||||
// Move to the next page
|
||||
build_data.take_snapshot();
|
||||
build_data.map = Map::new(0);
|
||||
|
||||
x = 1;
|
||||
y = 1;
|
||||
}
|
||||
}
|
||||
|
||||
counter += 1;
|
||||
}
|
||||
self.take_snapshot();
|
||||
build_data.take_snapshot();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
|
|||
|
||||
// 20 mobs : 6 items : 2 food : 1 trap
|
||||
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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue