From 3ce4994417dab1f96d7784997817e93d7bcc0c2d Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 7 Dec 2021 15:13:02 -0500 Subject: [PATCH] Generate a map from a REX Paint file --- src/map_builders/mod.rs | 41 ++++---- .../waveform_collapse/image_loader.rs | 25 +++++ src/map_builders/waveform_collapse/mod.rs | 95 +++++++++++++++++++ src/rex_assets.rs | 4 + 4 files changed, 146 insertions(+), 19 deletions(-) create mode 100644 src/map_builders/waveform_collapse/image_loader.rs create mode 100644 src/map_builders/waveform_collapse/mod.rs diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index ec0c4d8..999d604 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -7,6 +7,7 @@ mod drunkard; mod maze; mod simple_map; mod voronoi; +mod waveform_collapse; use crate::{Map, Position}; use bsp_dungeon::BspDungeonBuilder; @@ -19,6 +20,7 @@ use maze::MazeBuilder; use simple_map::SimpleMapBuilder; use specs::prelude::*; use voronoi::VoronoiCellBuilder; +use waveform_collapse::WaveformCollapseBuilder; pub trait MapBuilder { fn build_map(&mut self); @@ -30,23 +32,24 @@ pub trait MapBuilder { } pub fn random_builder(new_depth: i32) -> Box { - let mut rng = rltk::RandomNumberGenerator::new(); - match rng.roll_dice(1, 16) { - 1 => Box::new(BspDungeonBuilder::new(new_depth)), - 2 => Box::new(BspInteriorBuilder::new(new_depth)), - 3 => Box::new(CellularAutomataBuilder::new(new_depth)), - 4 => Box::new(DrunkardsWalkBuilder::open_area(new_depth)), - 5 => Box::new(DrunkardsWalkBuilder::open_halls(new_depth)), - 6 => Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)), - 7 => Box::new(DrunkardsWalkBuilder::fat_passages(new_depth)), - 8 => Box::new(DrunkardsWalkBuilder::fearful_symmetry(new_depth)), - 9 => Box::new(MazeBuilder::new(new_depth)), - 10 => Box::new(DLABuilder::walk_inwards(new_depth)), - 11 => Box::new(DLABuilder::walk_outwards(new_depth)), - 12 => Box::new(DLABuilder::central_attractor(new_depth)), - 13 => Box::new(DLABuilder::insectoid(new_depth)), - 14 => Box::new(VoronoiCellBuilder::pythagoras(new_depth)), - 15 => Box::new(VoronoiCellBuilder::manhattan(new_depth)), - _ => Box::new(SimpleMapBuilder::new(new_depth)), - } + // let mut rng = rltk::RandomNumberGenerator::new(); + // match rng.roll_dice(1, 16) { + // 1 => Box::new(BspDungeonBuilder::new(new_depth)), + // 2 => Box::new(BspInteriorBuilder::new(new_depth)), + // 3 => Box::new(CellularAutomataBuilder::new(new_depth)), + // 4 => Box::new(DrunkardsWalkBuilder::open_area(new_depth)), + // 5 => Box::new(DrunkardsWalkBuilder::open_halls(new_depth)), + // 6 => Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)), + // 7 => Box::new(DrunkardsWalkBuilder::fat_passages(new_depth)), + // 8 => Box::new(DrunkardsWalkBuilder::fearful_symmetry(new_depth)), + // 9 => Box::new(MazeBuilder::new(new_depth)), + // 10 => Box::new(DLABuilder::walk_inwards(new_depth)), + // 11 => Box::new(DLABuilder::walk_outwards(new_depth)), + // 12 => Box::new(DLABuilder::central_attractor(new_depth)), + // 13 => Box::new(DLABuilder::insectoid(new_depth)), + // 14 => Box::new(VoronoiCellBuilder::pythagoras(new_depth)), + // 15 => Box::new(VoronoiCellBuilder::manhattan(new_depth)), + // _ => Box::new(SimpleMapBuilder::new(new_depth)), + // } + Box::new(WaveformCollapseBuilder::new(new_depth)) } diff --git a/src/map_builders/waveform_collapse/image_loader.rs b/src/map_builders/waveform_collapse/image_loader.rs new file mode 100644 index 0000000..7a8522c --- /dev/null +++ b/src/map_builders/waveform_collapse/image_loader.rs @@ -0,0 +1,25 @@ +use rltk::rex::XpFile; +use crate::{Map, TileType}; + +/// Loads a RexPaint file, and converts it into our map format +pub fn load_rex_map(new_depth: i32, xp_file: &XpFile) -> Map { + let mut map = Map::new(new_depth); + + 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 < map.width as usize && y < map.height as usize { + let idx = map.xy_idx(x as i32, y as i32); + match cell.ch { + 32 => map.tiles[idx] = TileType::Floor, // # + 35 => map.tiles[idx] = TileType::Wall, // # + _ => {} + } + } + } + } + } + + map +} \ No newline at end of file diff --git a/src/map_builders/waveform_collapse/mod.rs b/src/map_builders/waveform_collapse/mod.rs new file mode 100644 index 0000000..f02b909 --- /dev/null +++ b/src/map_builders/waveform_collapse/mod.rs @@ -0,0 +1,95 @@ +mod image_loader; +use image_loader::*; + +use super::common::{ + generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, +}; +use super::MapBuilder; +use crate::{spawner, Map, Position, TileType, SHOW_MAPGEN_VISUALIZER}; +use rltk::RandomNumberGenerator; +use specs::prelude::*; +use std::collections::HashMap; + +pub struct WaveformCollapseBuilder { + map: Map, + starting_position: Position, + depth: i32, + history: Vec, + noise_areas: HashMap>, +} + +impl MapBuilder for WaveformCollapseBuilder { + fn build_map(&mut self) { + self.build(); + } + + fn spawn_entities(&mut self, ecs: &mut World) { + for area in self.noise_areas.iter() { + spawner::spawn_region(ecs, area.1, self.depth); + } + } + + fn get_map(&self) -> Map { + self.map.clone() + } + + fn get_starting_position(&self) -> Position { + self.starting_position.clone() + } + + fn get_snapshot_history(&self) -> Vec { + self.history.clone() + } + + fn take_snapshot(&mut self) { + if SHOW_MAPGEN_VISUALIZER { + let mut snapshot = self.map.clone(); + for v in snapshot.revealed_tiles.iter_mut() { + *v = true; + } + self.history.push(snapshot); + } + } +} + +impl WaveformCollapseBuilder { + pub fn new(new_depth: i32) -> WaveformCollapseBuilder { + WaveformCollapseBuilder { + map: Map::new(new_depth), + starting_position: Position { x: 0, y: 0 }, + depth: new_depth, + history: Vec::new(), + noise_areas: HashMap::new(), + } + } + + fn build(&mut self) { + let mut rng = RandomNumberGenerator::new(); + + self.map = load_rex_map(self.depth, &rltk::XpFile::from_resource("../resources/wfc-demo1.xp").unwrap()); + self.take_snapshot(); + + // Find a starting point; start at the middle and walk left until we find an open tile + self.starting_position = Position { + x: self.map.width / 2, + y: self.map.height / 2, + }; + let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y); + while self.map.tiles[start_idx] != TileType::Floor { + self.starting_position.x -= 1; + start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y); + } + self.take_snapshot(); + + // Find all tiles we can reach from the starting point + let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx); + self.take_snapshot(); + + // Place the stairs + self.map.tiles[exit_tile] = TileType::DownStairs; + self.take_snapshot(); + + // Now we build a noise map for use in spawning entities later + self.noise_areas = generate_voronoi_spawn_regions(&self.map, &mut rng); + } +} diff --git a/src/rex_assets.rs b/src/rex_assets.rs index cb0be47..52327fe 100644 --- a/src/rex_assets.rs +++ b/src/rex_assets.rs @@ -1,6 +1,8 @@ use rltk::rex::XpFile; rltk::embedded_resource!(SMALL_DUNGEON, "../resources/SmallDungeon_80x50.xp"); +rltk::embedded_resource!(WFC_DEMO_IMAGE1, "../resources/wfc-demo1.xp"); +rltk::embedded_resource!(WFC_DEMO_IMAGE2, "../resources/wfc-demo2.xp"); pub struct RexAssets { pub menu: XpFile, @@ -9,6 +11,8 @@ pub struct RexAssets { impl RexAssets { pub fn new() -> RexAssets { rltk::link_resource!(SMALL_DUNGEON, "../resources/SmallDungeon_80x50.xp"); + rltk::link_resource!(WFC_DEMO_IMAGE1, "../resources/wfc-demo1.xp"); + rltk::link_resource!(WFC_DEMO_IMAGE2, "../resources/wfc-demo2.xp"); RexAssets { menu: XpFile::from_resource("../resources/SmallDungeon_80x50.xp").unwrap(),