Remove the invented whole-frame nudge shake so nudges affect only recovered ball state, sound, and tilt behavior. Match monophonic SndPlaySound semantics by stopping the current WAV before each new resource and using full host mixer volume instead of an arbitrary 72 percent attenuation. Test Plan: - cargo test --all-targets - cargo clippy --all-targets --all-features -- -D warnings - rumdl check CHANGELOG.md RECONSTRUCTION.md README.md - git diff --check
275 lines
10 KiB
Rust
275 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) {
|
|
self.stop_all_sounds();
|
|
play_sound(
|
|
sound,
|
|
PlaySoundParams {
|
|
looped: false,
|
|
volume: 1.0,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|