Load and render DAT400-407 effect panels with the preserved wheel overlap, and add deterministic effect-7 coverage. Restore exact 1008:0996/0b9e score fields, configured-player rows, current/status indicators, signed status thresholds, and six 5x5 per-player ball-marker slots. Test Plan: - cargo test --all-targets - cargo clippy --all-targets --all-features -- -D warnings - rumdl check CHANGELOG.md RECONSTRUCTION.md README.md - cargo run --quiet -- --simulate effect-7 --step 0 --screenshot /tmp/tdkpin-effect7.png - cargo run --quiet -- --simulate launcher --step 0 --screenshot /tmp/tdkpin-display.png - git diff --check
274 lines
10 KiB
Rust
274 lines
10 KiB
Rust
use macroquad::{
|
|
audio::{PlaySoundParams, Sound, load_sound_from_bytes, play_sound, stop_sound},
|
|
prelude::{FilterMode, Image, Texture2D},
|
|
};
|
|
|
|
pub struct Assets {
|
|
pub active_table: Texture2D,
|
|
pub inactive_table: Texture2D,
|
|
pub loading: Texture2D,
|
|
pub loading_progress: Texture2D,
|
|
pub highscore_background: Texture2D,
|
|
pub help: [Texture2D; 5],
|
|
pub player_effects: [Texture2D; 8],
|
|
pub media: [Texture2D; 4],
|
|
pub diamond: [Texture2D; 9],
|
|
pub wheel: Texture2D,
|
|
pub panel_pair_narrow_a: Texture2D,
|
|
pub panel_pair_narrow_b: Texture2D,
|
|
pub panel_pair_wide_a: Texture2D,
|
|
pub panel_pair_wide_b: Texture2D,
|
|
pub panel_target: Texture2D,
|
|
pub robot: Texture2D,
|
|
pub plunger: Texture2D,
|
|
pub ball: Texture2D,
|
|
pub digits: Texture2D,
|
|
sounds: Vec<(u16, Sound)>,
|
|
}
|
|
|
|
impl Assets {
|
|
#[allow(clippy::too_many_lines)]
|
|
pub async fn load() -> Self {
|
|
let active_table = texture(include_bytes!("../assets/original/images/dat_00997.png"));
|
|
let inactive_table = texture(include_bytes!("../assets/original/images/dat_00998.png"));
|
|
let loading = texture(include_bytes!("../assets/original/images/dat_00995.png"));
|
|
let loading_progress = texture(include_bytes!("../assets/original/images/dat_00994.png"));
|
|
let highscore_background = texture(include_bytes!("../assets/original/images/dat_00993.png"));
|
|
let help = [
|
|
texture(include_bytes!("../assets/original/images/dat_01001.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_01002.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_01003.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_01004.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_01005.png")),
|
|
];
|
|
let player_effects = [
|
|
texture(include_bytes!("../assets/original/images/dat_00400.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00401.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00402.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00403.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00404.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00405.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00406.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00407.png")),
|
|
];
|
|
let media = [
|
|
texture(include_bytes!("../assets/original/images/dat_00701.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00702.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00703.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00704.png")),
|
|
];
|
|
let diamond = [
|
|
texture(include_bytes!("../assets/original/images/dat_00801.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00802.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00803.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00804.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00805.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00806.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00807.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00808.png")),
|
|
texture(include_bytes!("../assets/original/images/dat_00809.png")),
|
|
];
|
|
let wheel_bytes = include_bytes!("../assets/original/images/dat_00600.png");
|
|
let wheel = texture(wheel_bytes);
|
|
let panel_pair_narrow_a = masked_atlas_pair(wheel_bytes, 0, 90, 45, 90, 45, 90);
|
|
let panel_pair_narrow_b = masked_atlas_pair(wheel_bytes, 90, 90, 135, 90, 45, 90);
|
|
let panel_pair_wide_a = masked_atlas_pair(wheel_bytes, 271, 90, 362, 90, 91, 90);
|
|
let panel_pair_wide_b = masked_atlas_pair(wheel_bytes, 453, 90, 362, 90, 91, 90);
|
|
let panel_target = masked_atlas_pair(wheel_bytes, 455, 0, 455, 17, 17, 17);
|
|
let robot = texture(include_bytes!("../assets/original/images/dat_00900.png"));
|
|
let plunger = texture(include_bytes!("../assets/original/images/dat_00901.png"));
|
|
let ball = masked_texture(
|
|
include_bytes!("../assets/original/images/bitmap_00101.png"),
|
|
include_bytes!("../assets/original/images/bitmap_00102.png"),
|
|
);
|
|
let digits =
|
|
monochrome_texture(include_bytes!("../assets/original/images/bitmap_00500.png"));
|
|
|
|
let sound_bytes: [(u16, &[u8]); 16] = [
|
|
(
|
|
2001,
|
|
include_bytes!("../assets/original/audio/wav_02001.wav"),
|
|
),
|
|
(
|
|
2002,
|
|
include_bytes!("../assets/original/audio/wav_02002.wav"),
|
|
),
|
|
(
|
|
2004,
|
|
include_bytes!("../assets/original/audio/wav_02004.wav"),
|
|
),
|
|
(
|
|
2006,
|
|
include_bytes!("../assets/original/audio/wav_02006.wav"),
|
|
),
|
|
(
|
|
2007,
|
|
include_bytes!("../assets/original/audio/wav_02007.wav"),
|
|
),
|
|
(
|
|
2008,
|
|
include_bytes!("../assets/original/audio/wav_02008.wav"),
|
|
),
|
|
(
|
|
2011,
|
|
include_bytes!("../assets/original/audio/wav_02011.wav"),
|
|
),
|
|
(
|
|
2012,
|
|
include_bytes!("../assets/original/audio/wav_02012.wav"),
|
|
),
|
|
(
|
|
2013,
|
|
include_bytes!("../assets/original/audio/wav_02013.wav"),
|
|
),
|
|
(
|
|
2015,
|
|
include_bytes!("../assets/original/audio/wav_02015.wav"),
|
|
),
|
|
(
|
|
2016,
|
|
include_bytes!("../assets/original/audio/wav_02016.wav"),
|
|
),
|
|
(
|
|
2017,
|
|
include_bytes!("../assets/original/audio/wav_02017.wav"),
|
|
),
|
|
(
|
|
2019,
|
|
include_bytes!("../assets/original/audio/wav_02019.wav"),
|
|
),
|
|
(
|
|
2020,
|
|
include_bytes!("../assets/original/audio/wav_02020.wav"),
|
|
),
|
|
(
|
|
2021,
|
|
include_bytes!("../assets/original/audio/wav_02021.wav"),
|
|
),
|
|
(
|
|
2022,
|
|
include_bytes!("../assets/original/audio/wav_02022.wav"),
|
|
),
|
|
];
|
|
let mut sounds = Vec::with_capacity(sound_bytes.len());
|
|
for (id, bytes) in sound_bytes {
|
|
if let Ok(sound) = load_sound_from_bytes(bytes).await {
|
|
sounds.push((id, sound));
|
|
}
|
|
}
|
|
|
|
Self {
|
|
active_table,
|
|
inactive_table,
|
|
loading,
|
|
loading_progress,
|
|
highscore_background,
|
|
help,
|
|
player_effects,
|
|
media,
|
|
diamond,
|
|
wheel,
|
|
panel_pair_narrow_a,
|
|
panel_pair_narrow_b,
|
|
panel_pair_wide_a,
|
|
panel_pair_wide_b,
|
|
panel_target,
|
|
robot,
|
|
plunger,
|
|
ball,
|
|
digits,
|
|
sounds,
|
|
}
|
|
}
|
|
|
|
pub fn play(&self, id: u16, enabled: bool) {
|
|
if !enabled {
|
|
return;
|
|
}
|
|
if let Some((_, sound)) = self.sounds.iter().find(|(sound_id, _)| *sound_id == id) {
|
|
play_sound(
|
|
sound,
|
|
PlaySoundParams {
|
|
looped: false,
|
|
volume: 0.72,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn stop_all_sounds(&self) {
|
|
for (_, sound) in &self.sounds {
|
|
stop_sound(sound);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn texture(bytes: &[u8]) -> Texture2D {
|
|
let texture = Texture2D::from_file_with_format(bytes, None);
|
|
texture.set_filter(FilterMode::Nearest);
|
|
texture
|
|
}
|
|
|
|
fn masked_texture(color_bytes: &[u8], mask_bytes: &[u8]) -> Texture2D {
|
|
let mut color =
|
|
Image::from_file_with_format(color_bytes, None).expect("embedded color bitmap must decode");
|
|
let mask =
|
|
Image::from_file_with_format(mask_bytes, None).expect("embedded mask bitmap must decode");
|
|
assert_eq!((color.width, color.height), (mask.width, mask.height));
|
|
|
|
let (color_pixels, color_remainder) = color.bytes.as_chunks_mut::<4>();
|
|
let (mask_pixels, mask_remainder) = mask.bytes.as_chunks::<4>();
|
|
assert!(color_remainder.is_empty() && mask_remainder.is_empty());
|
|
for (pixel, mask_pixel) in color_pixels.iter_mut().zip(mask_pixels) {
|
|
pixel[3] = u8::MAX - mask_pixel[0];
|
|
}
|
|
let texture = Texture2D::from_image(&color);
|
|
texture.set_filter(FilterMode::Nearest);
|
|
texture
|
|
}
|
|
|
|
fn monochrome_texture(bytes: &[u8]) -> Texture2D {
|
|
let mut image =
|
|
Image::from_file_with_format(bytes, None).expect("embedded monochrome bitmap must decode");
|
|
let (pixels, remainder) = image.bytes.as_chunks_mut::<4>();
|
|
assert!(remainder.is_empty());
|
|
for pixel in pixels {
|
|
pixel[3] = u8::MAX - pixel[0];
|
|
pixel[..3].fill(u8::MAX);
|
|
}
|
|
let texture = Texture2D::from_image(&image);
|
|
texture.set_filter(FilterMode::Nearest);
|
|
texture
|
|
}
|
|
|
|
fn masked_atlas_pair(
|
|
bytes: &[u8],
|
|
image_x: usize,
|
|
image_y: usize,
|
|
mask_x: usize,
|
|
mask_y: usize,
|
|
width: usize,
|
|
height: usize,
|
|
) -> Texture2D {
|
|
let atlas = Image::from_file_with_format(bytes, None).expect("embedded DAT600 must decode");
|
|
let atlas_width = usize::from(atlas.width);
|
|
let mut output = Image {
|
|
bytes: vec![0; width * height * 4],
|
|
width: u16::try_from(width).unwrap_or(0),
|
|
height: u16::try_from(height).unwrap_or(0),
|
|
};
|
|
for y in 0..height {
|
|
for x in 0..width {
|
|
let output_index = (y * width + x) * 4;
|
|
let image_index = ((y + image_y) * atlas_width + image_x + x) * 4;
|
|
let mask_index = ((y + mask_y) * atlas_width + mask_x + x) * 4;
|
|
output.bytes[output_index..output_index + 3]
|
|
.copy_from_slice(&atlas.bytes[image_index..image_index + 3]);
|
|
output.bytes[output_index + 3] = u8::MAX - atlas.bytes[mask_index];
|
|
}
|
|
}
|
|
let texture = Texture2D::from_image(&output);
|
|
texture.set_filter(FilterMode::Nearest);
|
|
texture
|
|
}
|