fix(game): dispatch sensors for both ball slots
Store capture progression with each recovered ball slot and pass the original one-based ball number through type-3 and type-4 handling. Multiball captures now remove only the captured slot, promote the survivor when slot one exits, and preserve the effect-seven argument guard that prevents ball two from spawning a third ball. Test Plan: - cargo test --all-targets - cargo clippy --all-targets --all-features -- -D warnings - rumdl check CHANGELOG.md RECONSTRUCTION.md README.md - git diff --check
This commit is contained in:
+332
-59
@@ -190,6 +190,7 @@ pub struct Ball {
|
||||
pub velocity: Vec2,
|
||||
pub in_launcher: bool,
|
||||
spin: Real48,
|
||||
capture_age: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -212,6 +213,13 @@ enum CaptureStep {
|
||||
Complete,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum BallAction {
|
||||
Keep,
|
||||
Reset,
|
||||
Remove,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct RuleState {
|
||||
wheel_holes: [bool; 5],
|
||||
@@ -251,6 +259,7 @@ impl Default for Ball {
|
||||
velocity: Vec2::ZERO,
|
||||
in_launcher: true,
|
||||
spin: Real48::ZERO,
|
||||
capture_age: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,7 +299,6 @@ pub struct Game {
|
||||
target_effect: u8,
|
||||
claw_frame_seconds: f32,
|
||||
multiball_state: MultiballState,
|
||||
capture_age: i32,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
@@ -333,7 +341,6 @@ impl Game {
|
||||
target_effect: 0,
|
||||
claw_frame_seconds: CLAW_FRAME_SECONDS,
|
||||
multiball_state: MultiballState::Unavailable,
|
||||
capture_age: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -796,6 +803,7 @@ impl Game {
|
||||
velocity.y += GRAVITY_MILLI_PER_STEP;
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
self.apply_magnetic_fields(old_position, &mut velocity);
|
||||
let movement_velocity = velocity;
|
||||
let mut best_collision = self.find_static_collision(old_position, velocity, ball.spin);
|
||||
|
||||
let mut transferred_primary_velocity = None;
|
||||
@@ -849,7 +857,17 @@ impl Game {
|
||||
events.push(Event::Bumper);
|
||||
events.push(Event::Sound(2006));
|
||||
}
|
||||
self.secondary_ball = Some(ball);
|
||||
if self.check_sensor_objects_for_ball(
|
||||
&mut ball,
|
||||
2,
|
||||
2,
|
||||
old_position,
|
||||
movement_velocity,
|
||||
events,
|
||||
) == BallAction::Keep
|
||||
{
|
||||
self.secondary_ball = Some(ball);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_wall_rule(&mut self, object_id: u8, events: &mut Vec<Event>) {
|
||||
@@ -955,44 +973,94 @@ impl Game {
|
||||
movement_velocity: MilliVec,
|
||||
events: &mut Vec<Event>,
|
||||
) {
|
||||
let ball_count = if self.secondary_ball.is_some() { 2 } else { 1 };
|
||||
let mut ball = self.ball;
|
||||
match self.check_sensor_objects_for_ball(
|
||||
&mut ball,
|
||||
1,
|
||||
ball_count,
|
||||
old_position,
|
||||
movement_velocity,
|
||||
events,
|
||||
) {
|
||||
BallAction::Keep => self.ball = ball,
|
||||
BallAction::Reset => self.reset_ball_to_launcher(),
|
||||
BallAction::Remove => {
|
||||
self.ball = self
|
||||
.secondary_ball
|
||||
.take()
|
||||
.expect("a multiball capture must leave the other slot active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_sensor_objects_for_ball(
|
||||
&mut self,
|
||||
ball: &mut Ball,
|
||||
ball_number: u16,
|
||||
ball_count: u16,
|
||||
old_position: MilliVec,
|
||||
movement_velocity: MilliVec,
|
||||
events: &mut Vec<Event>,
|
||||
) -> BallAction {
|
||||
if !self.claw.active {
|
||||
match self.capture_record_step(
|
||||
ball,
|
||||
ball_number,
|
||||
ball_count,
|
||||
89,
|
||||
CLAW_TRIGGER_CENTER,
|
||||
CLAW_TRIGGER_RADIUS,
|
||||
old_position,
|
||||
events,
|
||||
) {
|
||||
CaptureStep::Holding => return,
|
||||
CaptureStep::Holding => return BallAction::Keep,
|
||||
CaptureStep::Complete => {
|
||||
let terminal_frame = self.next_claw_terminal_frame();
|
||||
self.begin_claw_capture_after_hold(terminal_frame, events);
|
||||
return;
|
||||
if ball_count == 1 {
|
||||
let terminal_frame = self.next_claw_terminal_frame();
|
||||
self.begin_claw_capture_after_hold(ball, terminal_frame, events);
|
||||
return BallAction::Keep;
|
||||
}
|
||||
return BallAction::Remove;
|
||||
}
|
||||
CaptureStep::Outside => {}
|
||||
}
|
||||
}
|
||||
|
||||
let current_position = MilliVec::from_position(self.ball.position);
|
||||
if self.check_lock_holes(old_position, events)
|
||||
|| self.check_wheel_reset(old_position, events)
|
||||
if let Some(action) =
|
||||
self.check_lock_holes(ball, ball_number, ball_count, old_position, events)
|
||||
{
|
||||
return;
|
||||
return action;
|
||||
}
|
||||
self.check_effect_sensor(old_position, current_position, movement_velocity, events);
|
||||
self.check_target_sensors(old_position, current_position, movement_velocity, events);
|
||||
if let Some(action) =
|
||||
self.check_wheel_reset(ball, ball_number, ball_count, old_position, events)
|
||||
{
|
||||
return action;
|
||||
}
|
||||
self.check_effect_sensor(
|
||||
ball,
|
||||
ball_number,
|
||||
old_position,
|
||||
movement_velocity,
|
||||
events,
|
||||
);
|
||||
self.check_target_sensors(ball, old_position, movement_velocity, events);
|
||||
BallAction::Keep
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_possible_truncation, clippy::too_many_arguments)]
|
||||
fn capture_record_step(
|
||||
&mut self,
|
||||
ball: &mut Ball,
|
||||
ball_number: u16,
|
||||
ball_count: u16,
|
||||
record_id: u8,
|
||||
center: Vec2,
|
||||
radius: f32,
|
||||
previous_position: MilliVec,
|
||||
events: &mut Vec<Event>,
|
||||
) -> CaptureStep {
|
||||
let current_position = MilliVec::from_position(self.ball.position);
|
||||
let current_position = MilliVec::from_position(ball.position);
|
||||
let center = MilliVec::from_position(center);
|
||||
let radius = (radius * 1_000.0).round() as i32;
|
||||
let dx = center.x.wrapping_sub(current_position.x);
|
||||
@@ -1003,27 +1071,28 @@ impl Game {
|
||||
let surface_distance = milli_distance(MilliVec { x: dx, y: dy }) - radius;
|
||||
let contact_index = usize::from(record_id);
|
||||
let contact = self.record_contacts[contact_index];
|
||||
let ball_count = if self.secondary_ball.is_some() { 2 } else { 1 };
|
||||
let contact_allowed =
|
||||
contact == 0 || (contact != 99 && ball_count == 1) || contact == 1;
|
||||
contact == 0 || (contact != 99 && ball_count == 1) || contact == ball_number;
|
||||
|
||||
if surface_distance < -11_000 && contact_allowed {
|
||||
if self.capture_age < 300 {
|
||||
let mut velocity = MilliVec::from_velocity_per_second(self.ball.velocity);
|
||||
if ball.capture_age < 300 {
|
||||
let mut velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||
let stationary = center.x.wrapping_sub(previous_position.x).wrapping_abs() < 500
|
||||
&& center.y.wrapping_sub(previous_position.y).wrapping_abs() < 500
|
||||
&& velocity.x.wrapping_abs() < 500
|
||||
&& velocity.y.wrapping_abs() < 500;
|
||||
if stationary {
|
||||
velocity = MilliVec::default();
|
||||
if self.capture_age == 0 {
|
||||
self.record_contacts[contact_index] = 1;
|
||||
events.push(Event::Sound(2015));
|
||||
if ball.capture_age == 0 {
|
||||
self.record_contacts[contact_index] = ball_number;
|
||||
if !self.tilted {
|
||||
events.push(Event::Sound(2015));
|
||||
}
|
||||
}
|
||||
self.capture_age = if record_id == 89 {
|
||||
ball.capture_age = if record_id == 89 {
|
||||
300
|
||||
} else {
|
||||
self.capture_age.wrapping_add(5)
|
||||
ball.capture_age.wrapping_add(5)
|
||||
};
|
||||
} else {
|
||||
let damping = Real48::from_bytes([0x80, 0x66, 0x66, 0x66, 0x66, 0x66]);
|
||||
@@ -1040,29 +1109,41 @@ impl Game {
|
||||
velocity.y.wrapping_sub(150)
|
||||
};
|
||||
}
|
||||
self.ball.velocity = velocity.to_velocity_per_second();
|
||||
ball.velocity = velocity.to_velocity_per_second();
|
||||
return CaptureStep::Holding;
|
||||
}
|
||||
|
||||
self.capture_age = 0;
|
||||
if record_id == 89 && ball_count == 1 {
|
||||
return CaptureStep::Complete;
|
||||
}
|
||||
ball.capture_age = 0;
|
||||
self.record_contacts[contact_index] = if record_id == 148 { 2 } else { 99 };
|
||||
if ball_count > 1 {
|
||||
self.record_contacts[contact_index] = 2;
|
||||
}
|
||||
return CaptureStep::Complete;
|
||||
}
|
||||
|
||||
if surface_distance > -11_000 && contact == 1 {
|
||||
if surface_distance > -11_000 && contact == ball_number {
|
||||
self.record_contacts[contact_index] = 0;
|
||||
self.capture_age = 0;
|
||||
ball.capture_age = 0;
|
||||
}
|
||||
CaptureStep::Outside
|
||||
}
|
||||
|
||||
fn check_lock_holes(
|
||||
&mut self,
|
||||
ball: &mut Ball,
|
||||
ball_number: u16,
|
||||
ball_count: u16,
|
||||
old_position: MilliVec,
|
||||
events: &mut Vec<Event>,
|
||||
) -> bool {
|
||||
) -> Option<BallAction> {
|
||||
for (index, sensor) in LOCK_HOLES.into_iter().enumerate() {
|
||||
match self.capture_record_step(
|
||||
ball,
|
||||
ball_number,
|
||||
ball_count,
|
||||
sensor.id,
|
||||
sensor.center,
|
||||
sensor.radius,
|
||||
@@ -1070,7 +1151,7 @@ impl Game {
|
||||
events,
|
||||
) {
|
||||
CaptureStep::Outside => continue,
|
||||
CaptureStep::Holding => return true,
|
||||
CaptureStep::Holding => return Some(BallAction::Keep),
|
||||
CaptureStep::Complete => {}
|
||||
}
|
||||
let filled_before = self.wheel_holes.iter().filter(|filled| **filled).count();
|
||||
@@ -1087,43 +1168,60 @@ impl Game {
|
||||
player.extra_balls = player.extra_balls.wrapping_add(1);
|
||||
self.panel_frame = Some(0);
|
||||
}
|
||||
self.reset_ball_to_launcher();
|
||||
events.push(Event::Lock);
|
||||
return true;
|
||||
return Some(if ball_count == 1 {
|
||||
BallAction::Reset
|
||||
} else {
|
||||
BallAction::Remove
|
||||
});
|
||||
}
|
||||
false
|
||||
None
|
||||
}
|
||||
|
||||
fn check_wheel_reset(
|
||||
&mut self,
|
||||
ball: &mut Ball,
|
||||
ball_number: u16,
|
||||
ball_count: u16,
|
||||
old_position: MilliVec,
|
||||
events: &mut Vec<Event>,
|
||||
) -> bool {
|
||||
) -> Option<BallAction> {
|
||||
match self.capture_record_step(
|
||||
ball,
|
||||
ball_number,
|
||||
ball_count,
|
||||
WHEEL_RESET_SENSOR.id,
|
||||
WHEEL_RESET_SENSOR.center,
|
||||
WHEEL_RESET_SENSOR.radius,
|
||||
old_position,
|
||||
events,
|
||||
) {
|
||||
CaptureStep::Outside => return false,
|
||||
CaptureStep::Holding => return true,
|
||||
CaptureStep::Outside => return None,
|
||||
CaptureStep::Holding => return Some(BallAction::Keep),
|
||||
CaptureStep::Complete => {}
|
||||
}
|
||||
self.wheel_holes.fill(false);
|
||||
self.multiball_state = MultiballState::Ready;
|
||||
self.reset_ball_to_launcher();
|
||||
events.push(Event::Wheel);
|
||||
true
|
||||
Some(if ball_count == 1 {
|
||||
BallAction::Reset
|
||||
} else {
|
||||
BallAction::Remove
|
||||
})
|
||||
}
|
||||
|
||||
fn check_effect_sensor(
|
||||
&mut self,
|
||||
ball: &mut Ball,
|
||||
ball_number: u16,
|
||||
old_position: MilliVec,
|
||||
current_position: MilliVec,
|
||||
movement_velocity: MilliVec,
|
||||
events: &mut Vec<Event>,
|
||||
) {
|
||||
if self.tilted {
|
||||
return;
|
||||
}
|
||||
let current_position = MilliVec::from_position(ball.position);
|
||||
let effect_index = usize::from(EFFECT_SENSOR.id);
|
||||
if self.object_active[effect_index] {
|
||||
let touched = path_intersects_circle(
|
||||
@@ -1139,7 +1237,7 @@ impl Game {
|
||||
EFFECT_SENSOR.center,
|
||||
EFFECT_SENSOR.radius,
|
||||
);
|
||||
if entered && !self.tilted {
|
||||
if entered {
|
||||
self.add_score(EFFECT_SENSOR.score, events);
|
||||
match self.target_effect {
|
||||
1..=5 => {
|
||||
@@ -1153,12 +1251,13 @@ impl Game {
|
||||
self.add_score(transfer, events);
|
||||
self.players[self.current_player].secondary_score = 0;
|
||||
}
|
||||
7 => {
|
||||
7 if ball_number != 2 && self.multiball_state == MultiballState::Ready => {
|
||||
self.secondary_ball = Some(Ball {
|
||||
position: vec2(17.0, 23.0),
|
||||
velocity: MilliVec { x: 0, y: 3_040 }.to_velocity_per_second(),
|
||||
in_launcher: false,
|
||||
spin: Real48::ZERO,
|
||||
capture_age: 0,
|
||||
});
|
||||
self.multiball_state = MultiballState::Unavailable;
|
||||
}
|
||||
@@ -1168,18 +1267,22 @@ impl Game {
|
||||
self.object_active[effect_index] = false;
|
||||
events.push(Event::Target);
|
||||
events.push(Event::Sound(2004));
|
||||
self.randomize_trigger_velocity();
|
||||
self.randomize_trigger_velocity(ball);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_target_sensors(
|
||||
&mut self,
|
||||
ball: &mut Ball,
|
||||
old_position: MilliVec,
|
||||
current_position: MilliVec,
|
||||
movement_velocity: MilliVec,
|
||||
events: &mut Vec<Event>,
|
||||
) {
|
||||
if self.tilted {
|
||||
return;
|
||||
}
|
||||
let current_position = MilliVec::from_position(ball.position);
|
||||
for sensor in TARGET_SENSORS {
|
||||
let touched = path_intersects_circle(
|
||||
old_position,
|
||||
@@ -1195,7 +1298,7 @@ impl Game {
|
||||
sensor.center,
|
||||
sensor.radius,
|
||||
);
|
||||
if !entered || self.tilted {
|
||||
if !entered {
|
||||
continue;
|
||||
}
|
||||
self.add_score(sensor.score, events);
|
||||
@@ -1207,19 +1310,19 @@ impl Game {
|
||||
let field_id = [153, 6, 154][usize::from(sensor.id - 150)];
|
||||
self.object_active[field_id] = true;
|
||||
}
|
||||
self.randomize_trigger_velocity();
|
||||
self.randomize_trigger_velocity(ball);
|
||||
}
|
||||
}
|
||||
|
||||
fn randomize_trigger_velocity(&mut self) {
|
||||
let mut velocity = MilliVec::from_velocity_per_second(self.ball.velocity);
|
||||
fn randomize_trigger_velocity(&mut self, ball: &mut Ball) {
|
||||
let mut velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||
let offset = Real48::from_bytes([0x81, 0x71, 0x3d, 0x0a, 0xd7, 0x03]);
|
||||
let span = Real48::from_bytes([0x7d, 0x71, 0x3d, 0x0a, 0xd7, 0x23]);
|
||||
let x_factor = offset.subtract(self.random.real48().multiply(span));
|
||||
let y_factor = offset.subtract(self.random.real48().multiply(span));
|
||||
velocity.x = Real48::from_i32(velocity.x).multiply(x_factor).round_i32();
|
||||
velocity.y = Real48::from_i32(velocity.y).multiply(y_factor).round_i32();
|
||||
self.ball.velocity = velocity.to_velocity_per_second();
|
||||
ball.velocity = velocity.to_velocity_per_second();
|
||||
}
|
||||
|
||||
fn reset_ball_to_launcher(&mut self) {
|
||||
@@ -1319,7 +1422,12 @@ impl Game {
|
||||
events.push(Event::Sound(2015));
|
||||
}
|
||||
|
||||
fn begin_claw_capture_after_hold(&mut self, terminal_frame: u8, events: &mut Vec<Event>) {
|
||||
fn begin_claw_capture_after_hold(
|
||||
&mut self,
|
||||
ball: &mut Ball,
|
||||
terminal_frame: u8,
|
||||
events: &mut Vec<Event>,
|
||||
) {
|
||||
debug_assert!(CLAW_TERMINAL_FRAMES.contains(&terminal_frame));
|
||||
if self.claw.active {
|
||||
return;
|
||||
@@ -1329,8 +1437,8 @@ impl Game {
|
||||
self.claw.bank = ClawSpriteBank::Closing;
|
||||
self.claw.ball_suspended = true;
|
||||
self.claw.frame_accumulator = 0.0;
|
||||
self.ball.velocity = Vec2::ZERO;
|
||||
self.ball.spin = Real48::ZERO;
|
||||
ball.velocity = Vec2::ZERO;
|
||||
ball.spin = Real48::ZERO;
|
||||
events.push(Event::ClawCapture);
|
||||
}
|
||||
|
||||
@@ -1419,7 +1527,6 @@ impl Game {
|
||||
self.flipper_inputs = Flippers::default();
|
||||
self.flippers = Flippers::default();
|
||||
self.pending_flipper_edges.fill(0);
|
||||
self.capture_age = 0;
|
||||
self.secondary_ball = None;
|
||||
self.nudge_shake = 0.0;
|
||||
self.launcher_charge = 0.0;
|
||||
@@ -1554,6 +1661,29 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn capture_primary_record_step(
|
||||
game: &mut Game,
|
||||
record_id: u8,
|
||||
center: Vec2,
|
||||
radius: f32,
|
||||
previous_position: MilliVec,
|
||||
events: &mut Vec<Event>,
|
||||
) -> CaptureStep {
|
||||
let mut ball = game.ball;
|
||||
let result = game.capture_record_step(
|
||||
&mut ball,
|
||||
1,
|
||||
if game.secondary_ball.is_some() { 2 } else { 1 },
|
||||
record_id,
|
||||
center,
|
||||
radius,
|
||||
previous_position,
|
||||
events,
|
||||
);
|
||||
game.ball = ball;
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_count_is_bounded() {
|
||||
assert_eq!(Game::new(0).players.len(), 1);
|
||||
@@ -1863,6 +1993,140 @@ mod tests {
|
||||
assert_eq!(game.ball.position, moving_ball.position);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_ball_runs_the_same_type_four_rule_dispatch() {
|
||||
let sensor = TARGET_SENSORS
|
||||
.into_iter()
|
||||
.find(|sensor| sensor.id == 150)
|
||||
.expect("record 150 must be present");
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
let primary_velocity = game.ball.velocity;
|
||||
let mut second = Ball {
|
||||
position: sensor.center,
|
||||
velocity: vec2(12.0, -34.0),
|
||||
in_launcher: false,
|
||||
spin: Real48::ZERO,
|
||||
capture_age: 0,
|
||||
};
|
||||
let old_position = MilliVec::from_position(
|
||||
sensor.center - vec2(sensor.radius + 1.0, 0.0),
|
||||
);
|
||||
let current_position = MilliVec::from_position(sensor.center);
|
||||
let movement = MilliVec {
|
||||
x: current_position.x.wrapping_sub(old_position.x),
|
||||
y: current_position.y.wrapping_sub(old_position.y),
|
||||
};
|
||||
let mut events = Vec::new();
|
||||
|
||||
assert_eq!(
|
||||
game.check_sensor_objects_for_ball(
|
||||
&mut second,
|
||||
2,
|
||||
2,
|
||||
old_position,
|
||||
movement,
|
||||
&mut events,
|
||||
),
|
||||
BallAction::Keep
|
||||
);
|
||||
|
||||
assert!(game.top_targets[0]);
|
||||
assert!(game.magnetic_field_active(153));
|
||||
assert_eq!(game.player().score, sensor.score);
|
||||
assert_eq!(game.ball.velocity, primary_velocity);
|
||||
assert_ne!(second.velocity, vec2(12.0, -34.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ball_two_consumes_effect_seven_without_spawning_a_third_ball() {
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
game.multiball_state = MultiballState::Ready;
|
||||
game.target_effect = 7;
|
||||
game.object_active[usize::from(EFFECT_SENSOR.id)] = true;
|
||||
let mut second = Ball {
|
||||
position: EFFECT_SENSOR.center,
|
||||
velocity: Vec2::ZERO,
|
||||
in_launcher: false,
|
||||
spin: Real48::ZERO,
|
||||
capture_age: 0,
|
||||
};
|
||||
let mut events = Vec::new();
|
||||
|
||||
assert_eq!(
|
||||
game.check_sensor_objects_for_ball(
|
||||
&mut second,
|
||||
2,
|
||||
2,
|
||||
MilliVec::from_position(EFFECT_SENSOR.center),
|
||||
MilliVec::default(),
|
||||
&mut events,
|
||||
),
|
||||
BallAction::Keep
|
||||
);
|
||||
|
||||
assert!(game.secondary_ball.is_none());
|
||||
assert_eq!(game.multiball_state, MultiballState::Ready);
|
||||
assert_eq!(game.target_effect, 0);
|
||||
assert!(!game.effect_target_active());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiball_capture_age_is_per_slot_and_removes_only_ball_two() {
|
||||
let sensor = LOCK_HOLES[0];
|
||||
let mut game = Game::new(1);
|
||||
game.ball.capture_age = 17;
|
||||
game.record_contacts[usize::from(sensor.id)] = 2;
|
||||
game.secondary_ball = Some(Ball {
|
||||
position: sensor.center,
|
||||
velocity: Vec2::ZERO,
|
||||
in_launcher: false,
|
||||
spin: Real48::ZERO,
|
||||
capture_age: 300,
|
||||
});
|
||||
let mut events = Vec::new();
|
||||
|
||||
game.advance_secondary_ball(&mut events);
|
||||
|
||||
assert!(game.secondary_ball.is_none());
|
||||
assert_eq!(game.ball.capture_age, 17);
|
||||
assert_eq!(game.record_contacts[usize::from(sensor.id)], 2);
|
||||
assert!(game.wheel_holes[0]);
|
||||
assert!(events.contains(&Event::Lock));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capturing_ball_one_during_multiball_promotes_ball_two() {
|
||||
let sensor = LOCK_HOLES[0];
|
||||
let mut game = Game::new(1);
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = sensor.center;
|
||||
game.ball.velocity = Vec2::ZERO;
|
||||
game.ball.capture_age = 300;
|
||||
game.record_contacts[usize::from(sensor.id)] = 1;
|
||||
let survivor = Ball {
|
||||
position: vec2(210.0, 240.0),
|
||||
velocity: vec2(-12.0, 34.0),
|
||||
in_launcher: false,
|
||||
spin: Real48::ZERO,
|
||||
capture_age: 23,
|
||||
};
|
||||
game.secondary_ball = Some(survivor);
|
||||
let mut events = Vec::new();
|
||||
|
||||
game.check_sensor_objects(
|
||||
MilliVec::from_position(sensor.center),
|
||||
MilliVec::default(),
|
||||
&mut events,
|
||||
);
|
||||
|
||||
assert!(game.secondary_ball.is_none());
|
||||
assert_eq!(game.ball.position, survivor.position);
|
||||
assert_eq!(game.ball.velocity, survivor.velocity);
|
||||
assert_eq!(game.ball.capture_age, survivor.capture_age);
|
||||
assert_eq!(game.record_contacts[usize::from(sensor.id)], 2);
|
||||
assert!(game.wheel_holes[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn center_drain_advances_to_a_fresh_ball() {
|
||||
let mut game = Game::new(1);
|
||||
@@ -2433,7 +2697,14 @@ mod tests {
|
||||
let mut events = Vec::new();
|
||||
|
||||
assert_eq!(
|
||||
game.capture_record_step(sensor.id, sensor.center, sensor.radius, previous, &mut events),
|
||||
capture_primary_record_step(
|
||||
&mut game,
|
||||
sensor.id,
|
||||
sensor.center,
|
||||
sensor.radius,
|
||||
previous,
|
||||
&mut events,
|
||||
),
|
||||
CaptureStep::Holding
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -2441,12 +2712,13 @@ mod tests {
|
||||
MilliVec { x: 750, y: -150 }
|
||||
);
|
||||
assert_eq!(game.record_contacts[usize::from(sensor.id)], 0);
|
||||
assert_eq!(game.capture_age, 0);
|
||||
assert_eq!(game.ball.capture_age, 0);
|
||||
|
||||
game.ball.position = sensor.center;
|
||||
game.ball.velocity = Vec2::ZERO;
|
||||
assert_eq!(
|
||||
game.capture_record_step(
|
||||
capture_primary_record_step(
|
||||
&mut game,
|
||||
sensor.id,
|
||||
sensor.center,
|
||||
sensor.radius,
|
||||
@@ -2456,12 +2728,13 @@ mod tests {
|
||||
CaptureStep::Holding
|
||||
);
|
||||
assert_eq!(game.record_contacts[usize::from(sensor.id)], 1);
|
||||
assert_eq!(game.capture_age, 5);
|
||||
assert_eq!(game.ball.capture_age, 5);
|
||||
assert_eq!(events, [Event::Sound(2015)]);
|
||||
|
||||
game.capture_age = 300;
|
||||
game.ball.capture_age = 300;
|
||||
assert_eq!(
|
||||
game.capture_record_step(
|
||||
capture_primary_record_step(
|
||||
&mut game,
|
||||
sensor.id,
|
||||
sensor.center,
|
||||
sensor.radius,
|
||||
@@ -2471,7 +2744,7 @@ mod tests {
|
||||
CaptureStep::Complete
|
||||
);
|
||||
assert_eq!(game.record_contacts[usize::from(sensor.id)], 99);
|
||||
assert_eq!(game.capture_age, 0);
|
||||
assert_eq!(game.ball.capture_age, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user