@@ -587,12 +587,11 @@ impl Sim {
|
||||
let below = ground.at(DVec3::new(f.land.x, 0.0, f.land.y), now);
|
||||
let end = DVec3::new(f.land.x, below.unwrap_or(f.from.y), f.land.y);
|
||||
let t = ((now - f.start) as f64 / (f.arrive - f.start).max(1) as f64).clamp(0.0, 1.0);
|
||||
let want = f.from.lerp(end, t) + DVec3::Y * (TUNE.height * (std::f64::consts::PI * t).sin().max(0.0).sqrt());
|
||||
let planned = f.from.lerp(end, t) + DVec3::Y * (TUNE.height * (std::f64::consts::PI * t).sin().max(0.0).sqrt());
|
||||
let from = f.pos;
|
||||
if now >= f.arrive && from.distance(end) <= TUNE.speed * 1.5 + 2.0 {
|
||||
self.land_strike(f, from, ground, player, out);
|
||||
continue;
|
||||
}
|
||||
// Time spent waiting for world data must not accumulate into a faster step.
|
||||
let delta = planned - from;
|
||||
let want = from + delta.clamp_length_max(TUNE.speed);
|
||||
// A missile that never reaches its point (the world never tells of the way) ends somewhere: it does
|
||||
// not stay in the air for ever.
|
||||
if now >= f.arrive + 600 {
|
||||
@@ -605,9 +604,11 @@ impl Sim {
|
||||
}
|
||||
let sweep = ground.trace(f.key(), from, want, false, now);
|
||||
let mut pos = want;
|
||||
let mut permitted = true;
|
||||
let contact = match sweep {
|
||||
world::Sweep::Hit { point, .. } => Some(point),
|
||||
world::Sweep::Wait { t } => {
|
||||
permitted = false;
|
||||
pos = from.lerp(want, (t - 1e-6).max(0.0));
|
||||
None
|
||||
}
|
||||
@@ -616,19 +617,32 @@ impl Sim {
|
||||
// the game doesn't have (not one still loading) is passed, high over the land.
|
||||
let low = ground.at(from, now).is_some_and(|g| from.y < g + 12.0);
|
||||
if let Some(t) = ground.unread(&sweep, &mut f.hold, low) {
|
||||
permitted = false;
|
||||
pos = from.lerp(want, t);
|
||||
}
|
||||
None
|
||||
}
|
||||
world::Sweep::Clear => None,
|
||||
world::Sweep::Clear => {
|
||||
ground.unread(&sweep, &mut f.hold, false);
|
||||
None
|
||||
},
|
||||
};
|
||||
if let Some(point) = contact {
|
||||
self.crash_strike(f, point, ground, player, out);
|
||||
// A surface exactly at the intended endpoint is the landing, not an intervening wall.
|
||||
if point.distance(end) < 1e-6 {
|
||||
self.land_strike(f, point, ground, player, out);
|
||||
} else {
|
||||
self.crash_strike(f, point, ground, player, out);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if permitted && pos.distance(end) < 1e-6 {
|
||||
self.land_strike(f, pos, ground, player, out);
|
||||
continue;
|
||||
}
|
||||
// The land's height says it met the ground where the blocks were not read: the same contact.
|
||||
let here = ground.at(pos, now);
|
||||
if ground.ground_hit(here, pos, 0.2, &sweep, now) {
|
||||
if permitted && ground.ground_hit(here, pos, 0.2, &sweep, now) {
|
||||
self.crash_strike(f, pos, ground, player, out);
|
||||
continue;
|
||||
}
|
||||
@@ -641,7 +655,7 @@ impl Sim {
|
||||
out.model(id + 1, "airdef:geran3_flame", pos + rot * DVec3::new(0.0, up * spec.scale, back * spec.scale), rot, spec.scale * size * flicker, true);
|
||||
}
|
||||
if let Some((event, pitch)) = DroneKind::Kalibr.engine_voice() {
|
||||
out.engine(id, event, pos, dir * TUNE.speed, 0.8, pitch, false);
|
||||
out.engine(id, event, pos, pos - from, 0.8, pitch, false);
|
||||
}
|
||||
f.pos = pos;
|
||||
rest.push(f);
|
||||
@@ -1413,6 +1427,52 @@ mod tests {
|
||||
assert_eq!(sim.strikes.fired, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_wall_is_resolved_before_landing() {
|
||||
let mut sim = Sim::new(11);
|
||||
sim.strikes.cover_override = Some(0);
|
||||
let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 2);
|
||||
let (mut ground, mut out) = (Ground::default(), Out::default());
|
||||
sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap();
|
||||
let f = sim.strikes.flights[0];
|
||||
let wall = Wall { at: f.land.y.floor() as i32 - 1, hole: None };
|
||||
for _ in 0..500 {
|
||||
serve(&mut ground, &wall, sim.tick);
|
||||
sim.step(&mut ground, None, &mut out);
|
||||
if sim.strikes.flights.is_empty() { break; }
|
||||
}
|
||||
assert!(sim.strikes.flights.is_empty());
|
||||
assert_eq!((sim.strikes.destroyed, sim.strikes.missed), (0, 1));
|
||||
assert!(sim.sites.get(id).unwrap().alive);
|
||||
assert_eq!(sim.sites.pending(id), 2);
|
||||
assert!(matches!(strike_events(&sim).as_slice(), [record::EventKind::Strike { result: record::StrikeResult::Blocked, .. }]));
|
||||
assert!((sim.blasts[0].at.z - f64::from(wall.at)).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delayed_world_answers_do_not_accelerate_the_strike() {
|
||||
let mut sim = Sim::new(11);
|
||||
sim.strikes.cover_override = Some(0);
|
||||
let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 1000.0), 0);
|
||||
let (mut ground, mut out) = (Ground::default(), Out::default());
|
||||
sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap();
|
||||
for _ in 0..20 { sim.step(&mut ground, None, &mut out); }
|
||||
let f = sim.strikes.flights[0];
|
||||
let open = Wall { at: 10_000, hole: None };
|
||||
warm(&mut ground, &open, f.from, DVec3::new(f.land.x, 110.0, f.land.y));
|
||||
let mut previous = f.pos;
|
||||
for _ in 0..500 {
|
||||
serve(&mut ground, &open, sim.tick);
|
||||
sim.step(&mut ground, None, &mut out);
|
||||
if let Some(f) = sim.strikes.flights.first() {
|
||||
assert!(previous.distance(f.pos) <= TUNE.speed + 1e-8, "jump at tick {}", sim.tick);
|
||||
previous = f.pos;
|
||||
} else { break; }
|
||||
}
|
||||
assert!(sim.strikes.flights.is_empty(), "strike failed to arrive");
|
||||
assert_eq!(sim.strikes.destroyed, 1, "{:?}", out.messages);
|
||||
}
|
||||
|
||||
// #106: an open way works as before: the missile reaches its point, the site dies, one result, one stock spent.
|
||||
#[test]
|
||||
fn a_counter_strike_on_an_open_way_still_reaches_its_site() {
|
||||
|
||||
Reference in New Issue
Block a user