diff --git a/README.md b/README.md index 69f450d..952a668 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,8 @@ CI: `.gitea/workflows/build.yml` на каждый пуш собирает то перенацеливает ещё не взлетевшие Ланцеты и Шахеды на них; если цель-комплекс уничтожен, дроны идут к следующему известному. Дроны под обстрелом прижимаются к земле. -**Ночь.** Ночью пушки попадают втрое хуже, если цель не освещена. Освещают **прожекторы** (луч ведёт ближайшую +**Ночь.** Исправные пушки с собственным свежим радарным сопровождением не теряют точность из-за темноты. +Цели освещают **прожекторы** (луч ведёт ближайшую цель из сети, приоритет — цели пушек) и **осветительные ракеты**: пушечные комплексы пускают их сами (раз в 25 с на цель в темноте), игрок — **осветительным пистолетом** или `/airdef flare`. Ракета висит ~30 с и освещает всё вокруг на ~60–90 м. diff --git a/airdef-mod/src/sim/lights.rs b/airdef-mod/src/sim/lights.rs index afeecbf..41921b1 100644 --- a/airdef-mod/src/sim/lights.rs +++ b/airdef-mod/src/sim/lights.rs @@ -1,5 +1,5 @@ -//! Night fighting: guns aim by sight, and at night they hit far less unless -//! the target is lit. Searchlights follow the network's tracks with their +//! Night fighting: darkness penalizes aiming without fresh radar fire control, +//! unless the target is lit. Searchlights follow the network's tracks with their //! beams; illumination flares (fired by the guns on their own, or by the //! player) hang over the sky for half a minute and light everything round. use super::*; @@ -24,6 +24,11 @@ pub const DARK_HITS: f64 = 0.4; /// Ticks between a gun's own flares. const FLARE_EVERY: u32 = 500; +/// Night only reduces a gun's hit chance when it has to aim by sight. +pub(super) fn hit_factor(night: bool, lit: bool, radar_fire_control: bool) -> f64 { + if night && !lit && !radar_fire_control { DARK_HITS } else { 1.0 } +} + pub struct Flare { id: u32, pub pos: DVec3, diff --git a/airdef-mod/src/sim/mod.rs b/airdef-mod/src/sim/mod.rs index 2959c26..caff02c 100644 --- a/airdef-mod/src/sim/mod.rs +++ b/airdef-mod/src/sim/mod.rs @@ -2728,8 +2728,11 @@ impl Sim { let event = if sam.kind == SamKind::Pantsir { "airdef:gun.pantsir" } else { "airdef:gun.gepard" }; out.sound(event, mount, 1.0, 0.95 + self.vis.f() as f32 * 0.1); } - // At night a gun sees what is lit (a searchlight, a flare) and little else. - let dark = if self.night && !self.lit.contains(&t.id) { lights::DARK_HITS } else { 1.0 }; + // A fresh working own-radar fire-control track does not depend on seeing the target. + // Guns without that radar picture still take the darkness penalty unless illuminated. + let radar_fire_control = sam.radar_up() + && sam.track(gt).is_some_and(|tr| tr.own_within(now, sight::GUN_AGE)); + let dark = lights::hit_factor(self.night, self.lit.contains(&t.id), radar_fire_control); // The aim point is the track's forecast: the farther it is from the truth, the more shells miss. let off = est.distance(t.pos); let p = gun.p * sam.gun_fit() * dark / (t.kind.spec().stealth * (1.0 + (off / 8.0).powi(2))) * (1.0 - dist / (gun.range * 1.3)).max(0.35); diff --git a/airdef-mod/src/sim/sensor_tests.rs b/airdef-mod/src/sim/sensor_tests.rs index aa56ef1..47f8496 100644 --- a/airdef-mod/src/sim/sensor_tests.rs +++ b/airdef-mod/src/sim/sensor_tests.rs @@ -181,6 +181,34 @@ fn a_close_blast_can_leave_a_firing_but_immobile_system() { assert!(found, "no seed left it firing but immobile"); } +#[test] +fn a_fresh_own_radar_track_keeps_gun_hits_equal_day_and_night() { + fn firing(seed: u64, night: bool) -> (u32, u32) { + let mut sim = Sim::new(seed); + let (mut ground, mut out) = flat(); + let gun = radar_at(&mut sim, SamKind::Gepard, 0.0); + sim.night = night; + sim.add_drone( + DroneKind::Shahed, + DVec3::new(0.0, 95.0, 35.0), + DVec3::ZERO, + DVec3::new(5000.0, 95.0, 35.0), + ); + let starting_bursts = sim.sams[gun].bursts; + for _ in 0..240 { + sim.step(&mut ground, None, &mut out); + } + (starting_bursts - sim.sams[gun].bursts, sim.sams[gun].kills) + } + + for seed in 0..32 { + let day = firing(seed, false); + let night = firing(seed, true); + assert!(day.0 > 0, "seed {seed} did not fire"); + assert_eq!(night, day, "seed {seed}: radar gun changed its fire or kills at night"); + } +} + #[test] fn failed_parts_block_what_they_serve_and_repair_restores_them() { let mut sim = Sim::new(2);