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_index = 0;
|
||||||
self.mapgen_timer = 0.0;
|
self.mapgen_timer = 0.0;
|
||||||
self.mapgen_history.clear();
|
self.mapgen_history.clear();
|
||||||
|
let mut rng = self.ecs.write_resource::<rltk::RandomNumberGenerator>();
|
||||||
|
let mut builder = map_builders::random_builder(new_depth, &mut rng);
|
||||||
|
builder.build_map(&mut rng);
|
||||||
|
std::mem::drop(rng);
|
||||||
|
self.mapgen_history = builder.build_data.history.clone();
|
||||||
let player_start;
|
let player_start;
|
||||||
// Scope for borrow checker
|
|
||||||
let mut builder = map_builders::random_builder(new_depth);
|
|
||||||
{
|
{
|
||||||
// Build a new map using RNG (to retain seed)
|
|
||||||
let mut rng = self.ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
builder.build_map(&mut rng);
|
|
||||||
self.mapgen_history = builder.get_snapshot_history();
|
|
||||||
let mut worldmap_resource = self.ecs.write_resource::<Map>();
|
let mut worldmap_resource = self.ecs.write_resource::<Map>();
|
||||||
*worldmap_resource = builder.get_map();
|
*worldmap_resource = builder.build_data.map.clone();
|
||||||
player_start = builder.get_starting_pos();
|
// Unwrap so we get a CTD if there's no starting pos.
|
||||||
// Spawn entities
|
player_start = builder.build_data.starting_position.as_mut().unwrap().clone();
|
||||||
}
|
}
|
||||||
|
// Spawn entities
|
||||||
builder.spawn_entities(&mut self.ecs);
|
builder.spawn_entities(&mut self.ecs);
|
||||||
|
|
||||||
// Place player and update resources
|
// Place player and update resources
|
||||||
|
|
|
||||||
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;
|
use rltk::RandomNumberGenerator;
|
||||||
|
|
||||||
pub struct BspDungeonBuilder {
|
pub struct BspDungeonBuilder {
|
||||||
map: Map,
|
|
||||||
starting_position: Position,
|
|
||||||
depth: i32,
|
|
||||||
rooms: Vec<Rect>,
|
|
||||||
history: Vec<Map>,
|
|
||||||
rects: Vec<Rect>,
|
rects: Vec<Rect>,
|
||||||
spawn_list: Vec<(usize, String)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapBuilder for BspDungeonBuilder {
|
impl InitialMapBuilder for BspDungeonBuilder {
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
#[allow(dead_code)]
|
||||||
return self.build(rng);
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
}
|
self.build(rng, build_data);
|
||||||
// Getters
|
|
||||||
fn get_map(&mut self) -> Map {
|
|
||||||
return self.map.clone();
|
|
||||||
}
|
|
||||||
fn get_starting_pos(&mut self) -> Position {
|
|
||||||
return self.starting_position.clone();
|
|
||||||
}
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
|
|
||||||
return &self.spawn_list;
|
|
||||||
}
|
|
||||||
// Mapgen visualisation stuff
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map> {
|
|
||||||
return self.history.clone();
|
|
||||||
}
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if SHOW_MAPGEN {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BspDungeonBuilder {
|
impl BspDungeonBuilder {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn new(new_depth: i32) -> BspDungeonBuilder {
|
pub fn new() -> Box<BspDungeonBuilder> {
|
||||||
BspDungeonBuilder {
|
Box::new(BspDungeonBuilder { rects: Vec::new() })
|
||||||
map: Map::new(new_depth),
|
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
rooms: Vec::new(),
|
|
||||||
history: Vec::new(),
|
|
||||||
rects: Vec::new(),
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(&mut self, mut rng: &mut RandomNumberGenerator) {
|
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
|
let mut rooms: Vec<Rect> = Vec::new();
|
||||||
self.rects.clear();
|
self.rects.clear();
|
||||||
self.rects.push(Rect::new(2, 2, self.map.width - 5, self.map.height - 5));
|
self.rects.push(Rect::new(2, 2, build_data.map.width - 5, build_data.map.height - 5)); // Start with a single map-sized rectangle
|
||||||
let first_room = self.rects[0];
|
let first_room = self.rects[0];
|
||||||
self.add_subrects(first_room); // Divide first room
|
self.add_subrects(first_room); // Divide the first room
|
||||||
|
|
||||||
// Up to 240 times, get a random rect and divide it. If it's possible
|
// Up to 240 times, we get a random rectangle and divide it. If its possible to squeeze a
|
||||||
// to place a room in there, place it and add it to the rooms list.
|
// room in there, we place it and add it to the rooms list.
|
||||||
let mut n_rooms = 0;
|
let mut n_rooms = 0;
|
||||||
while n_rooms < 240 {
|
while n_rooms < 240 {
|
||||||
let rect = self.get_random_rect(&mut rng);
|
let rect = self.get_random_rect(rng);
|
||||||
let candidate = self.get_random_subrect(rect, &mut rng);
|
let candidate = self.get_random_sub_rect(rect, rng);
|
||||||
|
|
||||||
if self.is_possible(candidate) {
|
if self.is_possible(candidate, &build_data.map) {
|
||||||
apply_room_to_map(&mut self.map, &candidate);
|
apply_room_to_map(&mut build_data.map, &candidate);
|
||||||
self.rooms.push(candidate);
|
rooms.push(candidate);
|
||||||
self.add_subrects(rect);
|
self.add_subrects(rect);
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
n_rooms += 1;
|
n_rooms += 1;
|
||||||
}
|
}
|
||||||
let start = self.rooms[0].centre();
|
|
||||||
self.starting_position = Position { x: start.0, y: start.1 };
|
|
||||||
|
|
||||||
// Sort rooms by left co-ordinate. Optional, but helps to make connected rooms line up.
|
// Now we sort the rooms
|
||||||
self.rooms.sort_by(|a, b| a.x1.cmp(&b.x1));
|
rooms.sort_by(|a, b| a.x1.cmp(&b.x1));
|
||||||
|
|
||||||
// Corridors
|
// Now we want corridors
|
||||||
for i in 0..self.rooms.len() - 1 {
|
for i in 0..rooms.len() - 1 {
|
||||||
let room = self.rooms[i];
|
let room = rooms[i];
|
||||||
let next_room = self.rooms[i + 1];
|
let next_room = rooms[i + 1];
|
||||||
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
|
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
|
||||||
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
|
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
|
||||||
let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
|
let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
|
||||||
let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
|
let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
|
||||||
self.draw_corridor(start_x, start_y, end_x, end_y);
|
draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y);
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
}
|
|
||||||
|
|
||||||
// Stairs
|
|
||||||
let stairs = self.rooms[self.rooms.len() - 1].centre();
|
|
||||||
let stairs_idx = self.map.xy_idx(stairs.0, stairs.1);
|
|
||||||
self.map.tiles[stairs_idx] = TileType::DownStair;
|
|
||||||
|
|
||||||
// Spawn entities
|
|
||||||
for room in self.rooms.iter().skip(1) {
|
|
||||||
spawner::spawn_room(&self.map, rng, room, self.depth, &mut self.spawn_list);
|
|
||||||
}
|
}
|
||||||
|
build_data.rooms = Some(rooms);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_subrects(&mut self, rect: Rect) {
|
fn add_subrects(&mut self, rect: Rect) {
|
||||||
let w = i32::abs(rect.x1 - rect.x2);
|
let width = i32::abs(rect.x1 - rect.x2);
|
||||||
let h = i32::abs(rect.y1 - rect.y2);
|
let height = i32::abs(rect.y1 - rect.y2);
|
||||||
let half_w = i32::max(w / 2, 1);
|
let half_width = i32::max(width / 2, 1);
|
||||||
let half_h = i32::max(h / 2, 1);
|
let half_height = i32::max(height / 2, 1);
|
||||||
|
|
||||||
self.rects.push(Rect::new(rect.x1, rect.y1, half_w, half_h));
|
self.rects.push(Rect::new(rect.x1, rect.y1, half_width, half_height));
|
||||||
self.rects.push(Rect::new(rect.x1, rect.y1 + half_h, half_w, half_h));
|
self.rects.push(Rect::new(rect.x1, rect.y1 + half_height, half_width, half_height));
|
||||||
self.rects.push(Rect::new(rect.x1 + half_w, rect.y1, half_w, half_h));
|
self.rects.push(Rect::new(rect.x1 + half_width, rect.y1, half_width, half_height));
|
||||||
self.rects.push(Rect::new(rect.x1 + half_w, rect.y1 + half_h, half_w, half_h));
|
self.rects.push(Rect::new(rect.x1 + half_width, rect.y1 + half_height, half_width, half_height));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_random_rect(&mut self, rng: &mut RandomNumberGenerator) -> Rect {
|
fn get_random_rect(&mut self, rng: &mut RandomNumberGenerator) -> Rect {
|
||||||
|
|
@ -121,26 +76,26 @@ impl BspDungeonBuilder {
|
||||||
return self.rects[0];
|
return self.rects[0];
|
||||||
}
|
}
|
||||||
let idx = (rng.roll_dice(1, self.rects.len() as i32) - 1) as usize;
|
let idx = (rng.roll_dice(1, self.rects.len() as i32) - 1) as usize;
|
||||||
return self.rects[idx];
|
self.rects[idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_random_subrect(&self, rect: Rect, rng: &mut RandomNumberGenerator) -> Rect {
|
fn get_random_sub_rect(&self, rect: Rect, rng: &mut RandomNumberGenerator) -> Rect {
|
||||||
let mut result = rect;
|
let mut result = rect;
|
||||||
let rect_width = i32::abs(rect.x1 - rect.x2);
|
let rect_width = i32::abs(rect.x1 - rect.x2);
|
||||||
let rect_height = i32::abs(rect.y1 - rect.y2);
|
let rect_height = i32::abs(rect.y1 - rect.y2);
|
||||||
|
|
||||||
let w = i32::max(3, rng.roll_dice(1, i32::min(rect_width, 14)) - 1) + 1;
|
let w = i32::max(3, rng.roll_dice(1, i32::min(rect_width, 10)) - 1) + 1;
|
||||||
let h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 14)) - 1) + 1;
|
let h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 10)) - 1) + 1;
|
||||||
|
|
||||||
result.x1 += rng.roll_dice(1, 6) - 1;
|
result.x1 += rng.roll_dice(1, 6) - 1;
|
||||||
result.y1 += rng.roll_dice(1, 6) - 1;
|
result.y1 += rng.roll_dice(1, 6) - 1;
|
||||||
result.x2 = result.x1 + w;
|
result.x2 = result.x1 + w;
|
||||||
result.y2 = result.y1 + h;
|
result.y2 = result.y1 + h;
|
||||||
|
|
||||||
return result;
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_possible(&self, rect: Rect) -> bool {
|
fn is_possible(&self, rect: Rect, map: &Map) -> bool {
|
||||||
let mut expanded = rect;
|
let mut expanded = rect;
|
||||||
expanded.x1 -= 2;
|
expanded.x1 -= 2;
|
||||||
expanded.x2 += 2;
|
expanded.x2 += 2;
|
||||||
|
|
@ -151,10 +106,10 @@ impl BspDungeonBuilder {
|
||||||
|
|
||||||
for y in expanded.y1..=expanded.y2 {
|
for y in expanded.y1..=expanded.y2 {
|
||||||
for x in expanded.x1..=expanded.x2 {
|
for x in expanded.x1..=expanded.x2 {
|
||||||
if x > self.map.width - 2 {
|
if x > map.width - 2 {
|
||||||
can_build = false;
|
can_build = false;
|
||||||
}
|
}
|
||||||
if y > self.map.height - 2 {
|
if y > map.height - 2 {
|
||||||
can_build = false;
|
can_build = false;
|
||||||
}
|
}
|
||||||
if x < 1 {
|
if x < 1 {
|
||||||
|
|
@ -164,34 +119,14 @@ impl BspDungeonBuilder {
|
||||||
can_build = false;
|
can_build = false;
|
||||||
}
|
}
|
||||||
if can_build {
|
if can_build {
|
||||||
let idx = self.map.xy_idx(x, y);
|
let idx = map.xy_idx(x, y);
|
||||||
if self.map.tiles[idx] != TileType::Wall {
|
if map.tiles[idx] != TileType::Wall {
|
||||||
can_build = false;
|
can_build = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return can_build;
|
can_build
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_corridor(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
|
|
||||||
let mut x = x1;
|
|
||||||
let mut y = y1;
|
|
||||||
|
|
||||||
while x != x2 || y != y2 {
|
|
||||||
if x < x2 {
|
|
||||||
x += 1;
|
|
||||||
} else if x > x2 {
|
|
||||||
x -= 1;
|
|
||||||
} else if y < y2 {
|
|
||||||
y += 1;
|
|
||||||
} else if y > y2 {
|
|
||||||
y -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = self.map.xy_idx(x, y);
|
|
||||||
self.map.tiles[idx] = TileType::Floor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,165 +1,102 @@
|
||||||
use super::{spawner, Map, MapBuilder, Position, Rect, TileType, SHOW_MAPGEN};
|
use super::{draw_corridor, BuilderMap, InitialMapBuilder, Rect, TileType};
|
||||||
use rltk::RandomNumberGenerator;
|
use rltk::RandomNumberGenerator;
|
||||||
|
|
||||||
|
const MIN_ROOM_SIZE: i32 = 8;
|
||||||
|
|
||||||
pub struct BspInteriorBuilder {
|
pub struct BspInteriorBuilder {
|
||||||
map: Map,
|
|
||||||
starting_position: Position,
|
|
||||||
depth: i32,
|
|
||||||
rooms: Vec<Rect>,
|
|
||||||
history: Vec<Map>,
|
|
||||||
rects: Vec<Rect>,
|
rects: Vec<Rect>,
|
||||||
spawn_list: Vec<(usize, String)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapBuilder for BspInteriorBuilder {
|
impl InitialMapBuilder for BspInteriorBuilder {
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
#[allow(dead_code)]
|
||||||
return self.build(rng);
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
}
|
self.build(rng, build_data);
|
||||||
// Getters
|
|
||||||
fn get_map(&mut self) -> Map {
|
|
||||||
return self.map.clone();
|
|
||||||
}
|
|
||||||
fn get_starting_pos(&mut self) -> Position {
|
|
||||||
return self.starting_position.clone();
|
|
||||||
}
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
|
|
||||||
return &self.spawn_list;
|
|
||||||
}
|
|
||||||
// Mapgen visualisation stuff
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map> {
|
|
||||||
return self.history.clone();
|
|
||||||
}
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if SHOW_MAPGEN {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BspInteriorBuilder {
|
impl BspInteriorBuilder {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn new(new_depth: i32) -> BspInteriorBuilder {
|
pub fn new() -> Box<BspInteriorBuilder> {
|
||||||
BspInteriorBuilder {
|
Box::new(BspInteriorBuilder { rects: Vec::new() })
|
||||||
map: Map::new(new_depth),
|
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
rooms: Vec::new(),
|
|
||||||
history: Vec::new(),
|
|
||||||
rects: Vec::new(),
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
|
let mut rooms: Vec<Rect> = Vec::new();
|
||||||
self.rects.clear();
|
self.rects.clear();
|
||||||
self.rects.push(Rect::new(1, 1, self.map.width - 2, self.map.height - 2)); // Start with a single map-sized rectangle
|
self.rects.push(Rect::new(1, 1, build_data.map.width - 2, build_data.map.height - 2)); // Start with a single map-sized rectangle
|
||||||
let first_room = self.rects[0];
|
let first_room = self.rects[0];
|
||||||
self.add_subrects(first_room, rng); // Divide the first room
|
self.add_subrects(first_room, rng); // Divide the first room
|
||||||
|
|
||||||
let rooms = self.rects.clone();
|
let rooms_copy = self.rects.clone();
|
||||||
for r in rooms.iter() {
|
for r in rooms_copy.iter() {
|
||||||
let room = *r;
|
let room = *r;
|
||||||
self.rooms.push(room);
|
//room.x2 -= 1;
|
||||||
|
//room.y2 -= 1;
|
||||||
|
rooms.push(room);
|
||||||
for y in room.y1..room.y2 {
|
for y in room.y1..room.y2 {
|
||||||
for x in room.x1..room.x2 {
|
for x in room.x1..room.x2 {
|
||||||
let idx = self.map.xy_idx(x, y);
|
let idx = build_data.map.xy_idx(x, y);
|
||||||
if idx > 0 && idx < ((self.map.width * self.map.height) - 1) as usize {
|
if idx > 0 && idx < ((build_data.map.width * build_data.map.height) - 1) as usize {
|
||||||
self.map.tiles[idx] = TileType::Floor;
|
build_data.map.tiles[idx] = TileType::Floor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = self.rooms[0].centre();
|
|
||||||
self.starting_position = Position { x: start.0, y: start.1 };
|
|
||||||
|
|
||||||
// Now we want corridors
|
// Now we want corridors
|
||||||
for i in 0..self.rooms.len() - 1 {
|
for i in 0..rooms.len() - 1 {
|
||||||
let room = self.rooms[i];
|
let room = rooms[i];
|
||||||
let next_room = self.rooms[i + 1];
|
let next_room = rooms[i + 1];
|
||||||
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
|
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
|
||||||
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
|
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
|
||||||
let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
|
let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
|
||||||
let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
|
let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
|
||||||
self.draw_corridor(start_x, start_y, end_x, end_y);
|
draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y);
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't forget the stairs
|
build_data.rooms = Some(rooms);
|
||||||
let stairs = self.rooms[self.rooms.len() - 1].centre();
|
|
||||||
let stairs_idx = self.map.xy_idx(stairs.0, stairs.1);
|
|
||||||
self.map.tiles[stairs_idx] = TileType::DownStair;
|
|
||||||
|
|
||||||
// Spawn entities
|
|
||||||
for room in self.rooms.iter().skip(1) {
|
|
||||||
spawner::spawn_room(&self.map, rng, room, self.depth, &mut self.spawn_list);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_subrects(&mut self, rect: Rect, rng: &mut RandomNumberGenerator) {
|
fn add_subrects(&mut self, rect: Rect, rng: &mut RandomNumberGenerator) {
|
||||||
const MIN_ROOM_SIZE: i32 = 6;
|
// Remove the last rect from the list
|
||||||
// Remove last rect
|
|
||||||
if !self.rects.is_empty() {
|
if !self.rects.is_empty() {
|
||||||
self.rects.remove(self.rects.len() - 1);
|
self.rects.remove(self.rects.len() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calc bounds
|
// Calculate boundaries
|
||||||
let w = rect.x2 - rect.x1;
|
let width = rect.x2 - rect.x1;
|
||||||
let h = rect.y2 - rect.y1;
|
let height = rect.y2 - rect.y1;
|
||||||
let half_w = w / 2;
|
let half_width = width / 2;
|
||||||
let half_h = h / 2;
|
let half_height = height / 2;
|
||||||
|
|
||||||
let split = rng.roll_dice(1, 4);
|
let split = rng.roll_dice(1, 4);
|
||||||
|
|
||||||
if split <= 2 {
|
if split <= 2 {
|
||||||
// Horizontal split
|
// Horizontal split
|
||||||
let h1 = Rect::new(rect.x1, rect.y1, half_w - 1, h);
|
let h1 = Rect::new(rect.x1, rect.y1, half_width - 1, height);
|
||||||
self.rects.push(h1);
|
self.rects.push(h1);
|
||||||
if half_w > MIN_ROOM_SIZE {
|
if half_width > MIN_ROOM_SIZE {
|
||||||
self.add_subrects(h1, rng);
|
self.add_subrects(h1, rng);
|
||||||
}
|
}
|
||||||
let h2 = Rect::new(rect.x1 + half_w, rect.y1, half_w, h);
|
let h2 = Rect::new(rect.x1 + half_width, rect.y1, half_width, height);
|
||||||
self.rects.push(h2);
|
self.rects.push(h2);
|
||||||
if half_w > MIN_ROOM_SIZE {
|
if half_width > MIN_ROOM_SIZE {
|
||||||
self.add_subrects(h2, rng);
|
self.add_subrects(h2, rng);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Vertical split
|
// Vertical split
|
||||||
let v1 = Rect::new(rect.x1, rect.y1, w, half_h - 1);
|
let v1 = Rect::new(rect.x1, rect.y1, width, half_height - 1);
|
||||||
self.rects.push(v1);
|
self.rects.push(v1);
|
||||||
if half_h > MIN_ROOM_SIZE {
|
if half_height > MIN_ROOM_SIZE {
|
||||||
self.add_subrects(v1, rng);
|
self.add_subrects(v1, rng);
|
||||||
}
|
}
|
||||||
let v2 = Rect::new(rect.x1, rect.y1 + half_h, w, half_h);
|
let v2 = Rect::new(rect.x1, rect.y1 + half_height, width, half_height);
|
||||||
self.rects.push(v2);
|
self.rects.push(v2);
|
||||||
if half_h > MIN_ROOM_SIZE {
|
if half_height > MIN_ROOM_SIZE {
|
||||||
self.add_subrects(v2, rng);
|
self.add_subrects(v2, rng);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_corridor(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
|
|
||||||
let mut x = x1;
|
|
||||||
let mut y = y1;
|
|
||||||
|
|
||||||
while x != x2 || y != y2 {
|
|
||||||
if x < x2 {
|
|
||||||
x += 1;
|
|
||||||
} else if x > x2 {
|
|
||||||
x -= 1;
|
|
||||||
} else if y < y2 {
|
|
||||||
y += 1;
|
|
||||||
} else if y > y2 {
|
|
||||||
y -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = self.map.xy_idx(x, y);
|
|
||||||
self.map.tiles[idx] = TileType::Floor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,143 +1,80 @@
|
||||||
use super::{
|
use super::{BuilderMap, InitialMapBuilder, TileType};
|
||||||
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
|
|
||||||
Position, TileType, SHOW_MAPGEN,
|
|
||||||
};
|
|
||||||
use rltk::RandomNumberGenerator;
|
use rltk::RandomNumberGenerator;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
const PASSES: i32 = 15;
|
pub struct CellularAutomataBuilder {}
|
||||||
|
|
||||||
pub struct CellularAutomataBuilder {
|
impl InitialMapBuilder for CellularAutomataBuilder {
|
||||||
map: Map,
|
#[allow(dead_code)]
|
||||||
starting_position: Position,
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
depth: i32,
|
self.build(rng, build_data);
|
||||||
history: Vec<Map>,
|
|
||||||
noise_areas: HashMap<i32, Vec<usize>>,
|
|
||||||
spawn_list: Vec<(usize, String)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MapBuilder for CellularAutomataBuilder {
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
|
||||||
return self.build(rng);
|
|
||||||
}
|
|
||||||
// Getters
|
|
||||||
fn get_map(&mut self) -> Map {
|
|
||||||
return self.map.clone();
|
|
||||||
}
|
|
||||||
fn get_starting_pos(&mut self) -> Position {
|
|
||||||
return self.starting_position.clone();
|
|
||||||
}
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
|
|
||||||
return &self.spawn_list;
|
|
||||||
}
|
|
||||||
// Mapgen visualisation stuff
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map> {
|
|
||||||
return self.history.clone();
|
|
||||||
}
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if SHOW_MAPGEN {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CellularAutomataBuilder {
|
impl CellularAutomataBuilder {
|
||||||
pub fn new(new_depth: i32) -> CellularAutomataBuilder {
|
#[allow(dead_code)]
|
||||||
CellularAutomataBuilder {
|
pub fn new() -> Box<CellularAutomataBuilder> {
|
||||||
map: Map::new(new_depth),
|
Box::new(CellularAutomataBuilder {})
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
#[allow(clippy::map_entry)]
|
||||||
// Set 55% of map to floor
|
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
for y in 1..self.map.height - 1 {
|
// First we completely randomize the map, setting 55% of it to be floor.
|
||||||
for x in 1..self.map.width - 1 {
|
for y in 1..build_data.map.height - 1 {
|
||||||
|
for x in 1..build_data.map.width - 1 {
|
||||||
let roll = rng.roll_dice(1, 100);
|
let roll = rng.roll_dice(1, 100);
|
||||||
let idx = self.map.xy_idx(x, y);
|
let idx = build_data.map.xy_idx(x, y);
|
||||||
if roll > 55 {
|
if roll > 55 {
|
||||||
self.map.tiles[idx] = TileType::Floor
|
build_data.map.tiles[idx] = TileType::Floor
|
||||||
} else {
|
} else {
|
||||||
self.map.tiles[idx] = TileType::Wall
|
build_data.map.tiles[idx] = TileType::Wall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
|
|
||||||
// Iteratively apply cellular automata rules
|
// Now we iteratively apply cellular automata rules
|
||||||
for _i in 0..PASSES {
|
for _i in 0..15 {
|
||||||
let mut newtiles = self.map.tiles.clone();
|
let mut newtiles = build_data.map.tiles.clone();
|
||||||
|
|
||||||
for y in 1..self.map.height - 1 {
|
for y in 1..build_data.map.height - 1 {
|
||||||
for x in 1..self.map.width - 1 {
|
for x in 1..build_data.map.width - 1 {
|
||||||
let idx = self.map.xy_idx(x, y);
|
let idx = build_data.map.xy_idx(x, y);
|
||||||
let mut neighbours = 0;
|
let mut neighbors = 0;
|
||||||
if self.map.tiles[idx - 1] == TileType::Wall {
|
if build_data.map.tiles[idx - 1] == TileType::Wall {
|
||||||
neighbours += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
if self.map.tiles[idx + 1] == TileType::Wall {
|
if build_data.map.tiles[idx + 1] == TileType::Wall {
|
||||||
neighbours += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
if self.map.tiles[idx - self.map.width as usize] == TileType::Wall {
|
if build_data.map.tiles[idx - build_data.map.width as usize] == TileType::Wall {
|
||||||
neighbours += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
if self.map.tiles[idx + self.map.width as usize] == TileType::Wall {
|
if build_data.map.tiles[idx + build_data.map.width as usize] == TileType::Wall {
|
||||||
neighbours += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
if self.map.tiles[idx - (self.map.width as usize - 1)] == TileType::Wall {
|
if build_data.map.tiles[idx - (build_data.map.width as usize - 1)] == TileType::Wall {
|
||||||
neighbours += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
if self.map.tiles[idx - (self.map.width as usize + 1)] == TileType::Wall {
|
if build_data.map.tiles[idx - (build_data.map.width as usize + 1)] == TileType::Wall {
|
||||||
neighbours += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
if self.map.tiles[idx + (self.map.width as usize - 1)] == TileType::Wall {
|
if build_data.map.tiles[idx + (build_data.map.width as usize - 1)] == TileType::Wall {
|
||||||
neighbours += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
if self.map.tiles[idx + (self.map.width as usize + 1)] == TileType::Wall {
|
if build_data.map.tiles[idx + (build_data.map.width as usize + 1)] == TileType::Wall {
|
||||||
neighbours += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if neighbours > 4 || neighbours == 0 {
|
if neighbors > 4 || neighbors == 0 {
|
||||||
newtiles[idx] = TileType::Wall;
|
newtiles[idx] = TileType::Wall;
|
||||||
} else {
|
} else {
|
||||||
newtiles[idx] = TileType::Floor;
|
newtiles[idx] = TileType::Floor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.map.tiles = newtiles.clone();
|
|
||||||
self.take_snapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find a starting point; start at the middle and walk left until we find an open tile
|
build_data.map.tiles = newtiles.clone();
|
||||||
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 };
|
build_data.take_snapshot();
|
||||||
let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
|
||||||
while self.map.tiles[start_idx] != TileType::Floor {
|
|
||||||
self.starting_position.x -= 1;
|
|
||||||
start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all tiles reachable from starting pos
|
|
||||||
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Place stairs
|
|
||||||
self.map.tiles[exit_tile] = TileType::DownStair;
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Noise map for spawning entities
|
|
||||||
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
|
|
||||||
|
|
||||||
// Spawn the entities
|
|
||||||
for area in self.noise_areas.iter() {
|
|
||||||
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use super::{Map, Rect, TileType};
|
use super::{Map, Rect, TileType};
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub fn apply_room_to_map(map: &mut Map, room: &Rect) {
|
pub fn apply_room_to_map(map: &mut Map, room: &Rect) {
|
||||||
for y in room.y1 + 1..=room.y2 {
|
for y in room.y1 + 1..=room.y2 {
|
||||||
|
|
@ -29,53 +28,24 @@ pub fn apply_vertical_tunnel(map: &mut Map, y1: i32, y2: i32, x: i32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_unreachable_areas_returning_most_distant(map: &mut Map, start_idx: usize) -> usize {
|
pub fn draw_corridor(map: &mut Map, x1: i32, y1: i32, x2: i32, y2: i32) {
|
||||||
map.populate_blocked();
|
let mut x = x1;
|
||||||
let map_starts: Vec<usize> = vec![start_idx];
|
let mut y = y1;
|
||||||
let dijkstra_map = rltk::DijkstraMap::new(map.width as usize, map.height as usize, &map_starts, map, 200.0);
|
|
||||||
let mut exit_tile = (0, 0.0f32);
|
while x != x2 || y != y2 {
|
||||||
for (i, tile) in map.tiles.iter_mut().enumerate() {
|
if x < x2 {
|
||||||
if *tile == TileType::Floor {
|
x += 1;
|
||||||
let distance_to_start = dijkstra_map.map[i];
|
} else if x > x2 {
|
||||||
// We can't get to this tile - so we'll make it a wall
|
x -= 1;
|
||||||
if distance_to_start == std::f32::MAX {
|
} else if y < y2 {
|
||||||
*tile = TileType::Wall;
|
y += 1;
|
||||||
} else {
|
} else if y > y2 {
|
||||||
// If it is further away than our current exit candidate, move the exit
|
y -= 1;
|
||||||
if distance_to_start > exit_tile.1 {
|
|
||||||
exit_tile.0 = i;
|
|
||||||
exit_tile.1 = distance_to_start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let idx = map.xy_idx(x, y);
|
||||||
|
map.tiles[idx] = TileType::Floor;
|
||||||
}
|
}
|
||||||
return exit_tile.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::map_entry)]
|
|
||||||
pub fn generate_voronoi_spawn_regions(map: &Map, rng: &mut rltk::RandomNumberGenerator) -> HashMap<i32, Vec<usize>> {
|
|
||||||
let mut noise_areas: HashMap<i32, Vec<usize>> = HashMap::new();
|
|
||||||
let mut noise = rltk::FastNoise::seeded(rng.roll_dice(1, 65536) as u64);
|
|
||||||
noise.set_noise_type(rltk::NoiseType::Cellular);
|
|
||||||
noise.set_frequency(0.08);
|
|
||||||
noise.set_cellular_distance_function(rltk::CellularDistanceFunction::Manhattan);
|
|
||||||
|
|
||||||
for y in 1..map.height - 1 {
|
|
||||||
for x in 1..map.width - 1 {
|
|
||||||
let idx = map.xy_idx(x, y);
|
|
||||||
if map.tiles[idx] == TileType::Floor {
|
|
||||||
let cell_value_f = noise.get_noise(x as f32, y as f32) * 10240.0;
|
|
||||||
let cell_value = cell_value_f as i32;
|
|
||||||
|
|
||||||
if noise_areas.contains_key(&cell_value) {
|
|
||||||
noise_areas.get_mut(&cell_value).unwrap().push(idx);
|
|
||||||
} else {
|
|
||||||
noise_areas.insert(cell_value, vec![idx]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return noise_areas;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
|
||||||
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::{
|
use super::{paint, BuilderMap, InitialMapBuilder, Position, Symmetry, TileType};
|
||||||
common::Symmetry, generate_voronoi_spawn_regions, paint, remove_unreachable_areas_returning_most_distant, spawner,
|
|
||||||
Map, MapBuilder, Position, TileType, SHOW_MAPGEN,
|
|
||||||
};
|
|
||||||
use rltk::RandomNumberGenerator;
|
use rltk::RandomNumberGenerator;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub enum DLAAlgorithm {
|
pub enum DLAAlgorithm {
|
||||||
WalkInwards,
|
WalkInwards,
|
||||||
WalkOutwards,
|
WalkOutwards,
|
||||||
|
|
@ -14,134 +10,95 @@ pub enum DLAAlgorithm {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DLABuilder {
|
pub struct DLABuilder {
|
||||||
map: Map,
|
|
||||||
starting_position: Position,
|
|
||||||
depth: i32,
|
|
||||||
history: Vec<Map>,
|
|
||||||
noise_areas: HashMap<i32, Vec<usize>>,
|
|
||||||
algorithm: DLAAlgorithm,
|
algorithm: DLAAlgorithm,
|
||||||
brush_size: i32,
|
brush_size: i32,
|
||||||
symmetry: Symmetry,
|
symmetry: Symmetry,
|
||||||
floor_percent: f32,
|
floor_percent: f32,
|
||||||
spawn_list: Vec<(usize, String)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapBuilder for DLABuilder {
|
impl InitialMapBuilder for DLABuilder {
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
#[allow(dead_code)]
|
||||||
return self.build(rng);
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
}
|
self.build(rng, build_data);
|
||||||
// Getters
|
|
||||||
fn get_map(&mut self) -> Map {
|
|
||||||
return self.map.clone();
|
|
||||||
}
|
|
||||||
fn get_starting_pos(&mut self) -> Position {
|
|
||||||
return self.starting_position.clone();
|
|
||||||
}
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
|
|
||||||
return &self.spawn_list;
|
|
||||||
}
|
|
||||||
// Mapgen visualisation stuff
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map> {
|
|
||||||
return self.history.clone();
|
|
||||||
}
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if SHOW_MAPGEN {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl DLABuilder {
|
impl DLABuilder {
|
||||||
pub fn walk_inwards(new_depth: i32) -> DLABuilder {
|
#[allow(dead_code)]
|
||||||
DLABuilder {
|
pub fn new() -> Box<DLABuilder> {
|
||||||
map: Map::new(new_depth),
|
Box::new(DLABuilder {
|
||||||
starting_position: Position { x: 0, y: 0 },
|
algorithm: DLAAlgorithm::WalkInwards,
|
||||||
depth: new_depth,
|
brush_size: 2,
|
||||||
history: Vec::new(),
|
symmetry: Symmetry::None,
|
||||||
noise_areas: HashMap::new(),
|
floor_percent: 0.25,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn walk_inwards() -> Box<DLABuilder> {
|
||||||
|
Box::new(DLABuilder {
|
||||||
algorithm: DLAAlgorithm::WalkInwards,
|
algorithm: DLAAlgorithm::WalkInwards,
|
||||||
brush_size: 1,
|
brush_size: 1,
|
||||||
symmetry: Symmetry::None,
|
symmetry: Symmetry::None,
|
||||||
floor_percent: 0.25,
|
floor_percent: 0.25,
|
||||||
spawn_list: Vec::new(),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn walk_outwards(new_depth: i32) -> DLABuilder {
|
#[allow(dead_code)]
|
||||||
DLABuilder {
|
pub fn walk_outwards() -> Box<DLABuilder> {
|
||||||
map: Map::new(new_depth),
|
Box::new(DLABuilder {
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
algorithm: DLAAlgorithm::WalkOutwards,
|
algorithm: DLAAlgorithm::WalkOutwards,
|
||||||
brush_size: 2,
|
brush_size: 2,
|
||||||
symmetry: Symmetry::None,
|
symmetry: Symmetry::None,
|
||||||
floor_percent: 0.25,
|
floor_percent: 0.25,
|
||||||
spawn_list: Vec::new(),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn central_attractor(new_depth: i32) -> DLABuilder {
|
#[allow(dead_code)]
|
||||||
DLABuilder {
|
pub fn central_attractor() -> Box<DLABuilder> {
|
||||||
map: Map::new(new_depth),
|
Box::new(DLABuilder {
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
algorithm: DLAAlgorithm::CentralAttractor,
|
algorithm: DLAAlgorithm::CentralAttractor,
|
||||||
brush_size: 2,
|
brush_size: 2,
|
||||||
symmetry: Symmetry::None,
|
symmetry: Symmetry::None,
|
||||||
floor_percent: 0.25,
|
floor_percent: 0.25,
|
||||||
spawn_list: Vec::new(),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insectoid(new_depth: i32) -> DLABuilder {
|
#[allow(dead_code)]
|
||||||
DLABuilder {
|
pub fn insectoid() -> Box<DLABuilder> {
|
||||||
map: Map::new(new_depth),
|
Box::new(DLABuilder {
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
algorithm: DLAAlgorithm::CentralAttractor,
|
algorithm: DLAAlgorithm::CentralAttractor,
|
||||||
brush_size: 2,
|
brush_size: 2,
|
||||||
symmetry: Symmetry::Horizontal,
|
symmetry: Symmetry::Horizontal,
|
||||||
floor_percent: 0.25,
|
floor_percent: 0.25,
|
||||||
spawn_list: Vec::new(),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::map_entry)]
|
#[allow(clippy::map_entry)]
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
// Carve starting seed
|
// Carve a starting seed
|
||||||
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 };
|
let starting_position = Position { x: build_data.map.width / 2, y: build_data.map.height / 2 };
|
||||||
let start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y);
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
self.map.tiles[start_idx] = TileType::Floor;
|
build_data.map.tiles[start_idx] = TileType::Floor;
|
||||||
self.map.tiles[start_idx - 1] = TileType::Floor;
|
build_data.map.tiles[start_idx - 1] = TileType::Floor;
|
||||||
self.map.tiles[start_idx + 1] = TileType::Floor;
|
build_data.map.tiles[start_idx + 1] = TileType::Floor;
|
||||||
self.map.tiles[start_idx - self.map.width as usize] = TileType::Floor;
|
build_data.map.tiles[start_idx - build_data.map.width as usize] = TileType::Floor;
|
||||||
self.map.tiles[start_idx + self.map.width as usize] = TileType::Floor;
|
build_data.map.tiles[start_idx + build_data.map.width as usize] = TileType::Floor;
|
||||||
|
|
||||||
// Random walker
|
// Random walker
|
||||||
let total_tiles = self.map.width * self.map.height;
|
let total_tiles = build_data.map.width * build_data.map.height;
|
||||||
let desired_floor_tiles = (self.floor_percent * total_tiles as f32) as usize;
|
let desired_floor_tiles = (self.floor_percent * total_tiles as f32) as usize;
|
||||||
let mut floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
|
let mut floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
|
||||||
while floor_tile_count < desired_floor_tiles {
|
while floor_tile_count < desired_floor_tiles {
|
||||||
match self.algorithm {
|
match self.algorithm {
|
||||||
DLAAlgorithm::WalkInwards => {
|
DLAAlgorithm::WalkInwards => {
|
||||||
let mut digger_x = rng.roll_dice(1, self.map.width - 3) + 1;
|
let mut digger_x = rng.roll_dice(1, build_data.map.width - 3) + 1;
|
||||||
let mut digger_y = rng.roll_dice(1, self.map.height - 3) + 1;
|
let mut digger_y = rng.roll_dice(1, build_data.map.height - 3) + 1;
|
||||||
let mut prev_x = digger_x;
|
let mut prev_x = digger_x;
|
||||||
let mut prev_y = digger_y;
|
let mut prev_y = digger_y;
|
||||||
let mut digger_idx = self.map.xy_idx(digger_x, digger_y);
|
let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
||||||
while self.map.tiles[digger_idx] == TileType::Wall {
|
while build_data.map.tiles[digger_idx] == TileType::Wall {
|
||||||
prev_x = digger_x;
|
prev_x = digger_x;
|
||||||
prev_y = digger_y;
|
prev_y = digger_y;
|
||||||
let stagger_direction = rng.roll_dice(1, 4);
|
let stagger_direction = rng.roll_dice(1, 4);
|
||||||
|
|
@ -152,7 +109,7 @@ impl DLABuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
if digger_x < self.map.width - 2 {
|
if digger_x < build_data.map.width - 2 {
|
||||||
digger_x += 1;
|
digger_x += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,20 +119,21 @@ impl DLABuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if digger_y < self.map.height - 2 {
|
if digger_y < build_data.map.height - 2 {
|
||||||
digger_y += 1;
|
digger_y += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
digger_idx = self.map.xy_idx(digger_x, digger_y);
|
digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
||||||
}
|
}
|
||||||
paint(&mut self.map, self.symmetry, self.brush_size, prev_x, prev_y);
|
paint(&mut build_data.map, self.symmetry, self.brush_size, prev_x, prev_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
DLAAlgorithm::WalkOutwards => {
|
DLAAlgorithm::WalkOutwards => {
|
||||||
let mut digger_x = self.starting_position.x;
|
let mut digger_x = starting_position.x;
|
||||||
let mut digger_y = self.starting_position.y;
|
let mut digger_y = starting_position.y;
|
||||||
let mut digger_idx = self.map.xy_idx(digger_x, digger_y);
|
let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
||||||
while self.map.tiles[digger_idx] == TileType::Floor {
|
while build_data.map.tiles[digger_idx] == TileType::Floor {
|
||||||
let stagger_direction = rng.roll_dice(1, 4);
|
let stagger_direction = rng.roll_dice(1, 4);
|
||||||
match stagger_direction {
|
match stagger_direction {
|
||||||
1 => {
|
1 => {
|
||||||
|
|
@ -184,7 +142,7 @@ impl DLABuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
if digger_x < self.map.width - 2 {
|
if digger_x < build_data.map.width - 2 {
|
||||||
digger_x += 1;
|
digger_x += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,56 +152,44 @@ impl DLABuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if digger_y < self.map.height - 2 {
|
if digger_y < build_data.map.height - 2 {
|
||||||
digger_y += 1;
|
digger_y += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
digger_idx = self.map.xy_idx(digger_x, digger_y);
|
digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
||||||
}
|
}
|
||||||
paint(&mut self.map, self.symmetry, self.brush_size, digger_x, digger_y);
|
paint(&mut build_data.map, self.symmetry, self.brush_size, digger_x, digger_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
DLAAlgorithm::CentralAttractor => {
|
DLAAlgorithm::CentralAttractor => {
|
||||||
let mut digger_x = rng.roll_dice(1, self.map.width - 3) + 1;
|
let mut digger_x = rng.roll_dice(1, build_data.map.width - 3) + 1;
|
||||||
let mut digger_y = rng.roll_dice(1, self.map.height - 3) + 1;
|
let mut digger_y = rng.roll_dice(1, build_data.map.height - 3) + 1;
|
||||||
let mut prev_x = digger_x;
|
let mut prev_x = digger_x;
|
||||||
let mut prev_y = digger_y;
|
let mut prev_y = digger_y;
|
||||||
let mut digger_idx = self.map.xy_idx(digger_x, digger_y);
|
let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
||||||
|
|
||||||
let mut path = rltk::line2d(
|
let mut path = rltk::line2d(
|
||||||
rltk::LineAlg::Bresenham,
|
rltk::LineAlg::Bresenham,
|
||||||
rltk::Point::new(digger_x, digger_y),
|
rltk::Point::new(digger_x, digger_y),
|
||||||
rltk::Point::new(self.starting_position.x, self.starting_position.y),
|
rltk::Point::new(starting_position.x, starting_position.y),
|
||||||
);
|
);
|
||||||
|
|
||||||
while self.map.tiles[digger_idx] == TileType::Wall && !path.is_empty() {
|
while build_data.map.tiles[digger_idx] == TileType::Wall && !path.is_empty() {
|
||||||
prev_x = digger_x;
|
prev_x = digger_x;
|
||||||
prev_y = digger_y;
|
prev_y = digger_y;
|
||||||
digger_x = path[0].x;
|
digger_x = path[0].x;
|
||||||
digger_y = path[0].y;
|
digger_y = path[0].y;
|
||||||
path.remove(0);
|
path.remove(0);
|
||||||
digger_idx = self.map.xy_idx(digger_x, digger_y);
|
digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
||||||
}
|
}
|
||||||
paint(&mut self.map, self.symmetry, self.brush_size, prev_x, prev_y);
|
paint(&mut build_data.map, self.symmetry, self.brush_size, prev_x, prev_y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.take_snapshot();
|
|
||||||
floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
|
|
||||||
}
|
|
||||||
// Find all tiles we can reach from the starting point
|
|
||||||
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Place the stairs
|
build_data.take_snapshot();
|
||||||
self.map.tiles[exit_tile] = TileType::DownStair;
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Now we build a noise map for use in spawning entities later
|
floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
|
||||||
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
|
|
||||||
|
|
||||||
// Spawn the entities
|
|
||||||
for area in self.noise_areas.iter() {
|
|
||||||
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
use super::{
|
use super::{paint, BuilderMap, InitialMapBuilder, Position, Symmetry, TileType};
|
||||||
generate_voronoi_spawn_regions, paint, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
|
|
||||||
Position, Symmetry, TileType, SHOW_MAPGEN,
|
|
||||||
};
|
|
||||||
use rltk::RandomNumberGenerator;
|
use rltk::RandomNumberGenerator;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub enum DrunkSpawnMode {
|
pub enum DrunkSpawnMode {
|
||||||
StartingPoint,
|
StartingPoint,
|
||||||
Random,
|
Random,
|
||||||
|
|
@ -20,53 +17,25 @@ pub struct DrunkardSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DrunkardsWalkBuilder {
|
pub struct DrunkardsWalkBuilder {
|
||||||
map: Map,
|
|
||||||
starting_position: Position,
|
|
||||||
depth: i32,
|
|
||||||
history: Vec<Map>,
|
|
||||||
noise_areas: HashMap<i32, Vec<usize>>,
|
|
||||||
settings: DrunkardSettings,
|
settings: DrunkardSettings,
|
||||||
spawn_list: Vec<(usize, String)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapBuilder for DrunkardsWalkBuilder {
|
impl InitialMapBuilder for DrunkardsWalkBuilder {
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
#[allow(dead_code)]
|
||||||
return self.build(rng);
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
}
|
self.build(rng, build_data);
|
||||||
// Getters
|
|
||||||
fn get_map(&mut self) -> Map {
|
|
||||||
return self.map.clone();
|
|
||||||
}
|
|
||||||
fn get_starting_pos(&mut self) -> Position {
|
|
||||||
return self.starting_position.clone();
|
|
||||||
}
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
|
|
||||||
return &self.spawn_list;
|
|
||||||
}
|
|
||||||
// Mapgen visualisation stuff
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map> {
|
|
||||||
return self.history.clone();
|
|
||||||
}
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if SHOW_MAPGEN {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl DrunkardsWalkBuilder {
|
impl DrunkardsWalkBuilder {
|
||||||
pub fn open_area(new_depth: i32) -> DrunkardsWalkBuilder {
|
#[allow(dead_code)]
|
||||||
DrunkardsWalkBuilder {
|
pub fn new(settings: DrunkardSettings) -> DrunkardsWalkBuilder {
|
||||||
map: Map::new(new_depth),
|
DrunkardsWalkBuilder { settings }
|
||||||
starting_position: Position { x: 0, y: 0 },
|
}
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
#[allow(dead_code)]
|
||||||
noise_areas: HashMap::new(),
|
pub fn open_area() -> Box<DrunkardsWalkBuilder> {
|
||||||
|
Box::new(DrunkardsWalkBuilder {
|
||||||
settings: DrunkardSettings {
|
settings: DrunkardSettings {
|
||||||
spawn_mode: DrunkSpawnMode::StartingPoint,
|
spawn_mode: DrunkSpawnMode::StartingPoint,
|
||||||
drunken_lifetime: 400,
|
drunken_lifetime: 400,
|
||||||
|
|
@ -74,17 +43,12 @@ impl DrunkardsWalkBuilder {
|
||||||
brush_size: 1,
|
brush_size: 1,
|
||||||
symmetry: Symmetry::None,
|
symmetry: Symmetry::None,
|
||||||
},
|
},
|
||||||
spawn_list: Vec::new(),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_halls(new_depth: i32) -> DrunkardsWalkBuilder {
|
#[allow(dead_code)]
|
||||||
DrunkardsWalkBuilder {
|
pub fn open_halls() -> Box<DrunkardsWalkBuilder> {
|
||||||
map: Map::new(new_depth),
|
Box::new(DrunkardsWalkBuilder {
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
settings: DrunkardSettings {
|
settings: DrunkardSettings {
|
||||||
spawn_mode: DrunkSpawnMode::Random,
|
spawn_mode: DrunkSpawnMode::Random,
|
||||||
drunken_lifetime: 400,
|
drunken_lifetime: 400,
|
||||||
|
|
@ -92,17 +56,12 @@ impl DrunkardsWalkBuilder {
|
||||||
brush_size: 1,
|
brush_size: 1,
|
||||||
symmetry: Symmetry::None,
|
symmetry: Symmetry::None,
|
||||||
},
|
},
|
||||||
spawn_list: Vec::new(),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn winding_passages(new_depth: i32) -> DrunkardsWalkBuilder {
|
#[allow(dead_code)]
|
||||||
DrunkardsWalkBuilder {
|
pub fn winding_passages() -> Box<DrunkardsWalkBuilder> {
|
||||||
map: Map::new(new_depth),
|
Box::new(DrunkardsWalkBuilder {
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
settings: DrunkardSettings {
|
settings: DrunkardSettings {
|
||||||
spawn_mode: DrunkSpawnMode::Random,
|
spawn_mode: DrunkSpawnMode::Random,
|
||||||
drunken_lifetime: 100,
|
drunken_lifetime: 100,
|
||||||
|
|
@ -110,17 +69,12 @@ impl DrunkardsWalkBuilder {
|
||||||
brush_size: 1,
|
brush_size: 1,
|
||||||
symmetry: Symmetry::None,
|
symmetry: Symmetry::None,
|
||||||
},
|
},
|
||||||
spawn_list: Vec::new(),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fat_passages(new_depth: i32) -> DrunkardsWalkBuilder {
|
#[allow(dead_code)]
|
||||||
DrunkardsWalkBuilder {
|
pub fn fat_passages() -> Box<DrunkardsWalkBuilder> {
|
||||||
map: Map::new(new_depth),
|
Box::new(DrunkardsWalkBuilder {
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
settings: DrunkardSettings {
|
settings: DrunkardSettings {
|
||||||
spawn_mode: DrunkSpawnMode::Random,
|
spawn_mode: DrunkSpawnMode::Random,
|
||||||
drunken_lifetime: 100,
|
drunken_lifetime: 100,
|
||||||
|
|
@ -128,17 +82,12 @@ impl DrunkardsWalkBuilder {
|
||||||
brush_size: 2,
|
brush_size: 2,
|
||||||
symmetry: Symmetry::None,
|
symmetry: Symmetry::None,
|
||||||
},
|
},
|
||||||
spawn_list: Vec::new(),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fearful_symmetry(new_depth: i32) -> DrunkardsWalkBuilder {
|
#[allow(dead_code)]
|
||||||
DrunkardsWalkBuilder {
|
pub fn fearful_symmetry() -> Box<DrunkardsWalkBuilder> {
|
||||||
map: Map::new(new_depth),
|
Box::new(DrunkardsWalkBuilder {
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
settings: DrunkardSettings {
|
settings: DrunkardSettings {
|
||||||
spawn_mode: DrunkSpawnMode::Random,
|
spawn_mode: DrunkSpawnMode::Random,
|
||||||
drunken_lifetime: 100,
|
drunken_lifetime: 100,
|
||||||
|
|
@ -146,51 +95,47 @@ impl DrunkardsWalkBuilder {
|
||||||
brush_size: 1,
|
brush_size: 1,
|
||||||
symmetry: Symmetry::Both,
|
symmetry: Symmetry::Both,
|
||||||
},
|
},
|
||||||
spawn_list: Vec::new(),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::map_entry)]
|
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
// Set a central starting point
|
||||||
// Central starting pos
|
let starting_position = Position { x: build_data.map.width / 2, y: build_data.map.height / 2 };
|
||||||
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 };
|
let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y);
|
||||||
let start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
build_data.map.tiles[start_idx] = TileType::Floor;
|
||||||
self.map.tiles[start_idx] = TileType::Floor;
|
|
||||||
|
|
||||||
let total_tiles = self.map.width * self.map.height;
|
let total_tiles = build_data.map.width * build_data.map.height;
|
||||||
let desired_floor_tiles = (self.settings.floor_percent * total_tiles as f32) as usize;
|
let desired_floor_tiles = (self.settings.floor_percent * total_tiles as f32) as usize;
|
||||||
let mut floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
|
let mut floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
|
||||||
let mut digger_count = 0;
|
let mut digger_count = 0;
|
||||||
let mut active_digger_count = 0;
|
|
||||||
|
|
||||||
while floor_tile_count < desired_floor_tiles {
|
while floor_tile_count < desired_floor_tiles {
|
||||||
let mut did_something = false;
|
let mut did_something = false;
|
||||||
let mut drunk_x;
|
let mut drunk_x;
|
||||||
let mut drunk_y;
|
let mut drunk_y;
|
||||||
match self.settings.spawn_mode {
|
match self.settings.spawn_mode {
|
||||||
DrunkSpawnMode::StartingPoint => {
|
DrunkSpawnMode::StartingPoint => {
|
||||||
drunk_x = self.starting_position.x;
|
drunk_x = starting_position.x;
|
||||||
drunk_y = self.starting_position.y;
|
drunk_y = starting_position.y;
|
||||||
}
|
}
|
||||||
DrunkSpawnMode::Random => {
|
DrunkSpawnMode::Random => {
|
||||||
if digger_count == 0 {
|
if digger_count == 0 {
|
||||||
drunk_x = self.starting_position.x;
|
drunk_x = starting_position.x;
|
||||||
drunk_y = self.starting_position.y;
|
drunk_y = starting_position.y;
|
||||||
} else {
|
} else {
|
||||||
drunk_x = rng.roll_dice(1, self.map.width - 3) + 1;
|
drunk_x = rng.roll_dice(1, build_data.map.width - 3) + 1;
|
||||||
drunk_y = rng.roll_dice(1, self.map.height - 3) + 1;
|
drunk_y = rng.roll_dice(1, build_data.map.height - 3) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut drunk_life = self.settings.drunken_lifetime;
|
let mut drunk_life = self.settings.drunken_lifetime;
|
||||||
|
|
||||||
while drunk_life > 0 {
|
while drunk_life > 0 {
|
||||||
let drunk_idx = self.map.xy_idx(drunk_x, drunk_y);
|
let drunk_idx = build_data.map.xy_idx(drunk_x, drunk_y);
|
||||||
if self.map.tiles[drunk_idx] == TileType::Wall {
|
if build_data.map.tiles[drunk_idx] == TileType::Wall {
|
||||||
did_something = true;
|
did_something = true;
|
||||||
}
|
}
|
||||||
paint(&mut self.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y);
|
paint(&mut build_data.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y);
|
||||||
self.map.tiles[drunk_idx] = TileType::DownStair;
|
build_data.map.tiles[drunk_idx] = TileType::DownStair;
|
||||||
|
|
||||||
let stagger_direction = rng.roll_dice(1, 4);
|
let stagger_direction = rng.roll_dice(1, 4);
|
||||||
match stagger_direction {
|
match stagger_direction {
|
||||||
|
|
@ -200,7 +145,7 @@ impl DrunkardsWalkBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
if drunk_x < self.map.width - 2 {
|
if drunk_x < build_data.map.width - 2 {
|
||||||
drunk_x += 1;
|
drunk_x += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -210,46 +155,25 @@ impl DrunkardsWalkBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if drunk_y < self.map.height - 2 {
|
if drunk_y < build_data.map.height - 2 {
|
||||||
drunk_y += 1;
|
drunk_y += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drunk_life -= 1;
|
drunk_life -= 1;
|
||||||
}
|
}
|
||||||
if did_something {
|
if did_something {
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
active_digger_count += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
digger_count += 1;
|
digger_count += 1;
|
||||||
for t in self.map.tiles.iter_mut() {
|
for t in build_data.map.tiles.iter_mut() {
|
||||||
if *t == TileType::DownStair {
|
if *t == TileType::DownStair {
|
||||||
*t = TileType::Floor;
|
*t = TileType::Floor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
|
floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
|
||||||
}
|
|
||||||
|
|
||||||
rltk::console::log(format!(
|
|
||||||
"{} dwarves gave up their sobriety, of whom {} actually found a wall.",
|
|
||||||
digger_count, active_digger_count
|
|
||||||
));
|
|
||||||
|
|
||||||
// Find all tiles reachable from starting pos
|
|
||||||
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Place stairs
|
|
||||||
self.map.tiles[exit_tile] = TileType::DownStair;
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Noise map for spawning entities
|
|
||||||
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
|
|
||||||
|
|
||||||
// Spawn the entities
|
|
||||||
for area in self.noise_areas.iter() {
|
|
||||||
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,209 +1,30 @@
|
||||||
use super::{
|
use super::{BuilderMap, InitialMapBuilder, Map, TileType};
|
||||||
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
|
|
||||||
Position, TileType, SHOW_MAPGEN,
|
|
||||||
};
|
|
||||||
use rltk::RandomNumberGenerator;
|
use rltk::RandomNumberGenerator;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub struct MazeBuilder {
|
pub struct MazeBuilder {}
|
||||||
map: Map,
|
|
||||||
starting_position: Position,
|
|
||||||
depth: i32,
|
|
||||||
history: Vec<Map>,
|
|
||||||
noise_areas: HashMap<i32, Vec<usize>>,
|
|
||||||
spawn_list: Vec<(usize, String)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MapBuilder for MazeBuilder {
|
impl InitialMapBuilder for MazeBuilder {
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
#[allow(dead_code)]
|
||||||
return self.build(rng);
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
}
|
self.build(rng, build_data);
|
||||||
// Getters
|
|
||||||
fn get_map(&mut self) -> Map {
|
|
||||||
return self.map.clone();
|
|
||||||
}
|
|
||||||
fn get_starting_pos(&mut self) -> Position {
|
|
||||||
return self.starting_position.clone();
|
|
||||||
}
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
|
|
||||||
return &self.spawn_list;
|
|
||||||
}
|
|
||||||
// Mapgen visualisation stuff
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map> {
|
|
||||||
return self.history.clone();
|
|
||||||
}
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if SHOW_MAPGEN {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MazeBuilder {
|
impl MazeBuilder {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn new(new_depth: i32) -> MazeBuilder {
|
pub fn new() -> Box<MazeBuilder> {
|
||||||
MazeBuilder {
|
Box::new(MazeBuilder {})
|
||||||
map: Map::new(new_depth),
|
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::map_entry)]
|
#[allow(clippy::map_entry)]
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
// Maze gen
|
// Maze gen
|
||||||
let mut maze = Grid::new((self.map.width / 2) - 2, (self.map.height / 2) - 2, rng);
|
let mut maze = Grid::new((build_data.map.width / 2) - 2, (build_data.map.height / 2) - 2, rng);
|
||||||
maze.generate_maze(self);
|
maze.generate_maze(build_data);
|
||||||
|
|
||||||
// Starting point @ top left
|
|
||||||
self.starting_position = Position { x: 2, y: 2 };
|
|
||||||
let start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
|
||||||
self.take_snapshot();
|
|
||||||
// Find all tiles we can reach from the starting point
|
|
||||||
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
|
|
||||||
self.take_snapshot();
|
|
||||||
// Place the stairs
|
|
||||||
self.map.tiles[exit_tile] = TileType::DownStair;
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Now we build a noise map for use in spawning entities later
|
|
||||||
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
|
|
||||||
|
|
||||||
// Spawn the entities
|
|
||||||
for area in self.noise_areas.iter() {
|
|
||||||
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Grid<'a> {
|
/* Maze code taken under MIT from https://github.com/cyucelen/mazeGenerator/ */
|
||||||
width: i32,
|
|
||||||
height: i32,
|
|
||||||
cells: Vec<Cell>,
|
|
||||||
backtrace: Vec<usize>,
|
|
||||||
current: usize,
|
|
||||||
rng: &'a mut RandomNumberGenerator,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Grid<'a> {
|
|
||||||
fn new(width: i32, height: i32, rng: &mut RandomNumberGenerator) -> Grid {
|
|
||||||
let mut grid = Grid { width, height, cells: Vec::new(), backtrace: Vec::new(), current: 0, rng };
|
|
||||||
|
|
||||||
for row in 0..height {
|
|
||||||
for column in 0..width {
|
|
||||||
grid.cells.push(Cell::new(row, column))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_index(&self, row: i32, column: i32) -> i32 {
|
|
||||||
if row < 0 || column < 0 || column > self.width - 1 || row > self.height - 1 {
|
|
||||||
-1
|
|
||||||
} else {
|
|
||||||
column + (row * self.width)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_available_neighbours(&self) -> Vec<usize> {
|
|
||||||
let mut neighbours: Vec<usize> = Vec::new();
|
|
||||||
let current_row = self.cells[self.current].row;
|
|
||||||
let current_column = self.cells[self.current].column;
|
|
||||||
|
|
||||||
let neighbour_indices: [i32; 4] = [
|
|
||||||
self.calculate_index(current_row - 1, current_column),
|
|
||||||
self.calculate_index(current_row, current_column + 1),
|
|
||||||
self.calculate_index(current_row + 1, current_column),
|
|
||||||
self.calculate_index(current_row, current_column - 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
for i in neighbour_indices.iter() {
|
|
||||||
if *i != -1 && !self.cells[*i as usize].visited {
|
|
||||||
neighbours.push(*i as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return neighbours;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_next_cell(&mut self) -> Option<usize> {
|
|
||||||
let neighbours = self.get_available_neighbours();
|
|
||||||
if neighbours.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if neighbours.len() == 1 {
|
|
||||||
return Some(neighbours[0]);
|
|
||||||
}
|
|
||||||
return Some(neighbours[(self.rng.roll_dice(1, neighbours.len() as i32) - 1) as usize]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_maze(&mut self, generator: &mut MazeBuilder) {
|
|
||||||
let mut i = 0;
|
|
||||||
loop {
|
|
||||||
self.cells[self.current].visited = true;
|
|
||||||
let next = self.find_next_cell();
|
|
||||||
|
|
||||||
match next {
|
|
||||||
Some(next) => {
|
|
||||||
self.cells[next].visited = true;
|
|
||||||
self.backtrace.push(self.current);
|
|
||||||
let (lower_part, higher_part) = self.cells.split_at_mut(std::cmp::max(self.current, next));
|
|
||||||
let cell1 = &mut lower_part[std::cmp::min(self.current, next)];
|
|
||||||
let cell2 = &mut higher_part[0];
|
|
||||||
cell1.remove_walls(cell2);
|
|
||||||
self.current = next;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if self.backtrace.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
self.current = self.backtrace[0];
|
|
||||||
self.backtrace.remove(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i % 50 == 0 {
|
|
||||||
self.copy_to_map(&mut generator.map);
|
|
||||||
generator.take_snapshot();
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy_to_map(&self, map: &mut Map) {
|
|
||||||
// Clear map
|
|
||||||
for i in map.tiles.iter_mut() {
|
|
||||||
*i = TileType::Wall;
|
|
||||||
}
|
|
||||||
|
|
||||||
for cell in self.cells.iter() {
|
|
||||||
let x = cell.column + 1;
|
|
||||||
let y = cell.row + 1;
|
|
||||||
let idx = map.xy_idx(x * 2, y * 2);
|
|
||||||
|
|
||||||
map.tiles[idx] = TileType::Floor;
|
|
||||||
if !cell.walls[TOP] {
|
|
||||||
map.tiles[idx - map.width as usize] = TileType::Floor
|
|
||||||
}
|
|
||||||
if !cell.walls[RIGHT] {
|
|
||||||
map.tiles[idx + 1] = TileType::Floor
|
|
||||||
}
|
|
||||||
if !cell.walls[BOTTOM] {
|
|
||||||
map.tiles[idx + map.width as usize] = TileType::Floor
|
|
||||||
}
|
|
||||||
if !cell.walls[LEFT] {
|
|
||||||
map.tiles[idx - 1] = TileType::Floor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TOP: usize = 0;
|
const TOP: usize = 0;
|
||||||
const RIGHT: usize = 1;
|
const RIGHT: usize = 1;
|
||||||
|
|
@ -235,10 +56,139 @@ impl Cell {
|
||||||
next.walls[LEFT] = false;
|
next.walls[LEFT] = false;
|
||||||
} else if y == 1 {
|
} else if y == 1 {
|
||||||
self.walls[TOP] = false;
|
self.walls[TOP] = false;
|
||||||
next.walls[BOTTOM] = false
|
next.walls[BOTTOM] = false;
|
||||||
} else if y == -1 {
|
} else if y == -1 {
|
||||||
self.walls[BOTTOM] = false;
|
self.walls[BOTTOM] = false;
|
||||||
next.walls[TOP] = false;
|
next.walls[TOP] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Grid<'a> {
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
cells: Vec<Cell>,
|
||||||
|
backtrace: Vec<usize>,
|
||||||
|
current: usize,
|
||||||
|
rng: &'a mut RandomNumberGenerator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Grid<'a> {
|
||||||
|
fn new(width: i32, height: i32, rng: &mut RandomNumberGenerator) -> Grid {
|
||||||
|
let mut grid = Grid { width, height, cells: Vec::new(), backtrace: Vec::new(), current: 0, rng };
|
||||||
|
|
||||||
|
for row in 0..height {
|
||||||
|
for column in 0..width {
|
||||||
|
grid.cells.push(Cell::new(row, column));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_index(&self, row: i32, column: i32) -> i32 {
|
||||||
|
if row < 0 || column < 0 || column > self.width - 1 || row > self.height - 1 {
|
||||||
|
-1
|
||||||
|
} else {
|
||||||
|
column + (row * self.width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_available_neighbors(&self) -> Vec<usize> {
|
||||||
|
let mut neighbors: Vec<usize> = Vec::new();
|
||||||
|
|
||||||
|
let current_row = self.cells[self.current].row;
|
||||||
|
let current_column = self.cells[self.current].column;
|
||||||
|
|
||||||
|
let neighbor_indices: [i32; 4] = [
|
||||||
|
self.calculate_index(current_row - 1, current_column),
|
||||||
|
self.calculate_index(current_row, current_column + 1),
|
||||||
|
self.calculate_index(current_row + 1, current_column),
|
||||||
|
self.calculate_index(current_row, current_column - 1),
|
||||||
|
];
|
||||||
|
|
||||||
|
for i in neighbor_indices.iter() {
|
||||||
|
if *i != -1 && !self.cells[*i as usize].visited {
|
||||||
|
neighbors.push(*i as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
neighbors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_next_cell(&mut self) -> Option<usize> {
|
||||||
|
let neighbors = self.get_available_neighbors();
|
||||||
|
if !neighbors.is_empty() {
|
||||||
|
if neighbors.len() == 1 {
|
||||||
|
return Some(neighbors[0]);
|
||||||
|
} else {
|
||||||
|
return Some(neighbors[(self.rng.roll_dice(1, neighbors.len() as i32) - 1) as usize]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_maze(&mut self, build_data: &mut BuilderMap) {
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
self.cells[self.current].visited = true;
|
||||||
|
let next = self.find_next_cell();
|
||||||
|
|
||||||
|
match next {
|
||||||
|
Some(next) => {
|
||||||
|
self.cells[next].visited = true;
|
||||||
|
self.backtrace.push(self.current);
|
||||||
|
// __lower_part__ __higher_part_
|
||||||
|
// / \ / \
|
||||||
|
// --------cell1------ | cell2-----------
|
||||||
|
let (lower_part, higher_part) = self.cells.split_at_mut(std::cmp::max(self.current, next));
|
||||||
|
let cell1 = &mut lower_part[std::cmp::min(self.current, next)];
|
||||||
|
let cell2 = &mut higher_part[0];
|
||||||
|
cell1.remove_walls(cell2);
|
||||||
|
self.current = next;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if !self.backtrace.is_empty() {
|
||||||
|
self.current = self.backtrace[0];
|
||||||
|
self.backtrace.remove(0);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i % 50 == 0 {
|
||||||
|
self.copy_to_map(&mut build_data.map);
|
||||||
|
build_data.take_snapshot();
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_to_map(&self, map: &mut Map) {
|
||||||
|
// Clear the map
|
||||||
|
for i in map.tiles.iter_mut() {
|
||||||
|
*i = TileType::Wall;
|
||||||
|
}
|
||||||
|
|
||||||
|
for cell in self.cells.iter() {
|
||||||
|
let x = cell.column + 1;
|
||||||
|
let y = cell.row + 1;
|
||||||
|
let idx = map.xy_idx(x * 2, y * 2);
|
||||||
|
|
||||||
|
map.tiles[idx] = TileType::Floor;
|
||||||
|
if !cell.walls[TOP] {
|
||||||
|
map.tiles[idx - map.width as usize] = TileType::Floor
|
||||||
|
}
|
||||||
|
if !cell.walls[RIGHT] {
|
||||||
|
map.tiles[idx + 1] = TileType::Floor
|
||||||
|
}
|
||||||
|
if !cell.walls[BOTTOM] {
|
||||||
|
map.tiles[idx + map.width as usize] = TileType::Floor
|
||||||
|
}
|
||||||
|
if !cell.walls[LEFT] {
|
||||||
|
map.tiles[idx - 1] = TileType::Floor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,63 +17,157 @@ mod voronoi;
|
||||||
use voronoi::VoronoiBuilder;
|
use voronoi::VoronoiBuilder;
|
||||||
mod prefab_builder;
|
mod prefab_builder;
|
||||||
use prefab_builder::PrefabBuilder;
|
use prefab_builder::PrefabBuilder;
|
||||||
|
mod room_based_spawner;
|
||||||
mod wfc;
|
mod wfc;
|
||||||
|
use room_based_spawner::*;
|
||||||
|
mod room_based_stairs;
|
||||||
|
use room_based_stairs::*;
|
||||||
|
mod room_based_starting_position;
|
||||||
|
use room_based_starting_position::*;
|
||||||
|
mod area_starting_points;
|
||||||
|
use area_starting_points::{AreaStartingPosition, XStart, YStart};
|
||||||
|
mod cull_unreachable;
|
||||||
|
use cull_unreachable::CullUnreachable;
|
||||||
|
mod distant_exit;
|
||||||
|
use distant_exit::DistantExit;
|
||||||
|
mod voronoi_spawning;
|
||||||
use common::*;
|
use common::*;
|
||||||
use rltk::RandomNumberGenerator;
|
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
use voronoi_spawning::VoronoiSpawning;
|
||||||
use wfc::WaveFunctionCollapseBuilder;
|
use wfc::WaveFunctionCollapseBuilder;
|
||||||
|
|
||||||
pub trait MapBuilder {
|
// Shared data to be passed around build chain
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator);
|
pub struct BuilderMap {
|
||||||
fn spawn_entities(&mut self, ecs: &mut World) {
|
pub spawn_list: Vec<(usize, String)>,
|
||||||
for entity in self.get_spawn_list().iter() {
|
pub map: Map,
|
||||||
|
pub starting_position: Option<Position>,
|
||||||
|
pub rooms: Option<Vec<Rect>>,
|
||||||
|
pub history: Vec<Map>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuilderMap {
|
||||||
|
fn take_snapshot(&mut self) {
|
||||||
|
if SHOW_MAPGEN {
|
||||||
|
let mut snapshot = self.map.clone();
|
||||||
|
for v in snapshot.revealed_tiles.iter_mut() {
|
||||||
|
*v = true;
|
||||||
|
}
|
||||||
|
self.history.push(snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BuilderChain {
|
||||||
|
starter: Option<Box<dyn InitialMapBuilder>>,
|
||||||
|
builders: Vec<Box<dyn MetaMapBuilder>>,
|
||||||
|
pub build_data: BuilderMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuilderChain {
|
||||||
|
pub fn new(new_depth: i32) -> BuilderChain {
|
||||||
|
BuilderChain {
|
||||||
|
starter: None,
|
||||||
|
builders: Vec::new(),
|
||||||
|
build_data: BuilderMap {
|
||||||
|
spawn_list: Vec::new(),
|
||||||
|
map: Map::new(new_depth),
|
||||||
|
starting_position: None,
|
||||||
|
rooms: None,
|
||||||
|
history: Vec::new(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_with(&mut self, starter: Box<dyn InitialMapBuilder>) {
|
||||||
|
match self.starter {
|
||||||
|
None => self.starter = Some(starter),
|
||||||
|
Some(_) => panic!("You can only have one starting builder."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with(&mut self, metabuilder: Box<dyn MetaMapBuilder>) {
|
||||||
|
self.builders.push(metabuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator) {
|
||||||
|
match &mut self.starter {
|
||||||
|
None => panic!("Cannot run a map builder chain without a starting build system"),
|
||||||
|
Some(starter) => {
|
||||||
|
// Build the starting map
|
||||||
|
starter.build_map(rng, &mut self.build_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build additional layers in turn
|
||||||
|
for metabuilder in self.builders.iter_mut() {
|
||||||
|
metabuilder.build_map(rng, &mut self.build_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_entities(&mut self, ecs: &mut World) {
|
||||||
|
for entity in self.build_data.spawn_list.iter() {
|
||||||
spawner::spawn_entity(ecs, &(&entity.0, &entity.1));
|
spawner::spawn_entity(ecs, &(&entity.0, &entity.1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn get_map(&mut self) -> Map;
|
|
||||||
fn get_starting_pos(&mut self) -> Position;
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)>;
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map>;
|
|
||||||
fn take_snapshot(&mut self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
pub trait InitialMapBuilder {
|
||||||
pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap);
|
||||||
let mut rng = rltk::RandomNumberGenerator::new();
|
}
|
||||||
let builder = rng.roll_dice(1, 17);
|
|
||||||
let mut result : Box<dyn MapBuilder>;
|
pub trait MetaMapBuilder {
|
||||||
rltk::console::log("Picking procgen type ->");
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap);
|
||||||
match builder {
|
}
|
||||||
1 => { result = Box::new(BspDungeonBuilder::new(new_depth)); }
|
|
||||||
2 => { result = Box::new(BspInteriorBuilder::new(new_depth)); }
|
fn random_initial_builder(rng: &mut rltk::RandomNumberGenerator) -> (Box<dyn InitialMapBuilder>, bool) {
|
||||||
3 => { result = Box::new(CellularAutomataBuilder::new(new_depth)); }
|
let builder = rng.roll_dice(1, 17);
|
||||||
4 => { result = Box::new(DrunkardsWalkBuilder::open_area(new_depth)); }
|
let result: (Box<dyn InitialMapBuilder>, bool);
|
||||||
5 => { result = Box::new(DrunkardsWalkBuilder::open_halls(new_depth)); }
|
match builder {
|
||||||
6 => { result = Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)); }
|
1 => result = (BspDungeonBuilder::new(), true),
|
||||||
7 => { result = Box::new(DrunkardsWalkBuilder::fat_passages(new_depth)); }
|
2 => result = (BspInteriorBuilder::new(), true),
|
||||||
8 => { result = Box::new(DrunkardsWalkBuilder::fearful_symmetry(new_depth)); }
|
3 => result = (CellularAutomataBuilder::new(), false),
|
||||||
9 => { result = Box::new(MazeBuilder::new(new_depth)); }
|
4 => result = (DrunkardsWalkBuilder::open_area(), false),
|
||||||
10 => { result = Box::new(DLABuilder::walk_inwards(new_depth)); }
|
5 => result = (DrunkardsWalkBuilder::open_halls(), false),
|
||||||
11 => { result = Box::new(DLABuilder::walk_outwards(new_depth)); }
|
6 => result = (DrunkardsWalkBuilder::winding_passages(), false),
|
||||||
12 => { result = Box::new(DLABuilder::central_attractor(new_depth)); }
|
7 => result = (DrunkardsWalkBuilder::fat_passages(), false),
|
||||||
13 => { result = Box::new(DLABuilder::insectoid(new_depth)); }
|
8 => result = (DrunkardsWalkBuilder::fearful_symmetry(), false),
|
||||||
14 => { result = Box::new(VoronoiBuilder::pythagoras(new_depth)); }
|
9 => result = (MazeBuilder::new(), false),
|
||||||
15 => { result = Box::new(VoronoiBuilder::manhattan(new_depth)); }
|
10 => result = (DLABuilder::walk_inwards(), false),
|
||||||
16 => { result = Box::new(PrefabBuilder::constant(new_depth, prefab_builder::prefab_levels::WFC_POPULATED)) },
|
11 => result = (DLABuilder::walk_outwards(), false),
|
||||||
_ => { result = Box::new(simple_map::SimpleMapBuilder::new(new_depth)); }
|
12 => result = (DLABuilder::central_attractor(), false),
|
||||||
}
|
13 => result = (DLABuilder::insectoid(), false),
|
||||||
|
14 => result = (VoronoiBuilder::pythagoras(), false),
|
||||||
if rng.roll_dice(1, 3)==1 {
|
15 => result = (VoronoiBuilder::manhattan(), false),
|
||||||
result = Box::new(WaveFunctionCollapseBuilder::derived_map(new_depth, result));
|
16 => result = (PrefabBuilder::constant(prefab_builder::prefab_levels::WFC_POPULATED), false),
|
||||||
rltk::console::log("-> wfc");
|
_ => result = (simple_map::SimpleMapBuilder::new(), true),
|
||||||
}
|
|
||||||
|
|
||||||
if rng.roll_dice(1, 20)==1 {
|
|
||||||
result = Box::new(PrefabBuilder::sectional(new_depth, prefab_builder::prefab_sections::UNDERGROUND_FORT ,result));
|
|
||||||
rltk::console::log("-> sectional");
|
|
||||||
}
|
|
||||||
|
|
||||||
result = Box::new(PrefabBuilder::vaults(new_depth, result));
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator) -> BuilderChain {
|
||||||
|
let mut builder = BuilderChain::new(new_depth);
|
||||||
|
let (random_starter, has_rooms) = random_initial_builder(rng);
|
||||||
|
builder.start_with(random_starter);
|
||||||
|
if has_rooms {
|
||||||
|
builder.with(RoomBasedSpawner::new());
|
||||||
|
builder.with(RoomBasedStairs::new());
|
||||||
|
builder.with(RoomBasedStartingPosition::new());
|
||||||
|
} else {
|
||||||
|
builder.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE));
|
||||||
|
builder.with(CullUnreachable::new());
|
||||||
|
builder.with(VoronoiSpawning::new());
|
||||||
|
builder.with(DistantExit::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
if rng.roll_dice(1, 3) == 1 {
|
||||||
|
builder.with(WaveFunctionCollapseBuilder::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
if rng.roll_dice(1, 20) == 1 {
|
||||||
|
builder.with(PrefabBuilder::sectional(prefab_builder::prefab_sections::UNDERGROUND_FORT));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.with(PrefabBuilder::vaults());
|
||||||
|
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,193 +1,290 @@
|
||||||
use super::{remove_unreachable_areas_returning_most_distant, Map, MapBuilder, Position, TileType, SHOW_MAPGEN};
|
use super::{BuilderMap, InitialMapBuilder, MetaMapBuilder, Position, TileType};
|
||||||
use rltk::RandomNumberGenerator;
|
use rltk::RandomNumberGenerator;
|
||||||
pub mod prefab_levels;
|
pub mod prefab_levels;
|
||||||
pub mod prefab_sections;
|
pub mod prefab_sections;
|
||||||
pub mod prefab_vaults;
|
pub mod prefab_vaults;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(PartialEq, Clone)]
|
|
||||||
pub enum PrefabMode {
|
pub enum PrefabMode {
|
||||||
RexLevel { template: &'static str },
|
RexLevel { template: &'static str },
|
||||||
Constant { level: prefab_levels::PrefabLevel },
|
Constant { level: prefab_levels::PrefabLevel },
|
||||||
Sectional { section: prefab_sections::PrefabSection },
|
Sectional { section: prefab_sections::PrefabSection },
|
||||||
Vaults,
|
RoomVaults,
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrefabBuilder {
|
|
||||||
map: Map,
|
|
||||||
starting_position: Position,
|
|
||||||
depth: i32,
|
|
||||||
history: Vec<Map>,
|
|
||||||
mode: PrefabMode,
|
|
||||||
spawn_list: Vec<(usize, String)>,
|
|
||||||
previous_builder: Option<Box<dyn MapBuilder>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MapBuilder for PrefabBuilder {
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
|
||||||
return self.build(rng);
|
|
||||||
}
|
|
||||||
// Getters
|
|
||||||
fn get_map(&mut self) -> Map {
|
|
||||||
return self.map.clone();
|
|
||||||
}
|
|
||||||
fn get_starting_pos(&mut self) -> Position {
|
|
||||||
return self.starting_position.clone();
|
|
||||||
}
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
|
|
||||||
return &self.spawn_list;
|
|
||||||
}
|
|
||||||
// Mapgen visualisation stuff
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map> {
|
|
||||||
return self.history.clone();
|
|
||||||
}
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if SHOW_MAPGEN {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
pub struct PrefabBuilder {
|
||||||
|
mode: PrefabMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetaMapBuilder for PrefabBuilder {
|
||||||
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
|
self.build(rng, build_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InitialMapBuilder for PrefabBuilder {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
|
self.build(rng, build_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PrefabBuilder {
|
impl PrefabBuilder {
|
||||||
pub fn rex_level(new_depth: i32, template: &'static str) -> PrefabBuilder {
|
#[allow(dead_code)]
|
||||||
PrefabBuilder {
|
pub fn new() -> Box<PrefabBuilder> {
|
||||||
map: Map::new(new_depth),
|
Box::new(PrefabBuilder { mode: PrefabMode::RoomVaults })
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
mode: PrefabMode::RexLevel { template },
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
previous_builder: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn constant(new_depth: i32, level: prefab_levels::PrefabLevel) -> PrefabBuilder {
|
|
||||||
PrefabBuilder {
|
|
||||||
map: Map::new(new_depth),
|
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
mode: PrefabMode::Constant { level },
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
previous_builder: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn sectional(
|
|
||||||
new_depth: i32,
|
|
||||||
section: prefab_sections::PrefabSection,
|
|
||||||
previous_builder: Box<dyn MapBuilder>,
|
|
||||||
) -> PrefabBuilder {
|
|
||||||
PrefabBuilder {
|
|
||||||
map: Map::new(new_depth),
|
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
mode: PrefabMode::Sectional { section },
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
previous_builder: Some(previous_builder),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn vaults(new_depth: i32, previous_builder: Box<dyn MapBuilder>) -> PrefabBuilder {
|
|
||||||
PrefabBuilder {
|
|
||||||
map: Map::new(new_depth),
|
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
mode: PrefabMode::Vaults,
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
previous_builder: Some(previous_builder),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
#[allow(dead_code)]
|
||||||
|
pub fn rex_level(template: &'static str) -> Box<PrefabBuilder> {
|
||||||
|
Box::new(PrefabBuilder { mode: PrefabMode::RexLevel { template } })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn constant(level: prefab_levels::PrefabLevel) -> Box<PrefabBuilder> {
|
||||||
|
Box::new(PrefabBuilder { mode: PrefabMode::Constant { level } })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn sectional(section: prefab_sections::PrefabSection) -> Box<PrefabBuilder> {
|
||||||
|
Box::new(PrefabBuilder { mode: PrefabMode::Sectional { section } })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn vaults() -> Box<PrefabBuilder> {
|
||||||
|
Box::new(PrefabBuilder { mode: PrefabMode::RoomVaults })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
PrefabMode::RexLevel { template } => self.load_rex_map(&template),
|
PrefabMode::RexLevel { template } => self.load_rex_map(&template, build_data),
|
||||||
PrefabMode::Constant { level } => self.load_ascii_map(&level),
|
PrefabMode::Constant { level } => self.load_ascii_map(&level, build_data),
|
||||||
PrefabMode::Sectional { section } => self.apply_sectional(§ion, rng),
|
PrefabMode::Sectional { section } => self.apply_sectional(§ion, rng, build_data),
|
||||||
PrefabMode::Vaults => self.apply_room_vaults(rng),
|
PrefabMode::RoomVaults => self.apply_room_vaults(rng, build_data),
|
||||||
}
|
}
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
|
}
|
||||||
|
|
||||||
// Find starting pos by starting at middle and walking left until finding a floor tile
|
fn char_to_map(&mut self, ch: char, idx: usize, build_data: &mut BuilderMap) {
|
||||||
if self.starting_position.x == 0 {
|
match ch {
|
||||||
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 };
|
' ' => build_data.map.tiles[idx] = TileType::Floor,
|
||||||
let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
'#' => build_data.map.tiles[idx] = TileType::Wall,
|
||||||
while self.map.tiles[start_idx] != TileType::Floor {
|
'>' => build_data.map.tiles[idx] = TileType::DownStair,
|
||||||
self.starting_position.x -= 1;
|
'≈' => build_data.map.tiles[idx] = TileType::Floor, // Placeholder for vines/brush
|
||||||
start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
'@' => {
|
||||||
|
let x = idx as i32 % build_data.map.width;
|
||||||
|
let y = idx as i32 / build_data.map.width;
|
||||||
|
build_data.map.tiles[idx] = TileType::Floor;
|
||||||
|
build_data.starting_position = Some(Position { x: x as i32, y: y as i32 });
|
||||||
|
}
|
||||||
|
'g' => {
|
||||||
|
build_data.map.tiles[idx] = TileType::Floor;
|
||||||
|
build_data.spawn_list.push((idx, "goblin".to_string()));
|
||||||
|
}
|
||||||
|
'G' => {
|
||||||
|
build_data.map.tiles[idx] = TileType::Floor;
|
||||||
|
build_data.spawn_list.push((idx, "goblin chieftain".to_string()));
|
||||||
|
}
|
||||||
|
'o' => {
|
||||||
|
build_data.map.tiles[idx] = TileType::Floor;
|
||||||
|
build_data.spawn_list.push((idx, "orc".to_string()));
|
||||||
|
}
|
||||||
|
'^' => {
|
||||||
|
build_data.map.tiles[idx] = TileType::Floor;
|
||||||
|
build_data.spawn_list.push((idx, "bear trap".to_string()));
|
||||||
|
}
|
||||||
|
'%' => {
|
||||||
|
build_data.map.tiles[idx] = TileType::Floor;
|
||||||
|
build_data.spawn_list.push((idx, "rations".to_string()));
|
||||||
|
}
|
||||||
|
'!' => {
|
||||||
|
build_data.map.tiles[idx] = TileType::Floor;
|
||||||
|
build_data.spawn_list.push((idx, "health potion".to_string()));
|
||||||
|
}
|
||||||
|
'/' => {
|
||||||
|
build_data.map.tiles[idx] = TileType::Floor;
|
||||||
|
build_data.spawn_list.push((idx, "magic missile wand".to_string()));
|
||||||
|
// Placeholder for wand spawn
|
||||||
|
}
|
||||||
|
'?' => {
|
||||||
|
build_data.map.tiles[idx] = TileType::Floor;
|
||||||
|
build_data.spawn_list.push((idx, "fireball scroll".to_string()));
|
||||||
|
// Placeholder for scroll spawn
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
rltk::console::log(format!("Unknown glyph '{}' when loading prefab", (ch as u8) as char));
|
||||||
}
|
}
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Find all tiles we can reach from the starting point
|
|
||||||
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Place the stairs
|
|
||||||
self.map.tiles[exit_tile] = TileType::DownStair;
|
|
||||||
self.take_snapshot();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_previous_iteration<F>(&mut self, rng: &mut RandomNumberGenerator, mut filter: F)
|
#[allow(dead_code)]
|
||||||
where
|
fn load_rex_map(&mut self, path: &str, build_data: &mut BuilderMap) {
|
||||||
F: FnMut(i32, i32, &(usize, String)) -> bool,
|
let xp_file = rltk::rex::XpFile::from_resource(path).unwrap();
|
||||||
|
|
||||||
|
for layer in &xp_file.layers {
|
||||||
|
for y in 0..layer.height {
|
||||||
|
for x in 0..layer.width {
|
||||||
|
let cell = layer.get(x, y).unwrap();
|
||||||
|
if x < build_data.map.width as usize && y < build_data.map.height as usize {
|
||||||
|
let idx = build_data.map.xy_idx(x as i32, y as i32);
|
||||||
|
// We're doing some nasty casting to make it easier to type things like '#' in the match
|
||||||
|
self.char_to_map(cell.ch as u8 as char, idx, build_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ascii_to_vec(template: &str) -> Vec<char> {
|
||||||
|
let mut string_vec: Vec<char> = template.chars().filter(|a| *a != '\r' && *a != '\n').collect();
|
||||||
|
for c in string_vec.iter_mut() {
|
||||||
|
if *c as u8 == 160u8 {
|
||||||
|
*c = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string_vec
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn load_ascii_map(&mut self, level: &prefab_levels::PrefabLevel, build_data: &mut BuilderMap) {
|
||||||
|
let string_vec = PrefabBuilder::read_ascii_to_vec(level.template);
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
for ty in 0..level.height {
|
||||||
|
for tx in 0..level.width {
|
||||||
|
if tx < build_data.map.width as usize && ty < build_data.map.height as usize {
|
||||||
|
let idx = build_data.map.xy_idx(tx as i32, ty as i32);
|
||||||
|
if i < string_vec.len() {
|
||||||
|
self.char_to_map(string_vec[i], idx, build_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_previous_iteration<F>(
|
||||||
|
&mut self,
|
||||||
|
mut filter: F,
|
||||||
|
_rng: &mut RandomNumberGenerator,
|
||||||
|
build_data: &mut BuilderMap,
|
||||||
|
) where
|
||||||
|
F: FnMut(i32, i32) -> bool,
|
||||||
{
|
{
|
||||||
// Build the map
|
let width = build_data.map.width;
|
||||||
let prev_builder = self.previous_builder.as_mut().unwrap();
|
build_data.spawn_list.retain(|(idx, _name)| {
|
||||||
prev_builder.build_map(rng);
|
let x = *idx as i32 % width;
|
||||||
self.starting_position = prev_builder.get_starting_pos();
|
let y = *idx as i32 / width;
|
||||||
self.map = prev_builder.get_map().clone();
|
filter(x, y)
|
||||||
self.history = prev_builder.get_snapshot_history();
|
});
|
||||||
for e in prev_builder.get_spawn_list().iter() {
|
build_data.take_snapshot();
|
||||||
let idx = e.0;
|
|
||||||
let x = idx as i32 % self.map.width;
|
|
||||||
let y = idx as i32 / self.map.width;
|
|
||||||
if filter(x, y, e) {
|
|
||||||
self.spawn_list.push((idx, e.1.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.take_snapshot();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_room_vaults(&mut self, rng: &mut RandomNumberGenerator) {
|
#[allow(dead_code)]
|
||||||
|
fn apply_sectional(
|
||||||
|
&mut self,
|
||||||
|
section: &prefab_sections::PrefabSection,
|
||||||
|
rng: &mut RandomNumberGenerator,
|
||||||
|
build_data: &mut BuilderMap,
|
||||||
|
) {
|
||||||
|
use prefab_sections::*;
|
||||||
|
|
||||||
|
let string_vec = PrefabBuilder::read_ascii_to_vec(section.template);
|
||||||
|
|
||||||
|
// Place the new section
|
||||||
|
let chunk_x;
|
||||||
|
match section.placement.0 {
|
||||||
|
HorizontalPlacement::Left => chunk_x = 0,
|
||||||
|
HorizontalPlacement::Center => chunk_x = (build_data.map.width / 2) - (section.width as i32 / 2),
|
||||||
|
HorizontalPlacement::Right => chunk_x = (build_data.map.width - 1) - section.width as i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk_y;
|
||||||
|
match section.placement.1 {
|
||||||
|
VerticalPlacement::Top => chunk_y = 0,
|
||||||
|
VerticalPlacement::Center => chunk_y = (build_data.map.height / 2) - (section.height as i32 / 2),
|
||||||
|
VerticalPlacement::Bottom => chunk_y = (build_data.map.height - 1) - section.height as i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the map
|
||||||
|
self.apply_previous_iteration(
|
||||||
|
|x, y| {
|
||||||
|
x < chunk_x
|
||||||
|
|| x > (chunk_x + section.width as i32)
|
||||||
|
|| y < chunk_y
|
||||||
|
|| y > (chunk_y + section.height as i32)
|
||||||
|
},
|
||||||
|
rng,
|
||||||
|
build_data,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
for ty in 0..section.height {
|
||||||
|
for tx in 0..section.width {
|
||||||
|
if tx > 0 && tx < build_data.map.width as usize - 1 && ty < build_data.map.height as usize - 1 && ty > 0
|
||||||
|
{
|
||||||
|
let idx = build_data.map.xy_idx(tx as i32 + chunk_x, ty as i32 + chunk_y);
|
||||||
|
if i < string_vec.len() {
|
||||||
|
self.char_to_map(string_vec[i], idx, build_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
build_data.take_snapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_room_vaults(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
use prefab_vaults::*;
|
use prefab_vaults::*;
|
||||||
|
|
||||||
// Apply prev builder, and keep all entities
|
// Apply the previous builder, and keep all entities it spawns (for now)
|
||||||
self.apply_previous_iteration(rng, |_x, _y, _e| true);
|
self.apply_previous_iteration(|_x, _y| true, rng, build_data);
|
||||||
|
|
||||||
// Roll for a vault
|
// Do we want a vault at all?
|
||||||
let vault_roll = rng.roll_dice(1, 6);
|
let vault_roll = rng.roll_dice(1, 6) + build_data.map.depth;
|
||||||
if vault_roll < 4 {
|
if vault_roll < 4 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all vaults
|
// Note that this is a place-holder and will be moved out of this function
|
||||||
let master_vault_list = vec![GOBLINS_4X4, GOBLINS2_4X4, CLASSIC_TRAP_5X5];
|
let master_vault_list = vec![
|
||||||
// Filter out vaults from outside the current depth
|
CLASSIC_TRAP_5X5,
|
||||||
let mut possible_vaults: Vec<&PrefabVault> =
|
CLASSIC_TRAP_DIAGONALGAP_5X5,
|
||||||
master_vault_list.iter().filter(|v| self.depth >= v.first_depth && self.depth <= v.last_depth).collect();
|
CLASSIC_TRAP_CARDINALGAP_5X5,
|
||||||
// Return if there's no possible vaults
|
GOBLINS_4X4,
|
||||||
|
GOBLINS2_4X4,
|
||||||
|
GOBLINS_5X5,
|
||||||
|
GOBLINS_6X6,
|
||||||
|
FLUFF_6X3,
|
||||||
|
FLUFF2_6X3,
|
||||||
|
HOUSE_NOTRAP_7X7,
|
||||||
|
HOUSE_TRAP_7X7,
|
||||||
|
ORC_HOUSE_8X8,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Filter the vault list down to ones that are applicable to the current depth
|
||||||
|
let mut possible_vaults: Vec<&PrefabVault> = master_vault_list
|
||||||
|
.iter()
|
||||||
|
.filter(|v| build_data.map.depth >= v.first_depth && build_data.map.depth <= v.last_depth)
|
||||||
|
.collect();
|
||||||
|
|
||||||
if possible_vaults.is_empty() {
|
if possible_vaults.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
} // Bail out if there's nothing to build
|
||||||
|
|
||||||
// Pick number of vaults
|
|
||||||
let n_vaults = i32::min(rng.roll_dice(1, 3), possible_vaults.len() as i32);
|
let n_vaults = i32::min(rng.roll_dice(1, 3), possible_vaults.len() as i32);
|
||||||
let mut used_tiles: HashSet<usize> = HashSet::new();
|
let mut used_tiles: HashSet<usize> = HashSet::new();
|
||||||
|
|
||||||
for _i in 0..n_vaults {
|
for _i in 0..n_vaults {
|
||||||
// Select a vault
|
let vault_index = if possible_vaults.len() == 1 {
|
||||||
let vault_idx = if possible_vaults.len() == 1 {
|
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
(rng.roll_dice(1, possible_vaults.len() as i32) - 1) as usize
|
(rng.roll_dice(1, possible_vaults.len() as i32) - 1) as usize
|
||||||
};
|
};
|
||||||
let vault = possible_vaults[vault_idx];
|
let vault = possible_vaults[vault_index];
|
||||||
// Decide if we want to flip the vault
|
// Decide if we want to flip the vault
|
||||||
let mut flip_x: bool = false;
|
let mut flip_x: bool = false;
|
||||||
let mut flip_y: bool = false;
|
let mut flip_y: bool = false;
|
||||||
|
|
@ -214,23 +311,25 @@ impl PrefabBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a list of all places the vault can fit
|
// We'll make a list of places in which the vault could fit
|
||||||
let mut vault_positions: Vec<Position> = Vec::new();
|
let mut vault_positions: Vec<Position> = Vec::new();
|
||||||
|
|
||||||
let mut idx = 0usize;
|
let mut idx = 0usize;
|
||||||
loop {
|
loop {
|
||||||
let x = (idx % self.map.width as usize) as i32;
|
let x = (idx % build_data.map.width as usize) as i32;
|
||||||
let y = (idx / self.map.width as usize) as i32;
|
let y = (idx / build_data.map.width as usize) as i32;
|
||||||
// Check for overflow
|
|
||||||
|
// Check that we won't overflow the map
|
||||||
if x > 1
|
if x > 1
|
||||||
&& (x + vault.width as i32) < self.map.width - 2
|
&& (x + vault.width as i32) < build_data.map.width - 2
|
||||||
&& y > 1
|
&& y > 1
|
||||||
&& (y + vault.height as i32) < self.map.height - 2
|
&& (y + vault.height as i32) < build_data.map.height - 2
|
||||||
{
|
{
|
||||||
let mut possible = true;
|
let mut possible = true;
|
||||||
for tile_y in 0..vault.height as i32 {
|
for ty in 0..vault.height as i32 {
|
||||||
for tile_x in 0..vault.width as i32 {
|
for tx in 0..vault.width as i32 {
|
||||||
let idx = self.map.xy_idx(tile_x + x, tile_y + y);
|
let idx = build_data.map.xy_idx(tx + x, ty + y);
|
||||||
if self.map.tiles[idx] != TileType::Floor {
|
if build_data.map.tiles[idx] != TileType::Floor {
|
||||||
possible = false;
|
possible = false;
|
||||||
}
|
}
|
||||||
if used_tiles.contains(&idx) {
|
if used_tiles.contains(&idx) {
|
||||||
|
|
@ -238,19 +337,19 @@ impl PrefabBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we find a position that works, push it
|
|
||||||
if possible {
|
if possible {
|
||||||
vault_positions.push(Position { x, y });
|
vault_positions.push(Position { x, y });
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Once we reach the end of the map, break
|
|
||||||
idx += 1;
|
idx += 1;
|
||||||
if idx >= self.map.tiles.len() - 1 {
|
if idx >= build_data.map.tiles.len() - 1 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a position, make the vault
|
|
||||||
if !vault_positions.is_empty() {
|
if !vault_positions.is_empty() {
|
||||||
let pos_idx = if vault_positions.len() == 1 {
|
let pos_idx = if vault_positions.len() == 1 {
|
||||||
0
|
0
|
||||||
|
|
@ -258,17 +357,19 @@ impl PrefabBuilder {
|
||||||
(rng.roll_dice(1, vault_positions.len() as i32) - 1) as usize
|
(rng.roll_dice(1, vault_positions.len() as i32) - 1) as usize
|
||||||
};
|
};
|
||||||
let pos = &vault_positions[pos_idx];
|
let pos = &vault_positions[pos_idx];
|
||||||
|
|
||||||
let chunk_x = pos.x;
|
let chunk_x = pos.x;
|
||||||
let chunk_y = pos.y;
|
let chunk_y = pos.y;
|
||||||
// Filter out entities from our spawn list that would have spawned inside this vault
|
|
||||||
let width = self.map.width; // For borrow checker.
|
let width = build_data.map.width; // The borrow checker really doesn't like it
|
||||||
let height = self.map.height; // As above.
|
let height = build_data.map.height; // when we access `self` inside the `retain`
|
||||||
self.spawn_list.retain(|e| {
|
build_data.spawn_list.retain(|e| {
|
||||||
let idx = e.0 as i32;
|
let idx = e.0 as i32;
|
||||||
let x = idx % width;
|
let x = idx % width;
|
||||||
let y = idx / height;
|
let y = idx / height;
|
||||||
x < chunk_x || x > chunk_x + vault.width as i32 || y < chunk_y || y > chunk_y + vault.height as i32
|
x < chunk_x || x > chunk_x + vault.width as i32 || y < chunk_y || y > chunk_y + vault.height as i32
|
||||||
});
|
});
|
||||||
|
|
||||||
let string_vec = PrefabBuilder::read_ascii_to_vec(vault.template);
|
let string_vec = PrefabBuilder::read_ascii_to_vec(vault.template);
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
for tile_y in 0..vault.height {
|
for tile_y in 0..vault.height {
|
||||||
|
|
@ -282,140 +383,17 @@ impl PrefabBuilder {
|
||||||
if flip_y {
|
if flip_y {
|
||||||
y_ = vault.width as i32 - 1 - y_;
|
y_ = vault.width as i32 - 1 - y_;
|
||||||
}
|
}
|
||||||
self.map.xy_idx(x_ + chunk_x, y_ as i32 + chunk_y);
|
let idx = build_data.map.xy_idx(x_ + chunk_x, y_ + chunk_y);
|
||||||
self.char_to_map(string_vec[i], idx);
|
if i < string_vec.len() {
|
||||||
|
self.char_to_map(string_vec[i], idx, build_data);
|
||||||
|
}
|
||||||
used_tiles.insert(idx);
|
used_tiles.insert(idx);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rltk::console::log("-> adding vault");
|
build_data.take_snapshot();
|
||||||
self.take_snapshot();
|
|
||||||
possible_vaults.remove(vault_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_sectional(&mut self, section: &prefab_sections::PrefabSection, rng: &mut RandomNumberGenerator) {
|
possible_vaults.remove(vault_index);
|
||||||
use prefab_sections::*;
|
|
||||||
let string_vec = PrefabBuilder::read_ascii_to_vec(section.template);
|
|
||||||
|
|
||||||
// Place the new section
|
|
||||||
let chunk_x;
|
|
||||||
match section.placement.0 {
|
|
||||||
HorizontalPlacement::Left => chunk_x = 0,
|
|
||||||
HorizontalPlacement::Center => chunk_x = (self.map.width / 2) - (section.width as i32 / 2),
|
|
||||||
HorizontalPlacement::Right => chunk_x = (self.map.width - 1) - section.width as i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
let chunk_y;
|
|
||||||
match section.placement.1 {
|
|
||||||
VerticalPlacement::Top => chunk_y = 0,
|
|
||||||
VerticalPlacement::Center => chunk_y = (self.map.height / 2) - (section.height as i32 / 2),
|
|
||||||
VerticalPlacement::Bottom => chunk_y = (self.map.height - 1) - section.height as i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the map
|
|
||||||
self.apply_previous_iteration(rng, |x, y, _e| {
|
|
||||||
x < chunk_x || x > (chunk_x + section.width as i32) || y < chunk_y || y > (chunk_y + section.height as i32)
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut i = 0;
|
|
||||||
for ty in 0..section.height {
|
|
||||||
for tx in 0..section.width {
|
|
||||||
if tx < self.map.width as usize && ty < self.map.height as usize {
|
|
||||||
let idx = self.map.xy_idx(tx as i32 + chunk_x, ty as i32 + chunk_y);
|
|
||||||
self.char_to_map(string_vec[i], idx);
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.take_snapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn char_to_map(&mut self, ch: char, idx: usize) {
|
|
||||||
match ch {
|
|
||||||
' ' => self.map.tiles[idx] = TileType::Floor,
|
|
||||||
'#' => self.map.tiles[idx] = TileType::Wall,
|
|
||||||
'>' => self.map.tiles[idx] = TileType::DownStair,
|
|
||||||
'@' => {
|
|
||||||
let x = idx as i32 % self.map.width;
|
|
||||||
let y = idx as i32 / self.map.width;
|
|
||||||
self.map.tiles[idx] = TileType::Floor;
|
|
||||||
self.starting_position = Position { x: x as i32, y: y as i32 };
|
|
||||||
}
|
|
||||||
'g' => {
|
|
||||||
self.map.tiles[idx] = TileType::Floor;
|
|
||||||
self.spawn_list.push((idx, "goblin".to_string()));
|
|
||||||
}
|
|
||||||
'G' => {
|
|
||||||
self.map.tiles[idx] = TileType::Floor;
|
|
||||||
self.spawn_list.push((idx, "goblin chieftain".to_string()));
|
|
||||||
}
|
|
||||||
'o' => {
|
|
||||||
self.map.tiles[idx] = TileType::Floor;
|
|
||||||
self.spawn_list.push((idx, "orc".to_string()));
|
|
||||||
}
|
|
||||||
'^' => {
|
|
||||||
self.map.tiles[idx] = TileType::Floor;
|
|
||||||
self.spawn_list.push((idx, "bear trap".to_string()));
|
|
||||||
}
|
|
||||||
'%' => {
|
|
||||||
self.map.tiles[idx] = TileType::Floor;
|
|
||||||
self.spawn_list.push((idx, "rations".to_string()));
|
|
||||||
}
|
|
||||||
'!' => {
|
|
||||||
self.map.tiles[idx] = TileType::Floor;
|
|
||||||
self.spawn_list.push((idx, "health potion".to_string()));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
rltk::console::log(format!("Unknown glyph loading map: {}", (ch as u8) as char));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn load_rex_map(&mut self, path: &str) {
|
|
||||||
let xp_file = rltk::rex::XpFile::from_resource(path).unwrap();
|
|
||||||
|
|
||||||
for layer in &xp_file.layers {
|
|
||||||
for y in 0..layer.height {
|
|
||||||
for x in 0..layer.width {
|
|
||||||
let cell = layer.get(x, y).unwrap();
|
|
||||||
if x < self.map.width as usize && y < self.map.height as usize {
|
|
||||||
// Saving these for later, for flipping the pref horizontally/vertically/both.
|
|
||||||
// let flipped_x = (self.map.width - 1) - x as i32;
|
|
||||||
// let flipped_y = (self.map.height - 1) - y as i32;
|
|
||||||
let idx = self.map.xy_idx(x as i32, y as i32);
|
|
||||||
// We're doing some nasty casting to make it easier to type things like '#' in the match
|
|
||||||
self.char_to_map(cell.ch as u8 as char, idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ascii_to_vec(template: &str) -> Vec<char> {
|
|
||||||
let mut string_vec: Vec<char> = template.chars().filter(|a| *a != '\r' && *a != '\n').collect();
|
|
||||||
for c in string_vec.iter_mut() {
|
|
||||||
if *c as u8 == 160u8 {
|
|
||||||
*c = ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string_vec;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn load_ascii_map(&mut self, level: &prefab_levels::PrefabLevel) {
|
|
||||||
let string_vec = PrefabBuilder::read_ascii_to_vec(level.template);
|
|
||||||
|
|
||||||
let mut i = 0;
|
|
||||||
for y in 0..level.height {
|
|
||||||
for x in 0..level.width {
|
|
||||||
if x < self.map.width as usize && y < self.map.height as usize {
|
|
||||||
let idx = self.map.xy_idx(x as i32, y as i32);
|
|
||||||
self.char_to_map(string_vec[i], idx);
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ pub struct PrefabVault {
|
||||||
pub can_flip: Flipping,
|
pub can_flip: Flipping,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const CLASSIC_TRAP_5X5: PrefabVault = PrefabVault {
|
pub const CLASSIC_TRAP_5X5: PrefabVault = PrefabVault {
|
||||||
template: CLASSIC_TRAP_5X5_V,
|
template: CLASSIC_TRAP_5X5_V,
|
||||||
width: 5,
|
width: 5,
|
||||||
|
|
@ -26,7 +25,6 @@ pub const CLASSIC_TRAP_5X5: PrefabVault = PrefabVault {
|
||||||
last_depth: 100,
|
last_depth: 100,
|
||||||
can_flip: Flipping::None,
|
can_flip: Flipping::None,
|
||||||
};
|
};
|
||||||
#[allow(dead_code)]
|
|
||||||
const CLASSIC_TRAP_5X5_V: &str = "
|
const CLASSIC_TRAP_5X5_V: &str = "
|
||||||
|
|
||||||
^^^
|
^^^
|
||||||
|
|
@ -35,7 +33,38 @@ const CLASSIC_TRAP_5X5_V: &str = "
|
||||||
|
|
||||||
";
|
";
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub const CLASSIC_TRAP_CARDINALGAP_5X5: PrefabVault = PrefabVault {
|
||||||
|
template: CLASSIC_TRAP_CARDINALGAP_5X5_V,
|
||||||
|
width: 5,
|
||||||
|
height: 5,
|
||||||
|
first_depth: 0,
|
||||||
|
last_depth: 100,
|
||||||
|
can_flip: Flipping::Both,
|
||||||
|
};
|
||||||
|
const CLASSIC_TRAP_CARDINALGAP_5X5_V: &str = "
|
||||||
|
|
||||||
|
^ ^
|
||||||
|
^!^
|
||||||
|
^^^
|
||||||
|
|
||||||
|
";
|
||||||
|
|
||||||
|
pub const CLASSIC_TRAP_DIAGONALGAP_5X5: PrefabVault = PrefabVault {
|
||||||
|
template: CLASSIC_TRAP_DIAGONALGAP_5X5_V,
|
||||||
|
width: 5,
|
||||||
|
height: 5,
|
||||||
|
first_depth: 0,
|
||||||
|
last_depth: 100,
|
||||||
|
can_flip: Flipping::Both,
|
||||||
|
};
|
||||||
|
const CLASSIC_TRAP_DIAGONALGAP_5X5_V: &str = "
|
||||||
|
|
||||||
|
^^
|
||||||
|
^!^
|
||||||
|
^^^
|
||||||
|
|
||||||
|
";
|
||||||
|
|
||||||
pub const GOBLINS_4X4: PrefabVault = PrefabVault {
|
pub const GOBLINS_4X4: PrefabVault = PrefabVault {
|
||||||
template: GOBLINS_4X4_V,
|
template: GOBLINS_4X4_V,
|
||||||
width: 4,
|
width: 4,
|
||||||
|
|
@ -51,7 +80,6 @@ const GOBLINS_4X4_V: &str = "
|
||||||
^g^
|
^g^
|
||||||
";
|
";
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const GOBLINS2_4X4: PrefabVault = PrefabVault {
|
pub const GOBLINS2_4X4: PrefabVault = PrefabVault {
|
||||||
template: GOBLINS2_4X4_V,
|
template: GOBLINS2_4X4_V,
|
||||||
width: 4,
|
width: 4,
|
||||||
|
|
@ -66,3 +94,119 @@ G# #
|
||||||
g#
|
g#
|
||||||
# g^
|
# g^
|
||||||
";
|
";
|
||||||
|
|
||||||
|
pub const GOBLINS_5X5: PrefabVault = PrefabVault {
|
||||||
|
template: GOBLINS_5X5_V,
|
||||||
|
width: 5,
|
||||||
|
height: 5,
|
||||||
|
first_depth: 0,
|
||||||
|
last_depth: 100,
|
||||||
|
can_flip: Flipping::Both,
|
||||||
|
};
|
||||||
|
const GOBLINS_5X5_V: &str = "
|
||||||
|
^#g
|
||||||
|
G#?#^
|
||||||
|
/g g#
|
||||||
|
## ^#
|
||||||
|
^# #
|
||||||
|
";
|
||||||
|
|
||||||
|
pub const GOBLINS_6X6: PrefabVault = PrefabVault {
|
||||||
|
template: GOBLINS_6X6_V,
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
first_depth: 0,
|
||||||
|
last_depth: 100,
|
||||||
|
can_flip: Flipping::Both,
|
||||||
|
};
|
||||||
|
const GOBLINS_6X6_V: &str = "
|
||||||
|
#
|
||||||
|
#^#g
|
||||||
|
#G#$#^
|
||||||
|
/gGg#
|
||||||
|
g##$^
|
||||||
|
^ # ^
|
||||||
|
";
|
||||||
|
|
||||||
|
pub const FLUFF_6X3: PrefabVault = PrefabVault {
|
||||||
|
template: FLUFF_6X3_V,
|
||||||
|
width: 6,
|
||||||
|
height: 3,
|
||||||
|
first_depth: 0,
|
||||||
|
last_depth: 100,
|
||||||
|
can_flip: Flipping::Both,
|
||||||
|
};
|
||||||
|
const FLUFF_6X3_V: &str = "
|
||||||
|
###≈^
|
||||||
|
^≈ #≈
|
||||||
|
≈##≈
|
||||||
|
";
|
||||||
|
|
||||||
|
pub const FLUFF2_6X3: PrefabVault = PrefabVault {
|
||||||
|
template: FLUFF2_6X3_V,
|
||||||
|
width: 6,
|
||||||
|
height: 3,
|
||||||
|
first_depth: 0,
|
||||||
|
last_depth: 100,
|
||||||
|
can_flip: Flipping::Both,
|
||||||
|
};
|
||||||
|
const FLUFF2_6X3_V: &str = "
|
||||||
|
^≈###
|
||||||
|
≈# ≈^
|
||||||
|
≈##≈
|
||||||
|
";
|
||||||
|
|
||||||
|
pub const HOUSE_NOTRAP_7X7: PrefabVault = PrefabVault {
|
||||||
|
template: HOUSE_NOTRAP_7X7_V,
|
||||||
|
width: 7,
|
||||||
|
height: 7,
|
||||||
|
first_depth: 0,
|
||||||
|
last_depth: 100,
|
||||||
|
can_flip: Flipping::Both,
|
||||||
|
};
|
||||||
|
const HOUSE_NOTRAP_7X7_V: &str = "
|
||||||
|
# #
|
||||||
|
# g #
|
||||||
|
# #
|
||||||
|
?
|
||||||
|
# #
|
||||||
|
# g #
|
||||||
|
# #
|
||||||
|
";
|
||||||
|
|
||||||
|
pub const HOUSE_TRAP_7X7: PrefabVault = PrefabVault {
|
||||||
|
template: HOUSE_TRAP_7X7_V,
|
||||||
|
width: 7,
|
||||||
|
height: 7,
|
||||||
|
first_depth: 0,
|
||||||
|
last_depth: 100,
|
||||||
|
can_flip: Flipping::Both,
|
||||||
|
};
|
||||||
|
const HOUSE_TRAP_7X7_V: &str = "
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
#^#
|
||||||
|
^? g
|
||||||
|
g#^#
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
";
|
||||||
|
|
||||||
|
pub const ORC_HOUSE_8X8: PrefabVault = PrefabVault {
|
||||||
|
template: ORC_HOUSE_8X8_V,
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
first_depth: 0,
|
||||||
|
last_depth: 100,
|
||||||
|
can_flip: Flipping::Both,
|
||||||
|
};
|
||||||
|
const ORC_HOUSE_8X8_V: &str = "
|
||||||
|
|
||||||
|
######
|
||||||
|
#o g #
|
||||||
|
# %#
|
||||||
|
# %o #
|
||||||
|
# ?#
|
||||||
|
##+###
|
||||||
|
|
||||||
|
";
|
||||||
|
|
|
||||||
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::{
|
use super::{apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, BuilderMap, InitialMapBuilder, Rect};
|
||||||
apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, spawner, Map, MapBuilder, Position, Rect,
|
|
||||||
TileType, SHOW_MAPGEN,
|
|
||||||
};
|
|
||||||
use rltk::RandomNumberGenerator;
|
use rltk::RandomNumberGenerator;
|
||||||
|
|
||||||
pub struct SimpleMapBuilder {
|
pub struct SimpleMapBuilder {}
|
||||||
map: Map,
|
|
||||||
starting_position: Position,
|
|
||||||
depth: i32,
|
|
||||||
rooms: Vec<Rect>,
|
|
||||||
history: Vec<Map>,
|
|
||||||
spawn_list: Vec<(usize, String)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MapBuilder for SimpleMapBuilder {
|
impl InitialMapBuilder for SimpleMapBuilder {
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
#[allow(dead_code)]
|
||||||
return self.rooms_and_corridors(rng);
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
}
|
self.rooms_and_corridors(rng, build_data);
|
||||||
// Getters
|
|
||||||
fn get_map(&mut self) -> Map {
|
|
||||||
return self.map.clone();
|
|
||||||
}
|
|
||||||
fn get_starting_pos(&mut self) -> Position {
|
|
||||||
return self.starting_position.clone();
|
|
||||||
}
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
|
|
||||||
return &self.spawn_list;
|
|
||||||
}
|
|
||||||
// Mapgen visualisation stuff
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map> {
|
|
||||||
return self.history.clone();
|
|
||||||
}
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if SHOW_MAPGEN {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimpleMapBuilder {
|
impl SimpleMapBuilder {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn new(new_depth: i32) -> SimpleMapBuilder {
|
pub fn new() -> Box<SimpleMapBuilder> {
|
||||||
SimpleMapBuilder {
|
Box::new(SimpleMapBuilder {})
|
||||||
map: Map::new(new_depth),
|
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
rooms: Vec::new(),
|
|
||||||
history: Vec::new(),
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rooms_and_corridors(&mut self, rng: &mut RandomNumberGenerator) {
|
fn rooms_and_corridors(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
const MAX_ROOMS: i32 = 30;
|
const MAX_ROOMS: i32 = 30;
|
||||||
const MIN_SIZE: i32 = 6;
|
const MIN_SIZE: i32 = 6;
|
||||||
const MAX_SIZE: i32 = 10;
|
const MAX_SIZE: i32 = 10;
|
||||||
|
let mut rooms: Vec<Rect> = Vec::new();
|
||||||
|
|
||||||
for _i in 0..MAX_ROOMS {
|
for _i in 0..MAX_ROOMS {
|
||||||
let w = rng.range(MIN_SIZE, MAX_SIZE);
|
let w = rng.range(MIN_SIZE, MAX_SIZE);
|
||||||
let h = rng.range(MIN_SIZE, MAX_SIZE);
|
let h = rng.range(MIN_SIZE, MAX_SIZE);
|
||||||
let x = rng.roll_dice(1, self.map.width - w - 1) - 1;
|
let x = rng.roll_dice(1, build_data.map.width - w - 1) - 1;
|
||||||
let y = rng.roll_dice(1, self.map.height - h - 1) - 1;
|
let y = rng.roll_dice(1, build_data.map.height - h - 1) - 1;
|
||||||
let new_room = Rect::new(x, y, w, h);
|
let new_room = Rect::new(x, y, w, h);
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
for other_room in self.rooms.iter() {
|
for other_room in rooms.iter() {
|
||||||
if new_room.intersect(other_room) {
|
if new_room.intersect(other_room) {
|
||||||
ok = false
|
ok = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
apply_room_to_map(&mut self.map, &new_room);
|
apply_room_to_map(&mut build_data.map, &new_room);
|
||||||
|
build_data.take_snapshot();
|
||||||
|
|
||||||
if !self.rooms.is_empty() {
|
if !rooms.is_empty() {
|
||||||
let (new_x, new_y) = new_room.centre();
|
let (new_x, new_y) = new_room.centre();
|
||||||
let (prev_x, prev_y) = self.rooms[self.rooms.len() - 1].centre();
|
let (prev_x, prev_y) = rooms[rooms.len() - 1].centre();
|
||||||
if rng.range(0, 2) == 1 {
|
if rng.range(0, 2) == 1 {
|
||||||
apply_horizontal_tunnel(&mut self.map, prev_x, new_x, prev_y);
|
apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, prev_y);
|
||||||
apply_vertical_tunnel(&mut self.map, prev_y, new_y, new_x);
|
apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, new_x);
|
||||||
} else {
|
} else {
|
||||||
apply_vertical_tunnel(&mut self.map, prev_y, new_y, prev_x);
|
apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, prev_x);
|
||||||
apply_horizontal_tunnel(&mut self.map, prev_x, new_x, new_y);
|
apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, new_y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rooms.push(new_room);
|
rooms.push(new_room);
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
build_data.rooms = Some(rooms);
|
||||||
let stairs_position = self.rooms[self.rooms.len() - 1].centre();
|
|
||||||
let stairs_idx = self.map.xy_idx(stairs_position.0, stairs_position.1);
|
|
||||||
self.map.tiles[stairs_idx] = TileType::DownStair;
|
|
||||||
|
|
||||||
let start_pos = self.rooms[0].centre();
|
|
||||||
self.starting_position = Position { x: start_pos.0, y: start_pos.1 };
|
|
||||||
|
|
||||||
// Spawn entities
|
|
||||||
for room in self.rooms.iter().skip(1) {
|
|
||||||
spawner::spawn_room(&self.map, rng, room, self.depth, &mut self.spawn_list);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
use super::{
|
use super::{BuilderMap, InitialMapBuilder, TileType};
|
||||||
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
|
|
||||||
Position, TileType, SHOW_MAPGEN,
|
|
||||||
};
|
|
||||||
use rltk::RandomNumberGenerator;
|
use rltk::RandomNumberGenerator;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub enum DistanceAlgorithm {
|
pub enum DistanceAlgorithm {
|
||||||
Pythagoras,
|
Pythagoras,
|
||||||
Manhattan,
|
Manhattan,
|
||||||
|
|
@ -14,94 +10,42 @@ pub enum DistanceAlgorithm {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VoronoiBuilder {
|
pub struct VoronoiBuilder {
|
||||||
map: Map,
|
|
||||||
starting_position: Position,
|
|
||||||
depth: i32,
|
|
||||||
history: Vec<Map>,
|
|
||||||
noise_areas: HashMap<i32, Vec<usize>>,
|
|
||||||
n_seeds: usize,
|
n_seeds: usize,
|
||||||
distance_algorithm: DistanceAlgorithm,
|
distance_algorithm: DistanceAlgorithm,
|
||||||
spawn_list: Vec<(usize, String)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapBuilder for VoronoiBuilder {
|
impl InitialMapBuilder for VoronoiBuilder {
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
|
||||||
return self.build(rng);
|
|
||||||
}
|
|
||||||
// Getters
|
|
||||||
fn get_map(&mut self) -> Map {
|
|
||||||
return self.map.clone();
|
|
||||||
}
|
|
||||||
fn get_starting_pos(&mut self) -> Position {
|
|
||||||
return self.starting_position.clone();
|
|
||||||
}
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
|
|
||||||
return &self.spawn_list;
|
|
||||||
}
|
|
||||||
// Mapgen visualisation stuff
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map> {
|
|
||||||
return self.history.clone();
|
|
||||||
}
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if SHOW_MAPGEN {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl VoronoiBuilder {
|
|
||||||
pub fn pythagoras(new_depth: i32) -> VoronoiBuilder {
|
|
||||||
VoronoiBuilder {
|
|
||||||
map: Map::new(new_depth),
|
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
n_seeds: 64,
|
|
||||||
distance_algorithm: DistanceAlgorithm::Pythagoras,
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn manhattan(new_depth: i32) -> VoronoiBuilder {
|
|
||||||
VoronoiBuilder {
|
|
||||||
map: Map::new(new_depth),
|
|
||||||
starting_position: Position { x: 0, y: 0 },
|
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
n_seeds: 64,
|
|
||||||
distance_algorithm: DistanceAlgorithm::Manhattan,
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn chebyshev(new_depth: i32) -> VoronoiBuilder {
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
VoronoiBuilder {
|
self.build(rng, build_data);
|
||||||
map: Map::new(new_depth),
|
}
|
||||||
starting_position: Position { x: 0, y: 0 },
|
}
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
impl VoronoiBuilder {
|
||||||
noise_areas: HashMap::new(),
|
#[allow(dead_code)]
|
||||||
n_seeds: 64,
|
pub fn new() -> Box<VoronoiBuilder> {
|
||||||
distance_algorithm: DistanceAlgorithm::Chebyshev,
|
Box::new(VoronoiBuilder { n_seeds: 64, distance_algorithm: DistanceAlgorithm::Pythagoras })
|
||||||
spawn_list: Vec::new(),
|
}
|
||||||
}
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn pythagoras() -> Box<VoronoiBuilder> {
|
||||||
|
Box::new(VoronoiBuilder { n_seeds: 64, distance_algorithm: DistanceAlgorithm::Pythagoras })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn manhattan() -> Box<VoronoiBuilder> {
|
||||||
|
Box::new(VoronoiBuilder { n_seeds: 64, distance_algorithm: DistanceAlgorithm::Manhattan })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::map_entry)]
|
#[allow(clippy::map_entry)]
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
// Make a Voronoi diagram. We'll do this the hard way to learn about the technique!
|
// Make a Voronoi diagram. We'll do this the hard way to learn about the technique!
|
||||||
let mut voronoi_seeds: Vec<(usize, rltk::Point)> = Vec::new();
|
let mut voronoi_seeds: Vec<(usize, rltk::Point)> = Vec::new();
|
||||||
|
|
||||||
while voronoi_seeds.len() < self.n_seeds {
|
while voronoi_seeds.len() < self.n_seeds {
|
||||||
let vx = rng.roll_dice(1, self.map.width - 1);
|
let vx = rng.roll_dice(1, build_data.map.width - 1);
|
||||||
let vy = rng.roll_dice(1, self.map.height - 1);
|
let vy = rng.roll_dice(1, build_data.map.height - 1);
|
||||||
let vidx = self.map.xy_idx(vx, vy);
|
let vidx = build_data.map.xy_idx(vx, vy);
|
||||||
let candidate = (vidx, rltk::Point::new(vx, vy));
|
let candidate = (vidx, rltk::Point::new(vx, vy));
|
||||||
if !voronoi_seeds.contains(&candidate) {
|
if !voronoi_seeds.contains(&candidate) {
|
||||||
voronoi_seeds.push(candidate);
|
voronoi_seeds.push(candidate);
|
||||||
|
|
@ -109,10 +53,10 @@ impl VoronoiBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut voronoi_distance = vec![(0, 0.0f32); self.n_seeds];
|
let mut voronoi_distance = vec![(0, 0.0f32); self.n_seeds];
|
||||||
let mut voronoi_membership: Vec<i32> = vec![0; self.map.width as usize * self.map.height as usize];
|
let mut voronoi_membership: Vec<i32> = vec![0; build_data.map.width as usize * build_data.map.height as usize];
|
||||||
for (i, vid) in voronoi_membership.iter_mut().enumerate() {
|
for (i, vid) in voronoi_membership.iter_mut().enumerate() {
|
||||||
let x = i as i32 % self.map.width;
|
let x = i as i32 % build_data.map.width;
|
||||||
let y = i as i32 / self.map.width;
|
let y = i as i32 / build_data.map.width;
|
||||||
|
|
||||||
for (seed, pos) in voronoi_seeds.iter().enumerate() {
|
for (seed, pos) in voronoi_seeds.iter().enumerate() {
|
||||||
let distance;
|
let distance;
|
||||||
|
|
@ -135,52 +79,29 @@ impl VoronoiBuilder {
|
||||||
*vid = voronoi_distance[0].0 as i32;
|
*vid = voronoi_distance[0].0 as i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
for y in 1..self.map.height - 1 {
|
for y in 1..build_data.map.height - 1 {
|
||||||
for x in 1..self.map.width - 1 {
|
for x in 1..build_data.map.width - 1 {
|
||||||
let mut neighbors = 0;
|
let mut neighbors = 0;
|
||||||
let my_idx = self.map.xy_idx(x, y);
|
let my_idx = build_data.map.xy_idx(x, y);
|
||||||
let my_seed = voronoi_membership[my_idx];
|
let my_seed = voronoi_membership[my_idx];
|
||||||
if voronoi_membership[self.map.xy_idx(x - 1, y)] != my_seed {
|
if voronoi_membership[build_data.map.xy_idx(x - 1, y)] != my_seed {
|
||||||
neighbors += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
if voronoi_membership[self.map.xy_idx(x + 1, y)] != my_seed {
|
if voronoi_membership[build_data.map.xy_idx(x + 1, y)] != my_seed {
|
||||||
neighbors += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
if voronoi_membership[self.map.xy_idx(x, y - 1)] != my_seed {
|
if voronoi_membership[build_data.map.xy_idx(x, y - 1)] != my_seed {
|
||||||
neighbors += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
if voronoi_membership[self.map.xy_idx(x, y + 1)] != my_seed {
|
if voronoi_membership[build_data.map.xy_idx(x, y + 1)] != my_seed {
|
||||||
neighbors += 1;
|
neighbors += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if neighbors < 2 {
|
if neighbors < 2 {
|
||||||
self.map.tiles[my_idx] = TileType::Floor;
|
build_data.map.tiles[my_idx] = TileType::Floor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
}
|
|
||||||
|
|
||||||
// Find a starting point; start at the middle and walk left until we find an open tile
|
|
||||||
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 };
|
|
||||||
let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
|
||||||
while self.map.tiles[start_idx] != TileType::Floor {
|
|
||||||
self.starting_position.x -= 1;
|
|
||||||
start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
|
||||||
}
|
|
||||||
self.take_snapshot();
|
|
||||||
// Find all tiles we can reach from the starting point
|
|
||||||
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
|
|
||||||
self.take_snapshot();
|
|
||||||
// Place the stairs
|
|
||||||
self.map.tiles[exit_tile] = TileType::DownStair;
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Now we build a noise map for use in spawning entities later
|
|
||||||
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
|
|
||||||
|
|
||||||
// Spawn the entities
|
|
||||||
for area in self.noise_areas.iter() {
|
|
||||||
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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::{
|
use super::{BuilderMap, Map, MetaMapBuilder, TileType};
|
||||||
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder,
|
|
||||||
Position, TileType, SHOW_MAPGEN,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
use common::MapChunk;
|
|
||||||
mod constraints;
|
|
||||||
mod solver;
|
|
||||||
use rltk::RandomNumberGenerator;
|
use rltk::RandomNumberGenerator;
|
||||||
use solver::Solver;
|
mod common;
|
||||||
use std::collections::HashMap;
|
use common::*;
|
||||||
|
mod constraints;
|
||||||
|
use constraints::*;
|
||||||
|
mod solver;
|
||||||
|
use solver::*;
|
||||||
|
|
||||||
pub struct WaveFunctionCollapseBuilder {
|
/// Provides a map builder using the Wave Function Collapse algorithm.
|
||||||
map: Map,
|
pub struct WaveFunctionCollapseBuilder {}
|
||||||
starting_position: Position,
|
|
||||||
depth: i32,
|
|
||||||
history: Vec<Map>,
|
|
||||||
noise_areas: HashMap<i32, Vec<usize>>,
|
|
||||||
derive_from: Option<Box<dyn MapBuilder>>,
|
|
||||||
spawn_list: Vec<(usize, String)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MapBuilder for WaveFunctionCollapseBuilder {
|
impl MetaMapBuilder for WaveFunctionCollapseBuilder {
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
return self.build(rng);
|
self.build(rng, build_data);
|
||||||
}
|
|
||||||
// Getters
|
|
||||||
fn get_map(&mut self) -> Map {
|
|
||||||
return self.map.clone();
|
|
||||||
}
|
|
||||||
fn get_starting_pos(&mut self) -> Position {
|
|
||||||
return self.starting_position.clone();
|
|
||||||
}
|
|
||||||
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
|
|
||||||
return &self.spawn_list;
|
|
||||||
}
|
|
||||||
// Mapgen visualisation stuff
|
|
||||||
fn get_snapshot_history(&self) -> Vec<Map> {
|
|
||||||
return self.history.clone();
|
|
||||||
}
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if SHOW_MAPGEN {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl WaveFunctionCollapseBuilder {
|
impl WaveFunctionCollapseBuilder {
|
||||||
pub fn new(new_depth: i32, derive_from: Option<Box<dyn MapBuilder>>) -> WaveFunctionCollapseBuilder {
|
/// Constructor for wfc.
|
||||||
WaveFunctionCollapseBuilder {
|
#[allow(dead_code)]
|
||||||
map: Map::new(new_depth),
|
pub fn new() -> Box<WaveFunctionCollapseBuilder> {
|
||||||
starting_position: Position { x: 0, y: 0 },
|
Box::new(WaveFunctionCollapseBuilder {})
|
||||||
depth: new_depth,
|
|
||||||
history: Vec::new(),
|
|
||||||
noise_areas: HashMap::new(),
|
|
||||||
derive_from,
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn derived_map(new_depth: i32, builder: Box<dyn MapBuilder>) -> WaveFunctionCollapseBuilder {
|
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
||||||
WaveFunctionCollapseBuilder::new(new_depth, Some(builder))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator) {
|
|
||||||
const CHUNK_SIZE: i32 = 8;
|
const CHUNK_SIZE: i32 = 8;
|
||||||
|
build_data.take_snapshot();
|
||||||
|
|
||||||
let prebuilder = &mut self.derive_from.as_mut().unwrap();
|
let patterns = build_patterns(&build_data.map, CHUNK_SIZE, true, true);
|
||||||
prebuilder.build_map(rng);
|
let constraints = patterns_to_constraints(patterns, CHUNK_SIZE);
|
||||||
self.map = prebuilder.get_map();
|
self.render_tile_gallery(&constraints, CHUNK_SIZE, build_data);
|
||||||
self.history = prebuilder.get_snapshot_history();
|
|
||||||
for t in self.map.tiles.iter_mut() {
|
|
||||||
if *t == TileType::DownStair {
|
|
||||||
*t = TileType::Floor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
let patterns = constraints::build_patterns(&self.map, CHUNK_SIZE, true, true);
|
build_data.map = Map::new(build_data.map.depth);
|
||||||
let constraints = common::patterns_to_constraints(patterns, CHUNK_SIZE);
|
|
||||||
self.render_tile_gallery(&constraints, CHUNK_SIZE);
|
|
||||||
|
|
||||||
// Call solver
|
|
||||||
self.map = Map::new(self.depth);
|
|
||||||
loop {
|
loop {
|
||||||
let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &self.map);
|
let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &build_data.map);
|
||||||
while !solver.iteration(&mut self.map, rng) {
|
while !solver.iteration(&mut build_data.map, rng) {
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
}
|
}
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
if solver.possible {
|
if solver.possible {
|
||||||
break;
|
break;
|
||||||
}
|
} // If it has hit an impossible condition, try again
|
||||||
}
|
|
||||||
|
|
||||||
// Find a starting point; start at the middle and walk left until we find an open tile
|
|
||||||
self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 };
|
|
||||||
let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
|
||||||
while self.map.tiles[start_idx] != TileType::Floor {
|
|
||||||
self.starting_position.x -= 1;
|
|
||||||
start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
|
|
||||||
}
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Find all tiles we can reach from the starting point
|
|
||||||
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Place the stairs
|
|
||||||
self.map.tiles[exit_tile] = TileType::DownStair;
|
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
// Now we build a noise map for use in spawning entities later
|
|
||||||
self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng);
|
|
||||||
|
|
||||||
// Spawn the entities
|
|
||||||
for area in self.noise_areas.iter() {
|
|
||||||
spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list);
|
|
||||||
}
|
}
|
||||||
|
build_data.spawn_list.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tile_gallery(&mut self, constraints: &Vec<MapChunk>, chunk_size: i32) {
|
fn render_tile_gallery(&mut self, constraints: &[MapChunk], chunk_size: i32, build_data: &mut BuilderMap) {
|
||||||
self.map = Map::new(0);
|
build_data.map = Map::new(0);
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
let mut x = 1;
|
let mut x = 1;
|
||||||
let mut y = 1;
|
let mut y = 1;
|
||||||
while counter < constraints.len() {
|
while counter < constraints.len() {
|
||||||
constraints::render_pattern_to_map(&mut self.map, &constraints[counter], chunk_size, x, y);
|
render_pattern_to_map(&mut build_data.map, &constraints[counter], chunk_size, x, y);
|
||||||
|
|
||||||
x += chunk_size + 1;
|
x += chunk_size + 1;
|
||||||
if x + chunk_size > self.map.width {
|
if x + chunk_size > build_data.map.width {
|
||||||
// Next row
|
// Move to the next row
|
||||||
x = 1;
|
x = 1;
|
||||||
y += chunk_size + 1;
|
y += chunk_size + 1;
|
||||||
self.take_snapshot();
|
|
||||||
|
|
||||||
if y + chunk_size > self.map.height {
|
if y + chunk_size > build_data.map.height {
|
||||||
// Next page
|
// Move to the next page
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
self.map = Map::new(0);
|
build_data.map = Map::new(0);
|
||||||
|
|
||||||
x = 1;
|
x = 1;
|
||||||
y = 1;
|
y = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
counter += 1;
|
counter += 1;
|
||||||
}
|
}
|
||||||
self.take_snapshot();
|
build_data.take_snapshot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
|
||||||
|
|
||||||
// 20 mobs : 6 items : 2 food : 1 trap
|
// 20 mobs : 6 items : 2 food : 1 trap
|
||||||
fn category_table() -> RandomTable {
|
fn category_table() -> RandomTable {
|
||||||
return RandomTable::new().add("mob", 20).add("item", 6).add("food", 2).add("trap", 1);
|
return RandomTable::new().add("mob", 12).add("item", 6).add("food", 2).add("trap", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_table() -> RandomTable {
|
fn debug_table() -> RandomTable {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue