mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-16 01:23:41 +01:00
adds mapmanip: mazegen (#29522)
* adds mapmanip: mazegen * newtype map tile values, better documenting * build libs * remove obsolete binaries * Build Rust library --------- Co-authored-by: paradisess13[bot] <165046124+paradisess13[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
b404a985ab
commit
b2c88e7d4e
@@ -14,6 +14,7 @@ use eyre::eyre;
|
||||
use eyre::Context;
|
||||
use eyre::ContextCompat;
|
||||
use itertools::Itertools;
|
||||
use procgen::{MazegenHauberkSettings, mapmanip_mazegen_hauberk};
|
||||
use rand::prelude::IteratorRandom;
|
||||
use rand::seq::SliceRandom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -23,6 +24,7 @@ use tools::insert_submap;
|
||||
use crate::logging::setup_panic_handler;
|
||||
|
||||
mod core;
|
||||
mod procgen;
|
||||
mod tools;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -47,6 +49,7 @@ pub enum MapManipulation {
|
||||
submaps_can_repeat: bool,
|
||||
},
|
||||
RandomOrientation,
|
||||
MazegenHauberk(MazegenHauberkSettings),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -119,6 +122,9 @@ pub fn mapmanip(
|
||||
MapManipulation::RandomOrientation => {
|
||||
mapmanip_orientation_randomize(&mut map).wrap_err("randomize orientation failure")
|
||||
}
|
||||
MapManipulation::MazegenHauberk(settings) => {
|
||||
mapmanip_mazegen_hauberk(&mut map, settings)
|
||||
}
|
||||
}
|
||||
.wrap_err(format!("mapmanip fail; manip n is: {n}/{config_len}"))?;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
pub enum Directions {
|
||||
North,
|
||||
South,
|
||||
East,
|
||||
West,
|
||||
}
|
||||
|
||||
pub static DIRECTIONS: [Directions; 4] = [
|
||||
Directions::North,
|
||||
Directions::South,
|
||||
Directions::East,
|
||||
Directions::West,
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Rect {
|
||||
pub x1: i32,
|
||||
pub y1: i32,
|
||||
pub x2: i32,
|
||||
pub y2: i32,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
pub fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
|
||||
Rect {
|
||||
x1: x,
|
||||
y1: y,
|
||||
x2: x + w,
|
||||
y2: y + h,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersects_with(&self, other: &Rect) -> bool {
|
||||
(self.x1 <= other.x2)
|
||||
&& (self.x2 >= other.x1)
|
||||
&& (self.y1 <= other.y2)
|
||||
&& (self.y2 >= other.y1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_direction(d: &Directions) -> (i32, i32) {
|
||||
match d {
|
||||
Directions::North => (0, -1),
|
||||
Directions::South => (0, 1),
|
||||
Directions::East => (1, 0),
|
||||
Directions::West => (-1, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn distance(dx: i32, dy: i32) -> f32 {
|
||||
((dx.pow(2) + dy.pow(2)) as f32).sqrt()
|
||||
}
|
||||
@@ -0,0 +1,573 @@
|
||||
use std::{collections::{HashMap, HashSet}, iter::Map};
|
||||
|
||||
use super::core::GridMap;
|
||||
|
||||
use dmmtools::dmm::{Coord2, Coord3, Prefab};
|
||||
use eyre::ContextCompat;
|
||||
use geometry::{distance, get_direction, Directions, Rect, DIRECTIONS};
|
||||
use rand::{seq::SliceRandom, thread_rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod geometry;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct MapTileVal(i8);
|
||||
|
||||
// positive values are reserved for indexes into the list of available room configurations
|
||||
const MAP_TILE_IGNORE: MapTileVal = MapTileVal(-1);
|
||||
const MAP_TILE_WALL: MapTileVal = MapTileVal(-2);
|
||||
const MAP_TILE_FLOOR: MapTileVal = MapTileVal(-3);
|
||||
const MAP_TILE_RESERVED_FLOOR: MapTileVal = MapTileVal(-4);
|
||||
|
||||
const MAPMANIP_MARKER_PREFIX: &str = "/obj/effect/map_effect/marker/mapmanip";
|
||||
const SCALE: i32 = 3;
|
||||
const MAX_ROOM_PLACEMENT_TRIES: i32 = 200;
|
||||
|
||||
const SAFE_VERTICAL_HALL: &[MapTileVal; 9] = &[
|
||||
MAP_TILE_WALL,
|
||||
MAP_TILE_FLOOR,
|
||||
MAP_TILE_WALL,
|
||||
MAP_TILE_WALL,
|
||||
MAP_TILE_FLOOR,
|
||||
MAP_TILE_WALL,
|
||||
MAP_TILE_WALL,
|
||||
MAP_TILE_FLOOR,
|
||||
MAP_TILE_WALL,
|
||||
];
|
||||
|
||||
const SAFE_HORIZONTAL_HALL: &[MapTileVal; 9] = &[
|
||||
MAP_TILE_WALL,
|
||||
MAP_TILE_WALL,
|
||||
MAP_TILE_WALL,
|
||||
MAP_TILE_FLOOR,
|
||||
MAP_TILE_FLOOR,
|
||||
MAP_TILE_FLOOR,
|
||||
MAP_TILE_WALL,
|
||||
MAP_TILE_WALL,
|
||||
MAP_TILE_WALL,
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct MazeRoomConfig {
|
||||
marker: String,
|
||||
width: i32,
|
||||
height: i32,
|
||||
min: i32,
|
||||
max: i32,
|
||||
}
|
||||
|
||||
pub(crate) type MazeRoomConfigs = Vec<MazeRoomConfig>;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub(crate) struct MazegenHauberkSettings {
|
||||
/// The type path for the area the maze is allowed to generate into.
|
||||
allowed_area: String,
|
||||
/// The turf that all floor cells will be turned into.
|
||||
default_floor: String,
|
||||
/// The submap insertion marker for 9x9 maps comprised of a horizontal
|
||||
/// hallway surrounded by walls.
|
||||
hallway_horizontal_marker: String,
|
||||
/// The submap insertion marker for 9x9 maps comprised of a vertical
|
||||
/// hallway surrounded by walls.
|
||||
hallway_vertical_marker: String,
|
||||
/// The submap insertion marker for 3x3 maps comprised of a section
|
||||
/// of hallway, with no guarantee of walls or floors on any side.
|
||||
hallway_node_marker: String,
|
||||
/// The chance that a hallway being grown will deviate from the direction
|
||||
/// it's taking and "wind" around.
|
||||
winding_percent: i32,
|
||||
/// The chance of adding a connector between two regions that have already
|
||||
/// been joined. Decreasing this leads to more loosely connected dungeons.
|
||||
extra_connector_chance: i32,
|
||||
/// The list of available maze room configurations. Each room configuration
|
||||
/// specifies its submap insertion marker, dimensions, and minimum and
|
||||
/// maximum requested number of rooms of this type generated.
|
||||
room_configs: MazeRoomConfigs,
|
||||
}
|
||||
|
||||
type CellGrid = Vec<Vec<MapTileVal>>;
|
||||
|
||||
type RegionGrid = Vec<Vec<i8>>;
|
||||
|
||||
/// A struct representing the data used to procedurally generate a maze with the
|
||||
/// "Hauberk" algorithm by Bob Nystrom described at
|
||||
/// https://journal.stuffwithstuff.com/2014/12/21/rooms-and-mazes/ and
|
||||
/// implemented in https://github.com/munificent/hauberk/ in Dart, with
|
||||
/// inspiration from the pre-existing rust implementation implemented in
|
||||
/// https://github.com/graysentinel/roguelike-rust/.
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// When the mazegen mapmanip runs, it first marks out all the tiles it can
|
||||
/// modify by checking to see if they have an area set to the `allowed_area`
|
||||
/// setting. It scales the map down to a grid at 1/3rd size, and performs the
|
||||
/// mazegen largely as described in Nystrom's post. It uses the passed in
|
||||
/// `room_configs` as a list of possible room sizes and quantities to attempt to
|
||||
/// spawn.
|
||||
///
|
||||
/// Once complete, each cell in the grid is converted to hallways and floors. If
|
||||
/// the mapmanip finds a "straight" vertical or horizontal hallway comprised of
|
||||
/// hallway cells along one axis, surrounded by wall cells, either the
|
||||
/// `hallway_horizontal_marker` or `hallway_vertical_marker` submap markers are
|
||||
/// inserted at the bottom left of the 3x3 area around the center cell being
|
||||
/// checked. This means that the horizontal and vertical hallway markers are
|
||||
/// given a 9x9 in-game tile area to work with, despite the hallway being either
|
||||
/// 3x9 or 9x3. This gives mappers some leeway to expand the hallways a column
|
||||
/// or row on either side, so long as they remain cognizant that these submap
|
||||
/// insertions may overlap. For example, if you have two vertical hallway
|
||||
/// markers, one right next to the other, the one on the left will share a 3x9
|
||||
/// column of in-game turfs with the one on the right. As long as these submaps
|
||||
/// do not extend too far out to each other, they can be cleverly used to expand
|
||||
/// the areas that hallways take up.
|
||||
///
|
||||
/// The `hallway_node_marker` submap marker is used for nearly all hallway cells
|
||||
/// that do not meet the above criteria for being part of a "straight" vertical
|
||||
/// or horizontal hallway. This submap should be 3x3 tiles, and not anticipate
|
||||
/// walls being on any of its sides.
|
||||
///
|
||||
/// ## JSON Config
|
||||
///
|
||||
/// A sample mapmanip configuration may look like this:
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "type": "MazegenHauberk",
|
||||
/// "allowed_area": "/area/mazegen",
|
||||
/// "default_floor": "/turf/simulated/floor/plasteel",
|
||||
/// "winding_percent": 20,
|
||||
/// "extra_connector_chance": 20,
|
||||
/// "hallway_horizontal_marker": "/obj/effect/map_effect/marker/mapmanip/submap/insert/deepmaints/hallway_horizontal",
|
||||
/// "hallway_vertical_marker": "/obj/effect/map_effect/marker/mapmanip/submap/insert/deepmaints/hallway_vertical",
|
||||
/// "hallway_node_marker": "/obj/effect/map_effect/marker/mapmanip/submap/insert/deepmaints/hallway_floor",
|
||||
/// "room_configs": [
|
||||
/// {
|
||||
/// "marker": "/obj/effect/map_effect/marker/mapmanip/submap/insert/deepmaints/small",
|
||||
/// "width": 3,
|
||||
/// "height": 3,
|
||||
/// "min": 7,
|
||||
/// "max": 9
|
||||
/// },
|
||||
/// {
|
||||
/// "marker": "/obj/effect/map_effect/marker/mapmanip/submap/insert/deepmaints/medium",
|
||||
/// "width": 5,
|
||||
/// "height": 5,
|
||||
/// "min": 2,
|
||||
/// "max": 3
|
||||
/// }
|
||||
/// ]
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Shortcomings
|
||||
///
|
||||
/// 1. Currently this mapmanip only works on the 1st z-level of a map file. This
|
||||
/// isn't an impossible thing to fix, I just didn't feel like setting it up
|
||||
/// to handle other z-levels.
|
||||
/// 2. As a result of the mazegen algorithm using odd-sized rooms to ensure
|
||||
/// consistent spacing for hallways and junctions, and the fact that the maze
|
||||
/// is generated on a grid scaled to 1/3rd the original map size, all rooms
|
||||
/// must have sides with dimensions that are both odd and multiples of three,
|
||||
/// e.g. 9, 15, 21.
|
||||
struct MazegenHauberk {
|
||||
settings: MazegenHauberkSettings,
|
||||
grid: CellGrid,
|
||||
width: i32,
|
||||
height: i32,
|
||||
regions: RegionGrid,
|
||||
rooms: Vec<Rect>,
|
||||
current_region: i8,
|
||||
}
|
||||
|
||||
impl MazegenHauberk {
|
||||
fn new(settings: MazegenHauberkSettings, width: i32, height: i32) -> Self {
|
||||
MazegenHauberk {
|
||||
settings,
|
||||
width,
|
||||
height,
|
||||
|
||||
grid: vec![vec![MAP_TILE_WALL; height as usize]; width as usize],
|
||||
regions: vec![vec![-1; height as usize]; width as usize],
|
||||
rooms: vec![],
|
||||
current_region: -1,
|
||||
}
|
||||
}
|
||||
|
||||
fn can_place_room(&self, room: &Rect) -> bool {
|
||||
if self.rooms.iter().any(|other| room.intersects_with(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for a in room.x1..room.x2 {
|
||||
for b in room.y1..room.y2 {
|
||||
let val = &self.grid[a as usize][b as usize];
|
||||
// positive values are other rooms
|
||||
if val == &MAP_TILE_IGNORE || val.0 >= 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn add_rooms(&mut self, rng: &mut impl Rng) {
|
||||
let mut room_config_idxes = vec![];
|
||||
for (idx, room_config) in self.settings.room_configs.iter().enumerate() {
|
||||
let room_type_count = rng.gen_range(room_config.min..=room_config.max);
|
||||
for _ in 0..room_type_count {
|
||||
room_config_idxes.push(idx);
|
||||
}
|
||||
}
|
||||
|
||||
room_config_idxes.shuffle(rng);
|
||||
for _ in 0..=MAX_ROOM_PLACEMENT_TRIES {
|
||||
if room_config_idxes.is_empty() {
|
||||
break;
|
||||
}
|
||||
let idx = room_config_idxes.last().unwrap();
|
||||
let width = self.settings.room_configs[*idx].width;
|
||||
let height = self.settings.room_configs[*idx].height;
|
||||
let new_room = Rect::new(
|
||||
rng.gen_range(0..(self.width - width) / 2) * 2 + 1,
|
||||
rng.gen_range(0..(self.height - height) / 2) * 2 + 1,
|
||||
width,
|
||||
height,
|
||||
);
|
||||
if !self.can_place_room(&new_room) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.create_room(new_room, *idx as i8);
|
||||
room_config_idxes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
fn connect_regions(&mut self, rng: &mut impl Rng) {
|
||||
let mut connector_regions = HashMap::new();
|
||||
|
||||
for x in 1..self.width - 1 {
|
||||
for y in 1..self.height - 1 {
|
||||
if self.grid[x as usize][y as usize] != MAP_TILE_WALL {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut regions = HashSet::new();
|
||||
for d in DIRECTIONS.iter() {
|
||||
let (dx, dy) = get_direction(d);
|
||||
let region = self.regions[(x + dx) as usize][(y + dy) as usize];
|
||||
if region != -1 {
|
||||
regions.insert(region);
|
||||
}
|
||||
}
|
||||
|
||||
if regions.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
connector_regions.insert(Coord2::new(x, y), regions);
|
||||
}
|
||||
}
|
||||
|
||||
let mut connectors: Vec<&Coord2> = connector_regions.keys().collect();
|
||||
|
||||
let mut merged = HashMap::new();
|
||||
let mut open_regions = HashSet::new();
|
||||
|
||||
for i in 0..=self.current_region {
|
||||
merged.insert(i, i);
|
||||
open_regions.insert(i);
|
||||
}
|
||||
|
||||
while open_regions.len() > 1 && !connectors.is_empty() {
|
||||
let connector = connectors.choose(rng).unwrap();
|
||||
self.carve(connector);
|
||||
|
||||
let regions: Vec<i8> = connector_regions[connector]
|
||||
.iter()
|
||||
.map(|region| merged[region])
|
||||
.collect();
|
||||
|
||||
let dest = regions.first().unwrap();
|
||||
let sources: Vec<&i8> = regions[1..].iter().collect();
|
||||
|
||||
for i in 0..=self.current_region {
|
||||
if sources.contains(&&merged[&i]) {
|
||||
merged.insert(i, *dest);
|
||||
}
|
||||
}
|
||||
|
||||
open_regions.retain(|&k| !sources.contains(&&k));
|
||||
|
||||
connectors.retain(|pos| {
|
||||
if distance(connector.x - pos.x, connector.y - pos.y) < 2.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let local_regions: HashSet<i8> =
|
||||
HashSet::from_iter(connector_regions[pos].iter().map(|region| merged[region]));
|
||||
if local_regions.len() > 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let new_junc = rng.gen_range(1..=100);
|
||||
if new_junc < self.settings.extra_connector_chance as u32 {
|
||||
self.carve(pos);
|
||||
}
|
||||
false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_dead_ends(&mut self) {
|
||||
let mut done = false;
|
||||
|
||||
while !done {
|
||||
done = true;
|
||||
|
||||
for x in 0..self.width {
|
||||
for y in 0..self.height {
|
||||
if self.grid[x as usize][y as usize] == MAP_TILE_WALL {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut exits = 0;
|
||||
for d in DIRECTIONS.iter() {
|
||||
let (dx, dy) = get_direction(d);
|
||||
let (target_x, target_y) = (x + dx, y + dy);
|
||||
if self.out_of_bounds(target_x, target_y) {
|
||||
continue;
|
||||
}
|
||||
let val = &self.grid[target_x as usize][target_y as usize];
|
||||
if val != &MAP_TILE_WALL && val != &MAP_TILE_IGNORE {
|
||||
exits += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if exits != 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
done = false;
|
||||
self.grid[x as usize][y as usize] = MAP_TILE_WALL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn can_carve(&self, pos: Coord2, d: &Directions) -> bool {
|
||||
let (dx, dy) = get_direction(d);
|
||||
let test_point = (pos.x + (dx * 3), pos.y + (dy * 3));
|
||||
if self.out_of_bounds(test_point.0, test_point.1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let (target_x, target_y) = (pos.x + dx, pos.y + dy);
|
||||
|
||||
self.grid[target_x as usize][target_y as usize] == MAP_TILE_WALL
|
||||
}
|
||||
|
||||
fn start_region(&mut self) {
|
||||
self.current_region += 1;
|
||||
}
|
||||
|
||||
fn carve(&mut self, pos: &Coord2) {
|
||||
self.grid[pos.x as usize][pos.y as usize] = MAP_TILE_FLOOR;
|
||||
self.regions[pos.x as usize][pos.y as usize] = self.current_region;
|
||||
}
|
||||
|
||||
fn create_room(&mut self, room: Rect, idx: i8) {
|
||||
self.start_region();
|
||||
for x in room.x1..room.x2 {
|
||||
for y in room.y1..room.y2 {
|
||||
self.grid[x as usize][y as usize] = MAP_TILE_RESERVED_FLOOR;
|
||||
self.regions[x as usize][y as usize] = self.current_region;
|
||||
}
|
||||
}
|
||||
self.rooms.push(room);
|
||||
self.grid[room.x1 as usize][room.y1 as usize] = MapTileVal(idx);
|
||||
}
|
||||
|
||||
/// Implementation of the "growing tree" algorithm from here:
|
||||
/// http://www.astrolog.org/labyrnth/algrithm.htm.
|
||||
fn grow_maze(&mut self, start: Coord2, rng: &mut impl Rng) {
|
||||
let mut cells = Vec::new();
|
||||
let mut last_dir = (0, 0);
|
||||
|
||||
self.start_region();
|
||||
self.carve(&start);
|
||||
|
||||
cells.push(start);
|
||||
|
||||
while !cells.is_empty() {
|
||||
let cell = cells.last().unwrap();
|
||||
|
||||
let mut unmade_cells = Vec::new();
|
||||
for d in DIRECTIONS.iter() {
|
||||
let (dx, dy) = get_direction(d);
|
||||
let target_pos = Coord2::new(cell.x + dx, cell.y + dy);
|
||||
if self.can_carve(target_pos, d) {
|
||||
unmade_cells.push((dx, dy));
|
||||
}
|
||||
}
|
||||
|
||||
if !unmade_cells.is_empty() {
|
||||
let dir = if unmade_cells.contains(&last_dir)
|
||||
&& rng.gen_range(1..=100) > self.settings.winding_percent as u32
|
||||
{
|
||||
last_dir
|
||||
} else {
|
||||
*unmade_cells.choose(rng).unwrap()
|
||||
};
|
||||
|
||||
let close_pos = Coord2::new(cell.x + dir.0, cell.y + dir.1);
|
||||
let far_pos = Coord2::new(cell.x + (dir.0 * 2), cell.y + (dir.1 * 2));
|
||||
self.carve(&close_pos);
|
||||
self.carve(&far_pos);
|
||||
|
||||
cells.push(far_pos);
|
||||
|
||||
last_dir = dir;
|
||||
} else {
|
||||
cells.pop();
|
||||
last_dir = (0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Maze generation entry point.
|
||||
fn generate(&mut self, rng: &mut impl Rng) {
|
||||
self.add_rooms(rng);
|
||||
|
||||
for y in (1..self.height).step_by(2) {
|
||||
for x in (1..self.width).step_by(2) {
|
||||
if self.grid[x as usize][y as usize] != MAP_TILE_WALL {
|
||||
continue;
|
||||
}
|
||||
|
||||
let start = Coord2::new(x, y);
|
||||
self.grow_maze(start, rng);
|
||||
}
|
||||
}
|
||||
|
||||
self.connect_regions(rng);
|
||||
self.remove_dead_ends();
|
||||
}
|
||||
|
||||
/// Get the grid values of the 3x3 cell area with (x, y) at the center.
|
||||
fn get_neighborhood(&self, x: i32, y: i32) -> Vec<MapTileVal> {
|
||||
let mut result = vec![];
|
||||
for dy in -1..=1 {
|
||||
for dx in -1..=1 {
|
||||
let x2 = x + dx;
|
||||
let y2 = y + dy;
|
||||
if x2 >= 0 && x2 < self.width && y2 >= 0 && y2 < self.height {
|
||||
result.push(self.grid[x2 as usize][y2 as usize].clone());
|
||||
} else {
|
||||
result.push(MAP_TILE_IGNORE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn out_of_bounds(&self, x: i32, y: i32) -> bool {
|
||||
x < 0 || x >= self.width || y < 0 || y >= self.height
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mapmanip_mazegen_hauberk(
|
||||
map: &mut GridMap,
|
||||
settings: &MazegenHauberkSettings,
|
||||
) -> eyre::Result<()> {
|
||||
let width = map.size.x / SCALE;
|
||||
let height = map.size.y / SCALE;
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let mut data = MazegenHauberk::new(settings.clone(), width, height);
|
||||
|
||||
// set up the grid to ignore any cells that aren't the correct /area type
|
||||
for x in 1..map.size.x {
|
||||
for y in 1..map.size.y {
|
||||
if let Some(tile) = map.grid.get(&Coord3::new(x, y, 1)) {
|
||||
if let Some(area) = tile.get_area() {
|
||||
if !area.path.starts_with(&settings.allowed_area) {
|
||||
data.grid[(x / SCALE) as usize][(y / SCALE) as usize] = MAP_TILE_IGNORE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// perform the maze generation
|
||||
data.generate(&mut rng);
|
||||
|
||||
// take the generated maze results and apply them to the grid map,
|
||||
// adding submap markers, changing /turf paths, and marking cells
|
||||
// as reserved by hallway submaps.
|
||||
for x in 0..width {
|
||||
for y in 0..height {
|
||||
let root_val = &data.grid[x as usize][y as usize];
|
||||
if root_val.0 >= 0 {
|
||||
let root_tile = map
|
||||
.grid
|
||||
.get_mut(&Coord3::new((x * 3) + 1, (y * 3) + 1, 1))
|
||||
.unwrap();
|
||||
root_tile.prefabs.push(Prefab::from_path(
|
||||
settings.room_configs[root_val.0 as usize].marker.clone(),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
if !(root_val == &MAP_TILE_FLOOR || root_val == &MAP_TILE_RESERVED_FLOOR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// look for solo connecting halls that are otherwise
|
||||
// filled in with walls and give them their own submap
|
||||
let neighborhood = data.get_neighborhood(x, y);
|
||||
if neighborhood == SAFE_HORIZONTAL_HALL {
|
||||
data.grid[x as usize][y as usize] = MAP_TILE_RESERVED_FLOOR;
|
||||
data.grid[(x + 2) as usize][y as usize] = MAP_TILE_RESERVED_FLOOR;
|
||||
let tile = map
|
||||
.grid
|
||||
.get_mut(&Coord3::new((x - 1) * 3 + 1, (y - 1) * 3 + 1, 1))
|
||||
.unwrap();
|
||||
tile.prefabs
|
||||
.retain(|prefab| !prefab.path.starts_with(MAPMANIP_MARKER_PREFIX));
|
||||
tile.prefabs
|
||||
.insert(0, Prefab::from_path(&settings.hallway_horizontal_marker));
|
||||
} else if neighborhood == SAFE_VERTICAL_HALL {
|
||||
data.grid[x as usize][y as usize] = MAP_TILE_RESERVED_FLOOR;
|
||||
data.grid[x as usize][(y + 2) as usize] = MAP_TILE_RESERVED_FLOOR;
|
||||
let tile = map
|
||||
.grid
|
||||
.get_mut(&Coord3::new((x - 1) * 3 + 1, (y - 1) * 3 + 1, 1))
|
||||
.unwrap();
|
||||
tile.prefabs
|
||||
.retain(|prefab| !prefab.path.starts_with(MAPMANIP_MARKER_PREFIX));
|
||||
tile.prefabs
|
||||
.insert(0, Prefab::from_path(&settings.hallway_vertical_marker));
|
||||
} else {
|
||||
data.grid[x as usize][y as usize] = MAP_TILE_FLOOR;
|
||||
let tile = map
|
||||
.grid
|
||||
.get_mut(&Coord3::new(x * 3 + 1, y * 3 + 1, 1))
|
||||
.unwrap();
|
||||
tile.prefabs
|
||||
.insert(0, Prefab::from_path(&settings.hallway_node_marker));
|
||||
}
|
||||
|
||||
for a in (x * 3)..(x + 1) * 3 {
|
||||
for b in (y * 3)..(y + 1) * 3 {
|
||||
let tile = map.grid.get_mut(&Coord3::new(a + 1, b + 1, 1)).unwrap();
|
||||
tile.remove_turf().wrap_err("map tile has no turf")?;
|
||||
tile.prefabs
|
||||
.insert(0, Prefab::from_path(&settings.default_floor));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user