// The wild underground: rivers of water and lava across free maps (with neutral bridges) and // natural caverns until about a quarter of the map is open - some with a pool, veins along the // walls, now and then a small treasure or a creature in chains waiting to be freed. // Everything the map or the mission placed on purpose (core, portals, gate, fortresses, // islands, sealed caves, dwarf caves, rival dungeons) is left alone. use crate::game::*; use crate::map::*; const OPEN_SHARE: f32 = 0.25; impl Game { pub fn add_wilds(&mut self, seed: u32, rivers: bool) { let mut rng = Rng(seed.wrapping_mul(0x2c1b_3c6d) ^ 0x297a_2d39 | 1); for _ in 0..6 { rng.next(); } let (w, h) = (mw() as i32, mh() as i32); let area = (w * h) as f32; if rivers { // one river per ~100x100 tiles, at least one; some of lava let n = ((area / 10000.0).round() as i32).clamp(1, 4); for k in 0..n { // away from the start: vertical rivers in the right part, horizontal ones above or below the core let vertical = rng.f() < 0.5; let pos = if vertical { 0.42 + rng.f() * 0.4 } else if rng.f() < 0.5 { 0.12 + rng.f() * 0.18 } else { 0.7 + rng.f() * 0.18 }; let r = River { lava: rng.f() < 0.4, vertical, pos, width: 3 + (rng.f() < 0.5) as i32, bridges: 2 + (area > 15000.0) as u32 }; let bridges = self.map.carve_river(&r, seed.wrapping_add(k as u32 * 7919)); // free maps: the bridges belong to nobody (anybody may claim them) for b in bridges { self.map.t[b].owner = Owner::None; } } } // what must not be touched: owned ground, buildings, mission pieces and their surroundings let n = self.map.t.len(); let mut keep = vec![false; n]; let mark = |keep: &mut Vec, x: i32, y: i32, r: i32| { for yy in y - r..=y + r { for xx in x - r..=x + r { if inb(xx, yy) { keep[idx(xx, yy)] = true; } } } }; for i in 0..n { let t = &self.map.t[i]; let special = t.owner != Owner::None || matches!(t.kind, Kind::Core | Kind::Portal | Kind::Gate | Kind::Tower | Kind::Shard | Kind::Treasury | Kind::ManaSpring | Kind::RuneChest | Kind::Bridge | Kind::LavaBridge); if special { let (x, y) = xy(i); mark(&mut keep, x, y, 3); } } for c in self.dwarf_caves.clone() { mark(&mut keep, c.at.0, c.at.1, 7); } for e in self.ents.iter().filter(|e| e.alive && (e.chained || e.hero || e.dwarf() || e.rival())) { mark(&mut keep, e.x as i32, e.y as i32, 4); } let (cx, cy) = (self.map.core.0 + 1, self.map.core.1 + 1); mark(&mut keep, cx, cy, 9); let open = |m: &Map| m.t.iter().filter(|t| !t.solid() && t.kind != Kind::Rock).count() as f32; let mut tries = 0; while open(&self.map) < area * OPEN_SHARE && tries < 400 { tries += 1; let (x, y) = (rng.range(4, w - 5), rng.range(4, h - 5)); if keep[idx(x, y)] { continue; } self.carve_cavern(x, y, &keep, &mut rng); } // giant mushrooms in the open: the underground's little wood self.grow_mushrooms(seed); // and now and then a den of wild beasts self.place_beasts(seed); } // a cavern: a few overlapping blobs, ragged edges fn carve_cavern(&mut self, x: i32, y: i32, keep: &[bool], rng: &mut Rng) { let (w, h) = (mw() as i32, mh() as i32); let blobs = 2 + rng.range(0, 3); let mut tiles: Vec = Vec::new(); let (mut bx, mut by) = (x, y); for _ in 0..blobs { let r = rng.range(2, 5); for yy in by - r..=by + r { for xx in bx - r..=bx + r { if xx < 3 || yy < 3 || xx >= w - 3 || yy >= h - 3 { continue; } let d = (((xx - bx).pow(2) + (yy - by).pow(2)) as f32).sqrt(); if d > r as f32 + 0.3 - rng.f() * 0.9 { continue; } let i = idx(xx, yy); let t = &self.map.t[i]; if keep[i] || !t.solid() || t.kind == Kind::Rock || t.kind == Kind::Steel { continue; } tiles.push(i); } } bx += rng.range(-r, r); by += rng.range(-r, r); } if tiles.len() < 6 { return; } for &i in &tiles { let t = &mut self.map.t[i]; t.kind = Kind::Floor; t.owner = Owner::None; t.gold = 0; t.marked = false; t.reinforced = false; } // veins in the walls around for &i in &tiles { let (tx, ty) = xy(i); for &(dx, dy) in &DIR4 { let (nx, ny) = (tx + dx, ty + dy); if !inb(nx, ny) || keep[idx(nx, ny)] { continue; } let t = &mut self.map.t[idx(nx, ny)]; if t.kind == Kind::Earth && rng.f() < 0.07 { let r = rng.f(); let (k, g) = if r < 0.06 { (Kind::Ruby, rng.range(500, 900)) } else if r < 0.3 { (Kind::Silver, rng.range(300, 600)) } else { (Kind::Gold, rng.range(400, 900)) }; t.kind = k; t.gold = g; t.hp = 6.0; } } } // the middle: sometimes a pool, rarely a treasure or a creature in chains let (sx, sy) = tiles.iter().fold((0i64, 0i64), |a, &i| (a.0 + xy(i).0 as i64, a.1 + xy(i).1 as i64)); let (mx, my) = ((sx / tiles.len() as i64) as i32, (sy / tiles.len() as i64) as i32); let inside = |m: &Map, x: i32, y: i32| inb(x, y) && m.t[idx(x, y)].kind == Kind::Floor && m.t[idx(x, y)].owner == Owner::None; let roll = rng.f(); if tiles.len() > 30 && roll < 0.35 { let kind = if rng.f() < 0.4 { Kind::Lava } else { Kind::Water }; for yy in my - 1..=my + 1 { for xx in mx - 1..=mx + 1 { // only where floor surrounds it, so the cavern stays passable around the pool if inside(&self.map, xx, yy) && DIR8.iter().all(|&(dx, dy)| !self.map.get(xx + dx, yy + dy).map_or(true, |t| t.solid())) { self.map.t[idx(xx, yy)].kind = kind; } } } } else if roll < 0.47 && inside(&self.map, mx, my) { for &(dx, dy) in &[(0, 0), (1, 0)] { if inside(&self.map, mx + dx, my + dy) { let t = &mut self.map.t[idx(mx + dx, my + dy)]; t.kind = Kind::Treasury; t.gold = rng.range(200, 450); t.res = (rng.f() < 0.3) as u8 * 2; } } } else if roll < 0.53 && inside(&self.map, mx, my) { let kinds = [EK::Spider, EK::Kobold, EK::Ogre, EK::Bat, EK::Mage]; let kind = kinds[rng.range(0, kinds.len() as i32 - 1) as usize]; let level = rng.range(2, 4) as u32; let id = self.spawn(kind, false, mx as f32 + 0.5, my as f32 + 0.5, level); if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) { e.chained = true; } } } } #[cfg(test)] mod tests { use super::*; fn open_share(g: &Game) -> f32 { g.map.t.iter().filter(|t| !t.solid() && t.kind != Kind::Rock).count() as f32 / g.map.t.len() as f32 } #[test] fn free_maps_have_rivers_and_caverns_and_the_heroes_find_a_way() { for seed in 1..=8u32 { for &(w, h) in &[(64usize, 64usize), (128, 128)] { let g = Game::new(seed, w, h); let share = open_share(&g); assert!(share > 0.2, "seed {} {}x{}: only {:.0}% open", seed, w, h, share * 100.0); assert!(g.map.t.iter().any(|t| t.liquid()), "rivers or pools"); let (gx, gy) = g.map.gate; assert!(g.map.hero_path_to_core(idx(gx + 1, gy + 1)).is_some(), "seed {}: heroes reach the core", seed); let (cx, cy) = g.map.core; assert_eq!(g.map.t[idx(cx + 1, cy + 1)].kind, Kind::Core); } } } }