diff --git a/tdkpin-rs/src/game.rs b/tdkpin-rs/src/game.rs index fba7bdd..3ad49fa 100644 --- a/tdkpin-rs/src/game.rs +++ b/tdkpin-rs/src/game.rs @@ -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);