From 815eacbc9be7da29d839c1a92277e6719263bfea Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Fri, 4 Feb 2022 15:13:18 -0500 Subject: [PATCH] Complete Section 5.33, completing the currently written tutorial --- src/map_builders.rs | 1 + src/map_builders/dark_elves.rs | 264 +++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) diff --git a/src/map_builders.rs b/src/map_builders.rs index 30ed5ee..62e3f65 100644 --- a/src/map_builders.rs +++ b/src/map_builders.rs @@ -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), } } diff --git a/src/map_builders/dark_elves.rs b/src/map_builders/dark_elves.rs index 5e94592..cfefc55 100644 --- a/src/map_builders/dark_elves.rs +++ b/src/map_builders/dark_elves.rs @@ -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 { + 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 = 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 = 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; + } + } + } + } +}