Return loaded RM48U to depot by truck
Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
@@ -60,6 +60,7 @@ pub fn tree() -> Tree {
|
||||
Node::lit("raid").runs(),
|
||||
Node::lit("sites").runs(),
|
||||
Node::lit("ground-stock").then(Node::arg("склад", Value::int_from(1)).then(Node::arg("количество", Value::int_from(0)).runs())),
|
||||
Node::lit("ground-cancel").then(Node::arg("комплекс", Value::int_from(1)).runs()),
|
||||
Node::lit("ground-unload").then(Node::arg("комплекс", Value::int_from(1)).runs()),
|
||||
Node::lit("ground-load").then(Node::arg("комплекс", Value::int_from(1)).runs()),
|
||||
Node::lit("strike").then(Node::arg("номер", Value::int_from(1)).runs()),
|
||||
@@ -239,7 +240,7 @@ mod tests {
|
||||
fn completion_offers_the_next_word_only() {
|
||||
let top = complete("", &[]);
|
||||
for w in ["menu", "monitor", "grid", "replay", "scenario", "why", "raid", "repair", "import", "ew", "flare", "move",
|
||||
"target", "give", "deploy", "wave", "clear", "targets", "damage", "truth", "shader", "hold", "lastchance", "roles", "doctrine", "stats", "sites", "strike", "ground-stock", "ground-load", "ground-unload"] {
|
||||
"target", "give", "deploy", "wave", "clear", "targets", "damage", "truth", "shader", "hold", "lastchance", "roles", "doctrine", "stats", "sites", "strike", "ground-stock", "ground-load", "ground-unload", "ground-cancel"] {
|
||||
assert!(top.contains(&w.to_string()), "{w} in {top:?}");
|
||||
}
|
||||
assert_eq!(top.len(), 33);
|
||||
|
||||
@@ -25,6 +25,8 @@ pub struct SamRec {
|
||||
pub comps: Vec<f64>,
|
||||
pub ground_rounds: u32,
|
||||
pub ground_request: bool,
|
||||
pub ground_return: bool,
|
||||
pub return_cargo: bool,
|
||||
pub ground_stock: u32,
|
||||
pub air_48n6_stock: u32,
|
||||
pub mode: sim::battery::SamMode,
|
||||
@@ -143,6 +145,9 @@ 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);
|
||||
let ground_return = l.flag(30, "заявка на вывоз RM48U")?.unwrap_or(false);
|
||||
let return_cargo = l.flag(31, "возвращаемый груз RM48U")?.unwrap_or(false);
|
||||
if (ground_return && (kind != SamKind::S400 || ground_request || ground_rounds == 0)) || (return_cargo && (kind != SamKind::Tzm || ground_stock == 0)) { return Err(l.bad("возврат RM48U", "несовместимое состояние")); }
|
||||
if radar_role != sim::battery::RadarRole::Multifunction && kind != SamKind::Radar {
|
||||
return Err(l.bad("роль РЛС", "роль поддерживается только отдельной РЛС"));
|
||||
}
|
||||
@@ -155,7 +160,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, radar_on });
|
||||
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, ground_return, return_cargo });
|
||||
}
|
||||
"target" => {
|
||||
if out.targets.len() >= MAX_UNITS {
|
||||
|
||||
+61
-3
@@ -898,7 +898,7 @@ impl AirDef {
|
||||
let comps = if fresh { new.comps } else { s.comps };
|
||||
let parts = comps.0.iter().map(|v| format!("{v:.3}")).collect::<Vec<_>>().join(" ");
|
||||
text += &format!(
|
||||
"sam {} {} {} {} {} {} {} {} {} {} {} {} {dest} {parts} {} {} {} {} {} {} {} {}\n",
|
||||
"sam {} {} {} {} {} {} {} {} {} {} {} {} {dest} {parts} {} {} {} {} {} {} {} {} {} {}\n",
|
||||
s.kind.key(),
|
||||
s.pos.x,
|
||||
s.pos.y,
|
||||
@@ -911,7 +911,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.radar_on,
|
||||
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, !fresh && s.ground_return, !fresh && s.return_cargo,
|
||||
);
|
||||
}
|
||||
for (i, t) in self.sim.targets.iter().enumerate() {
|
||||
@@ -1021,6 +1021,8 @@ impl AirDef {
|
||||
}
|
||||
sam.ground_rounds = r.ground_rounds;
|
||||
sam.ground_request = r.ground_request;
|
||||
sam.ground_return = r.ground_return;
|
||||
sam.return_cargo = r.return_cargo;
|
||||
sam.ground_stock = r.ground_stock;
|
||||
sam.air_48n6_stock = r.air_48n6_stock;
|
||||
sam.fit_cells();
|
||||
@@ -1120,7 +1122,7 @@ fn durable_mark(sim: &Sim) -> u64 {
|
||||
let at = s.dest.unwrap_or(s.pos);
|
||||
let place = (at.x * 10.0) as i64 as u64 ^ ((at.z * 10.0) as i64 as u64).rotate_left(21) ^ (if s.dest.is_some() { 0 } else { (s.heading * 100.0) as i64 as u64 }).rotate_left(42);
|
||||
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 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) ^ u64::from(s.ground_return).rotate_left(56) ^ u64::from(s.return_cargo).rotate_left(57);
|
||||
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) ^ u64::from(s.radar_on).rotate_left(23)
|
||||
})
|
||||
@@ -2386,6 +2388,12 @@ impl Mod for AirDef {
|
||||
self.dirty = true;
|
||||
Some(message)
|
||||
}
|
||||
["ground-cancel", n] => {
|
||||
let i = n.parse::<usize>().ok().and_then(|v| v.checked_sub(1)).ok_or("/airdef ground-cancel <номер комплекса>")?;
|
||||
let message = self.sim.cancel_ground_request(i)?;
|
||||
self.dirty = true;
|
||||
Some(message)
|
||||
}
|
||||
["strike", n] => {
|
||||
// A manual order: the simulation validates availability; player position is no launcher.
|
||||
let p = player().ok_or("Нет игрока")?;
|
||||
@@ -2589,6 +2597,56 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_round_drives_and_survives_both_state_round_trips() {
|
||||
struct Flat;
|
||||
impl sim::world::WorldSource for Flat {
|
||||
fn solid(&self, _: i32, y: i32, _: i32) -> Option<bool> { Some(y < 64) }
|
||||
}
|
||||
let mut game = test_mod();
|
||||
for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 0.0)] {
|
||||
let id = game.sim.id();
|
||||
game.sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0));
|
||||
}
|
||||
game.sim.sams[1].ground_rounds = 1;
|
||||
game.sim.sams[1].missiles = 3;
|
||||
game.sim.unload_ground_round(1).unwrap();
|
||||
assert!(game.sim.sams[1].ground_return);
|
||||
assert!(game.sim.load_ground_round(1).is_err());
|
||||
let saved = game.state_text(false, false);
|
||||
let mut restored = test_mod();
|
||||
restored.apply_state(&saved).unwrap();
|
||||
game = restored;
|
||||
assert!(game.sim.sams[1].ground_return);
|
||||
let mut out = Out::default();
|
||||
let mut picked = false;
|
||||
let mut far: f64 = 0.0;
|
||||
for _ in 0..12000 {
|
||||
let now = game.sim.tick;
|
||||
let cols = game.ground.take_wanted().into_iter().map(|(x, z)| (x, z, 64)).collect();
|
||||
game.ground.answer(cols, now);
|
||||
let sections = game.ground.world.take_wanted(now);
|
||||
game.ground.world.serve(&Flat, sections, now);
|
||||
game.sim.step(&mut game.ground, None, &mut out);
|
||||
far = far.max(game.sim.sams[2].pos.x);
|
||||
if !picked && game.sim.sams[2].return_cargo {
|
||||
assert_eq!((game.sim.sams[1].ground_rounds, game.sim.sams[0].ground_stock), (0, 0));
|
||||
let saved = game.state_text(false, false);
|
||||
let mut restored = test_mod();
|
||||
restored.apply_state(&saved).unwrap();
|
||||
game = restored;
|
||||
assert!(game.sim.sams[2].return_cargo);
|
||||
assert_eq!(game.sim.sams[2].ground_stock, 1);
|
||||
picked = true;
|
||||
}
|
||||
if picked && game.sim.sams[0].ground_stock == 1 { break; }
|
||||
out = Out::default();
|
||||
}
|
||||
assert!(picked && far > 140.0);
|
||||
assert_eq!((game.sim.sams[0].ground_stock, game.sim.sams[1].ground_rounds, game.sim.sams[2].ground_stock), (1, 0, 0));
|
||||
assert!(!game.sim.sams[2].return_cargo);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delivery_request_and_cargo_survive_full_state_round_trip() {
|
||||
struct Flat;
|
||||
|
||||
@@ -2341,7 +2341,7 @@ impl AirDef {
|
||||
lines.push((sam.status(), WHITE));
|
||||
lines.push((format!("Исправен {:.0}% · {:.0} {:.0}", sam.health * 100.0, sam.pos.x, sam.pos.z), GREY));
|
||||
if k.armed() {
|
||||
if sam.kind == SamKind::S400 { lines.push((format!("Ударных RM48U: {} (общие ячейки с ПВО)", sam.ground_rounds), WHITE)); }
|
||||
if sam.kind == SamKind::S400 { lines.push((format!("Ударных RM48U: {} (общие ячейки с ПВО){}", sam.ground_rounds, if sam.ground_return { " · ожидает вывоза" } else if sam.ground_request { " · ожидает доставки" } else { "" }), WHITE)); }
|
||||
lines.push((format!("Боезапас: {} ракет, {} очередей{}", sam.missiles, sam.bursts, if sam.reloading > 0 { format!(" · перезарядка {} с", sam.reloading / 20) } else { String::new() }), WHITE));
|
||||
lines.push((format!("Дальность огня: {}", distance(k.range())), WHITE));
|
||||
}
|
||||
|
||||
@@ -350,6 +350,8 @@ impl Sim {
|
||||
self.sams[i].air_48n6_stock = 0;
|
||||
self.sams[i].ground_rounds = 0;
|
||||
self.sams[i].ground_request = false;
|
||||
self.sams[i].ground_return = false;
|
||||
self.sams[i].return_cargo = false;
|
||||
let missiles = missiles + stock * 2 + typed;
|
||||
let charge = spec.missile.as_ref().map_or(0.0, |m| m.kind.charge() * missiles as f64) + bursts as f64 / 60.0
|
||||
+ stock as f64 * 1.5;
|
||||
|
||||
@@ -10,6 +10,7 @@ const DEPOT_REACH: f64 = 60.0;
|
||||
const HANDOVER: f64 = 10.0;
|
||||
/// What a truck carries.
|
||||
const TRUCK_LOAD: u32 = 2;
|
||||
const STOCK_LIMIT: u32 = 1_000_000;
|
||||
|
||||
impl Sim {
|
||||
/// Whether ammunition is counted: a depot was set up (without one, magazines refill as
|
||||
@@ -26,6 +27,7 @@ impl Sim {
|
||||
if !s.alive() || s.moving() || s.preparing.is_some() || s.reloading > 0 || s.part(Part::Launcher) <= 0.0 {
|
||||
return Err("Комплекс должен быть исправен и развёрнут".into());
|
||||
}
|
||||
if s.ground_return { return Err("Сначала отмените заявку на вывоз RM48U".into()); }
|
||||
if s.ground_rounds >= s.cells() { return Err("Нет свободной ячейки для RM48U".into()); }
|
||||
let depot = self.sams.iter().enumerate().filter(|(_, d)| d.kind == SamKind::Depot && d.alive() && d.ground_stock > 0 && d.pos.distance(s.pos) < DEPOT_REACH)
|
||||
.min_by(|(_, a), (_, b)| a.pos.distance(s.pos).total_cmp(&b.pos.distance(s.pos)).then(a.id.cmp(&b.id)))
|
||||
@@ -56,10 +58,18 @@ impl Sim {
|
||||
if s.kind != SamKind::S400 || !s.alive() || s.moving() || s.preparing.is_some() || s.reloading > 0 || s.part(Part::Launcher) <= 0.0 || s.ground_rounds == 0 {
|
||||
return Err("Нужен исправный развёрнутый С-400 с загруженной RM48U".into());
|
||||
}
|
||||
if s.ground_request { return Err("Сначала отмените заявку на доставку RM48U".into()); }
|
||||
if s.ground_return { return Err("Заявка на вывоз RM48U уже ожидает ТЗМ".into()); }
|
||||
let depot = self.sams.iter().enumerate().filter(|(_, d)| d.kind == SamKind::Depot && d.alive() && d.pos.distance(s.pos) < DEPOT_REACH)
|
||||
.min_by(|(_, a), (_, b)| a.pos.distance(s.pos).total_cmp(&b.pos.distance(s.pos)).then(a.id.cmp(&b.id)))
|
||||
.map(|(j, _)| j).ok_or("Нет живого склада рядом (60 блоков)")?;
|
||||
let stock = self.sams[depot].ground_stock.checked_add(1).ok_or("Запас склада переполнен")?;
|
||||
.map(|(j, _)| j);
|
||||
let Some(depot) = depot else {
|
||||
if !self.sams.iter().any(|d| d.kind == SamKind::Depot && d.alive() && d.ground_stock < STOCK_LIMIT) { return Err("Нет доступного склада для возврата RM48U".into()); }
|
||||
if !self.sams.iter().any(|t| t.kind == SamKind::Tzm && t.alive() && t.drive_speed() > 0.0 && t.ground_stock.saturating_add(t.air_48n6_stock) < TRUCK_LOAD) { return Err("Нет исправной ТЗМ со свободным местом для RM48U".into()); }
|
||||
self.sams[i].ground_return = true;
|
||||
return Ok(format!("С-400 №{}: RM48U зарезервирована, ожидается вывоз ТЗМ", i + 1));
|
||||
};
|
||||
let stock = self.sams[depot].ground_stock.checked_add(1).filter(|n| *n <= STOCK_LIMIT).ok_or("Запас склада переполнен")?;
|
||||
self.sams[i].ground_rounds -= 1;
|
||||
self.sams[depot].ground_stock = stock;
|
||||
if self.sams[depot].air_48n6_stock > 0 {
|
||||
@@ -69,6 +79,14 @@ impl Sim {
|
||||
Ok(format!("С-400 №{}: выгружена RM48U; ударных {}, ПВО {}", i + 1, self.sams[i].ground_rounds, self.sams[i].missiles))
|
||||
}
|
||||
|
||||
pub fn cancel_ground_request(&mut self, i: usize) -> Result<String, String> {
|
||||
let s = self.sams.get_mut(i).ok_or("Нет такого комплекса")?;
|
||||
if s.kind != SamKind::S400 || (!s.ground_request && !s.ground_return) { return Err("Нет ожидающей заявки RM48U".into()); }
|
||||
s.ground_request = false;
|
||||
s.ground_return = false;
|
||||
Ok(format!("С-400 №{}: заявка RM48U отменена", i + 1))
|
||||
}
|
||||
|
||||
/// Reloads from depots near the systems, and the trucks' rounds.
|
||||
pub(super) fn step_supply(&mut self, out: &mut Out) {
|
||||
if !self.logistics() {
|
||||
@@ -105,6 +123,42 @@ impl Sim {
|
||||
}
|
||||
let at = truck.pos;
|
||||
let truck_id = truck.id;
|
||||
// A return is picked up physically; its cargo remains marked until a depot accepts it.
|
||||
let can_pick = truck.ground_stock == 0 && truck.air_48n6_stock < TRUCK_LOAD;
|
||||
let return_work = truck.return_cargo || (can_pick && self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_return));
|
||||
if return_work {
|
||||
let taken: Vec<u32> = self.sams.iter().filter(|s| s.kind == SamKind::Tzm && s.alive() && s.id != truck_id).filter_map(|s| s.job).collect();
|
||||
let target = if truck.return_cargo {
|
||||
self.sams.iter().filter(|s| s.kind == SamKind::Depot && s.alive() && s.ground_stock < STOCK_LIMIT && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
} else {
|
||||
self.sams.iter().filter(|s| s.kind == SamKind::S400 && s.alive() && s.ground_return && s.ground_rounds > 0 && !s.moving() && s.preparing.is_none() && s.reloading == 0 && s.part(Part::Launcher) > 0.0 && !taken.contains(&s.id) && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
}.map(|s| (s.id, s.pos));
|
||||
self.sams[t].job = target.map(|x| x.0);
|
||||
if let Some((id, to)) = target {
|
||||
if at.distance(to) > HANDOVER {
|
||||
let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6);
|
||||
self.order_move_near(t, to + side, 3.5);
|
||||
} else if let Some(j) = self.sams.iter().position(|s| s.id == id) {
|
||||
if self.sams[t].return_cargo {
|
||||
self.sams[j].ground_stock += 1;
|
||||
self.sams[t].ground_stock -= 1;
|
||||
self.sams[t].return_cargo = false;
|
||||
} else {
|
||||
self.sams[j].ground_rounds -= 1;
|
||||
self.sams[j].ground_return = false;
|
||||
self.sams[t].ground_stock += 1;
|
||||
self.sams[t].return_cargo = true;
|
||||
if self.sams[t].air_48n6_stock > 0 && self.sams[j].missiles + self.sams[j].ground_rounds < self.sams[j].cells() {
|
||||
self.sams[t].air_48n6_stock -= 1;
|
||||
self.sams[j].missiles += 1;
|
||||
}
|
||||
}
|
||||
self.sams[t].job = None;
|
||||
out.sound("minecraft:block.piston.extend", at, 1.5, 0.8);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Typed strike cargo takes precedence over generic reloads. Jobs are reconstructed
|
||||
// after loading a save; the request and physical cargo survive independently.
|
||||
let typed_work = truck.ground_stock > 0 || truck.air_48n6_stock > 0 ||
|
||||
@@ -123,6 +177,11 @@ impl Sim {
|
||||
self.sams[j].air_48n6_stock = n;
|
||||
self.sams[t].air_48n6_stock = 0;
|
||||
}
|
||||
if self.sams[t].ground_stock > 0 && !self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request) && self.sams[j].ground_stock < STOCK_LIMIT {
|
||||
let take = self.sams[t].ground_stock.min(STOCK_LIMIT - self.sams[j].ground_stock);
|
||||
self.sams[j].ground_stock += take;
|
||||
self.sams[t].ground_stock -= take;
|
||||
}
|
||||
if self.sams[t].ground_stock == 0 && self.sams[j].ground_stock > 0 && self.sams[t].air_48n6_stock == 0 && self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request) {
|
||||
self.sams[j].ground_stock -= 1;
|
||||
self.sams[t].ground_stock += 1;
|
||||
@@ -147,10 +206,10 @@ impl Sim {
|
||||
} else { self.sams[t].job = None; }
|
||||
}
|
||||
let taken: Vec<u32> = self.sams.iter().filter(|s| s.kind == SamKind::Tzm && s.alive() && s.id != truck_id).filter_map(|s| s.job).collect();
|
||||
let next = if self.sams[t].ground_stock > 0 {
|
||||
let next = if self.sams[t].ground_stock > 0 && self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request) {
|
||||
self.sams.iter().filter(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request && !s.moving() && s.preparing.is_none() && s.reloading == 0 && s.part(Part::Launcher) > 0.0 && s.ground_rounds < s.cells() && !taken.contains(&s.id) && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
} else {
|
||||
self.sams.iter().filter(|s| s.kind == SamKind::Depot && s.alive() && (self.sams[t].air_48n6_stock > 0 || s.ground_stock > 0) && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
self.sams.iter().filter(|s| s.kind == SamKind::Depot && s.alive() && (self.sams[t].air_48n6_stock > 0 || (self.sams[t].ground_stock > 0 && s.ground_stock < STOCK_LIMIT) || (self.sams[t].ground_stock == 0 && s.ground_stock > 0)) && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
}.map(|s| (s.id, s.pos));
|
||||
if let Some((id, to)) = next {
|
||||
self.sams[t].job = Some(id);
|
||||
@@ -229,6 +288,36 @@ impl Sim {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn return_waits_for_capacity_and_ready_carrier_and_can_be_cancelled() {
|
||||
let mut sim = Sim::new(3);
|
||||
for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 160.0)] {
|
||||
let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0));
|
||||
}
|
||||
sim.sams[1].ground_rounds = 1;
|
||||
sim.sams[1].missiles = 3;
|
||||
sim.sams[2].air_48n6_stock = 2;
|
||||
assert!(sim.unload_ground_round(1).is_err());
|
||||
sim.sams[2].air_48n6_stock = 1;
|
||||
sim.unload_ground_round(1).unwrap();
|
||||
sim.cancel_ground_request(1).unwrap();
|
||||
assert!(!sim.sams[1].ground_return);
|
||||
sim.unload_ground_round(1).unwrap();
|
||||
sim.tick = 2;
|
||||
sim.sams[1].reloading = 1;
|
||||
sim.step_supply(&mut Out::default());
|
||||
assert_eq!(sim.sams[1].ground_rounds, 1);
|
||||
sim.sams[1].reloading = 0;
|
||||
sim.step_supply(&mut Out::default());
|
||||
assert_eq!((sim.sams[1].ground_rounds, sim.sams[1].missiles), (0, 4));
|
||||
assert_eq!((sim.sams[2].ground_stock, sim.sams[2].air_48n6_stock), (1, 0));
|
||||
assert!(sim.sams[2].return_cargo);
|
||||
sim.direct_hit(sim.sams[2].pos + DVec3::Y, DVec3::ZERO, 1, &mut Out::default());
|
||||
sim.repair();
|
||||
assert_eq!((sim.sams[0].ground_stock, sim.sams[1].ground_rounds, sim.sams[2].ground_stock), (0, 0, 0));
|
||||
assert!(!sim.sams[2].return_cargo);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_delivery_uses_stock_and_shared_cells_and_survives_interruption() {
|
||||
let mut sim = Sim::new(1);
|
||||
|
||||
@@ -1397,6 +1397,8 @@ pub struct Sam {
|
||||
pub ground_rounds: u32,
|
||||
/// Pending player request for one delivered ground round.
|
||||
pub ground_request: bool,
|
||||
pub ground_return: bool,
|
||||
pub return_cargo: bool,
|
||||
/// Separate typed rounds and returned S-400 air rounds held by a depot.
|
||||
pub ground_stock: u32,
|
||||
pub air_48n6_stock: u32,
|
||||
@@ -1487,6 +1489,8 @@ impl Sam {
|
||||
missiles: spec.missile.as_ref().map_or(0, |m| m.magazine),
|
||||
ground_rounds: 0,
|
||||
ground_request: false,
|
||||
ground_return: false,
|
||||
return_cargo: false,
|
||||
ground_stock: 0,
|
||||
air_48n6_stock: 0,
|
||||
bursts: spec.gun.as_ref().map_or(0, |g| g.bursts),
|
||||
@@ -1570,7 +1574,7 @@ impl Sam {
|
||||
}
|
||||
match self.kind {
|
||||
SamKind::Depot => return format!("{text}: {} перезарядок, RM48U {}, снятых ПВО {}", self.cargo, self.ground_stock, self.air_48n6_stock),
|
||||
SamKind::Tzm => return format!("{text}: на борту {}/2, RM48U {}, снятых ПВО {}{}", self.cargo, self.ground_stock, self.air_48n6_stock, if self.job.is_some() { ", в пути" } else { "" }),
|
||||
SamKind::Tzm => return format!("{text}: на борту {}/2, RM48U {}, снятых ПВО {}{}", self.cargo, self.ground_stock, self.air_48n6_stock, if self.return_cargo { ", везёт RM48U на склад" } else if self.job.is_some() { ", в пути" } else { "" }),
|
||||
SamKind::Ew => {
|
||||
let state = if self.ew_on { "ГЛУШИТ" } else if self.ew_lock { "остывает" } else { "готова" };
|
||||
return format!("{text}: {state}, нагрев {:.0}%", self.heat * 100.0);
|
||||
@@ -1600,6 +1604,7 @@ impl Sam {
|
||||
if let Some(m) = &spec.missile {
|
||||
if self.kind == SamKind::S400 {
|
||||
text += &format!(" ПВО {} · RM48U {} / {} ячеек", self.missiles, self.ground_rounds, self.cells());
|
||||
if self.ground_return { text += " · ожидает вывоза RM48U"; }
|
||||
} else {
|
||||
text += &format!(" ракеты {}/{}", self.missiles, m.magazine);
|
||||
}
|
||||
|
||||
@@ -704,7 +704,7 @@ impl Sim {
|
||||
let dist = (est.pos - DVec2::new(s.pos.x, s.pos.z)).length();
|
||||
(matches!(catalog::unit(s.kind).ground_strike(), catalog::GroundStrike::Rm48uGame)
|
||||
&& s.alive() && !s.moving() && s.reloading == 0 && s.cooldown == 0
|
||||
&& s.part(Part::Launcher) > 0.0 && s.ground_rounds > 0 && s.preparing.is_none() && dist <= TUNE.range)
|
||||
&& s.part(Part::Launcher) > 0.0 && s.ground_rounds > u32::from(s.ground_return) && s.preparing.is_none() && dist <= TUNE.range)
|
||||
.then_some((i, dist))
|
||||
}).min_by(|(i,a), (j,b)| self.sams[*j].ground_rounds.cmp(&self.sams[*i].ground_rounds)
|
||||
.then(a.total_cmp(b)).then(self.sams[*i].id.cmp(&self.sams[*j].id)))
|
||||
@@ -728,7 +728,7 @@ impl Sim {
|
||||
let sam = &self.sams[i];
|
||||
let valid = valid_site && sam.preparing.is_some_and(|q| q.ground && q.target == p.site)
|
||||
&& sam.alive() && !sam.moving() && sam.reloading == 0 && sam.cooldown == 0
|
||||
&& sam.part(Part::Launcher) > 0.0 && sam.ground_rounds > 0;
|
||||
&& sam.part(Part::Launcher) > 0.0 && sam.ground_rounds > u32::from(sam.ground_return);
|
||||
if !valid {
|
||||
out.messages.push(format!("Подготовка RM48U по ПУ №{} отменена: изменилось состояние носителя или цели", p.site));
|
||||
if self.sams[i].preparing.is_some_and(|q| q.ground && q.target == p.site) { self.sams[i].cancel_prepare(); }
|
||||
@@ -1334,6 +1334,24 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_request_reserves_one_round_from_strikes() {
|
||||
let mut sim = Sim::new(11);
|
||||
let a = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 0);
|
||||
let mut sam = Sam::new(123, SamKind::S400, DVec3::ZERO, 0.0);
|
||||
sam.missiles = 3; sam.ground_rounds = 1; sam.ground_return = true; sam.cooldown = 0;
|
||||
sim.sams.push(sam);
|
||||
let mut out = Out::default();
|
||||
assert!(sim.order_strike(a, DVec3::ZERO, &mut out).is_err());
|
||||
sim.sams[0].ground_rounds = 2;
|
||||
sim.sams[0].missiles = 2;
|
||||
sim.order_strike(a, DVec3::ZERO, &mut out).unwrap();
|
||||
sim.tick += launch::OPEN_TICKS;
|
||||
sim.prepare_ground_strikes(&mut out);
|
||||
assert_eq!(sim.sams[0].ground_rounds, 1);
|
||||
assert!(sim.sams[0].ground_return);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ground_orders_reserve_the_carrier_and_target_until_ejection() {
|
||||
let mut sim = Sim::new(11);
|
||||
|
||||
Reference in New Issue
Block a user