use binrw::{BinRead, BinWrite};
#[derive(Debug, Clone, Copy, Default, BinRead, BinWrite)]
#[brw(little)]
pub struct Mh2oHeader {
pub offset_instances: u32,
pub layer_count: u32,
pub offset_attributes: u32,
}
impl Mh2oHeader {
pub const GRID_SIZE: usize = 16;
pub const TOTAL_COUNT: usize = Self::GRID_SIZE * Self::GRID_SIZE;
pub fn has_liquid(&self) -> bool {
self.layer_count > 0 && self.offset_instances != 0
}
pub fn has_attributes(&self) -> bool {
self.offset_attributes != 0
}
}
#[derive(Debug, Clone, Copy, Default, BinRead, BinWrite)]
#[brw(little)]
pub struct Mh2oAttributes {
pub fishable: u64,
pub deep: u64,
}
impl Mh2oAttributes {
pub const TILE_SIZE: usize = 8;
pub fn is_fishable(&self, x: usize, y: usize) -> bool {
if x >= Self::TILE_SIZE || y >= Self::TILE_SIZE {
return false;
}
let bit_index = y * Self::TILE_SIZE + x;
(self.fishable >> bit_index) & 1 != 0
}
pub fn is_deep(&self, x: usize, y: usize) -> bool {
if x >= Self::TILE_SIZE || y >= Self::TILE_SIZE {
return false;
}
let bit_index = y * Self::TILE_SIZE + x;
(self.deep >> bit_index) & 1 != 0
}
pub fn fishable_count(&self) -> u32 {
self.fishable.count_ones()
}
pub fn deep_count(&self) -> u32 {
self.deep.count_ones()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_mh2o_header_size() {
assert_eq!(std::mem::size_of::<Mh2oHeader>(), 12);
}
#[test]
fn test_mh2o_header_constants() {
assert_eq!(Mh2oHeader::GRID_SIZE, 16);
assert_eq!(Mh2oHeader::TOTAL_COUNT, 256);
}
#[test]
fn test_mh2o_header_parse() {
let data = [
0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, ];
let mut cursor = Cursor::new(&data);
let header = Mh2oHeader::read_le(&mut cursor).unwrap();
assert_eq!(header.offset_instances, 16);
assert_eq!(header.layer_count, 2);
assert_eq!(header.offset_attributes, 32);
assert!(header.has_liquid());
assert!(header.has_attributes());
}
#[test]
fn test_mh2o_header_no_liquid() {
let header = Mh2oHeader {
offset_instances: 0,
layer_count: 0,
offset_attributes: 0,
};
assert!(!header.has_liquid());
assert!(!header.has_attributes());
}
#[test]
fn test_mh2o_header_round_trip() {
let original = Mh2oHeader {
offset_instances: 100,
layer_count: 3,
offset_attributes: 200,
};
let mut buffer = Cursor::new(Vec::new());
original.write_le(&mut buffer).unwrap();
let data = buffer.into_inner();
let mut cursor = Cursor::new(data);
let parsed = Mh2oHeader::read_le(&mut cursor).unwrap();
assert_eq!(original.offset_instances, parsed.offset_instances);
assert_eq!(original.layer_count, parsed.layer_count);
assert_eq!(original.offset_attributes, parsed.offset_attributes);
}
#[test]
fn test_mh2o_attributes_size() {
assert_eq!(std::mem::size_of::<Mh2oAttributes>(), 16);
}
#[test]
fn test_mh2o_attributes_parse() {
let data = [
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let mut cursor = Cursor::new(&data);
let attrs = Mh2oAttributes::read_le(&mut cursor).unwrap();
assert_eq!(attrs.fishable, 0xFF);
assert_eq!(attrs.deep, 0xFF00);
}
#[test]
fn test_mh2o_attributes_fishable() {
let attrs = Mh2oAttributes {
fishable: 0xAAAA, deep: 0,
};
assert!(!attrs.is_fishable(0, 0)); assert!(attrs.is_fishable(1, 0)); assert!(!attrs.is_fishable(2, 0)); assert!(attrs.is_fishable(3, 0));
assert!(!attrs.is_fishable(0, 1)); assert!(attrs.is_fishable(1, 1)); }
#[test]
fn test_mh2o_attributes_deep() {
let attrs = Mh2oAttributes {
fishable: 0,
deep: 0x00_00_00_FF_00_00_00_00, };
for x in 0..8 {
assert!(attrs.is_deep(x, 4), "Failed for x={}", x);
}
for x in 0..8 {
assert!(!attrs.is_deep(x, 0), "Row 0, x={} should not be deep", x);
assert!(!attrs.is_deep(x, 1), "Row 1, x={} should not be deep", x);
}
}
#[test]
fn test_mh2o_attributes_bounds_checking() {
let attrs = Mh2oAttributes {
fishable: u64::MAX,
deep: u64::MAX,
};
assert!(!attrs.is_fishable(8, 0));
assert!(!attrs.is_fishable(0, 8));
assert!(!attrs.is_deep(8, 0));
assert!(!attrs.is_deep(0, 8));
}
#[test]
fn test_mh2o_attributes_count() {
let attrs = Mh2oAttributes {
fishable: 0xFF, deep: 0xFFFF, };
assert_eq!(attrs.fishable_count(), 8);
assert_eq!(attrs.deep_count(), 16);
}
#[test]
fn test_mh2o_attributes_round_trip() {
let original = Mh2oAttributes {
fishable: 0x123456789ABCDEF0,
deep: 0xFEDCBA9876543210,
};
let mut buffer = Cursor::new(Vec::new());
original.write_le(&mut buffer).unwrap();
let data = buffer.into_inner();
let mut cursor = Cursor::new(data);
let parsed = Mh2oAttributes::read_le(&mut cursor).unwrap();
assert_eq!(original.fishable, parsed.fishable);
assert_eq!(original.deep, parsed.deep);
}
}