darkomen 0.5.0

Warhammer: Dark Omen library and CLI in Rust
Documentation
use std::{
    fmt,
    io::{BufWriter, Write},
};

use encoding_rs::WINDOWS_1252;

use super::*;

#[derive(Debug)]
pub enum EncodeError {
    IoError(std::io::Error),
    InvalidString,
    StringTooLong,
    TooManyEntries,
}

impl std::error::Error for EncodeError {}

impl From<std::io::Error> for EncodeError {
    fn from(err: std::io::Error) -> Self {
        EncodeError::IoError(err)
    }
}

impl std::fmt::Display for EncodeError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
        match self {
            EncodeError::IoError(e) => write!(f, "IO error: {e}"),
            EncodeError::InvalidString => write!(f, "invalid string"),
            EncodeError::StringTooLong => write!(f, "string too long"),
            EncodeError::TooManyEntries => write!(f, "too many entries"),
        }
    }
}

pub struct Encoder<W: Write> {
    writer: BufWriter<W>,
}

impl<W: Write> Encoder<W> {
    pub fn new(writer: W) -> Self {
        Encoder {
            writer: BufWriter::new(writer),
        }
    }

    pub fn encode(&mut self, heads_database: &HeadsDatabase) -> Result<(), EncodeError> {
        self.write_header(heads_database)?;
        self.write_entries(&heads_database.entries)?;
        Ok(())
    }

    fn write_header(&mut self, heads_database: &HeadsDatabase) -> Result<(), EncodeError> {
        if heads_database.entries.len() > 255 {
            return Err(EncodeError::TooManyEntries);
        }
        self.writer
            .write_all(&[heads_database.entries.len() as u8])?;
        Ok(())
    }

    fn write_entries(&mut self, entries: &[HeadEntry]) -> Result<(), EncodeError> {
        for entry in entries {
            let (windows_1252_bytes, _, had_errors) = WINDOWS_1252.encode(&entry.name);
            if had_errors {
                return Err(EncodeError::InvalidString);
            }

            if windows_1252_bytes.len() > 2 {
                return Err(EncodeError::StringTooLong);
            }

            let mut name_bytes = [0u8; 2];
            name_bytes[..windows_1252_bytes.len()].copy_from_slice(&windows_1252_bytes);
            self.writer.write_all(&name_bytes)?;

            self.writer.write_all(&[entry.flags.bits()])?;
            self.writer.write_all(&[entry.battle_sequences_id])?;
            self.writer.write_all(&[entry.meet_sequences_id])?;

            self.write_mouth(&entry.mouth)?;
            self.write_eyes(&entry.eyes)?;

            self.write_model_slot(&entry.body)?;
            self.write_model_slot(&entry.head)?;

            self.writer.write_all(&[entry.battle_keyframes_id])?;
            self.writer.write_all(&[entry.meet_keyframes_id])?;

            self.write_model_slot(&entry.neck)?;

            for accessory in &entry.accessories {
                self.write_model_slot(accessory)?;
            }

            self.write_model_slot(&entry.head_accessory)?;
        }
        Ok(())
    }

    fn write_model_slot(&mut self, model_slot: &ModelSlot) -> Result<(), EncodeError> {
        self.writer.write_all(&[model_slot.model_id])?;
        self.writer
            .write_all(&model_slot.translation.x.to_le_bytes())?;
        self.writer
            .write_all(&model_slot.translation.y.to_le_bytes())?;
        self.writer
            .write_all(&model_slot.translation.z.to_le_bytes())?;
        Ok(())
    }

    fn write_mouth(&mut self, mouth: &Option<Mouth>) -> Result<(), EncodeError> {
        let mouth = mouth.as_ref().unwrap_or(&Mouth {
            size: U8Vec2::ZERO,
            position: U8Vec2::ZERO,
        });
        self.writer.write_all(&mouth.size.x.to_le_bytes())?;
        self.writer.write_all(&mouth.size.y.to_le_bytes())?;
        self.writer.write_all(&mouth.position.x.to_le_bytes())?;
        self.writer.write_all(&mouth.position.y.to_le_bytes())?;
        Ok(())
    }

    fn write_eyes(&mut self, eyes: &Option<Eyes>) -> Result<(), EncodeError> {
        let eyes = eyes.as_ref().unwrap_or(&Eyes {
            size: U8Vec2::ZERO,
            position: U8Vec2::ZERO,
        });
        self.writer.write_all(&eyes.size.x.to_le_bytes())?;
        self.writer.write_all(&eyes.size.y.to_le_bytes())?;
        self.writer.write_all(&eyes.position.x.to_le_bytes())?;
        self.writer.write_all(&eyes.position.y.to_le_bytes())?;
        Ok(())
    }
}