fix(multiball): clear stale release-ball state
The release-ball effect was inferred from the persistent multiball state, which remains active while two balls are in play. After the first release this left the top-left special-hole indicator lit and caused later sprite hits to arm another release. Use record 148's contact state as the release prerequisite and show the special-hole indicator only while that pre-release state is ready. Test Plan: - `cargo test --workspace --all-targets --all-features` -- passed (135 tests) - `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed - `cargo build --profile production` -- passed - `git diff --check` -- passed
This commit is contained in:
+51
-10
@@ -1421,10 +1421,7 @@ impl Game {
|
||||
}
|
||||
if wall.flags & 0x0004 != 0 {
|
||||
events.push(Event::Sound(2012));
|
||||
if matches!(
|
||||
self.multiball_state,
|
||||
MultiballState::Ready | MultiballState::Active
|
||||
) {
|
||||
if self.record_contacts[usize::from(SPECIAL_HOLE_SENSOR.id)] != 0 {
|
||||
self.target_effect = 7;
|
||||
} else {
|
||||
let previous = self.target_effect;
|
||||
@@ -1860,7 +1857,8 @@ impl Game {
|
||||
self.add_score(transfer, events);
|
||||
self.players[self.current_player].secondary_score = 0;
|
||||
}
|
||||
7 if ball_number != 2
|
||||
7 if self.record_contacts[usize::from(SPECIAL_HOLE_SENSOR.id)] != 0
|
||||
&& self.record_contacts[usize::from(SPECIAL_HOLE_SENSOR.id)] != ball_number
|
||||
&& self.secondary_ball.is_none()
|
||||
&& matches!(
|
||||
self.multiball_state,
|
||||
@@ -2026,10 +2024,8 @@ impl Game {
|
||||
}
|
||||
|
||||
pub fn special_hole_active(&self) -> bool {
|
||||
matches!(
|
||||
self.multiball_state,
|
||||
MultiballState::Ready | MultiballState::Active
|
||||
)
|
||||
self.multiball_state == MultiballState::Ready
|
||||
&& self.record_contacts[usize::from(SPECIAL_HOLE_SENSOR.id)] != 0
|
||||
}
|
||||
|
||||
pub fn player_effect(&self) -> u8 {
|
||||
@@ -2550,6 +2546,7 @@ mod tests {
|
||||
game.object_active.fill(false);
|
||||
game.object_active[usize::from(EFFECT_SENSOR.id)] = true;
|
||||
game.multiball_state = MultiballState::Ready;
|
||||
game.record_contacts[usize::from(SPECIAL_HOLE_SENSOR.id)] = 2;
|
||||
game.target_effect = 7;
|
||||
|
||||
game.timer_tick(0.050, 5, &mut Vec::new());
|
||||
@@ -3138,7 +3135,7 @@ mod tests {
|
||||
.secondary_ball
|
||||
.expect("effect seven should spawn a second ball");
|
||||
assert_eq!(game.multiball_state, MultiballState::Active);
|
||||
assert!(game.special_hole_active());
|
||||
assert!(!game.special_hole_active());
|
||||
assert_eq!(spawned.position, vec2(17.0, 23.0));
|
||||
assert_eq!(
|
||||
MilliVec::from_velocity_per_second(spawned.velocity),
|
||||
@@ -3195,6 +3192,50 @@ mod tests {
|
||||
assert_eq!(game.special_hole_gate, SpecialHoleGate::Enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_ball_consumes_the_special_hole_and_does_not_rearm_it() {
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
let mut events = Vec::new();
|
||||
game.multiball_state = MultiballState::Ready;
|
||||
game.record_contacts[148] = 2;
|
||||
game.apply_wall_rule(84, &mut events);
|
||||
assert_eq!(game.target_effect, 7);
|
||||
assert!(game.special_hole_active());
|
||||
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = EFFECT_SENSOR.center;
|
||||
game.check_sensor_objects(
|
||||
MilliVec::from_position(EFFECT_SENSOR.center),
|
||||
MilliVec::default(),
|
||||
&mut events,
|
||||
);
|
||||
assert!(game.secondary_ball.is_some());
|
||||
assert_eq!(game.target_effect, 0);
|
||||
assert!(!game.special_hole_active());
|
||||
|
||||
for _ in 0..4 {
|
||||
game.advance_secondary_ball(&mut events);
|
||||
}
|
||||
assert_eq!(game.record_contacts[148], 0);
|
||||
game.drain(&mut events);
|
||||
assert!(game.secondary_ball.is_none());
|
||||
|
||||
events.clear();
|
||||
game.apply_wall_rule(84, &mut events);
|
||||
assert!((1..=6).contains(&game.target_effect));
|
||||
assert!(game.effect_target_active());
|
||||
|
||||
game.target_effect = 7;
|
||||
game.trigger_flags[usize::from(EFFECT_SENSOR.id)] = false;
|
||||
game.ball.position = EFFECT_SENSOR.center;
|
||||
game.check_sensor_objects(
|
||||
MilliVec::from_position(EFFECT_SENSOR.center),
|
||||
MilliVec::default(),
|
||||
&mut events,
|
||||
);
|
||||
assert!(game.secondary_ball.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_two_response_continues_after_special_respawn_like_live_original() {
|
||||
let mut game = Game::new(1);
|
||||
|
||||
Reference in New Issue
Block a user