use crate::error::{Error, Result};
use bitflags::bitflags;
use std::io::{Read, Write};
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MphdFlags: u32 {
const WDT_USES_GLOBAL_MAP_OBJ = 0x0001;
const ADT_HAS_MCCV = 0x0002;
const ADT_HAS_BIG_ALPHA = 0x0004;
const ADT_HAS_DOODADREFS_SORTED_BY_SIZE_CAT = 0x0008;
const ADT_HAS_LIGHTING_VERTICES = 0x0010;
const ADT_HAS_UPSIDE_DOWN_GROUND = 0x0020;
const UNK_FIRELANDS = 0x0040;
const ADT_HAS_HEIGHT_TEXTURING = 0x0080;
const UNK_LOAD_LOD = 0x0100;
const WDT_HAS_MAID = 0x0200;
const UNK_FLAG_0x0400 = 0x0400;
const UNK_FLAG_0x0800 = 0x0800;
const UNK_FLAG_0x1000 = 0x1000;
const UNK_FLAG_0x2000 = 0x2000;
const UNK_FLAG_0x4000 = 0x4000;
const UNK_FLAG_0x8000 = 0x8000;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MphdChunk {
pub flags: MphdFlags,
pub something: u32,
pub unused: [u32; 6],
pub lgt_file_data_id: Option<u32>, pub occ_file_data_id: Option<u32>, pub fogs_file_data_id: Option<u32>, pub mpv_file_data_id: Option<u32>, pub tex_file_data_id: Option<u32>, pub wdl_file_data_id: Option<u32>, pub pd4_file_data_id: Option<u32>, }
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileDataIds {
pub lgt: u32,
pub occ: u32,
pub fogs: u32,
pub mpv: u32,
pub tex: u32,
pub wdl: u32,
pub pd4: u32,
}
impl MphdChunk {
pub fn new() -> Self {
Self {
flags: MphdFlags::empty(),
something: 0,
unused: [0; 6],
lgt_file_data_id: None,
occ_file_data_id: None,
fogs_file_data_id: None,
mpv_file_data_id: None,
tex_file_data_id: None,
wdl_file_data_id: None,
pd4_file_data_id: None,
}
}
pub fn is_wmo_only(&self) -> bool {
self.flags.contains(MphdFlags::WDT_USES_GLOBAL_MAP_OBJ)
}
pub fn has_maid(&self) -> bool {
self.flags.contains(MphdFlags::WDT_HAS_MAID)
}
pub fn set_file_data_ids(&mut self, ids: FileDataIds) {
self.flags |= MphdFlags::WDT_HAS_MAID;
self.lgt_file_data_id = Some(ids.lgt);
self.occ_file_data_id = Some(ids.occ);
self.fogs_file_data_id = Some(ids.fogs);
self.mpv_file_data_id = Some(ids.mpv);
self.tex_file_data_id = Some(ids.tex);
self.wdl_file_data_id = Some(ids.wdl);
self.pd4_file_data_id = Some(ids.pd4);
}
pub fn clear_file_data_ids(&mut self) {
self.flags.remove(MphdFlags::WDT_HAS_MAID);
self.lgt_file_data_id = None;
self.occ_file_data_id = None;
self.fogs_file_data_id = None;
self.mpv_file_data_id = None;
self.tex_file_data_id = None;
self.wdl_file_data_id = None;
self.pd4_file_data_id = None;
}
}
impl Default for MphdChunk {
fn default() -> Self {
Self::new()
}
}
impl super::Chunk for MphdChunk {
fn magic() -> &'static [u8; 4] {
b"DHPM" }
fn expected_size() -> Option<usize> {
Some(32) }
fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
if size != 32 {
return Err(Error::InvalidChunkSize {
chunk: "MPHD".to_string(),
expected: 32,
found: size,
});
}
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
let flags_raw = u32::from_le_bytes(buf);
let flags = MphdFlags::from_bits(flags_raw).ok_or_else(|| Error::InvalidChunkData {
chunk: "MPHD".to_string(),
message: format!("Invalid flags: 0x{flags_raw:08X}"),
})?;
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
let something = u32::from_le_bytes(buf);
let mut unused = [0u32; 6];
for item in &mut unused {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
*item = u32::from_le_bytes(buf);
}
let mut chunk = Self {
flags,
something,
unused,
lgt_file_data_id: None,
occ_file_data_id: None,
fogs_file_data_id: None,
mpv_file_data_id: None,
tex_file_data_id: None,
wdl_file_data_id: None,
pd4_file_data_id: None,
};
if flags.contains(MphdFlags::WDT_HAS_MAID) {
chunk.lgt_file_data_id = Some(chunk.something);
chunk.occ_file_data_id = Some(chunk.unused[0]);
chunk.fogs_file_data_id = Some(chunk.unused[1]);
chunk.mpv_file_data_id = Some(chunk.unused[2]);
chunk.tex_file_data_id = Some(chunk.unused[3]);
chunk.wdl_file_data_id = Some(chunk.unused[4]);
chunk.pd4_file_data_id = Some(chunk.unused[5]);
}
Ok(chunk)
}
fn write(&self, writer: &mut impl Write) -> Result<()> {
writer.write_all(&self.flags.bits().to_le_bytes())?;
if self.has_maid() {
writer.write_all(&self.lgt_file_data_id.unwrap_or(0).to_le_bytes())?;
writer.write_all(&self.occ_file_data_id.unwrap_or(0).to_le_bytes())?;
writer.write_all(&self.fogs_file_data_id.unwrap_or(0).to_le_bytes())?;
writer.write_all(&self.mpv_file_data_id.unwrap_or(0).to_le_bytes())?;
writer.write_all(&self.tex_file_data_id.unwrap_or(0).to_le_bytes())?;
writer.write_all(&self.wdl_file_data_id.unwrap_or(0).to_le_bytes())?;
writer.write_all(&self.pd4_file_data_id.unwrap_or(0).to_le_bytes())?;
} else {
writer.write_all(&self.something.to_le_bytes())?;
for &val in &self.unused {
writer.write_all(&val.to_le_bytes())?;
}
}
Ok(())
}
fn size(&self) -> usize {
32
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunks::Chunk;
#[test]
fn test_mphd_flags() {
let mut flags = MphdFlags::empty();
assert!(!flags.contains(MphdFlags::WDT_USES_GLOBAL_MAP_OBJ));
flags |= MphdFlags::WDT_USES_GLOBAL_MAP_OBJ;
assert!(flags.contains(MphdFlags::WDT_USES_GLOBAL_MAP_OBJ));
flags |= MphdFlags::ADT_HAS_MCCV | MphdFlags::ADT_HAS_BIG_ALPHA;
assert_eq!(flags.bits(), 0x0007);
}
#[test]
fn test_mphd_read_write() {
let mut chunk = MphdChunk::new();
chunk.flags = MphdFlags::WDT_USES_GLOBAL_MAP_OBJ | MphdFlags::ADT_HAS_HEIGHT_TEXTURING;
chunk.something = 12345;
let mut buffer = Vec::new();
chunk.write(&mut buffer).unwrap();
let mut reader = std::io::Cursor::new(buffer);
let read_chunk = MphdChunk::read(&mut reader, 32).unwrap();
assert_eq!(chunk.flags, read_chunk.flags);
assert_eq!(chunk.something, read_chunk.something);
}
#[test]
fn test_mphd_file_data_ids() {
let mut chunk = MphdChunk::new();
chunk.set_file_data_ids(FileDataIds {
lgt: 100,
occ: 101,
fogs: 102,
mpv: 103,
tex: 104,
wdl: 105,
pd4: 106,
});
assert!(chunk.has_maid());
assert_eq!(chunk.lgt_file_data_id, Some(100));
assert_eq!(chunk.pd4_file_data_id, Some(106));
let mut buffer = Vec::new();
chunk.write(&mut buffer).unwrap();
let mut reader = std::io::Cursor::new(buffer);
let read_chunk = MphdChunk::read(&mut reader, 32).unwrap();
assert_eq!(read_chunk.lgt_file_data_id, Some(100));
assert_eq!(read_chunk.pd4_file_data_id, Some(106));
}
}