From 26b258f00f71131ffa541c12108f701426cccd3d Mon Sep 17 00:00:00 2001 From: chexit Date: Mon, 2 Nov 2026 23:49:40 +0300 Subject: [PATCH] Add persisted radar emission switch Co-authored-by: Codex --- airdef-mod/src/layout.rs | 4 +++- airdef-mod/src/lib.rs | 17 +++++++++++++---- airdef-mod/src/sim/mod.rs | 3 +++ airdef-mod/src/sim/record.rs | 6 +++++- airdef-mod/src/sim/sight.rs | 2 +- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/airdef-mod/src/layout.rs b/airdef-mod/src/layout.rs index 31488b3..3d6e552 100644 --- a/airdef-mod/src/layout.rs +++ b/airdef-mod/src/layout.rs @@ -30,6 +30,7 @@ pub struct SamRec { pub mode: sim::battery::SamMode, pub radar_role: sim::battery::RadarRole, pub spent_cells: u8, + pub radar_on: bool, } #[derive(Default)] @@ -141,6 +142,7 @@ impl Layout { }; let ground_request = l.num(28, "заявка RM48U", 0.0, 1.0)?.unwrap_or(0.0) == 1.0; if ground_request && kind != SamKind::S400 { return Err(l.bad("заявка RM48U", "нужен С-400")); } + let radar_on = l.flag(29, "излучение РЛС")?.unwrap_or(true); if radar_role != sim::battery::RadarRole::Multifunction && kind != SamKind::Radar { return Err(l.bad("роль РЛС", "роль поддерживается только отдельной РЛС")); } @@ -153,7 +155,7 @@ impl Layout { if let Some((m, _, _, _)) = ammo && m + ground_rounds > 4 && kind == SamKind::S400 { return Err(l.bad("боезапас", "больше четырёх ячеек")); } - out.sams.push(SamRec { kind, pos, heading, roles, health, cargo, ammo, dest, comps, ground_rounds, ground_stock, air_48n6_stock, mode, radar_role, spent_cells, ground_request }); + out.sams.push(SamRec { kind, pos, heading, roles, health, cargo, ammo, dest, comps, ground_rounds, ground_stock, air_48n6_stock, mode, radar_role, spent_cells, ground_request, radar_on }); } "target" => { if out.targets.len() >= MAX_UNITS { diff --git a/airdef-mod/src/lib.rs b/airdef-mod/src/lib.rs index 02727c2..66685c5 100644 --- a/airdef-mod/src/lib.rs +++ b/airdef-mod/src/lib.rs @@ -897,7 +897,7 @@ impl AirDef { let comps = if fresh { new.comps } else { s.comps }; let parts = comps.0.iter().map(|v| format!("{v:.3}")).collect::>().join(" "); text += &format!( - "sam {} {} {} {} {} {} {} {} {} {} {} {} {dest} {parts} {} {} {} {} {} {} {}\n", + "sam {} {} {} {} {} {} {} {} {} {} {} {} {dest} {parts} {} {} {} {} {} {} {} {}\n", s.kind.key(), s.pos.x, s.pos.y, @@ -910,7 +910,7 @@ impl AirDef { bursts, u8::from(empty), reloading, - s.ground_rounds, s.ground_stock, s.air_48n6_stock, s.mode.key(), s.radar_role.key(), if fresh { 0 } else { s.spent_cells }, u8::from(!fresh && s.ground_request), + s.ground_rounds, s.ground_stock, s.air_48n6_stock, s.mode.key(), s.radar_role.key(), if fresh { 0 } else { s.spent_cells }, u8::from(!fresh && s.ground_request), s.radar_on, ); } for (i, t) in self.sim.targets.iter().enumerate() { @@ -1025,6 +1025,7 @@ impl AirDef { sam.fit_cells(); sam.set_mode(r.mode); sam.set_radar_role(r.radar_role); + sam.radar_on = r.radar_on; sam.spent_cells = r.spent_cells; sam.sync_tube_cells(); self.sim.sams.push(sam); @@ -1120,7 +1121,7 @@ fn durable_mark(sim: &Sim) -> u64 { let ammo = u64::from(s.missiles) | u64::from(s.bursts) << 16 | u64::from(s.cargo) << 32 | u64::from(s.empty) << 48 | u64::from(s.reloading > 0) << 49; let typed = u64::from(s.ground_rounds) ^ u64::from(s.ground_stock).rotate_left(17) ^ u64::from(s.air_48n6_stock).rotate_left(39) ^ u64::from(s.ground_request).rotate_left(55); let parts = s.comps.0.iter().fold(0u64, |a, v| a.wrapping_mul(131).wrapping_add((v * 100.0) as u64)); - ((s.health * 1000.0) as u64).wrapping_mul(1_000_003) ^ place ^ ammo.rotate_left(7) ^ parts.rotate_left(13) ^ typed.rotate_left(29) ^ (s.mode as u64).rotate_left(3) ^ (s.radar_role as u64).rotate_left(11) ^ u64::from(s.spent_cells).rotate_left(19) + ((s.health * 1000.0) as u64).wrapping_mul(1_000_003) ^ place ^ ammo.rotate_left(7) ^ parts.rotate_left(13) ^ typed.rotate_left(29) ^ (s.mode as u64).rotate_left(3) ^ (s.radar_role as u64).rotate_left(11) ^ u64::from(s.spent_cells).rotate_left(19) ^ u64::from(s.radar_on).rotate_left(23) }) .chain(sim.target_health.iter().map(|h| (h * 10.0) as u64)) .fold(sim.sams.len() as u64, |a, h| a.wrapping_mul(31).wrapping_add(h)) @@ -2278,7 +2279,7 @@ impl Mod for AirDef { ["battery"] => { let status = self.sim.battery_status(); for line in &status { say(line); } - Some("/airdef battery <номер> integrated|launcher; /airdef radar <номер> multi|search|engagement|height".into()) + Some("/airdef battery <номер> integrated|launcher; /airdef radar <номер> on|off|multi|search|engagement|height".into()) } ["battery", n, mode] => { let i = n.parse::().ok().and_then(|n| n.checked_sub(1)).ok_or("Нужен номер комплекса")?; @@ -2288,6 +2289,14 @@ impl Mod for AirDef { self.dirty = true; Some(format!("№{} {}: режим {}", i + 1, sam.kind.name(), mode.key())) } + ["radar", n, state @ ("on" | "off")] => { + let i = n.parse::().ok().and_then(|n| n.checked_sub(1)).ok_or("Нужен номер комплекса")?; + let sam = self.sim.sams.get_mut(i).ok_or("Нет такого комплекса")?; + if sam.kind.spec().sensor <= 0.0 || sam.mode == sim::battery::SamMode::LauncherOnly { return Err("У комплекса нет активной РЛС".into()); } + sam.radar_on = *state == "on"; + self.dirty = true; + Some(format!("№{} {}: излучение РЛС {}", i + 1, sam.kind.name(), state)) + } ["radar", n, role] => { let i = n.parse::().ok().and_then(|n| n.checked_sub(1)).ok_or("Нужен номер РЛС")?; let role = sim::battery::RadarRole::from_key(role).ok_or("Роль: multi, search, engagement или height")?; diff --git a/airdef-mod/src/sim/mod.rs b/airdef-mod/src/sim/mod.rs index cb543f9..0c9f35b 100644 --- a/airdef-mod/src/sim/mod.rs +++ b/airdef-mod/src/sim/mod.rs @@ -1364,6 +1364,8 @@ pub struct Sam { pub kind: SamKind, pub mode: battery::SamMode, pub radar_role: battery::RadarRole, + /// Player-controlled active radar emission; independent of component health and data link. + pub radar_on: bool, pub spent_cells: u8, preparing: Option, cap_cell: u8, @@ -1458,6 +1460,7 @@ impl Sam { kind, mode: battery::SamMode::Integrated, radar_role: battery::RadarRole::Multifunction, + radar_on: true, spent_cells: 0, preparing: None, cap_cell: 0, diff --git a/airdef-mod/src/sim/record.rs b/airdef-mod/src/sim/record.rs index 1face30..282b0da 100644 --- a/airdef-mod/src/sim/record.rs +++ b/airdef-mod/src/sim/record.rs @@ -25,6 +25,7 @@ pub struct SamKey { parts: [f32; 6], mode: battery::SamMode, radar_role: battery::RadarRole, + radar_on: bool, spent_cells: u8, cap_cell: u8, cap_open: f32, @@ -45,6 +46,7 @@ impl SamKey { parts: sam.comps.0.map(|v| v as f32), mode: sam.mode, radar_role: sam.radar_role, + radar_on: sam.radar_on, spent_cells: sam.spent_cells, cap_cell: sam.cap_cell, cap_open: sam.cap_open, @@ -61,7 +63,7 @@ impl SamKey { && self.beam == o.beam && self.health == o.health && self.parts == o.parts - && self.mode == o.mode && self.radar_role == o.radar_role + && self.mode == o.mode && self.radar_role == o.radar_role && self.radar_on == o.radar_on && self.spent_cells == o.spent_cells && self.cap_cell == o.cap_cell && near(self.cap_open, o.cap_open, 0.001) } @@ -108,6 +110,7 @@ impl SamTrack { parts: a.parts, mode: a.mode, radar_role: a.radar_role, + radar_on: a.radar_on, spent_cells: a.spent_cells, cap_cell: a.cap_cell, cap_open: if a.cap_cell == b.cap_cell && a.spent_cells == b.spent_cells { l(a.cap_open, b.cap_open) } else { a.cap_open }, @@ -127,6 +130,7 @@ impl SamTrack { sam.comps = Comps(k.parts.map(|v| v as f64)); sam.mode = k.mode; sam.radar_role = k.radar_role; + sam.radar_on = k.radar_on; sam.spent_cells = k.spent_cells; sam.cap_cell = k.cap_cell; sam.cap_open = k.cap_open; diff --git a/airdef-mod/src/sim/sight.rs b/airdef-mod/src/sim/sight.rs index 70516c5..c5ff5f0 100644 --- a/airdef-mod/src/sim/sight.rs +++ b/airdef-mod/src/sim/sight.rs @@ -327,7 +327,7 @@ impl Sam { } /// Its own radar works: it has one, it is not failed and the unit is deployed. pub fn radar_up(&self) -> bool { - self.mode != battery::SamMode::LauncherOnly && self.alive() && !self.moving() && self.kind.spec().sensor > 0.0 && self.part(Part::Sensor) > 0.0 + self.radar_on && self.mode != battery::SamMode::LauncherOnly && self.alive() && !self.moving() && self.kind.spec().sensor > 0.0 && self.part(Part::Sensor) > 0.0 } pub fn track(&self, id: u32) -> Option<&Track> { self.tracks.iter().find(|t| t.id == id)