// Wild beasts: cave bears and giant beetles live in the open caverns of random maps. They defend // their den like the dwarves defend their cave (they never stray far) and count for nobody. A // master with room in a prison tames a beast he beats: instead of dying it gives in and joins. use crate::game::*; use crate::map::*; pub fn beast(k: EK) -> bool { matches!(k, EK::CaveBear | EK::Beetle) } impl Game { // a few dens in the caverns of a random map (deterministic, from the seed) pub fn place_beasts(&mut self, seed: u32) { let mut rng = Rng(seed.wrapping_mul(0x85eb_ca6b) ^ 0x27d4_eb2f | 1); let (cx, cy) = (self.map.core.0 + 1, self.map.core.1 + 1); let area = (mw() * mh()) as f32; // none on small maps (no room), one per ~95x95 tiles on bigger ones let dens = (area / 9000.0) as usize; let open: Vec = (0..self.map.t.len()) .filter(|&i| { let (x, y) = xy(i); let t = &self.map.t[i]; t.kind == Kind::Floor && t.owner == Owner::None && !self.map.sky(i) && (x - cx).abs().max((y - cy).abs()) > 18 && DIR8.iter().all(|&(dx, dy)| self.map.walk(x + dx, y + dy)) && !self.dwarf_caves.iter().any(|c| (c.at.0 - x).abs() + (c.at.1 - y).abs() < 12) }) .collect(); if open.is_empty() { return; } let mut placed: Vec<(i32, i32)> = Vec::new(); for _ in 0..dens * 6 { if placed.len() >= dens { break; } let i = open[(rng.next() as usize) % open.len()]; let (x, y) = xy(i); if placed.iter().any(|&(px, py)| (px - x).abs() + (py - y).abs() < 20) { continue; } placed.push((x, y)); let kind = if rng.f() < 0.5 { EK::CaveBear } else { EK::Beetle }; let n = 1 + (rng.next() % 2) as usize; for k in 0..n { let level = 2 + rng.next() % 3; let id = self.spawn(kind, false, x as f32 + 0.5 + k as f32 * 0.6, y as f32 + 0.5, level); if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) { e.side = crate::dwarves::DWARF_SIDE; e.home = Some((x as f32 + 0.5, y as f32 + 0.5)); e.settled = true; } } } } // a beaten wild beast: with room in a prison it is tamed instead of killed (true) pub(crate) fn try_tame(&mut self, j: usize) -> bool { let e = &self.ents[j]; if !beast(e.kind) || e.side != crate::dwarves::DWARF_SIDE || self.prison_space() == 0 { return false; } let side = self.me_side; let e = &mut self.ents[j]; e.side = side; e.home = None; e.guard = false; e.hp = e.max_hp * 0.4; e.happy = 0.6; e.hunger = 0.5; e.energy = 0.5; e.job = Job::None; e.path.clear(); e.bed = None; e.settled = false; let (x, y, kind) = (e.x, e.y, e.kind); self.fx(7, x, y, 0.0, 0.0, 1.0, 0xff40c0ff); self.sfx(Sfx::Convert, x, y); self.msg("msg.beast_tamed", vec![format!("@{}", st(kind).name)]); true } } #[cfg(test)] mod tests { use super::*; #[test] fn beasts_live_in_caverns_and_can_be_tamed() { let g0 = Game::new(9, 128, 96); let beasts: Vec<&Ent> = g0.ents.iter().filter(|e| e.alive && beast(e.kind)).collect(); assert!(!beasts.is_empty(), "a den on a bigger map"); assert!(beasts.iter().all(|e| e.dwarf() && e.home.is_some()), "neutral, at home"); let mut g = g0; let j = g.ents.iter().position(|e| e.alive && beast(e.kind)).unwrap(); // without a prison the beast dies assert!(!g.try_tame(j)); // a prison: the beaten beast joins let (cx, cy) = g.map.core; g.build_rect(9, cx - 6, cy - 6, cx - 3, cy - 4); g.gold = 99999.0; let t = &mut g.map.t[idx(cx - 5, cy - 5)]; if t.kind != Kind::Prison { t.kind = Kind::Prison; t.owner = Owner::Player; } g.recount(); assert!(g.prison_space() > 0); g.ents[j].hp = 0.0; g.kill(j); assert!(g.ents[j].alive && g.ents[j].side == 0 && !g.ents[j].dwarf(), "tamed"); } }