120 lines
4.4 KiB
Rust
120 lines
4.4 KiB
Rust
use raptor_replica::{
|
|
content::{ContentPack, Registry},
|
|
modding::{self, ActiveMods, Command, ScriptRuntime},
|
|
persistence::Pilot,
|
|
simulation::{Input, Outcome, World},
|
|
sound,
|
|
};
|
|
use serde_json::json;
|
|
|
|
fn wav(channels: u16, bits: u16, data: &[u8]) -> Vec<u8> {
|
|
let align = channels * bits / 8;
|
|
let mut bytes = b"RIFF".to_vec();
|
|
bytes.extend((36u32 + data.len() as u32 + (data.len() as u32 % 2)).to_le_bytes());
|
|
bytes.extend(b"WAVEfmt ");
|
|
bytes.extend(16u32.to_le_bytes());
|
|
bytes.extend(1u16.to_le_bytes());
|
|
bytes.extend(channels.to_le_bytes());
|
|
bytes.extend(11025u32.to_le_bytes());
|
|
bytes.extend((11025u32 * align as u32).to_le_bytes());
|
|
bytes.extend(align.to_le_bytes());
|
|
bytes.extend(bits.to_le_bytes());
|
|
bytes.extend(b"data");
|
|
bytes.extend((data.len() as u32).to_le_bytes());
|
|
bytes.extend(data);
|
|
if data.len() % 2 == 1 {
|
|
bytes.push(0);
|
|
}
|
|
bytes
|
|
}
|
|
fn registry() -> Registry {
|
|
let mut registry = Registry::default();
|
|
let pack: ContentPack=serde_json::from_value(json!({
|
|
"items":[{"id":"base:energy","name":"Shield","price":0,"max_count":100},
|
|
{"id":"base:forward_guns","name":"Guns","price":0}],
|
|
"missions":[{"id":"base:sector_1_wave_1","name":"Test","duration":1,"auto_complete":false,"script":"mission.rhai"}],
|
|
"sounds":[{"id":"base:test","name":"Test","file":"test.wav"}]
|
|
})).unwrap();
|
|
registry.apply(pack, "base").unwrap();
|
|
registry.validate().unwrap();
|
|
registry
|
|
}
|
|
#[test]
|
|
fn wav_handles_padding_unsigned_eight_bit_and_stereo_sixteen_bit() {
|
|
let decoded = sound::decode(&wav(1, 8, &[0, 128, 255])).unwrap();
|
|
assert_eq!(decoded.data, [0, 128, 255]);
|
|
assert_eq!(decoded.rate, 11025);
|
|
let samples: Vec<_> = [-32768i16, 32767, -32768, -32768, 32767, 32767]
|
|
.into_iter()
|
|
.flat_map(i16::to_le_bytes)
|
|
.collect();
|
|
assert_eq!(
|
|
sound::decode(&wav(2, 16, &samples)).unwrap().data,
|
|
[128, 0, 255]
|
|
);
|
|
}
|
|
#[test]
|
|
fn malformed_wave_ranges_formats_and_padding_fail() {
|
|
let bytes = wav(1, 8, &[128, 200, 0]);
|
|
for end in 0..bytes.len() {
|
|
assert!(sound::decode(&bytes[..end]).is_err());
|
|
}
|
|
assert!(sound::decode(&wav(3, 8, &[0, 0, 0])).is_err());
|
|
assert!(sound::decode(&wav(1, 32, &[0, 0, 0, 0])).is_err());
|
|
let mut huge = bytes.clone();
|
|
huge[40..44].copy_from_slice(&u32::MAX.to_le_bytes());
|
|
assert!(sound::decode(&huge).is_err());
|
|
}
|
|
#[test]
|
|
fn scripted_completion_emits_end_hook_once_and_sound_ids_are_checked() {
|
|
let registry = registry();
|
|
let mut world = World::new(&Pilot::default().mission, &Pilot::default(), ®istry).unwrap();
|
|
let mut scripts = ScriptRuntime::new();
|
|
scripts.add("base","mission.rhai",r#"
|
|
fn on_tick(event,state,random) { #{commands:[#{op:"complete"}],state:state} }
|
|
fn on_mission_end(event,state,random) { #{commands:[#{op:"play_sound",sound:"base:test"}],state:#{ended:1}} }
|
|
"#).unwrap();
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert_eq!(world.outcome, Outcome::Complete);
|
|
assert_eq!(world.mod_sounds.len(), 1);
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert_eq!(world.mod_sounds.len(), 1);
|
|
assert!(
|
|
world
|
|
.apply_commands(
|
|
&[Command::PlaySound {
|
|
sound: "base:missing".into()
|
|
}],
|
|
®istry
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
#[test]
|
|
fn package_local_sounds_validate_and_missing_files_reject_activation() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let package = dir.path().join("tone");
|
|
std::fs::create_dir_all(&package).unwrap();
|
|
std::fs::write(
|
|
package.join("mod.toml"),
|
|
"id='tone'\nname='Tone'\nversion='1.0.0'\napi_version=1\ncontent='content.json'\n",
|
|
)
|
|
.unwrap();
|
|
std::fs::write(
|
|
package.join("content.json"),
|
|
serde_json::to_vec(&json!({"sounds":[{"id":"tone:shot","name":"Shot","file":"shot.wav"}]}))
|
|
.unwrap(),
|
|
)
|
|
.unwrap();
|
|
std::fs::write(package.join("shot.wav"), wav(1, 8, &[128, 200, 50])).unwrap();
|
|
let found = modding::discover(dir.path());
|
|
assert!(ActiveMods::build(&Registry::default(), &found, &["tone".into()], None).is_ok());
|
|
std::fs::remove_file(package.join("shot.wav")).unwrap();
|
|
let found = modding::discover(dir.path());
|
|
assert!(ActiveMods::build(&Registry::default(), &found, &["tone".into()], None).is_err());
|
|
}
|