fix(render): restore timed record overlays
Replace the shared bumper cooldown and synthetic circle outlines with the original per-record countdown bytes. Bumpers 51-53 use five active callbacks, side targets 140-147 use twenty, and top targets 150-152 use ten; countdowns pause while the ball is idle and decrement once per active detail callback. Render each active record by copying its exact inclusive binary render bounds from overlay A at the same destination, then naturally fall back to overlay B when the countdown reaches zero. This removes invented vector effects and keeps visual lifetime tied to gameplay timer state. Test Plan: - `cargo test --all-targets` -- passed, 75 tests - `cargo clippy --all-targets -- -D warnings` -- passed - `rumdl check CHANGELOG.md RECONSTRUCTION.md` -- passed - `git diff --cached --check` -- passed
This commit is contained in:
+28
-5
@@ -4,7 +4,6 @@ use crate::{
|
||||
persistence::{
|
||||
HighScore, Language, Persistence, SavedData, insert_high_score, qualifies_high_score,
|
||||
},
|
||||
table::BUMPERS,
|
||||
};
|
||||
use macroquad::prelude::*;
|
||||
use std::path::Path;
|
||||
@@ -383,10 +382,34 @@ impl App {
|
||||
{
|
||||
self.draw_target_rotation(game, state);
|
||||
}
|
||||
for (index, bumper) in BUMPERS.into_iter().enumerate() {
|
||||
if game.bumper_flash[index] > 0.0 {
|
||||
draw_circle_lines(bumper.center.x, bumper.center.y, 17.0, 3.0, WHITE);
|
||||
draw_circle_lines(bumper.center.x, bumper.center.y, 13.0, 2.0, YELLOW);
|
||||
for (record_id, left, top, right, bottom) in [
|
||||
(51, 150, 133, 180, 163),
|
||||
(52, 191, 95, 221, 125),
|
||||
(53, 204, 150, 234, 180),
|
||||
(140, 17, 174, 31, 188),
|
||||
(141, 11, 155, 25, 169),
|
||||
(142, 10, 136, 24, 150),
|
||||
(143, 10, 117, 24, 131),
|
||||
(144, 10, 98, 24, 112),
|
||||
(145, 10, 79, 24, 93),
|
||||
(146, 10, 60, 24, 79),
|
||||
(147, 10, 41, 24, 55),
|
||||
(150, 199, 40, 211, 50),
|
||||
(151, 228, 41, 240, 51),
|
||||
(152, 257, 42, 269, 52),
|
||||
] {
|
||||
if game.record_countdown(record_id) != 0 {
|
||||
let width = (right - left + 1) as f32;
|
||||
let height = (bottom - top + 1) as f32;
|
||||
draw_texture_region(
|
||||
&self.assets.active_table,
|
||||
left as f32,
|
||||
top as f32,
|
||||
width,
|
||||
height,
|
||||
left as f32,
|
||||
top as f32,
|
||||
);
|
||||
}
|
||||
}
|
||||
if game.player().diamond_segments > 0 {
|
||||
|
||||
+35
-15
@@ -267,7 +267,6 @@ pub struct Game {
|
||||
pub flippers: Flippers,
|
||||
flipper_inputs: Flippers,
|
||||
pub claw: Claw,
|
||||
pub bumper_flash: [f32; 3],
|
||||
pub target_rotation_state: Option<u8>,
|
||||
pub panel_frame: Option<u16>,
|
||||
pub nudge_shake: f32,
|
||||
@@ -275,7 +274,7 @@ pub struct Game {
|
||||
pub finished: bool,
|
||||
pub last_collision_id: Option<u8>,
|
||||
accumulator: f32,
|
||||
bumper_cooldown: f32,
|
||||
record_countdowns: [u8; 176],
|
||||
tilt_counter: u16,
|
||||
tilt_counter_accumulator: f32,
|
||||
launcher_was_down: bool,
|
||||
@@ -311,7 +310,6 @@ impl Game {
|
||||
flippers: Flippers::default(),
|
||||
flipper_inputs: Flippers::default(),
|
||||
claw: Claw::default(),
|
||||
bumper_flash: [0.0; 3],
|
||||
target_rotation_state: None,
|
||||
panel_frame: None,
|
||||
nudge_shake: 0.0,
|
||||
@@ -319,7 +317,7 @@ impl Game {
|
||||
finished: false,
|
||||
last_collision_id: None,
|
||||
accumulator: 0.0,
|
||||
bumper_cooldown: 0.0,
|
||||
record_countdowns: [0; 176],
|
||||
tilt_counter: 0,
|
||||
tilt_counter_accumulator: 0.0,
|
||||
launcher_was_down: false,
|
||||
@@ -456,6 +454,9 @@ impl Game {
|
||||
|
||||
fn timer_tick(&mut self, elapsed: f32, substeps: u8, events: &mut Vec<Event>) {
|
||||
self.update_claw(elapsed, events);
|
||||
if !self.ball.in_launcher {
|
||||
self.update_record_countdowns();
|
||||
}
|
||||
self.update_target_rotation(events);
|
||||
let panel_active = self.update_panel_completion(events);
|
||||
if !self.claw.ball_suspended && !panel_active {
|
||||
@@ -494,6 +495,18 @@ impl Game {
|
||||
self.apply_flipper_kicks();
|
||||
}
|
||||
|
||||
fn update_record_countdowns(&mut self) {
|
||||
for countdown in &mut self.record_countdowns[1..] {
|
||||
if *countdown != 0 {
|
||||
*countdown -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_countdown(&self, record_id: usize) -> u8 {
|
||||
self.record_countdowns[record_id]
|
||||
}
|
||||
|
||||
fn update_target_rotation(&mut self, events: &mut Vec<Event>) {
|
||||
let Some(state) = self.target_rotation_state else {
|
||||
return;
|
||||
@@ -614,11 +627,7 @@ impl Game {
|
||||
|
||||
#[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_shake = (self.nudge_shake - dt).max(0.0);
|
||||
for flash in &mut self.bumper_flash {
|
||||
*flash = (*flash - dt).max(0.0);
|
||||
}
|
||||
if self.ball.in_launcher {
|
||||
self.ball.position = LAUNCHER_POSITION;
|
||||
self.ball.velocity = Vec2::ZERO;
|
||||
@@ -701,11 +710,9 @@ impl Game {
|
||||
if let Some(index) = hit_circle
|
||||
.and_then(|circle_id| BUMPERS.iter().position(|bumper| bumper.id == circle_id))
|
||||
&& !self.tilted
|
||||
&& self.bumper_cooldown <= 0.0
|
||||
{
|
||||
self.add_score(self.player().bumper_value, events);
|
||||
self.bumper_cooldown = 0.08;
|
||||
self.bumper_flash[index] = 0.16;
|
||||
self.record_countdowns[usize::from(BUMPERS[index].id)] = 5;
|
||||
events.push(Event::Bumper);
|
||||
events.push(Event::Sound(2006));
|
||||
}
|
||||
@@ -836,11 +843,9 @@ impl Game {
|
||||
} else if let Some((object_id, false)) = hit
|
||||
&& let Some(index) = BUMPERS.iter().position(|bumper| bumper.id == object_id)
|
||||
&& !self.tilted
|
||||
&& self.bumper_cooldown <= 0.0
|
||||
{
|
||||
self.add_score(self.player().bumper_value, events);
|
||||
self.bumper_cooldown = 0.08;
|
||||
self.bumper_flash[index] = 0.16;
|
||||
self.record_countdowns[usize::from(BUMPERS[index].id)] = 5;
|
||||
events.push(Event::Bumper);
|
||||
events.push(Event::Sound(2006));
|
||||
}
|
||||
@@ -1196,6 +1201,7 @@ impl Game {
|
||||
self.add_score(sensor.score, events);
|
||||
events.push(Event::Target);
|
||||
events.push(Event::Sound(2004));
|
||||
self.record_countdowns[usize::from(sensor.id)] = if sensor.id <= 147 { 20 } else { 10 };
|
||||
if (150..=152).contains(&sensor.id) {
|
||||
self.top_targets[usize::from(sensor.id - 150)] = true;
|
||||
let field_id = [153, 6, 154][usize::from(sensor.id - 150)];
|
||||
@@ -1407,7 +1413,6 @@ impl Game {
|
||||
self.tilted = false;
|
||||
self.tilt_counter = 0;
|
||||
self.tilt_counter_accumulator = 0.0;
|
||||
self.bumper_flash.fill(0.0);
|
||||
self.target_rotation_state = None;
|
||||
self.panel_frame = None;
|
||||
self.claw = Claw::default();
|
||||
@@ -2519,6 +2524,21 @@ mod tests {
|
||||
assert_eq!(game.target_rotation_state, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_countdowns_advance_once_per_active_timer_callback() {
|
||||
let mut game = Game::new(1);
|
||||
game.record_countdowns[51] = 5;
|
||||
|
||||
game.timer_tick(0.030, 0, &mut Vec::new());
|
||||
assert_eq!(game.record_countdown(51), 5, "launcher idle pauses countdowns");
|
||||
|
||||
game.ball.in_launcher = false;
|
||||
for expected in (0..5).rev() {
|
||||
game.timer_tick(0.030, 0, &mut Vec::new());
|
||||
assert_eq!(game.record_countdown(51), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claw_release_table_decodes_the_original_thousandth_pixel_coordinates() {
|
||||
for (frame, expected_position, expected_velocity) in [
|
||||
|
||||
Reference in New Issue
Block a user