use binrw::{BinRead, BinWrite};
#[derive(Debug, Clone, Copy, BinRead, BinWrite)]
#[brw(little)]
pub struct SoundEmitter {
pub sound_entry_id: u32,
pub position: [f32; 3],
pub size_min: [f32; 3],
#[brw(pad_after = 0)]
pub _padding: [u8; 0],
}
impl Default for SoundEmitter {
fn default() -> Self {
Self {
sound_entry_id: 0,
position: [0.0; 3],
size_min: [0.0; 3],
_padding: [],
}
}
}
impl SoundEmitter {
pub fn radius(&self) -> f32 {
self.size_min[0].max(self.size_min[1]).max(self.size_min[2])
}
pub fn is_in_range(&self, pos: [f32; 3]) -> bool {
let dx = pos[0] - self.position[0];
let dy = pos[1] - self.position[1];
let dz = pos[2] - self.position[2];
let dist_sq = dx * dx + dy * dy + dz * dz;
let radius = self.radius();
dist_sq <= radius * radius
}
}
#[derive(Debug, Clone, Default, BinRead, BinWrite)]
#[brw(little)]
pub struct McseChunk {
#[br(parse_with = binrw::helpers::until_eof)]
pub emitters: Vec<SoundEmitter>,
}
impl McseChunk {
pub const ENTRY_SIZE: usize = 28;
pub fn count(&self) -> usize {
self.emitters.len()
}
pub fn validate_count(&self, expected_count: usize) -> bool {
self.emitters.len() == expected_count
}
pub fn find_in_range(&self, pos: [f32; 3]) -> Vec<usize> {
self.emitters
.iter()
.enumerate()
.filter(|(_, e)| e.is_in_range(pos))
.map(|(i, _)| i)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use binrw::{BinRead, BinWrite};
use std::io::Cursor;
#[test]
fn test_sound_emitter_size() {
assert_eq!(
std::mem::size_of::<SoundEmitter>(),
28,
"SoundEmitter must be exactly 28 bytes"
);
}
#[test]
fn test_sound_emitter_parsing() {
let data: Vec<u8> = vec![
0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC8, 0x42, 0x00, 0x00, 0x48, 0x43, 0x00, 0x00, 0x96, 0x43, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0xA0, 0x41, 0x00, 0x00, 0xF0, 0x41, ];
let mut cursor = Cursor::new(&data);
let emitter = SoundEmitter::read(&mut cursor).unwrap();
assert_eq!(emitter.sound_entry_id, 42);
assert_eq!(emitter.position, [100.0, 200.0, 300.0]);
assert_eq!(emitter.size_min, [10.0, 20.0, 30.0]);
}
#[test]
fn test_sound_emitter_default() {
let emitter = SoundEmitter::default();
assert_eq!(emitter.sound_entry_id, 0);
assert_eq!(emitter.position, [0.0, 0.0, 0.0]);
assert_eq!(emitter.size_min, [0.0, 0.0, 0.0]);
}
#[test]
fn test_sound_emitter_radius() {
let emitter = SoundEmitter {
sound_entry_id: 1,
position: [0.0; 3],
size_min: [10.0, 20.0, 15.0],
_padding: [],
};
assert_eq!(emitter.radius(), 20.0);
}
#[test]
fn test_sound_emitter_radius_equal_components() {
let emitter = SoundEmitter {
sound_entry_id: 1,
position: [0.0; 3],
size_min: [25.0, 25.0, 25.0],
_padding: [],
};
assert_eq!(emitter.radius(), 25.0);
}
#[test]
fn test_sound_emitter_is_in_range_inside() {
let emitter = SoundEmitter {
sound_entry_id: 1,
position: [0.0, 0.0, 0.0],
size_min: [10.0, 10.0, 10.0],
_padding: [],
};
assert!(emitter.is_in_range([0.0, 0.0, 0.0]));
assert!(emitter.is_in_range([5.0, 0.0, 0.0]));
assert!(emitter.is_in_range([0.0, 5.0, 0.0]));
assert!(emitter.is_in_range([0.0, 0.0, 5.0]));
assert!(emitter.is_in_range([10.0, 0.0, 0.0]));
}
#[test]
fn test_sound_emitter_is_in_range_outside() {
let emitter = SoundEmitter {
sound_entry_id: 1,
position: [0.0, 0.0, 0.0],
size_min: [10.0, 10.0, 10.0],
_padding: [],
};
assert!(!emitter.is_in_range([15.0, 0.0, 0.0]));
assert!(!emitter.is_in_range([0.0, 15.0, 0.0]));
assert!(!emitter.is_in_range([0.0, 0.0, 15.0]));
assert!(!emitter.is_in_range([8.0, 8.0, 8.0]));
}
#[test]
fn test_sound_emitter_is_in_range_offset_position() {
let emitter = SoundEmitter {
sound_entry_id: 1,
position: [100.0, 200.0, 300.0],
size_min: [25.0, 25.0, 25.0],
_padding: [],
};
assert!(emitter.is_in_range([100.0, 200.0, 300.0]));
assert!(emitter.is_in_range([110.0, 200.0, 300.0]));
assert!(!emitter.is_in_range([130.0, 200.0, 300.0]));
}
#[test]
fn test_mcse_chunk_parsing_single() {
let data: Vec<u8> = vec![
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x20, 0x41, ];
let mut cursor = Cursor::new(&data);
let chunk = McseChunk::read(&mut cursor).unwrap();
assert_eq!(chunk.count(), 1);
assert_eq!(chunk.emitters[0].sound_entry_id, 1);
assert_eq!(chunk.emitters[0].position, [0.0, 0.0, 0.0]);
assert_eq!(chunk.emitters[0].size_min, [10.0, 10.0, 10.0]);
}
#[test]
fn test_mcse_chunk_parsing_multiple() {
let mut data = Vec::new();
data.extend_from_slice(&[
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x20, 0x41, ]);
data.extend_from_slice(&[
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC8, 0x42, 0x00, 0x00, 0x48, 0x43, 0x00, 0x00, 0x96, 0x43, 0x00, 0x00, 0xA0, 0x41, 0x00, 0x00, 0xA0, 0x41, 0x00, 0x00, 0xA0, 0x41, ]);
let mut cursor = Cursor::new(&data);
let chunk = McseChunk::read(&mut cursor).unwrap();
assert_eq!(chunk.count(), 2);
assert_eq!(chunk.emitters[0].sound_entry_id, 1);
assert_eq!(chunk.emitters[0].position, [0.0, 0.0, 0.0]);
assert_eq!(chunk.emitters[0].size_min, [10.0, 10.0, 10.0]);
assert_eq!(chunk.emitters[1].sound_entry_id, 2);
assert_eq!(chunk.emitters[1].position, [100.0, 200.0, 300.0]);
assert_eq!(chunk.emitters[1].size_min, [20.0, 20.0, 20.0]);
}
#[test]
fn test_mcse_chunk_empty() {
let data: Vec<u8> = vec![];
let mut cursor = Cursor::new(&data);
let chunk = McseChunk::read(&mut cursor).unwrap();
assert_eq!(chunk.count(), 0);
assert!(chunk.emitters.is_empty());
}
#[test]
fn test_mcse_chunk_validate_count() {
let data: Vec<u8> = vec![
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x20, 0x41, ];
let mut cursor = Cursor::new(&data);
let chunk = McseChunk::read(&mut cursor).unwrap();
assert!(chunk.validate_count(1));
assert!(!chunk.validate_count(0));
assert!(!chunk.validate_count(2));
}
#[test]
fn test_mcse_chunk_find_in_range_single() {
let data: Vec<u8> = vec![
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x20, 0x41, ];
let mut cursor = Cursor::new(&data);
let chunk = McseChunk::read(&mut cursor).unwrap();
let indices = chunk.find_in_range([5.0, 0.0, 0.0]);
assert_eq!(indices, vec![0]);
let indices = chunk.find_in_range([20.0, 0.0, 0.0]);
assert!(indices.is_empty());
}
#[test]
fn test_mcse_chunk_find_in_range_multiple() {
let mut data = Vec::new();
data.extend_from_slice(&[
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x20, 0x41, ]);
data.extend_from_slice(&[
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC8, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x41, 0x00, 0x00, 0xA0, 0x41, 0x00, 0x00, 0xA0, 0x41, ]);
let mut cursor = Cursor::new(&data);
let chunk = McseChunk::read(&mut cursor).unwrap();
let indices = chunk.find_in_range([5.0, 0.0, 0.0]);
assert_eq!(indices, vec![0]);
let indices = chunk.find_in_range([110.0, 0.0, 0.0]);
assert_eq!(indices, vec![1]);
let indices = chunk.find_in_range([50.0, 0.0, 0.0]);
assert!(indices.is_empty());
}
#[test]
fn test_mcse_chunk_default() {
let chunk = McseChunk::default();
assert_eq!(chunk.count(), 0);
assert!(chunk.emitters.is_empty());
}
#[test]
fn test_sound_emitter_round_trip() {
let original = SoundEmitter {
sound_entry_id: 42,
position: [100.0, 200.0, 300.0],
size_min: [10.0, 20.0, 30.0],
_padding: [],
};
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
original.write(&mut cursor).unwrap();
let mut cursor = Cursor::new(&buffer);
let parsed = SoundEmitter::read(&mut cursor).unwrap();
assert_eq!(parsed.sound_entry_id, original.sound_entry_id);
assert_eq!(parsed.position, original.position);
assert_eq!(parsed.size_min, original.size_min);
}
#[test]
fn test_mcse_chunk_round_trip() {
let original = McseChunk {
emitters: vec![
SoundEmitter {
sound_entry_id: 1,
position: [0.0, 0.0, 0.0],
size_min: [10.0, 10.0, 10.0],
_padding: [],
},
SoundEmitter {
sound_entry_id: 2,
position: [100.0, 200.0, 300.0],
size_min: [20.0, 20.0, 20.0],
_padding: [],
},
],
};
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
original.write(&mut cursor).unwrap();
let mut cursor = Cursor::new(&buffer);
let parsed = McseChunk::read(&mut cursor).unwrap();
assert_eq!(parsed.count(), original.count());
for (p, o) in parsed.emitters.iter().zip(original.emitters.iter()) {
assert_eq!(p.sound_entry_id, o.sound_entry_id);
assert_eq!(p.position, o.position);
assert_eq!(p.size_min, o.size_min);
}
}
#[test]
fn test_mcse_entry_size_constant() {
assert_eq!(McseChunk::ENTRY_SIZE, 28);
}
}