use crate::io_ext::{ReadExt, WriteExt};
use std::io::{Read, Seek, Write};
use crate::common::M2ArrayString;
use crate::error::Result;
use crate::version::M2Version;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum M2TextureType {
Hardcoded = 0,
Body = 1,
Item = 2,
WeaponArmorBasic = 3,
WeaponBlade = 4,
WeaponHandle = 5,
Environment = 6,
Hair = 7,
SkinExtra = 8,
UiSkin = 9,
TaurenMane = 10,
Monster1 = 11,
Monster2 = 12,
Monster3 = 13,
ItemIcon = 14,
Unknown = 255,
}
impl M2TextureType {
pub fn from_u32(value: u32) -> Option<Self> {
match value {
0 => Some(Self::Hardcoded),
1 => Some(Self::Body),
2 => Some(Self::Item),
3 => Some(Self::WeaponArmorBasic),
4 => Some(Self::WeaponBlade),
5 => Some(Self::WeaponHandle),
6 => Some(Self::Environment),
7 => Some(Self::Hair),
8 => Some(Self::SkinExtra),
9 => Some(Self::UiSkin),
10 => Some(Self::TaurenMane),
11 => Some(Self::Monster1),
12 => Some(Self::Monster2),
13 => Some(Self::Monster3),
14 => Some(Self::ItemIcon),
_ => None,
}
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct M2TextureFlags: u32 {
const WRAP_X = 0x01;
const WRAP_Y = 0x02;
const NOT_REPLACEABLE = 0x04;
}
}
#[derive(Debug, Clone)]
pub struct M2Texture {
pub texture_type: M2TextureType,
pub flags: M2TextureFlags,
pub filename: M2ArrayString,
}
impl M2Texture {
pub fn parse<R: Read + Seek>(reader: &mut R, _version: u32) -> Result<Self> {
let texture_type_raw = reader.read_u32_le()?;
let texture_type =
M2TextureType::from_u32(texture_type_raw).unwrap_or(M2TextureType::Unknown);
let flags = M2TextureFlags::from_bits_retain(reader.read_u32_le()?);
let filename = M2ArrayString::parse(reader)?;
Ok(Self {
texture_type,
flags,
filename,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u32_le(self.texture_type as u32)?;
writer.write_u32_le(self.flags.bits())?;
self.filename.write(writer)?;
Ok(())
}
pub fn convert(&self, _target_version: M2Version) -> Self {
self.clone()
}
pub fn new(texture_type: M2TextureType, filename: M2ArrayString) -> Self {
Self {
texture_type,
flags: M2TextureFlags::empty(),
filename,
}
}
}
#[cfg(test)]
mod tests {
use crate::common::{FixedString, M2Array};
use super::*;
use std::io::{Cursor, SeekFrom};
#[test]
fn test_texture_parse() {
let mut data = Vec::new();
let dummy = [0, 0, 0];
data.extend_from_slice(&dummy);
let filename_str = "test\0";
data.extend_from_slice(filename_str.as_bytes());
data.extend_from_slice(&1u32.to_le_bytes());
data.extend_from_slice(&3u32.to_le_bytes());
data.extend_from_slice(&(filename_str.len() as u32).to_le_bytes());
data.extend_from_slice(&(dummy.len() as u32).to_le_bytes());
let mut cursor = Cursor::new(data);
cursor
.seek(SeekFrom::Start((filename_str.len() + dummy.len()) as u64))
.unwrap();
let texture =
M2Texture::parse(&mut cursor, M2Version::Vanilla.to_header_version()).unwrap();
assert_eq!(texture.texture_type, M2TextureType::Body);
assert_eq!(
texture.flags,
M2TextureFlags::WRAP_X | M2TextureFlags::WRAP_Y
);
assert_eq!(texture.filename.array.count, 5);
assert_eq!(texture.filename.array.offset, 3);
}
#[test]
fn test_texture_write() {
let texture = M2Texture {
texture_type: M2TextureType::Body,
flags: M2TextureFlags::WRAP_X | M2TextureFlags::WRAP_Y,
filename: M2ArrayString {
string: FixedString { data: Vec::new() },
array: M2Array::new(10, 0x100),
},
};
let mut data = Vec::new();
texture.write(&mut data).unwrap();
assert_eq!(
data,
[
1, 0, 0, 0, 3, 0, 0, 0, 10, 0, 0, 0, 0, 1, 0, 0, ]
);
}
#[test]
fn test_texture_conversion() {
let texture = M2Texture {
texture_type: M2TextureType::Body,
flags: M2TextureFlags::WRAP_X | M2TextureFlags::WRAP_Y,
filename: M2ArrayString {
string: FixedString { data: Vec::new() },
array: M2Array::new(10, 0x100),
},
};
let converted = texture.convert(M2Version::Cataclysm);
assert_eq!(converted.texture_type, texture.texture_type);
assert_eq!(converted.flags, texture.flags);
assert_eq!(converted.filename.array.count, texture.filename.array.count);
assert_eq!(
converted.filename.array.offset,
texture.filename.array.offset
);
}
}