191 lines
7.0 KiB
Rust
191 lines
7.0 KiB
Rust
// 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<usize> = (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<usize> = (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<usize> = (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));
|
|
}
|
|
}
|