bb931ab95c
Co-authored-by: Codex <codex@openai.com>
378 lines
23 KiB
Rust
378 lines
23 KiB
Rust
//! Logistics: once a depot stands, magazines are no longer refilled from
|
||
//! nowhere. A system out of missiles waits; one within reach of a depot
|
||
//! reloads from its stock; the reload trucks fill up at a depot and drive
|
||
//! reloads to the systems that wait, nearest first, and back.
|
||
use super::*;
|
||
|
||
/// How near a depot a system reloads straight from it.
|
||
const DEPOT_REACH: f64 = 60.0;
|
||
/// How near a truck must come to hand a reload over, or to load up at a depot.
|
||
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
|
||
/// before). A destroyed depot still counts: losing the last one leaves the systems short,
|
||
/// not supplied from nowhere again.
|
||
pub fn logistics(&self) -> bool {
|
||
self.sams.iter().any(|s| s.kind == SamKind::Depot)
|
||
}
|
||
|
||
/// Prepare one explicitly typed round at a nearby living depot. Any displaced air round is retained there.
|
||
pub fn load_ground_round(&mut self, i: usize) -> Result<String, String> {
|
||
let s = self.sams.get(i).ok_or("Нет такого комплекса")?;
|
||
if s.kind != SamKind::S400 { return Err("Для этого комплекса нет игрового профиля RM48U".into()); }
|
||
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)))
|
||
.map(|(j, _)| j);
|
||
let Some(depot) = depot else {
|
||
if s.ground_request { return Err("Заявка RM48U уже ожидает ТЗМ".into()); }
|
||
if !self.sams.iter().any(|d| d.alive() && ((d.kind == SamKind::Depot && d.ground_stock > 0) || (d.kind == SamKind::Tzm && d.ground_stock > 0))) {
|
||
return Err("Нет живого склада с RM48U рядом (60 блоков)".into());
|
||
}
|
||
if !self.sams.iter().any(|t| t.kind == SamKind::Tzm && t.alive()) { return Err("Для доставки RM48U нужна исправная ТЗМ".into()); }
|
||
self.sams[i].ground_request = true;
|
||
return Ok(format!("С-400 №{}: заявка RM48U ожидает доставки ТЗМ", i + 1));
|
||
};
|
||
let displaced = u32::from(self.sams[i].missiles + self.sams[i].ground_rounds >= self.sams[i].cells());
|
||
let air_48n6_stock = self.sams[depot].air_48n6_stock.checked_add(displaced).ok_or("Запас склада переполнен")?;
|
||
self.sams[i].missiles -= displaced;
|
||
self.sams[i].ground_rounds += 1;
|
||
self.sams[i].ground_request = false;
|
||
if self.sams[i].ground_rounds >= self.sams[i].cells() && self.sams[i].kind.spec().gun.is_none() { self.sams[i].empty = false; }
|
||
self.sams[depot].ground_stock -= 1;
|
||
self.sams[depot].air_48n6_stock = air_48n6_stock;
|
||
Ok(format!("С-400 №{}: загружена RM48U (игровой профиль), ударных {}, ПВО {}; на складе RM48U {}, снятых ракет ПВО {}", i + 1, self.sams[i].ground_rounds, self.sams[i].missiles, self.sams[depot].ground_stock, self.sams[depot].air_48n6_stock))
|
||
}
|
||
|
||
/// Return a loaded ground round to a nearby depot; restore a previously removed air round if available.
|
||
pub fn unload_ground_round(&mut self, i: usize) -> Result<String, String> {
|
||
let s = self.sams.get(i).ok_or("Нет такого комплекса")?;
|
||
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);
|
||
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 {
|
||
self.sams[depot].air_48n6_stock -= 1;
|
||
self.sams[i].missiles += 1;
|
||
}
|
||
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() {
|
||
for s in &mut self.sams {
|
||
s.empty = false;
|
||
}
|
||
return;
|
||
}
|
||
let now = self.tick;
|
||
// Systems by a depot reload from it.
|
||
for i in 0..self.sams.len() {
|
||
if !self.sams[i].empty || !self.sams[i].alive() {
|
||
continue;
|
||
}
|
||
let pos = self.sams[i].pos;
|
||
if let Some(d) = self
|
||
.sams
|
||
.iter()
|
||
.position(|d| d.kind == SamKind::Depot && d.alive() && d.cargo > 0 && d.pos.distance(pos) < DEPOT_REACH)
|
||
{
|
||
self.sams[d].cargo -= 1;
|
||
self.reload_now(i, out);
|
||
}
|
||
}
|
||
// A destroyed truck drops its job, for another to take.
|
||
for s in self.sams.iter_mut().filter(|s| s.kind == SamKind::Tzm && !s.alive()) {
|
||
s.job = None;
|
||
}
|
||
// The trucks: a job each, when idle.
|
||
for t in 0..self.sams.len() {
|
||
let truck = &self.sams[t];
|
||
if truck.kind != SamKind::Tzm || !truck.alive() || truck.moving() || truck.drive_speed() <= 0.0 || now % 10 != t as u64 % 10 {
|
||
continue;
|
||
}
|
||
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 ||
|
||
(self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request)
|
||
&& self.sams.iter().any(|s| s.kind == SamKind::Depot && s.alive() && s.ground_stock > 0));
|
||
if typed_work {
|
||
if let Some(id) = self.sams[t].job {
|
||
let valid = self.sams.iter().find(|s| s.id == id).is_some_and(|s| s.alive() && !self.routes.out_of_reach(truck_id, s.id, now) && (s.kind == SamKind::Depot || (self.sams[t].ground_stock > 0 && s.kind == SamKind::S400 && s.ground_request)));
|
||
if !valid { self.sams[t].job = None; }
|
||
}
|
||
if let Some(id) = self.sams[t].job {
|
||
if let Some(j) = self.sams.iter().position(|s| s.id == id && s.alive()) {
|
||
if self.sams[j].pos.distance(at) <= HANDOVER {
|
||
if self.sams[j].kind == SamKind::Depot {
|
||
if let Some(n) = self.sams[j].air_48n6_stock.checked_add(self.sams[t].air_48n6_stock) {
|
||
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;
|
||
}
|
||
} else if self.sams[j].kind == SamKind::S400 && self.sams[j].ground_request && !self.sams[j].moving() && self.sams[j].preparing.is_none() && self.sams[j].reloading == 0 && self.sams[j].part(Part::Launcher) > 0.0 && self.sams[j].ground_rounds < self.sams[j].cells() && self.sams[t].ground_stock > 0 {
|
||
let displaced = u32::from(self.sams[j].missiles + self.sams[j].ground_rounds >= self.sams[j].cells());
|
||
self.sams[j].missiles -= displaced;
|
||
self.sams[j].ground_rounds += 1;
|
||
if self.sams[j].ground_rounds >= self.sams[j].cells() && self.sams[j].kind.spec().gun.is_none() { self.sams[j].empty = false; }
|
||
self.sams[j].ground_request = false;
|
||
self.sams[t].ground_stock -= 1;
|
||
self.sams[t].air_48n6_stock += displaced;
|
||
out.sound("minecraft:block.piston.extend", at, 1.5, 0.8);
|
||
}
|
||
self.sams[t].job = None;
|
||
} else {
|
||
let to = self.sams[j].pos;
|
||
let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6);
|
||
self.order_move_near(t, to + side, 3.5);
|
||
continue;
|
||
}
|
||
} 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 && 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 || (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);
|
||
let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6);
|
||
if to.distance(at) > HANDOVER { self.order_move_near(t, to + side, 3.5); }
|
||
}
|
||
continue;
|
||
}
|
||
// Where it was going: hand over, or load up.
|
||
if let Some(job) = truck.job
|
||
&& let Some(j) = self.sams.iter().position(|s| s.id == job)
|
||
{
|
||
if self.sams[j].pos.distance(at) <= HANDOVER {
|
||
if self.sams[j].kind == SamKind::Depot {
|
||
let take = TRUCK_LOAD.saturating_sub(self.sams[t].cargo).min(self.sams[j].cargo);
|
||
self.sams[j].cargo -= take;
|
||
self.sams[t].cargo += take;
|
||
out.sound("minecraft:block.piston.extend", at, 1.5, 0.8);
|
||
} else if self.sams[j].empty && self.sams[t].cargo > 0 {
|
||
self.sams[t].cargo -= 1;
|
||
self.reload_now(j, out);
|
||
}
|
||
self.sams[t].job = None;
|
||
} else if self.sams[j].alive() && (self.sams[j].empty || self.sams[j].kind == SamKind::Depot) {
|
||
// Stopped short of it (it drove off meanwhile): on to where it stands now.
|
||
let to = self.sams[j].pos;
|
||
let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6);
|
||
self.order_move_near(t, to + side, 3.5);
|
||
continue;
|
||
} else {
|
||
self.sams[t].job = None;
|
||
}
|
||
}
|
||
// A new job: the nearest waiting system no other truck is driving to, or a depot to load at.
|
||
let taken: Vec<u32> = self.sams.iter().filter(|s| s.kind == SamKind::Tzm && s.alive()).filter_map(|s| s.job).collect();
|
||
let next = if self.sams[t].cargo > 0 {
|
||
self.sams
|
||
.iter()
|
||
.filter(|s| s.empty && s.alive() && !s.moving() && !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))
|
||
} else {
|
||
None
|
||
};
|
||
let next = next.or_else(|| {
|
||
(self.sams[t].cargo < TRUCK_LOAD)
|
||
.then(|| {
|
||
self.sams
|
||
.iter()
|
||
.filter(|d| d.kind == SamKind::Depot && d.alive() && d.cargo > 0 && !self.routes.out_of_reach(truck_id, d.id, now))
|
||
.min_by(|a, b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||
.map(|d| (d.id, d.pos))
|
||
})
|
||
.flatten()
|
||
});
|
||
if let Some((id, to)) = next {
|
||
self.sams[t].job = Some(id);
|
||
// Stop beside it, not on it.
|
||
let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6);
|
||
if to.distance(at) > HANDOVER {
|
||
self.order_move_near(t, to + side, 3.5);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// System `i` gets a reload: its magazine fills after its reload time.
|
||
fn reload_now(&mut self, i: usize, out: &mut Out) {
|
||
let sam = &mut self.sams[i];
|
||
sam.empty = false;
|
||
sam.reloading = sam.reload_time();
|
||
out.sound("minecraft:block.piston.extend", sam.pos, 2.0, 0.6);
|
||
}
|
||
}
|
||
|
||
#[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);
|
||
for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 200.0), (SamKind::Tzm, 0.0)] {
|
||
let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0));
|
||
}
|
||
assert!(sim.load_ground_round(1).is_err());
|
||
sim.sams[0].ground_stock = 1;
|
||
assert!(sim.load_ground_round(0).is_err());
|
||
sim.load_ground_round(1).unwrap();
|
||
sim.tick = 2;
|
||
sim.step_supply(&mut Out::default()); // assign depot
|
||
sim.step_supply(&mut Out::default()); // physically take one
|
||
assert_eq!((sim.sams[0].ground_stock, sim.sams[2].ground_stock), (0, 1));
|
||
assert_eq!(sim.sams[1].ground_rounds, 0);
|
||
// Interrupted truck retains cargo and request; a restored job is not required.
|
||
sim.sams[2].job = None;
|
||
sim.sams[2].state = SamState::Search;
|
||
sim.sams[2].pos = sim.sams[1].pos;
|
||
sim.step_supply(&mut Out::default());
|
||
sim.step_supply(&mut Out::default());
|
||
assert_eq!((sim.sams[1].missiles, sim.sams[1].ground_rounds), (3, 1));
|
||
assert_eq!((sim.sams[2].ground_stock, sim.sams[2].air_48n6_stock), (0, 1));
|
||
assert!(!sim.sams[1].ground_request);
|
||
sim.sams[2].job = None;
|
||
sim.sams[2].state = SamState::Search;
|
||
sim.sams[2].pos = sim.sams[0].pos;
|
||
sim.step_supply(&mut Out::default());
|
||
sim.step_supply(&mut Out::default());
|
||
assert_eq!((sim.sams[0].air_48n6_stock, sim.sams[2].air_48n6_stock), (1, 0));
|
||
assert!(sim.load_ground_round(1).is_err());
|
||
}
|
||
#[test]
|
||
fn pending_delivery_waits_for_compatible_type_and_cell_after_unload() {
|
||
let mut sim = Sim::new(2);
|
||
for kind in [SamKind::Depot, SamKind::S400, SamKind::Tzm] {
|
||
let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::ZERO, 0.0));
|
||
}
|
||
sim.sams[1].ground_request = true;
|
||
sim.sams[2].ground_stock = 1;
|
||
sim.sams[1].kind = SamKind::Patriot;
|
||
sim.tick = 2;
|
||
for _ in 0..4 { sim.step_supply(&mut Out::default()); }
|
||
assert_eq!(sim.sams[2].ground_stock, 1);
|
||
assert_eq!(sim.sams[1].ground_rounds, 0);
|
||
sim.sams[1].kind = SamKind::S400;
|
||
sim.sams[1].missiles = 0;
|
||
sim.sams[1].ground_rounds = 4;
|
||
for _ in 0..4 { sim.step_supply(&mut Out::default()); }
|
||
assert_eq!(sim.sams[2].ground_stock, 1);
|
||
sim.unload_ground_round(1).unwrap();
|
||
for _ in 0..4 { sim.step_supply(&mut Out::default()); }
|
||
assert_eq!((sim.sams[1].ground_rounds, sim.sams[2].ground_stock, sim.sams[0].ground_stock), (4, 0, 1));
|
||
assert!(!sim.sams[1].ground_request);
|
||
}
|
||
|
||
}
|