diff --git a/airdef-mod/src/lib.rs b/airdef-mod/src/lib.rs index 0d5a789..a99ad1e 100644 --- a/airdef-mod/src/lib.rs +++ b/airdef-mod/src/lib.rs @@ -674,7 +674,7 @@ impl AirDef { } _ => replay::Mode::Map, }; - self.replay = Some(replay::Player::new(n - 1, mode, &self.sim)); + self.replay = Some(replay::Player::new(rec.number, mode, &self.sim)); self.grid = false; input::claim_wheel(true); Ok(()) @@ -947,7 +947,7 @@ impl Mod for AirDef { _ => replay::Control::Pause, }; if let Some(r) = &mut self.replay - && let Some(rec) = self.sim.replays.get(r.rec) + && let Some(rec) = self.sim.replay(r.rec) { r.control(c, rec); } @@ -964,7 +964,7 @@ impl Mod for AirDef { } Event::Wheel(w) => { if let Some(r) = &mut self.replay - && let Some(rec) = self.sim.replays.get(r.rec) + && let Some(rec) = self.sim.replay(r.rec) { r.wheel(w, rec); } diff --git a/airdef-mod/src/replay.rs b/airdef-mod/src/replay.rs index b82990f..230fbe6 100644 --- a/airdef-mod/src/replay.rs +++ b/airdef-mod/src/replay.rs @@ -31,8 +31,8 @@ pub struct Clip { } pub struct Player { - /// Which of the sim's recordings. - pub rec: usize, + /// Which raid's recording (its number: the list drops the oldest as new ones come). + pub rec: u32, pub mode: Mode, t: f64, speed: f64, @@ -188,7 +188,7 @@ pub fn reel(rec: &Recording) -> Vec { } impl Player { - pub fn new(rec: usize, mode: Mode, sim: &Sim) -> Self { + pub fn new(rec: u32, mode: Mode, sim: &Sim) -> Self { let start = match &mode { Mode::Clips { list, .. } => list.first().map_or(0.0, |c| c.from), _ => 0.0, @@ -291,7 +291,7 @@ impl Player { /// One tick: time runs, the ghosts fly, the director films. Models and sounds go to `out`. pub fn tick(&mut self, sim: &Sim, listener: DVec3, out: &mut Out) { - let Some(rec) = sim.replays.get(self.rec) else { + let Some(rec) = sim.replay(self.rec) else { self.done = true; return; }; @@ -491,7 +491,7 @@ impl Player { /// The replay's screen: the film with its titles, or the map. pub fn hud(&self, sim: &Sim, (gw, gh): (f32, f32)) -> Vec { - let Some(rec) = sim.replays.get(self.rec) else { return Vec::new() }; + let Some(rec) = sim.replay(self.rec) else { return Vec::new() }; if matches!(self.mode, Mode::Map) { return self.map(rec, gw, gh); } diff --git a/airdef-mod/src/sim/damage.rs b/airdef-mod/src/sim/damage.rs index 262d074..cb6dca2 100644 --- a/airdef-mod/src/sim/damage.rs +++ b/airdef-mod/src/sim/damage.rs @@ -179,6 +179,10 @@ impl Sim { sam.burn = 0; sam.state = SamState::Search; sam.reloading = 0; + sam.empty = false; + // Rebuilt where it stands: a march under way is called off. + sam.dest = None; + sam.march = 0; sam.missiles = spec.missile.as_ref().map_or(0, |m| m.magazine); sam.bursts = spec.gun.as_ref().map_or(0, |g| g.bursts); } diff --git a/airdef-mod/src/sim/lights.rs b/airdef-mod/src/sim/lights.rs index 0a14cd1..6c68051 100644 --- a/airdef-mod/src/sim/lights.rs +++ b/airdef-mod/src/sim/lights.rs @@ -113,11 +113,13 @@ impl Sim { self.lit.push(t); } let mut along = 1.0; - for k in 0..10u32 { + // Up to 14 segments (the unit's part ids 2..15), each longer than the last: one + // reaches about 550 blocks, past the beam's reach, about 9 blocks wide out there. + for k in 0..14u32 { if along >= reach { break; } - let size = 0.8 + along * 0.025; + let size = 0.8 + along * 0.12; let len = 3.0 * size; out.model(id + 2 + k, "airdef:beam", head + dir * (along + len / 2.0), rot, size, true); along += len; diff --git a/airdef-mod/src/sim/mod.rs b/airdef-mod/src/sim/mod.rs index 8a3eb11..db0c176 100644 --- a/airdef-mod/src/sim/mod.rs +++ b/airdef-mod/src/sim/mod.rs @@ -1272,6 +1272,10 @@ impl Sim { self.queue.clear(); self.plan = None; self.wave = None; + // Ammunition still to cook off, flares in the sky. + self.later.clear(); + self.flares.clear(); + self.lit.clear(); } /// One game tick. @@ -1845,7 +1849,7 @@ impl Sim { }; for b in barrels { let muzzle = mount + rot * *b * s; - let dir = (lead - muzzle).normalize(); + let dir = (lead - muzzle).normalize_or(DVec3::NEG_Z); let len = muzzle.distance(lead).min(gun.range * 1.2); // Every round is a red tracer flying out at the shell's speed. let scatter = DVec3::new(self.rng.s(), self.rng.s(), self.rng.s()) * 0.012; diff --git a/airdef-mod/src/sim/raid.rs b/airdef-mod/src/sim/raid.rs index e943c99..ca28644 100644 --- a/airdef-mod/src/sim/raid.rs +++ b/airdef-mod/src/sim/raid.rs @@ -195,6 +195,11 @@ impl Sim { /// (each route found in full, a few a tick) and launched when its plan is ready. pub fn order_raid(&mut self, order: &RaidOrder, objects: &[DVec3], out: &mut Out) -> u32 { let Some(¢re) = objects.first() else { return 0 }; + // One raid is planned at a time: another order waits for this one's drones to be sent. + if self.plan.is_some() { + out.messages.push("Противник ещё готовит прошлый налёт: подожди пару секунд".into()); + return 0; + } let total: u32 = order.kinds.iter().map(|k| k.1).sum(); if total == 0 { return 0; @@ -473,7 +478,8 @@ impl Sim { let kills = self.kills - w.kills; let through = self.hits - w.hits; let launched = self.launched - w.launched; - let fired: u32 = self.sams.iter().map(|s| s.fired).sum::() - w.fired; + // A system taken away during the raid takes its count along: never below none. + let fired: u32 = self.sams.iter().map(|s| s.fired).sum::().saturating_sub(w.fired); let lost = w.sams.saturating_sub(self.sams.iter().filter(|s| s.alive()).count()); let objects = if self.target_health.is_empty() { String::new() diff --git a/airdef-mod/src/sim/record.rs b/airdef-mod/src/sim/record.rs index 82161bf..262df78 100644 --- a/airdef-mod/src/sim/record.rs +++ b/airdef-mod/src/sim/record.rs @@ -142,6 +142,13 @@ impl Recording { } } +impl Sim { + /// The recording of raid `number`, while it is kept. + pub fn replay(&self, number: u32) -> Option<&Recording> { + self.replays.iter().find(|r| r.number == number) + } +} + impl Sim { /// Something worth a replay happened. pub(super) fn log(&mut self, pos: DVec3, kind: EventKind) { diff --git a/tools/airdef.py b/tools/airdef.py index 622f66b..b46bd90 100644 --- a/tools/airdef.py +++ b/tools/airdef.py @@ -80,7 +80,9 @@ def fork(): capture_output=True, text=True).stdout.strip() if dirty: sys.exit("horizon/: есть незакоммиченные изменения, а движок надо обновить. Закоммить или убери их и запусти снова.") - if branch == "airdef": + exists = subprocess.run(["git", "-C", FORK, "rev-parse", "--verify", "-q", "refs/heads/airdef"], + capture_output=True).returncode == 0 + if exists: print("horizon/: коммит или патчи изменились, ветка airdef пересобирается (прежняя: airdef-prev)") run(["git", "-C", FORK, "branch", "-f", "airdef-prev", "airdef"]) run(["git", "-C", FORK, "fetch", "--quiet", "origin", commit])