Complete Section 5.33, completing the currently written tutorial

This commit is contained in:
Timothy Warren 2022-02-04 15:13:18 -05:00
parent 3e55061f97
commit 815eacbc9b
2 changed files with 265 additions and 0 deletions

View File

@ -358,6 +358,7 @@ pub fn level_builder(new_depth: i32, width: i32, height: i32) -> BuilderChain {
8 => mushroom_builder(new_depth, width, height),
9 => mushroom_exit(new_depth, width, height),
10 => dark_elf_city(new_depth, width, height),
11 => dark_elf_plaza(new_depth, width, height),
_ => random_builder(new_depth, width, height),
}
}

View File

@ -19,3 +19,267 @@ pub fn dark_elf_city(new_depth: i32, width: i32, height: i32) -> BuilderChain {
chain
}
pub fn dark_elf_plaza(new_depth: i32, width: i32, height: i32) -> BuilderChain {
let mut chain = BuilderChain::new(new_depth, width, height, "Dark Elven Plaza");
chain
.start_with(PlazaMapBuilder::new())
.with(AreaStartingPosition::new(XStart::Left, YStart::Center))
.with(CullUnreachable::new());
chain
}
pub struct PlazaMapBuilder {}
impl InitialMapBuilder for PlazaMapBuilder {
#[allow(dead_code)]
fn build_map(&mut self, build_data: &mut BuilderMap) {
self.empty_map(build_data).spawn_zones(build_data);
}
}
impl PlazaMapBuilder {
#[allow(dead_code)]
pub fn new() -> Box<PlazaMapBuilder> {
Box::new(PlazaMapBuilder {})
}
fn empty_map(&mut self, build_data: &mut BuilderMap) -> &mut Self {
build_data
.map
.tiles
.iter_mut()
.for_each(|t| *t = TileType::Floor);
self
}
fn spawn_zones(&mut self, build_data: &mut BuilderMap) {
let mut voronoi_seeds: Vec<(usize, Point)> = Vec::new();
while voronoi_seeds.len() < 32 {
let vx = roll_dice(1, build_data.map.width - 1);
let vy = roll_dice(1, build_data.map.height - 1);
let vidx = build_data.map.xy_idx(vx, vy);
let candidate = (vidx, Point::new(vx, vy));
if !voronoi_seeds.contains(&candidate) {
voronoi_seeds.push(candidate);
}
}
let mut voronoi_distance = vec![(0, 0.0_f32); 32];
let mut voronoi_membership =
vec![0_i32; build_data.map.width as usize * build_data.map.height as usize];
for (i, vid) in voronoi_membership.iter_mut().enumerate() {
let x = i as i32 % build_data.map.width;
let y = i as i32 / build_data.map.width;
for (seed, pos) in voronoi_seeds.iter().enumerate() {
let distance = DistanceAlg::PythagorasSquared.distance2d(Point::new(x, y), pos.1);
voronoi_distance[seed] = (seed, distance);
}
voronoi_distance.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
*vid = voronoi_distance[0].0 as i32;
}
// Make a list of zone sizes and cull empty ones
let mut zone_sizes: Vec<(i32, usize)> = Vec::with_capacity(32);
for zone in 0..32 {
let num_tiles = voronoi_membership.iter().filter(|z| **z == zone).count();
if num_tiles > 0 {
zone_sizes.push((zone, num_tiles));
}
}
zone_sizes.sort_by(|a, b| b.1.cmp(&a.1));
// Start making zonal terrain
zone_sizes
.iter()
.enumerate()
.for_each(|(i, (zone, _))| match i {
0 => self.portal_park(build_data, &voronoi_membership, *zone, &voronoi_seeds),
1 | 2 => self.park(build_data, &voronoi_membership, *zone, &voronoi_seeds),
i if i > 20 => {
self.fill_zone(build_data, &voronoi_membership, *zone, TileType::Wall)
}
_ => match roll_dice(1, 6) {
1 => {
self.fill_zone(build_data, &voronoi_membership, *zone, TileType::DeepWater)
}
2 => self.fill_zone(
build_data,
&voronoi_membership,
*zone,
TileType::ShallowWater,
),
3 => self.stalactite_display(build_data, &voronoi_membership, *zone),
_ => {}
},
});
// Clear the path
self.make_roads(build_data, &voronoi_membership);
}
fn portal_park(
&mut self,
build_data: &mut BuilderMap,
voronoi_membership: &[i32],
zone: i32,
seeds: &[(usize, Point)],
) {
let zone_tiles: Vec<usize> = voronoi_membership
.iter()
.enumerate()
.filter(|(_, tile_zone)| **tile_zone == zone)
.map(|(idx, _)| idx)
.collect();
// Start all gravel
zone_tiles
.iter()
.for_each(|idx| build_data.map.tiles[*idx] = TileType::Gravel);
// Add the exit
let center = seeds[zone as usize].1;
let idx = build_data.map.xy_idx(center.x, center.y);
build_data.map.tiles[idx] = TileType::DownStairs;
// Add some altars around the exit
[
build_data.map.xy_idx(center.x - 2, center.y),
build_data.map.xy_idx(center.x + 2, center.y),
build_data.map.xy_idx(center.x, center.y - 2),
build_data.map.xy_idx(center.x, center.y + 2),
]
.iter()
.for_each(|idx| build_data.spawn_list.push((*idx, "Altar".to_string())));
let demon_spawn = build_data.map.xy_idx(center.x + 1, center.y + 1);
build_data
.spawn_list
.push((demon_spawn, "Vokoth".to_string()));
}
fn fill_zone(
&mut self,
build_data: &mut BuilderMap,
voronoi_membership: &[i32],
zone: i32,
tile_type: TileType,
) {
voronoi_membership
.iter()
.enumerate()
.filter(|(_, tile_zone)| **tile_zone == zone)
.for_each(|(idx, _)| build_data.map.tiles[idx] = tile_type);
}
fn stalactite_display(
&mut self,
build_data: &mut BuilderMap,
voronoi_membership: &[i32],
zone: i32,
) {
voronoi_membership
.iter()
.enumerate()
.filter(|(_, tile_zone)| **tile_zone == zone)
.for_each(|(idx, _)| {
build_data.map.tiles[idx] = match roll_dice(1, 10) {
1 => TileType::Stalactite,
2 => TileType::Stalagmite,
_ => TileType::Grass,
};
});
}
fn park(
&mut self,
build_data: &mut BuilderMap,
voronoi_membership: &[i32],
zone: i32,
seeds: &[(usize, Point)],
) {
let zone_tiles: Vec<usize> = voronoi_membership
.iter()
.enumerate()
.filter(|(_, tile_zone)| **tile_zone == zone)
.map(|(idx, _)| idx)
.collect();
// Start all grass
zone_tiles
.iter()
.for_each(|idx| build_data.map.tiles[*idx] = TileType::Grass);
// Add a stone area in the middle
let center = seeds[zone as usize].1;
for y in center.y - 2..=center.y + 2 {
for x in center.x - 2..=center.x + 2 {
let idx = build_data.map.xy_idx(x, y);
build_data.map.tiles[idx] = TileType::Road;
if roll_dice(1, 6) > 2 {
build_data.map.bloodstains.insert(idx);
}
}
}
// With an altar at the center
build_data.spawn_list.push((
build_data.map.xy_idx(center.x, center.y),
"Altar".to_string(),
));
// And chairs for spectators, and the spectators themselves
let available_enemies = match crate::rng::roll_dice(1, 3) {
1 => vec!["Arbat Dark Elf", "Arbat Dark Elf Leader", "Arbat Orc Slave"],
2 => vec!["Barbo Dark Elf", "Barbo Goblin Archer"],
_ => vec!["Cirro Dark Elf", "Cirro Dark Priestess", "Cirro Spider"],
};
zone_tiles.iter().for_each(|idx| {
if build_data.map.tiles[*idx] == TileType::Grass {
match crate::rng::roll_dice(1, 10) {
1 => build_data.spawn_list.push((*idx, "Chair".to_string())),
2 => {
let to_spawn = crate::rng::range(0, available_enemies.len() as i32);
build_data
.spawn_list
.push((*idx, available_enemies[to_spawn as usize].to_string()));
}
_ => {}
}
}
});
}
fn make_roads(&mut self, build_data: &mut BuilderMap, voronoi_membership: &[i32]) {
for y in 1..build_data.map.height - 1 {
for x in 1..build_data.map.width - 1 {
let mut neighbors = 0;
let my_idx = build_data.map.xy_idx(x, y);
let my_seed = voronoi_membership[my_idx];
if voronoi_membership[build_data.map.xy_idx(x - 1, y)] != my_seed {
neighbors += 1;
}
if voronoi_membership[build_data.map.xy_idx(x + 1, y)] != my_seed {
neighbors += 1;
}
if voronoi_membership[build_data.map.xy_idx(x, y - 1)] != my_seed {
neighbors += 1;
}
if voronoi_membership[build_data.map.xy_idx(x, y + 1)] != my_seed {
neighbors += 1;
}
if neighbors > 1 {
build_data.map.tiles[my_idx] = TileType::Road;
}
}
}
}
}