use crate::chunks::WDT_MAP_SIZE;
use crate::error::{Error, Result};
use std::io::{Read, Write};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MaidSection {
RootAdt,
Obj0Adt,
Obj1Adt,
Tex0Adt,
LodAdt,
MapTexture,
MapTextureN,
MinimapTexture,
}
impl MaidSection {
pub fn all() -> &'static [MaidSection] {
&[
MaidSection::RootAdt,
MaidSection::Obj0Adt,
MaidSection::Obj1Adt,
MaidSection::Tex0Adt,
MaidSection::LodAdt,
MaidSection::MapTexture,
MaidSection::MapTextureN,
MaidSection::MinimapTexture,
]
}
pub fn index(&self) -> usize {
match self {
MaidSection::RootAdt => 0,
MaidSection::Obj0Adt => 1,
MaidSection::Obj1Adt => 2,
MaidSection::Tex0Adt => 3,
MaidSection::LodAdt => 4,
MaidSection::MapTexture => 5,
MaidSection::MapTextureN => 6,
MaidSection::MinimapTexture => 7,
}
}
pub fn name(&self) -> &'static str {
match self {
MaidSection::RootAdt => "root ADT",
MaidSection::Obj0Adt => "_obj0.adt",
MaidSection::Obj1Adt => "_obj1.adt",
MaidSection::Tex0Adt => "_tex0.adt",
MaidSection::LodAdt => "_lod.adt",
MaidSection::MapTexture => "map texture",
MaidSection::MapTextureN => "map normal texture",
MaidSection::MinimapTexture => "minimap texture",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MaidChunk {
sections: Vec<Vec<Vec<u32>>>,
}
impl MaidChunk {
pub fn new() -> Self {
let mut sections = Vec::new();
for _ in MaidSection::all() {
let mut section = Vec::with_capacity(WDT_MAP_SIZE);
for _ in 0..WDT_MAP_SIZE {
section.push(vec![0u32; WDT_MAP_SIZE]);
}
sections.push(section);
}
Self { sections }
}
pub fn get(&self, section: MaidSection, x: usize, y: usize) -> Option<u32> {
self.sections
.get(section.index())
.and_then(|s| s.get(y))
.and_then(|row| row.get(x))
.copied()
}
pub fn set(
&mut self,
section: MaidSection,
x: usize,
y: usize,
file_data_id: u32,
) -> Result<()> {
let section_data =
self.sections
.get_mut(section.index())
.ok_or_else(|| Error::InvalidChunkData {
chunk: "MAID".to_string(),
message: format!("Invalid section index: {}", section.index()),
})?;
let row = section_data
.get_mut(y)
.ok_or_else(|| Error::InvalidChunkData {
chunk: "MAID".to_string(),
message: format!("Invalid Y coordinate: {y}"),
})?;
let cell = row.get_mut(x).ok_or_else(|| Error::InvalidChunkData {
chunk: "MAID".to_string(),
message: format!("Invalid X coordinate: {x}"),
})?;
*cell = file_data_id;
Ok(())
}
pub fn get_root_adt_ids(&self) -> &Vec<Vec<u32>> {
&self.sections[MaidSection::RootAdt.index()]
}
pub fn has_tile(&self, x: usize, y: usize) -> bool {
self.get(MaidSection::RootAdt, x, y)
.map(|id| id != 0)
.unwrap_or(false)
}
pub fn count_existing_tiles(&self) -> usize {
self.sections[MaidSection::RootAdt.index()]
.iter()
.flat_map(|row| row.iter())
.filter(|&&id| id != 0)
.count()
}
pub fn section_count(&self) -> usize {
self.sections.len()
}
pub fn with_section_count(section_count: usize) -> Self {
let mut sections = Vec::new();
for _ in 0..section_count {
let mut section = Vec::with_capacity(WDT_MAP_SIZE);
for _ in 0..WDT_MAP_SIZE {
section.push(vec![0u32; WDT_MAP_SIZE]);
}
sections.push(section);
}
Self { sections }
}
}
impl Default for MaidChunk {
fn default() -> Self {
Self::new()
}
}
impl super::Chunk for MaidChunk {
fn magic() -> &'static [u8; 4] {
b"DIAM" }
fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
const ENTRIES_PER_SECTION: usize = WDT_MAP_SIZE * WDT_MAP_SIZE;
const BYTES_PER_SECTION: usize = ENTRIES_PER_SECTION * 4;
if !size.is_multiple_of(BYTES_PER_SECTION) {
return Err(Error::InvalidChunkData {
chunk: "MAID".to_string(),
message: format!(
"Size {size} is not a multiple of section size {BYTES_PER_SECTION}"
),
});
}
let section_count = size / BYTES_PER_SECTION;
let mut sections = Vec::with_capacity(section_count);
for _section_idx in 0..section_count {
let mut section = Vec::with_capacity(WDT_MAP_SIZE);
for _y in 0..WDT_MAP_SIZE {
let mut row = Vec::with_capacity(WDT_MAP_SIZE);
for _x in 0..WDT_MAP_SIZE {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
let file_data_id = u32::from_le_bytes(buf);
row.push(file_data_id);
}
section.push(row);
}
sections.push(section);
}
Ok(Self { sections })
}
fn write(&self, writer: &mut impl Write) -> Result<()> {
for section in &self.sections {
for row in section {
for &file_data_id in row {
writer.write_all(&file_data_id.to_le_bytes())?;
}
}
}
Ok(())
}
fn size(&self) -> usize {
const ENTRIES_PER_SECTION: usize = WDT_MAP_SIZE * WDT_MAP_SIZE;
const BYTES_PER_SECTION: usize = ENTRIES_PER_SECTION * 4;
self.sections.len() * BYTES_PER_SECTION
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunks::Chunk;
#[test]
fn test_maid_new() {
let maid = MaidChunk::new();
assert_eq!(maid.section_count(), 8);
assert_eq!(maid.count_existing_tiles(), 0);
}
#[test]
fn test_maid_set_get() {
let mut maid = MaidChunk::new();
maid.set(MaidSection::RootAdt, 10, 20, 12345).unwrap();
maid.set(MaidSection::Obj0Adt, 10, 20, 23456).unwrap();
assert_eq!(maid.get(MaidSection::RootAdt, 10, 20), Some(12345));
assert_eq!(maid.get(MaidSection::Obj0Adt, 10, 20), Some(23456));
assert_eq!(maid.get(MaidSection::RootAdt, 0, 0), Some(0));
assert!(maid.has_tile(10, 20));
assert!(!maid.has_tile(0, 0));
assert_eq!(maid.count_existing_tiles(), 1);
}
#[test]
fn test_maid_read_write() {
let mut maid = MaidChunk::new();
maid.set(MaidSection::RootAdt, 0, 0, 100).unwrap();
maid.set(MaidSection::RootAdt, 63, 63, 200).unwrap();
maid.set(MaidSection::Tex0Adt, 32, 32, 300).unwrap();
let mut buffer = Vec::new();
maid.write(&mut buffer).unwrap();
let mut reader = std::io::Cursor::new(buffer);
let read_maid = MaidChunk::read(&mut reader, maid.size()).unwrap();
assert_eq!(read_maid.get(MaidSection::RootAdt, 0, 0), Some(100));
assert_eq!(read_maid.get(MaidSection::RootAdt, 63, 63), Some(200));
assert_eq!(read_maid.get(MaidSection::Tex0Adt, 32, 32), Some(300));
assert_eq!(read_maid.section_count(), maid.section_count());
}
#[test]
fn test_maid_variable_sections() {
let maid = MaidChunk::with_section_count(5);
assert_eq!(maid.section_count(), 5);
let maid = MaidChunk::with_section_count(10);
assert_eq!(maid.section_count(), 10);
}
}