fix(input): restore original nudge and tilt behavior
Replace the float nudge direction, cooldown, and decaying meter with the scan-code-specific Win16 behavior. Left Shift and keypad 3 now use the original random directional impulse, while Space uses the reconstructed random horizontal and vertical formulas with scalar 15 and wrapping millipixel velocities. Track the original 16-bit tilt counter: each nudge adds 25, compares against 30 + Random(10), and the selected detail timer decrements a nonzero counter once per callback. Allow the same input while a ball waits in the launcher, retain bonus state when tilt latches, clear flippers immediately, and keep Right Shift only as a documented modern alias for keypad 3. Test Plan: - `cargo test --all-targets` -- passed, 57 tests - `cargo clippy --all-targets -- -D warnings` -- passed - `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/README.md tdkpin-rs/RECONSTRUCTION.md` -- passed - `git diff --cached --check` -- passed
This commit is contained in:
+5
-13
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
assets::Assets,
|
||||
game::{Controls, Event, Game},
|
||||
game::{Controls, Event, Game, Nudge},
|
||||
persistence::{HighScore, Language, Persistence, SavedData, insert_high_score},
|
||||
table::BUMPERS,
|
||||
};
|
||||
@@ -174,21 +174,13 @@ impl App {
|
||||
|| is_key_down(KeyCode::D)
|
||||
|| is_key_down(KeyCode::Right);
|
||||
let nudge = if is_key_pressed(KeyCode::LeftShift) {
|
||||
-1.0
|
||||
Nudge::Left
|
||||
} else if is_key_pressed(KeyCode::RightShift) || is_key_pressed(KeyCode::Kp3) {
|
||||
1.0
|
||||
Nudge::Right
|
||||
} else if is_key_pressed(KeyCode::Space) {
|
||||
if self
|
||||
.game
|
||||
.as_ref()
|
||||
.is_some_and(|game| game.ball.position.x < WIDTH / 4.0)
|
||||
{
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
}
|
||||
Nudge::Center
|
||||
} else {
|
||||
0.0
|
||||
Nudge::None
|
||||
};
|
||||
let controls = Controls {
|
||||
left_flipper,
|
||||
|
||||
+129
-50
@@ -19,7 +19,6 @@ const LEFT_FLIPPER_RAISED_TIP: Vec2 = Vec2::new(133.0, 377.0);
|
||||
const RIGHT_FLIPPER_PIVOT: Vec2 = Vec2::new(210.0, 397.0);
|
||||
const RIGHT_FLIPPER_REST_TIP: Vec2 = Vec2::new(179.0, 419.0);
|
||||
const RIGHT_FLIPPER_RAISED_TIP: Vec2 = Vec2::new(181.0, 376.0);
|
||||
const TILT_THRESHOLD: f32 = 1.15;
|
||||
const LAUNCHER_POSITION: Vec2 = Vec2::new(325.0, 413.0);
|
||||
const LAUNCHER_FRAME_THRESHOLDS: [f32; 10] =
|
||||
[0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95];
|
||||
@@ -41,7 +40,16 @@ pub struct Controls {
|
||||
pub left_flipper: bool,
|
||||
pub right_flipper: bool,
|
||||
pub launch_down: bool,
|
||||
pub nudge: f32,
|
||||
pub nudge: Nudge,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Nudge {
|
||||
#[default]
|
||||
None,
|
||||
Left,
|
||||
Right,
|
||||
Center,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -229,8 +237,8 @@ pub struct Game {
|
||||
pub last_collision_id: Option<u8>,
|
||||
accumulator: f32,
|
||||
bumper_cooldown: f32,
|
||||
nudge_cooldown: f32,
|
||||
nudge_meter: f32,
|
||||
tilt_counter: u16,
|
||||
tilt_counter_accumulator: f32,
|
||||
launcher_was_down: bool,
|
||||
launcher_hold_seconds: f32,
|
||||
launcher_next_repeat: f32,
|
||||
@@ -270,8 +278,8 @@ impl Game {
|
||||
last_collision_id: None,
|
||||
accumulator: 0.0,
|
||||
bumper_cooldown: 0.0,
|
||||
nudge_cooldown: 0.0,
|
||||
nudge_meter: 0.0,
|
||||
tilt_counter: 0,
|
||||
tilt_counter_accumulator: 0.0,
|
||||
launcher_was_down: false,
|
||||
launcher_hold_seconds: 0.0,
|
||||
launcher_next_repeat: LAUNCHER_REPEAT_DELAY_SECONDS,
|
||||
@@ -339,6 +347,7 @@ impl Game {
|
||||
}
|
||||
self.last_collision_id = None;
|
||||
self.claw_frame_seconds = DETAIL_TIMER_SECONDS[usize::from(detail.clamp(1, 5) - 1)];
|
||||
self.advance_tilt_counter(frame_time.min(0.05));
|
||||
let mut events = Vec::new();
|
||||
let old_flippers = self.flippers;
|
||||
self.flippers.left_raised = controls.left_flipper && !self.tilted;
|
||||
@@ -376,24 +385,8 @@ impl Game {
|
||||
events.push(Event::Sound(2002));
|
||||
}
|
||||
}
|
||||
if !self.tilted
|
||||
&& !self.ball.in_launcher
|
||||
&& controls.nudge.abs() > 0.1
|
||||
&& self.nudge_cooldown <= 0.0
|
||||
{
|
||||
self.ball.velocity.x += controls.nudge * 55.0;
|
||||
self.nudge_meter += controls.nudge.abs() * 0.34;
|
||||
self.nudge_cooldown = 0.22;
|
||||
self.nudge_shake = 0.18;
|
||||
events.push(Event::Sound(2019));
|
||||
if self.nudge_meter >= TILT_THRESHOLD {
|
||||
self.tilted = true;
|
||||
self.bonus = 0;
|
||||
events.push(Event::Tilt);
|
||||
events.push(Event::Sound(2020));
|
||||
} else {
|
||||
events.push(Event::Nudge);
|
||||
}
|
||||
if !self.tilted && controls.nudge != Nudge::None {
|
||||
self.apply_nudge(controls.nudge, &mut events);
|
||||
}
|
||||
|
||||
self.accumulator = (self.accumulator + frame_time.min(0.05)).min(0.1);
|
||||
@@ -405,19 +398,79 @@ impl Game {
|
||||
events
|
||||
}
|
||||
|
||||
fn advance_tilt_counter(&mut self, elapsed: f32) {
|
||||
self.tilt_counter_accumulator += elapsed;
|
||||
while self.tilt_counter_accumulator >= self.claw_frame_seconds {
|
||||
self.tilt_counter_accumulator -= self.claw_frame_seconds;
|
||||
if self.tilt_counter != 0 {
|
||||
self.tilt_counter -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn apply_nudge(&mut self, nudge: Nudge, events: &mut Vec<Event>) {
|
||||
const SCALAR: i32 = 15;
|
||||
events.push(Event::Sound(2019));
|
||||
let mut velocity = MilliVec::from_velocity_per_second(self.ball.velocity);
|
||||
match nudge {
|
||||
Nudge::Left | Nudge::Right => {
|
||||
let amount = (50 - i32::from(self.random.below(20))) * SCALAR;
|
||||
let signed = if nudge == Nudge::Left { -amount } else { amount };
|
||||
velocity.x = velocity.x.wrapping_add(signed);
|
||||
}
|
||||
Nudge::Center => {
|
||||
let horizontal =
|
||||
(f64::from(SCALAR) * (self.random.unit_interval() * 20.0 - 10.0)).round()
|
||||
as i32;
|
||||
let vertical = -(60 - i32::from(self.random.below(20))) * SCALAR;
|
||||
velocity.x = velocity.x.wrapping_add(horizontal);
|
||||
velocity.y = velocity.y.wrapping_add(vertical);
|
||||
}
|
||||
Nudge::None => return,
|
||||
}
|
||||
self.ball.velocity = velocity.to_velocity_per_second();
|
||||
|
||||
if let Some(secondary) = &mut self.secondary_ball {
|
||||
let mut slot_velocity = MilliVec::from_velocity_per_second(secondary.velocity);
|
||||
match nudge {
|
||||
Nudge::Left | Nudge::Right => {
|
||||
let amount = if nudge == Nudge::Left { -600 } else { 600 };
|
||||
slot_velocity.x = slot_velocity.x.wrapping_add(amount);
|
||||
}
|
||||
Nudge::Center => {
|
||||
let horizontal = (i32::from(self.random.below(21)) - 10) * SCALAR;
|
||||
let vertical = -(i32::from(self.random.below(100)) + 50) * SCALAR;
|
||||
slot_velocity.x = slot_velocity.x.wrapping_add(horizontal);
|
||||
slot_velocity.y = slot_velocity.y.wrapping_add(vertical);
|
||||
}
|
||||
Nudge::None => {}
|
||||
}
|
||||
secondary.velocity = slot_velocity.to_velocity_per_second();
|
||||
}
|
||||
|
||||
self.nudge_shake = 0.18;
|
||||
self.tilt_counter = self.tilt_counter.wrapping_add(25);
|
||||
let threshold = self.random.below(10) + 30;
|
||||
if threshold < self.tilt_counter {
|
||||
self.tilted = true;
|
||||
self.flippers = Flippers::default();
|
||||
self.pending_flipper_edges.fill(0);
|
||||
events.push(Event::Tilt);
|
||||
events.push(Event::Sound(2020));
|
||||
} else {
|
||||
events.push(Event::Nudge);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn fixed_update(&mut self, dt: f32, events: &mut Vec<Event>) {
|
||||
self.bumper_cooldown = (self.bumper_cooldown - dt).max(0.0);
|
||||
self.nudge_cooldown = (self.nudge_cooldown - dt).max(0.0);
|
||||
self.wheel_animation = (self.wheel_animation - dt).max(0.0);
|
||||
self.nudge_shake = (self.nudge_shake - dt).max(0.0);
|
||||
for flash in &mut self.bumper_flash {
|
||||
*flash = (*flash - dt).max(0.0);
|
||||
}
|
||||
if !self.tilted {
|
||||
self.nudge_meter = (self.nudge_meter - dt * 0.34).max(0.0);
|
||||
}
|
||||
|
||||
self.update_claw(dt, events);
|
||||
if self.claw.ball_suspended {
|
||||
self.pending_flipper_edges.fill(0);
|
||||
@@ -1109,7 +1162,8 @@ impl Game {
|
||||
self.top_targets.fill(false);
|
||||
self.wheel_holes.fill(false);
|
||||
self.tilted = false;
|
||||
self.nudge_meter = 0.0;
|
||||
self.tilt_counter = 0;
|
||||
self.tilt_counter_accumulator = 0.0;
|
||||
self.bumper_flash.fill(0.0);
|
||||
self.wheel_animation = 0.0;
|
||||
self.claw = Claw::default();
|
||||
@@ -1515,22 +1569,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tilt_latches_and_disables_flippers_and_scoring() {
|
||||
let mut game = Game::new(1);
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
assert!(launch_ball(&mut game, 1).contains(&Event::Launch));
|
||||
game.bonus = 4_000;
|
||||
for _ in 0..4 {
|
||||
game.nudge_cooldown = 0.0;
|
||||
for _ in 0..2 {
|
||||
game.update(
|
||||
1.0 / 60.0,
|
||||
0.0,
|
||||
3,
|
||||
Controls {
|
||||
nudge: 1.0,
|
||||
nudge: Nudge::Right,
|
||||
..Controls::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
assert!(game.tilted);
|
||||
assert_eq!(game.bonus, 0);
|
||||
assert_eq!(game.bonus, 4_000);
|
||||
|
||||
game.flippers.left_raised = true;
|
||||
game.flippers.right_raised = true;
|
||||
@@ -1713,10 +1766,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn nudge_and_tilt_emit_the_original_ordered_sound_sequence() {
|
||||
let mut game = Game::new(1);
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
game.ball.in_launcher = false;
|
||||
let controls = Controls {
|
||||
nudge: 1.0,
|
||||
nudge: Nudge::Right,
|
||||
..Controls::default()
|
||||
};
|
||||
|
||||
@@ -1724,15 +1777,43 @@ mod tests {
|
||||
game.update(0.0, 3, controls),
|
||||
[Event::Sound(2019), Event::Nudge]
|
||||
);
|
||||
assert_eq!(
|
||||
MilliVec::from_velocity_per_second(game.ball.velocity),
|
||||
MilliVec { x: 690, y: 0 }
|
||||
);
|
||||
assert_eq!(game.tilt_counter, 25);
|
||||
|
||||
game.nudge_cooldown = 0.0;
|
||||
game.nudge_meter = TILT_THRESHOLD - 0.01;
|
||||
game.tilt_counter = 15;
|
||||
assert_eq!(
|
||||
game.update(0.0, 3, controls),
|
||||
[Event::Sound(2019), Event::Tilt, Event::Sound(2020)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn center_nudge_and_tilt_decay_match_the_detail_timer() {
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
let events = game.update(
|
||||
0.0,
|
||||
3,
|
||||
Controls {
|
||||
nudge: Nudge::Center,
|
||||
..Controls::default()
|
||||
},
|
||||
);
|
||||
assert_eq!(events, [Event::Sound(2019), Event::Nudge]);
|
||||
assert_eq!(
|
||||
MilliVec::from_velocity_per_second(game.ball.velocity),
|
||||
MilliVec { x: -84, y: -660 }
|
||||
);
|
||||
assert_eq!(game.tilt_counter, 25);
|
||||
|
||||
game.update(0.029, 3, Controls::default());
|
||||
assert_eq!(game.tilt_counter, 25);
|
||||
game.update(0.001_1, 3, Controls::default());
|
||||
assert_eq!(game.tilt_counter, 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claw_sprite_rects_follow_the_recovered_four_row_sheet() {
|
||||
let source = |frame, bank| {
|
||||
@@ -1997,24 +2078,22 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn waiting_ball_cannot_be_tilted() {
|
||||
let mut game = Game::new(1);
|
||||
fn waiting_ball_uses_the_same_nudge_tilt_counter() {
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
|
||||
for _ in 0..12 {
|
||||
game.nudge_cooldown = 0.0;
|
||||
for attempt in 0..2 {
|
||||
let events = game.update(
|
||||
1.0 / 60.0,
|
||||
0.0,
|
||||
3,
|
||||
Controls {
|
||||
nudge: 1.0,
|
||||
nudge: Nudge::Right,
|
||||
..Controls::default()
|
||||
},
|
||||
);
|
||||
assert!(!events.contains(&Event::Nudge));
|
||||
assert!(!events.contains(&Event::Tilt));
|
||||
assert_eq!(events.contains(&Event::Tilt), attempt == 1);
|
||||
}
|
||||
|
||||
assert!(!game.tilted);
|
||||
assert!(launch_ball(&mut game, 1).contains(&Event::Launch));
|
||||
assert!(game.tilted);
|
||||
assert!(game.ball.in_launcher);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user