// The surface world: part of a map lies above ground - grass in daylight, forests whose trees // imps fell for wood, winding paths, and a cliff with a few cave mouths between the dungeon in // the rock and the open land. (The knights' fortress up there: see fortress.rs.) use crate::game::*; use crate::map::*; // value noise in 0..1, smooth over `cell` tiles fn noise(x: i32, y: i32, cell: i32, seed: u32) -> f32 { let h = |x: i32, y: i32| { let mut v = (x as u32).wrapping_mul(374761393) ^ (y as u32).wrapping_mul(668265263) ^ seed.wrapping_mul(2246822519); v = (v ^ (v >> 13)).wrapping_mul(1274126177); ((v ^ (v >> 16)) & 0xffff) as f32 / 65535.0 }; let (cx, cy) = (x.div_euclid(cell), y.div_euclid(cell)); let (fx, fy) = ((x.rem_euclid(cell)) as f32 / cell as f32, (y.rem_euclid(cell)) as f32 / cell as f32); let s = |t: f32| t * t * (3.0 - 2.0 * t); let (sx, sy) = (s(fx), s(fy)); let a = h(cx, cy) * (1.0 - sx) + h(cx + 1, cy) * sx; let b = h(cx, cy + 1) * (1.0 - sx) + h(cx + 1, cy + 1) * sx; a * (1.0 - sy) + b * sy } impl Game { // Everything east of `from` (share of the width, the edge a little ragged) becomes surface: // open grass with forests and paths. A cliff along the edge keeps the dungeon apart, broken // by `mouths` cave entrances. Mission pieces (core, portals, gate, towers ...) stay. pub fn add_surface(&mut self, seed: u32, from: f32, mouths: u32) { let (w, h) = (mw() as i32, mh() as i32); let edge = |y: i32| (from * w as f32) as i32 + ((noise(0, y, 6, seed ^ 0x51) - 0.5) * 6.0) as i32; let mut mouth_rows: Vec = (0..mouths).map(|k| ((k as f32 + 0.5) / mouths as f32 * (h - 16) as f32) as i32 + 8 + ((noise(k as i32, 3, 2, seed) - 0.5) * 8.0) as i32).collect(); mouth_rows.sort_unstable(); // winding paths across the land (stay open, no trees) let paths: Vec = (0..2).map(|k| 0.25 + 0.5 * k as f32 + (noise(k, 9, 2, seed) - 0.5) * 0.2).collect(); for y in 0..h { let ex = edge(y); for x in 0..w { if x < ex - 1 { continue; } let i = idx(x, y); let t = self.map.t[i]; let keep = matches!(t.kind, Kind::Core | Kind::Portal | Kind::Gate | Kind::Tower | Kind::Shard | Kind::ManaSpring | Kind::RuneChest | Kind::Water | Kind::Lava | Kind::Bridge | Kind::LavaBridge | Kind::Door) || t.owner != Owner::None || x < 2 || y < 2 || x >= w - 2 || y >= h - 2; // the cliff: two tiles of rock along the edge, open where a cave mouth is if x <= ex { let mouth = mouth_rows.iter().any(|&m| (y - m).abs() <= 1); if !keep { let tl = &mut self.map.t[i]; if mouth { tl.kind = Kind::Floor; } else { tl.kind = Kind::Rock; } tl.owner = Owner::None; tl.gold = 0; tl.marked = false; tl.reinforced = false; } continue; } self.map.set_sky(i, true); // no lava under the open sky: rivers up here carry water let tl = &mut self.map.t[i]; match tl.kind { Kind::Lava | Kind::Obsidian => tl.kind = Kind::Water, Kind::LavaBridge => tl.kind = Kind::Bridge, Kind::Drawbridge => tl.res &= !DB_LAVA, _ => {} } if keep { continue; } let on_path = paths.iter().any(|&p| { let py = p * h as f32 + (noise(x, 0, 9, seed ^ 0x77) - 0.5) * 14.0; (y as f32 - py).abs() < 1.2 }); let wood = noise(x, y, 5, seed ^ 0xf0); let rocks = noise(x, y, 4, seed ^ 0x0b); let tl = &mut self.map.t[i]; tl.owner = Owner::None; tl.gold = 0; tl.marked = false; tl.reinforced = false; tl.hp = 0.0; if !on_path && rocks > 0.86 { tl.kind = Kind::Rock; } else if !on_path && wood > 0.52 && (x * 7 + y * 13) % 3 != 0 { tl.kind = Kind::Tree; tl.hp = crate::wood::TREE_HP; } else { tl.kind = Kind::Floor; } } } // a clearing around the knights' fortresses (and anything else they hold) above ground let held: Vec = (0..self.map.t.len()).filter(|&i| self.map.sky(i) && self.map.t[i].owner == Owner::Hero).collect(); let mut cleared = vec![false; self.map.t.len()]; for &i in &held { let (x, y) = xy(i); for yy in y - 3..=y + 3 { for xx in x - 3..=x + 3 { if !inb(xx, yy) || xx < 2 || yy < 2 || xx >= w - 2 || yy >= h - 2 { continue; } let j = idx(xx, yy); if cleared[j] || !self.map.sky(j) { continue; } cleared[j] = true; let t = &mut self.map.t[j]; if t.owner == Owner::None && matches!(t.kind, Kind::Tree | Kind::Rock) { t.kind = Kind::Floor; t.hp = 0.0; } } } } // the cave mouths run through into the dungeon: dig until open ground is reached for &m in &mouth_rows { let ex = edge(m); let mut x = ex - 2; while x > 2 { let i = idx(x, m); if self.map.t[i].walkable() { break; } if self.map.t[i].solid() && !matches!(self.map.t[i].kind, Kind::Steel) { let tl = &mut self.map.t[i]; tl.kind = Kind::Floor; tl.owner = Owner::None; tl.gold = 0; } x -= 1; } } self.recount(); // torches on stands light the clearing at night (and look fine by day) self.place_all_torches(); } // the share of the map above ground (0: a dungeon map) #[allow(dead_code)] // tests and debugging pub fn surface_share(&self) -> f32 { if self.map.sky.is_empty() { return 0.0; } self.map.sky.iter().filter(|&&s| s).count() as f32 / self.map.sky.len() as f32 } } #[cfg(test)] mod tests { use super::*; #[test] fn half_the_map_becomes_a_forest_above_ground() { let mut g = Game::new(21, 128, 96); let core = g.map.core; g.add_surface(21, 0.5, 3); let share = g.surface_share(); assert!(share > 0.35 && share < 0.6, "share {}", share); let trees = g.map.t.iter().filter(|t| t.kind == Kind::Tree).count(); assert!(trees > 200, "{} trees", trees); assert_eq!(g.map.core, core); assert_eq!(g.map.t[idx(core.0 + 1, core.1 + 1)].kind, Kind::Core); // the surface can be reached from the core (cave mouths, trees can be felled) let goal = |i: usize| g.map.sky(i) && g.map.t[i].walkable(); assert!(g.map.search(idx(core.0 + 1, core.1 + 3), g.map.hero_cost_as(false), goal, true).is_some()); } #[test] fn fortresses_above_ground_stand_in_a_clearing() { let mut g = Game::new(22, 128, 96); let spec = crate::fortress::FortSpec { x: 90, y: 40, w: 15, h: 13, doors: vec![3], towers: vec![TowerKind::Arrow], tower_level: 1, guards: vec![(EK::Knight, 1)], patrols: Vec::new(), vaults: 1, vault_value: 500, shard: false, heart: false }; g.build_fortress(&spec); g.add_surface(22, 0.5, 3); let wall = g.map.t.iter().enumerate().filter(|(i, t)| g.map.sky(*i) && t.owner == Owner::Hero && t.kind == Kind::Earth).count(); assert!(wall > 20, "the fortress walls stand"); // no tree right next to the walls for y in 37..56 { for x in 87..108 { let near = (x >= 89 && x <= 105 && y >= 39 && y <= 53) as bool; if near && g.map.t[idx(x, y)].owner == Owner::None { assert_ne!(g.map.t[idx(x, y)].kind, Kind::Tree, "tree at {},{}", x, y); } } } } #[test] fn the_finale_is_fought_above_ground() { let g = crate::campaign::start_mission(29, None); assert!(g.surface_share() > 0.3, "share {}", g.surface_share()); assert_eq!(g.rivals.len(), 2); assert!(g.gate_hp > 0.0 && g.has_gate()); let (gx, gy) = g.map.gate; assert!(g.map.sky(idx(gx + 1, gy + 1)), "the gate of the order stands in the forest"); // the heroes find their way from the gate to the core assert!(g.map.hero_path_to_core(idx(gx + 1, gy + 1)).is_some()); } #[test] fn no_lava_under_the_open_sky() { let mut g = Game::new(31, 128, 96); // a lava river across the whole map for x in 2..126 { for y in 40..44 { g.map.t[idx(x, y)].kind = Kind::Lava; } } g.add_surface(31, 0.5, 3); for i in 0..g.map.t.len() { if g.map.sky(i) { assert!(!matches!(g.map.t[i].kind, Kind::Lava | Kind::LavaBridge), "lava above ground"); } } assert!(g.map.t.iter().any(|t| t.kind == Kind::Lava), "the lava below stays"); } }