use std::io::{Read, Seek};
use std::time::{Duration, Instant};
use crate::chunk_discovery::{ChunkDiscovery, discover_chunks};
use crate::chunks::mh2o::Mh2oChunk;
use crate::chunks::{
DoodadPlacement, MampChunk, MbbbChunk, MbmhChunk, MbmiChunk, MbnvChunk, McalChunk, McinChunk,
MclyChunk, McnkChunk, MfboChunk, MhdrChunk, MtxfChunk, MtxpChunk, WmoPlacement,
};
use crate::error::Result;
use crate::file_type::AdtFileType;
use crate::version::AdtVersion;
#[derive(Debug, Clone)]
pub struct RootAdt {
pub version: AdtVersion,
pub mhdr: MhdrChunk,
pub mcin: McinChunk,
pub textures: Vec<String>,
pub models: Vec<String>,
pub model_indices: Vec<u32>,
pub wmos: Vec<String>,
pub wmo_indices: Vec<u32>,
pub doodad_placements: Vec<DoodadPlacement>,
pub wmo_placements: Vec<WmoPlacement>,
pub mcnk_chunks: Vec<McnkChunk>,
pub flight_bounds: Option<MfboChunk>,
pub water_data: Option<Mh2oChunk>,
pub texture_flags: Option<MtxfChunk>,
pub texture_amplifier: Option<MampChunk>,
pub texture_params: Option<MtxpChunk>,
pub blend_mesh_headers: Option<MbmhChunk>,
pub blend_mesh_bounds: Option<MbbbChunk>,
pub blend_mesh_vertices: Option<MbnvChunk>,
pub blend_mesh_indices: Option<MbmiChunk>,
}
impl RootAdt {
#[must_use]
pub fn has_water(&self) -> bool {
self.water_data.is_some()
}
#[must_use]
pub fn has_flight_bounds(&self) -> bool {
self.flight_bounds.is_some()
}
#[must_use]
pub fn terrain_chunk_count(&self) -> usize {
self.mcnk_chunks.len()
}
#[must_use]
pub fn texture_count(&self) -> usize {
self.textures.len()
}
#[must_use]
pub fn model_count(&self) -> usize {
self.models.len()
}
#[must_use]
pub fn wmo_count(&self) -> usize {
self.wmos.len()
}
pub fn textures_mut(&mut self) -> &mut Vec<String> {
&mut self.textures
}
pub fn models_mut(&mut self) -> &mut Vec<String> {
&mut self.models
}
pub fn wmos_mut(&mut self) -> &mut Vec<String> {
&mut self.wmos
}
pub fn doodad_placements_mut(&mut self) -> &mut Vec<DoodadPlacement> {
&mut self.doodad_placements
}
pub fn wmo_placements_mut(&mut self) -> &mut Vec<WmoPlacement> {
&mut self.wmo_placements
}
pub fn mcnk_chunks_mut(&mut self) -> &mut Vec<McnkChunk> {
&mut self.mcnk_chunks
}
pub fn water_data_mut(&mut self) -> Option<&mut Mh2oChunk> {
self.water_data.as_mut()
}
pub fn flight_bounds_mut(&mut self) -> Option<&mut MfboChunk> {
self.flight_bounds.as_mut()
}
pub fn texture_flags_mut(&mut self) -> Option<&mut MtxfChunk> {
self.texture_flags.as_mut()
}
pub fn texture_amplifier_mut(&mut self) -> Option<&mut MampChunk> {
self.texture_amplifier.as_mut()
}
pub fn texture_params_mut(&mut self) -> Option<&mut MtxpChunk> {
self.texture_params.as_mut()
}
}
#[derive(Debug, Clone)]
pub struct McnkChunkTexture {
pub index: usize,
pub layers: Option<MclyChunk>,
pub alpha_maps: Option<McalChunk>,
}
#[derive(Debug, Clone)]
pub struct McnkChunkObject {
pub index: usize,
pub doodad_refs: Vec<u32>,
pub wmo_refs: Vec<u32>,
}
#[derive(Debug, Clone)]
pub struct Tex0Adt {
pub version: AdtVersion,
pub textures: Vec<String>,
pub texture_params: Option<MtxpChunk>,
pub mcnk_textures: Vec<McnkChunkTexture>,
}
#[derive(Debug, Clone)]
pub struct Obj0Adt {
pub version: AdtVersion,
pub models: Vec<String>,
pub model_indices: Vec<u32>,
pub wmos: Vec<String>,
pub wmo_indices: Vec<u32>,
pub doodad_placements: Vec<DoodadPlacement>,
pub wmo_placements: Vec<WmoPlacement>,
pub mcnk_objects: Vec<McnkChunkObject>,
}
#[derive(Debug, Clone)]
pub struct LodAdt {
pub version: AdtVersion,
}
pub type TextureAdt = Tex0Adt;
pub type ObjectAdt = Obj0Adt;
#[derive(Debug, Clone)]
pub enum ParsedAdt {
Root(Box<RootAdt>),
Tex0(Tex0Adt),
Tex1(Tex0Adt),
Obj0(Obj0Adt),
Obj1(Obj0Adt),
Lod(LodAdt),
}
impl ParsedAdt {
#[must_use]
pub const fn is_root(&self) -> bool {
matches!(self, Self::Root(_))
}
#[must_use]
pub const fn is_split(&self) -> bool {
!self.is_root()
}
#[must_use]
pub const fn file_type(&self) -> AdtFileType {
match self {
Self::Root(_) => AdtFileType::Root,
Self::Tex0(_) => AdtFileType::Tex0,
Self::Tex1(_) => AdtFileType::Tex1,
Self::Obj0(_) => AdtFileType::Obj0,
Self::Obj1(_) => AdtFileType::Obj1,
Self::Lod(_) => AdtFileType::Lod,
}
}
#[must_use]
pub fn version(&self) -> AdtVersion {
match self {
Self::Root(r) => r.version,
Self::Tex0(t) | Self::Tex1(t) => t.version,
Self::Obj0(o) | Self::Obj1(o) => o.version,
Self::Lod(l) => l.version,
}
}
}
#[derive(Debug, Clone)]
pub struct AdtMetadata {
pub version: AdtVersion,
pub file_type: AdtFileType,
pub chunk_count: usize,
pub discovery_duration: Duration,
pub parse_duration: Duration,
pub warnings: Vec<String>,
pub discovery: ChunkDiscovery,
}
pub fn parse_adt<R: Read + Seek>(reader: &mut R) -> Result<ParsedAdt> {
let (adt, _metadata) = parse_adt_with_metadata(reader)?;
Ok(adt)
}
pub fn parse_adt_with_metadata<R: Read + Seek>(reader: &mut R) -> Result<(ParsedAdt, AdtMetadata)> {
let discovery_start = Instant::now();
let discovery = discover_chunks(reader)?;
let discovery_duration = discovery_start.elapsed();
let version = AdtVersion::from_discovery(&discovery);
let file_type = AdtFileType::from_discovery(&discovery);
log::debug!(
"Discovered {} chunks, version: {:?}, file type: {:?}",
discovery.total_chunks,
version,
file_type
);
let parse_start = Instant::now();
let (adt, warnings) = match file_type {
AdtFileType::Root => {
let (root, warnings) = crate::root_parser::parse_root_adt(reader, &discovery, version)?;
(ParsedAdt::Root(Box::new(root)), warnings)
}
AdtFileType::Tex0 | AdtFileType::Tex1 => {
let (tex, warnings) = crate::split_parser::parse_tex_adt(reader, &discovery, version)?;
let adt = if file_type == AdtFileType::Tex0 {
ParsedAdt::Tex0(tex)
} else {
ParsedAdt::Tex1(tex)
};
(adt, warnings)
}
AdtFileType::Obj0 | AdtFileType::Obj1 => {
let (obj, warnings) = crate::split_parser::parse_obj_adt(reader, &discovery, version)?;
let adt = if file_type == AdtFileType::Obj0 {
ParsedAdt::Obj0(obj)
} else {
ParsedAdt::Obj1(obj)
};
(adt, warnings)
}
AdtFileType::Lod => {
let (lod, warnings) = crate::split_parser::parse_lod_adt(reader, &discovery, version)?;
(ParsedAdt::Lod(lod), warnings)
}
};
let parse_duration = parse_start.elapsed();
let metadata = AdtMetadata {
version,
file_type,
chunk_count: discovery.total_chunks,
discovery_duration,
parse_duration,
warnings,
discovery,
};
Ok((adt, metadata))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parsed_adt_is_root() {
let root = RootAdt {
version: AdtVersion::WotLK,
mhdr: MhdrChunk::default(),
mcin: McinChunk {
entries: McinChunk::default().entries,
},
textures: vec![],
models: vec![],
model_indices: vec![],
wmos: vec![],
wmo_indices: vec![],
doodad_placements: vec![],
wmo_placements: vec![],
mcnk_chunks: vec![],
flight_bounds: None,
water_data: None,
texture_flags: None,
texture_amplifier: None,
texture_params: None,
blend_mesh_headers: None,
blend_mesh_bounds: None,
blend_mesh_vertices: None,
blend_mesh_indices: None,
};
let parsed = ParsedAdt::Root(Box::new(root));
assert!(parsed.is_root());
assert!(!parsed.is_split());
assert_eq!(parsed.file_type(), AdtFileType::Root);
}
#[test]
fn test_parsed_adt_is_split() {
let tex = Tex0Adt {
version: AdtVersion::Cataclysm,
textures: vec![],
texture_params: None,
mcnk_textures: vec![],
};
let parsed = ParsedAdt::Tex0(tex);
assert!(!parsed.is_root());
assert!(parsed.is_split());
assert_eq!(parsed.file_type(), AdtFileType::Tex0);
}
#[test]
fn test_root_adt_helpers() {
let root = RootAdt {
version: AdtVersion::WotLK,
mhdr: MhdrChunk::default(),
mcin: McinChunk {
entries: McinChunk::default().entries,
},
textures: vec!["texture1.blp".into(), "texture2.blp".into()],
models: vec!["model1.m2".into()],
model_indices: vec![0],
wmos: vec![],
wmo_indices: vec![],
doodad_placements: vec![],
wmo_placements: vec![],
mcnk_chunks: vec![],
flight_bounds: None,
water_data: None,
texture_flags: None,
texture_amplifier: None,
texture_params: None,
blend_mesh_headers: None,
blend_mesh_bounds: None,
blend_mesh_vertices: None,
blend_mesh_indices: None,
};
assert_eq!(root.texture_count(), 2);
assert_eq!(root.model_count(), 1);
assert_eq!(root.wmo_count(), 0);
assert_eq!(root.terrain_chunk_count(), 0);
assert!(!root.has_water());
assert!(!root.has_flight_bounds());
}
}