use std::collections::HashMap;
use std::io::{self, Read, Seek, Write};
use crate::error::{Result, WdlError};
use crate::version::WdlVersion;
pub const MVER_MAGIC: [u8; 4] = [b'R', b'E', b'V', b'M'];
pub const MWMO_MAGIC: [u8; 4] = [b'O', b'M', b'W', b'M'];
pub const MWID_MAGIC: [u8; 4] = [b'D', b'I', b'W', b'M'];
pub const MODF_MAGIC: [u8; 4] = [b'F', b'D', b'O', b'M'];
pub const MAOF_MAGIC: [u8; 4] = [b'F', b'O', b'A', b'M'];
pub const MARE_MAGIC: [u8; 4] = [b'E', b'R', b'A', b'M'];
pub const MAHO_MAGIC: [u8; 4] = [b'O', b'H', b'A', b'M'];
pub const MLDD_MAGIC: [u8; 4] = [b'D', b'D', b'L', b'M'];
pub const MLDX_MAGIC: [u8; 4] = [b'X', b'D', b'L', b'M'];
pub const MLMD_MAGIC: [u8; 4] = [b'D', b'M', b'L', b'M'];
pub const MLMX_MAGIC: [u8; 4] = [b'X', b'M', b'L', b'M'];
#[derive(Debug, Clone, PartialEq)]
pub struct Vec3d {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vec3d {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub fn origin() -> Self {
Self::new(0.0, 0.0, 0.0)
}
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
let x = f32::from_le_bytes(buf);
reader.read_exact(&mut buf)?;
let y = f32::from_le_bytes(buf);
reader.read_exact(&mut buf)?;
let z = f32::from_le_bytes(buf);
Ok(Self::new(x, y, z))
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.x.to_le_bytes())?;
writer.write_all(&self.y.to_le_bytes())?;
writer.write_all(&self.z.to_le_bytes())?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BoundingBox {
pub min: Vec3d,
pub max: Vec3d,
}
impl BoundingBox {
pub fn new(min: Vec3d, max: Vec3d) -> Self {
Self { min, max }
}
pub fn zero() -> Self {
Self::new(Vec3d::origin(), Vec3d::origin())
}
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let min = Vec3d::read(reader)?;
let max = Vec3d::read(reader)?;
Ok(Self::new(min, max))
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.min.write(writer)?;
self.max.write(writer)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Chunk {
pub magic: [u8; 4],
pub size: u32,
pub data: Vec<u8>,
}
impl Chunk {
pub fn new(magic: [u8; 4], data: Vec<u8>) -> Self {
let size = data.len() as u32;
Self { magic, size, data }
}
pub fn read<R: Read + Seek>(reader: &mut R) -> io::Result<Self> {
let mut magic = [0u8; 4];
if reader.read_exact(&mut magic).is_err() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"unexpected end of file",
));
}
let mut size_buf = [0u8; 4];
reader.read_exact(&mut size_buf)?;
let size = u32::from_le_bytes(size_buf);
let mut data = vec![0u8; size as usize];
reader.read_exact(&mut data)?;
Ok(Self { magic, size, data })
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.magic)?;
writer.write_all(&self.size.to_le_bytes())?;
writer.write_all(&self.data)?;
Ok(())
}
pub fn magic_str(&self) -> String {
String::from_utf8_lossy(&self.magic).to_string()
}
}
#[derive(Debug, Clone)]
pub struct ModelPlacement {
pub id: u32,
pub wmo_id: u32,
pub position: Vec3d,
pub rotation: Vec3d,
pub bounds: BoundingBox,
pub flags: u16,
pub doodad_set: u16,
pub name_set: u16,
pub padding: u16,
}
impl ModelPlacement {
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut buf4 = [0u8; 4];
let mut buf2 = [0u8; 2];
reader.read_exact(&mut buf4)?;
let wmo_id = u32::from_le_bytes(buf4);
reader.read_exact(&mut buf4)?;
let id = u32::from_le_bytes(buf4);
let position = Vec3d::read(reader)?;
let rotation = Vec3d::read(reader)?;
let bounds = BoundingBox::read(reader)?;
reader.read_exact(&mut buf2)?;
let flags = u16::from_le_bytes(buf2);
reader.read_exact(&mut buf2)?;
let doodad_set = u16::from_le_bytes(buf2);
reader.read_exact(&mut buf2)?;
let name_set = u16::from_le_bytes(buf2);
reader.read_exact(&mut buf2)?;
let padding = u16::from_le_bytes(buf2);
Ok(Self {
id,
wmo_id,
position,
rotation,
bounds,
flags,
doodad_set,
name_set,
padding,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.wmo_id.to_le_bytes())?;
writer.write_all(&self.id.to_le_bytes())?;
self.position.write(writer)?;
self.rotation.write(writer)?;
self.bounds.write(writer)?;
writer.write_all(&self.flags.to_le_bytes())?;
writer.write_all(&self.doodad_set.to_le_bytes())?;
writer.write_all(&self.name_set.to_le_bytes())?;
writer.write_all(&self.padding.to_le_bytes())?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct M2Placement {
pub id: u32,
pub m2_id: u32,
pub position: Vec3d,
pub rotation: Vec3d,
pub scale: f32,
pub flags: u32,
}
impl M2Placement {
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
let id = u32::from_le_bytes(buf);
reader.read_exact(&mut buf)?;
let m2_id = u32::from_le_bytes(buf);
let position = Vec3d::read(reader)?;
let rotation = Vec3d::read(reader)?;
reader.read_exact(&mut buf)?;
let scale = f32::from_le_bytes(buf);
reader.read_exact(&mut buf)?;
let flags = u32::from_le_bytes(buf);
Ok(Self {
id,
m2_id,
position,
rotation,
scale,
flags,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.id.to_le_bytes())?;
writer.write_all(&self.m2_id.to_le_bytes())?;
self.position.write(writer)?;
self.rotation.write(writer)?;
writer.write_all(&self.scale.to_le_bytes())?;
writer.write_all(&self.flags.to_le_bytes())?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct M2VisibilityInfo {
pub bounds: BoundingBox,
pub radius: f32,
}
impl M2VisibilityInfo {
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let bounds = BoundingBox::read(reader)?;
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
let radius = f32::from_le_bytes(buf);
Ok(Self { bounds, radius })
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.bounds.write(writer)?;
writer.write_all(&self.radius.to_le_bytes())?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct HeightMapTile {
pub outer_values: Vec<i16>,
pub inner_values: Vec<i16>,
}
impl Default for HeightMapTile {
fn default() -> Self {
Self::new()
}
}
impl HeightMapTile {
pub const OUTER_COUNT: usize = 17 * 17;
pub const INNER_COUNT: usize = 16 * 16;
pub const TOTAL_COUNT: usize = Self::OUTER_COUNT + Self::INNER_COUNT;
pub fn new() -> Self {
Self {
outer_values: vec![0; Self::OUTER_COUNT],
inner_values: vec![0; Self::INNER_COUNT],
}
}
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut outer_values = Vec::with_capacity(Self::OUTER_COUNT);
let mut buf = [0u8; 2];
for _ in 0..Self::OUTER_COUNT {
reader.read_exact(&mut buf)?;
outer_values.push(i16::from_le_bytes(buf));
}
let mut inner_values = Vec::with_capacity(Self::INNER_COUNT);
for _ in 0..Self::INNER_COUNT {
reader.read_exact(&mut buf)?;
inner_values.push(i16::from_le_bytes(buf));
}
Ok(Self {
outer_values,
inner_values,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
for value in &self.outer_values {
writer.write_all(&value.to_le_bytes())?;
}
for value in &self.inner_values {
writer.write_all(&value.to_le_bytes())?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct HolesData {
pub hole_masks: [u16; 16],
}
impl Default for HolesData {
fn default() -> Self {
Self::new()
}
}
impl HolesData {
pub const MASK_COUNT: usize = 16;
pub fn new() -> Self {
Self {
hole_masks: [0xFFFF; Self::MASK_COUNT],
}
}
pub fn all_holes() -> Self {
Self {
hole_masks: [0; Self::MASK_COUNT],
}
}
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut hole_masks = [0u16; Self::MASK_COUNT];
let mut buf = [0u8; 2];
for mask in &mut hole_masks {
reader.read_exact(&mut buf)?;
*mask = u16::from_le_bytes(buf);
}
Ok(Self { hole_masks })
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
for mask in &self.hole_masks {
writer.write_all(&mask.to_le_bytes())?;
}
Ok(())
}
pub fn has_hole(&self, x: usize, y: usize) -> bool {
if x >= 16 || y >= 16 {
return false;
}
let mask = self.hole_masks[y];
(mask & (1 << x)) == 0
}
pub fn set_hole(&mut self, x: usize, y: usize, has_hole: bool) {
if x >= 16 || y >= 16 {
return;
}
if has_hole {
self.hole_masks[y] &= !(1 << x);
} else {
self.hole_masks[y] |= 1 << x;
}
}
}
#[derive(Debug)]
pub struct WdlFile {
pub version: WdlVersion,
pub version_number: u32,
pub map_tile_offsets: [u32; 64 * 64],
pub heightmap_tiles: HashMap<(u32, u32), HeightMapTile>,
pub holes_data: HashMap<(u32, u32), HolesData>,
pub wmo_filenames: Vec<String>,
pub wmo_indices: Vec<u32>,
pub wmo_placements: Vec<ModelPlacement>,
pub m2_placements: Vec<M2Placement>,
pub m2_visibility: Vec<M2VisibilityInfo>,
pub wmo_legion_placements: Vec<M2Placement>,
pub wmo_legion_visibility: Vec<M2VisibilityInfo>,
pub chunks: Vec<Chunk>,
}
impl WdlFile {
pub fn new() -> Self {
Self {
version: WdlVersion::default(),
version_number: WdlVersion::default().version_number(),
map_tile_offsets: [0; 64 * 64],
heightmap_tiles: HashMap::new(),
holes_data: HashMap::new(),
wmo_filenames: Vec::new(),
wmo_indices: Vec::new(),
wmo_placements: Vec::new(),
m2_placements: Vec::new(),
m2_visibility: Vec::new(),
wmo_legion_placements: Vec::new(),
wmo_legion_visibility: Vec::new(),
chunks: Vec::new(),
}
}
pub fn with_version(version: WdlVersion) -> Self {
let mut file = Self::new();
file.version = version;
file.version_number = version.version_number();
file
}
pub fn validate(&self) -> Result<()> {
if self.version_number != self.version.version_number() {
return Err(WdlError::ValidationError(format!(
"Version number mismatch: file has {}, expected {}",
self.version_number,
self.version.version_number()
)));
}
Ok(())
}
pub fn convert_to(&self, target_version: WdlVersion) -> Result<Self> {
let mut new_file = WdlFile::with_version(target_version);
new_file.map_tile_offsets = self.map_tile_offsets;
new_file.heightmap_tiles = self.heightmap_tiles.clone();
new_file.holes_data = self.holes_data.clone();
if target_version.has_wmo_chunks() {
new_file.wmo_filenames = self.wmo_filenames.clone();
new_file.wmo_indices = self.wmo_indices.clone();
new_file.wmo_placements = self.wmo_placements.clone();
}
if target_version.has_ml_chunks() {
new_file.m2_placements = self.m2_placements.clone();
new_file.m2_visibility = self.m2_visibility.clone();
new_file.wmo_legion_placements = self.wmo_legion_placements.clone();
new_file.wmo_legion_visibility = self.wmo_legion_visibility.clone();
if !self.version.has_ml_chunks() && self.version.has_wmo_chunks() {
}
}
Ok(new_file)
}
}
impl Default for WdlFile {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vec3d() {
let vec = Vec3d::new(1.0, 2.0, 3.0);
assert_eq!(vec.x, 1.0);
assert_eq!(vec.y, 2.0);
assert_eq!(vec.z, 3.0);
let origin = Vec3d::origin();
assert_eq!(origin.x, 0.0);
assert_eq!(origin.y, 0.0);
assert_eq!(origin.z, 0.0);
}
#[test]
fn test_bounding_box() {
let min = Vec3d::new(1.0, 2.0, 3.0);
let max = Vec3d::new(4.0, 5.0, 6.0);
let bbox = BoundingBox::new(min.clone(), max.clone());
assert_eq!(bbox.min, min);
assert_eq!(bbox.max, max);
let zero = BoundingBox::zero();
assert_eq!(zero.min, Vec3d::origin());
assert_eq!(zero.max, Vec3d::origin());
}
#[test]
fn test_holes_data() {
let mut holes = HolesData::new();
for x in 0..16 {
for y in 0..16 {
assert!(!holes.has_hole(x, y));
}
}
holes.set_hole(5, 7, true);
assert!(holes.has_hole(5, 7));
holes.set_hole(5, 7, false);
assert!(!holes.has_hole(5, 7));
let all_holes = HolesData::all_holes();
for x in 0..16 {
for y in 0..16 {
assert!(all_holes.has_hole(x, y));
}
}
}
}