// Random events in free games and versus matches (rare; can be switched off): an earthquake // brings down tunnels near the dungeon, lava or water overflows its banks, or the imps find a // new gold vein. Shared state and the shared random numbers: every client of a match sees the // same. Campaigns have none (their stories are told by the missions). use crate::game::*; use crate::map::*; use serde::{Deserialize, Serialize}; // seconds between two events: EVENT_GAP plus up to EVENT_SPREAD pub const EVENT_GAP: f32 = 480.0; pub const EVENT_SPREAD: f32 = 240.0; #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct Events { pub t: f32, // seconds until the next one } impl Game { // switches random events on (new free game or match) or off pub fn set_events(&mut self, on: bool) { self.happenings = if on && self.mission.is_none() { Some(Events { t: EVENT_GAP + self.rng.f() * EVENT_SPREAD }) } else { None }; } pub(crate) fn update_events(&mut self, dt: f32) { let Some(ev) = self.happenings.as_mut() else { return }; ev.t -= dt; if ev.t > 0.0 { return; } ev.t = EVENT_GAP + self.rng.f() * EVENT_SPREAD; let kind = self.rng.next() % 4; let happened = match kind { 0 => self.event_quake(), 1 => self.event_overflow(Kind::Lava), 2 => self.event_overflow(Kind::Water), _ => self.event_vein(), }; // nothing of that kind possible here: a gold vein instead (there is always earth) if !happened && kind != 3 { self.event_vein(); } } // own walkable ground within `r` of tile i? fn near_own(&self, i: usize, r: i32) -> bool { let (x, y) = xy(i); (-r..=r).any(|dy| (-r..=r).any(|dx| self.map.get(x + dx, y + dy).map_or(false, |t| t.owner != Owner::None && t.owner != Owner::Hero && t.walkable()))) } fn announce(&mut self, key: &'static str, i: usize) { let (x, y) = xy(i); let dir = format!("@{}", self.direction_key((x, y))); // every master hears of it let me = self.me_side; for s in self.master_sides() { self.switch_to(s); self.msg(key, vec![dir.clone()]); self.sfx_ui(Sfx::CoreHit); } self.switch_to(me); self.add_mark(x as f32 + 0.5, y as f32 + 0.5, crate::game::MARK_BRIDGE, 255, 8.0, true); } // an earthquake: plain tunnels (unowned, not rooms) near a dungeon cave in fn event_quake(&mut self) -> bool { let n = self.map.t.len(); let cands: Vec = (0..n).filter(|&i| { let t = &self.map.t[i]; let (x, y) = xy(i); t.kind == Kind::Floor && t.owner == Owner::None && x > 2 && y > 2 && x < mw() as i32 - 3 && y < mh() as i32 - 3 && !self.map.sky(i) && self.near_own(i, 6) }).collect(); if cands.len() < 4 { return false; } let centre = cands[(self.rng.next() as usize) % cands.len()]; let (cx, cy) = xy(centre); let mut fell = 0; for &i in &cands { let (x, y) = xy(i); if (x - cx).abs() + (y - cy).abs() > 3 || self.rng.f() < 0.35 { continue; } if self.ents.iter().any(|e| e.alive && e.tile() == i) { continue; // nobody is buried alive (the quake is not that mean) } let t = &mut self.map.t[i]; t.kind = Kind::Earth; t.hp = 6.0; t.marked = false; fell += 1; } if fell == 0 { return false; } self.announce("msg.event_quake", centre); true } // a river overflows its banks: unowned open ground next to it fills fn event_overflow(&mut self, river: Kind) -> bool { let n = self.map.t.len(); let bank: Vec = (0..n).filter(|&i| { let t = &self.map.t[i]; let (x, y) = xy(i); // no lava above ground t.kind == Kind::Floor && t.owner == Owner::None && !(river == Kind::Lava && self.map.sky(i)) && DIR4.iter().any(|&(dx, dy)| self.map.get(x + dx, y + dy).map_or(false, |o| o.kind == river)) }).collect(); if bank.is_empty() { return false; } let start = bank[(self.rng.next() as usize) % bank.len()]; let (sx, sy) = xy(start); let mut done = 0; for &i in &bank { let (x, y) = xy(i); if (x - sx).abs() + (y - sy).abs() > 4 || done >= 8 { continue; } self.map.t[i].kind = river; self.ground_lost(i); done += 1; } self.announce(if river == Kind::Lava { "msg.event_lava" } else { "msg.event_flood" }, start); true } // the imps find a gold vein: earth near a dungeon turns to gold fn event_vein(&mut self) -> bool { let n = self.map.t.len(); let cands: Vec = (0..n).filter(|&i| self.map.t[i].kind == Kind::Earth && !self.map.t[i].reinforced && self.near_own(i, 5)).collect(); if cands.is_empty() { return false; } let centre = cands[(self.rng.next() as usize) % cands.len()]; let (cx, cy) = xy(centre); for &i in &cands { let (x, y) = xy(i); if (x - cx).abs() + (y - cy).abs() <= 2 { let t = &mut self.map.t[i]; t.kind = Kind::Gold; t.gold = 900; } } self.announce("msg.event_vein", centre); true } } #[cfg(test)] mod tests { use super::*; #[test] fn free_games_have_rare_events() { let mut g = Game::new(12, 80, 64); g.set_events(true); assert!(g.happenings.is_some()); let golds = |g: &Game| g.map.t.iter().filter(|t| t.kind == Kind::Gold).count(); let before = golds(&g); g.happenings.as_mut().unwrap().t = 0.0; g.rng = Rng(3); // a gold vein (or another event) happens now g.update_events(0.1); assert!(g.happenings.as_ref().unwrap().t >= EVENT_GAP); assert!(g.msgs.iter().any(|m| m.key.starts_with("msg.event_")), "an event was told"); let _ = before; // campaigns have none let mut m = crate::campaign::start_mission(3, None); m.set_events(true); assert!(m.happenings.is_none()); } #[test] fn every_event_can_happen() { let mut g = Game::new(12, 80, 64); let (cx, cy) = g.map.core; // a little lava and water next to unowned ground near the dungeon for (x, k) in [(cx + 6, Kind::Lava), (cx + 9, Kind::Water)] { for y in cy - 2..cy + 3 { g.map.t[idx(x, y)].kind = k; g.map.t[idx(x + 1, y)].kind = Kind::Floor; g.map.t[idx(x + 1, y)].owner = Owner::None; } } assert!(g.event_vein()); assert!(g.event_overflow(Kind::Lava)); assert!(g.event_overflow(Kind::Water)); } }