Separate battery status from player-facing text

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
2026-11-04 15:53:48 +03:00
parent 63aefc7621
commit 375fdf2d80
4 changed files with 77 additions and 9 deletions
+42
View File
@@ -0,0 +1,42 @@
//! Player-facing labels for battery configuration snapshots.
use crate::sim::battery::{BatteryStatus, RadarRole, SamMode};
pub fn radar_role_name(role: RadarRole) -> &'static str {
match role {
RadarRole::Multifunction => "многофункциональная",
RadarRole::Search => "обзорная",
RadarRole::Engagement => "сопровождения",
RadarRole::Height => "мачтовая",
}
}
pub fn status(status: BatteryStatus) -> String {
match status {
BatteryStatus::Radar { index, role, emitting, operational } => format!(
"№{} РЛС: {}, излучение {}, {}", index + 1, radar_role_name(role),
if emitting { "вкл" } else { "выкл" },
if operational { "работает" } else { "нет связи или РЛС не работает" }
),
BatteryStatus::Launcher { index, kind, mode, linked } => format!(
"№{} {}: {}{}", index + 1, kind.name(), mode.key(),
if mode == SamMode::LauncherOnly {
if linked { ", РЛС связана" } else { ", нет связанной РЛС сопровождения" }
} else { "" }
),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::SamKind;
#[test]
fn renders_battery_status_in_existing_format() {
assert_eq!(status(BatteryStatus::Radar { index: 0, role: RadarRole::Search, emitting: false, operational: false }), "№1 РЛС: обзорная, излучение выкл, нет связи или РЛС не работает");
assert_eq!(status(BatteryStatus::Radar { index: 1, role: RadarRole::Engagement, emitting: true, operational: true }), "№2 РЛС: сопровождения, излучение вкл, работает");
assert_eq!(status(BatteryStatus::Launcher { index: 2, kind: SamKind::S400, mode: SamMode::LauncherOnly, linked: true }), "№3 С-400: launcher, РЛС связана");
assert_eq!(status(BatteryStatus::Launcher { index: 3, kind: SamKind::S500, mode: SamMode::LauncherOnly, linked: false }), "№4 С-500: launcher, нет связанной РЛС сопровождения");
assert_eq!(status(BatteryStatus::Launcher { index: 4, kind: SamKind::S400, mode: SamMode::Integrated, linked: false }), "№5 С-400: integrated");
}
}
+2 -1
View File
@@ -12,6 +12,7 @@ use horizon_mod_sdk::{
ui::{self, ButtonWidget, ItemWidget, ListWidget, Screen, Widget},
};
mod battery_view;
mod cinema;
mod capabilities;
mod engines;
@@ -2301,7 +2302,7 @@ impl Mod for AirDef {
["deploy", kind] => Some(self.deploy_configured(kind)?),
["battery"] => {
let status = self.sim.battery_status();
for line in &status { say(line); }
for entry in status { say(&battery_view::status(entry)); }
Some("/airdef battery <номер> integrated|launcher; /airdef radar <номер> on|off|multi|search|engagement|height".into())
}
["battery", n, mode] => {
+1 -1
View File
@@ -2330,7 +2330,7 @@ impl AirDef {
let k = sam.kind;
lines.push((format!("{} №{}", k.name(), n + 1), CYAN));
if k == SamKind::Radar {
lines.push((format!("Роль РЛС: {}", sam.radar_role.name()), WHITE));
lines.push((format!("Роль РЛС: {}", crate::battery_view::radar_role_name(sam.radar_role)), WHITE));
} else if sam.mode == sim::battery::SamMode::LauncherOnly {
lines.push(("Отдельная пусковая: внешняя РЛС сопровождения".into(), WHITE));
}
+32 -7
View File
@@ -33,9 +33,6 @@ impl RadarRole {
pub fn from_key(key: &str) -> Option<Self> {
match key { "multi" => Some(Self::Multifunction), "search" => Some(Self::Search), "engagement" => Some(Self::Engagement), "height" => Some(Self::Height), _ => None }
}
pub fn name(self) -> &'static str {
match self { Self::Multifunction => "многофункциональная", Self::Search => "обзорная", Self::Engagement => "сопровождения", Self::Height => "мачтовая" }
}
pub fn authorizes(self) -> bool { matches!(self, Self::Multifunction | Self::Engagement) }
pub(super) fn reach_factor(self) -> f64 { match self { Self::Multifunction => 1.0, Self::Search => 1.15, Self::Engagement => 0.8, Self::Height => 1.05 } }
}
@@ -68,15 +65,22 @@ impl Sam {
}
}
/// A snapshot of the relevant configuration, independent of its presentation.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BatteryStatus {
Radar { index: usize, role: RadarRole, emitting: bool, operational: bool },
Launcher { index: usize, kind: SamKind, mode: SamMode, linked: bool },
}
impl Sim {
/// Current configurations and whether a launcher has an operational radar link.
pub fn battery_status(&self) -> Vec<String> {
self.sams.iter().enumerate().filter_map(|(i, sam)| {
pub fn battery_status(&self) -> Vec<BatteryStatus> {
self.sams.iter().enumerate().filter_map(|(index, sam)| {
if sam.kind == SamKind::Radar {
Some(format!("№{} РЛС: {}, излучение {}, {}", i + 1, sam.radar_role.name(), if sam.radar_on { "вкл" } else { "выкл" }, if sam.radar_up() && sam.link_range() > 0.0 { "работает" } else { "нет связи или РЛС не работает" }))
Some(BatteryStatus::Radar { index, role: sam.radar_role, emitting: sam.radar_on, operational: sam.radar_up() && sam.link_range() > 0.0 })
} else if matches!(sam.kind, SamKind::S400 | SamKind::S500) {
let linked = self.sams.iter().any(|r| r.kind == SamKind::Radar && r.radar_role.authorizes() && r.radar_up() && r.link_range() > 0.0 && sam.link_range() > 0.0 && r.pos.distance(sam.pos) <= r.link_range().min(sam.link_range()));
Some(format!("№{} {}: {}{}", i + 1, sam.kind.name(), sam.mode.key(), if sam.mode == SamMode::LauncherOnly { if linked { ", РЛС связана" } else { ", нет связанной РЛС сопровождения" } } else { "" }))
Some(BatteryStatus::Launcher { index, kind: sam.kind, mode: sam.mode, linked })
} else { None }
}).collect()
}
@@ -87,6 +91,27 @@ mod tests {
use super::*;
use super::super::{sight, why};
#[test]
fn status_reports_real_radar_links_and_preserves_indices() {
let mut sim = Sim::new(1);
sim.sams.push(Sam::new(1, SamKind::Pantsir, DVec3::ZERO, 0.0));
let mut radar = Sam::new(2, SamKind::Radar, DVec3::ZERO, 0.0);
radar.radar_role = RadarRole::Engagement;
sim.sams.push(radar);
let mut launcher = Sam::new(3, SamKind::S400, DVec3::new(10.0, 0.0, 0.0), 0.0);
launcher.set_mode(SamMode::LauncherOnly);
sim.sams.push(launcher);
assert_eq!(sim.battery_status(), vec![
BatteryStatus::Radar { index: 1, role: RadarRole::Engagement, emitting: true, operational: true },
BatteryStatus::Launcher { index: 2, kind: SamKind::S400, mode: SamMode::LauncherOnly, linked: true },
]);
sim.sams[1].radar_on = false;
assert_eq!(sim.battery_status(), vec![
BatteryStatus::Radar { index: 1, role: RadarRole::Engagement, emitting: false, operational: false },
BatteryStatus::Launcher { index: 2, kind: SamKind::S400, mode: SamMode::LauncherOnly, linked: false },
]);
}
fn track(now: u64) -> Track {
Track { id: 42, kind: DroneKind::Kh101, pos: DVec3::new(0.0, 100.0, -200.0), vel: DVec3::ZERO, tick: now, own_tick: now, ttl: 400, hits: 3, locked: false }
}