use std::io::Write;
use byteorder::{BigEndian, WriteBytesExt};
use crate::error::UffError;
use crate::types::*;
const MAGIC: [u8; 4] = [b'U', b'F', b'F', 0x00];
fn write_str<W: Write>(w: &mut W, s: &str) -> Result<(), UffError> {
let bytes = s.as_bytes();
w.write_u16::<BigEndian>(bytes.len() as u16)?;
w.write_all(bytes)?;
Ok(())
}
fn write_hitbox<W: Write>(w: &mut W, h: &Hitbox) -> Result<(), UffError> {
write_str(w, &h.name)?;
w.write_u8(h.box_type as u8)?;
let flags = (h.enabled as u8) | ((h.knockback as u8) << 1);
w.write_u8(flags)?;
w.write_f32::<BigEndian>(h.x)?;
w.write_f32::<BigEndian>(h.y)?;
w.write_f32::<BigEndian>(h.width)?;
w.write_f32::<BigEndian>(h.height)?;
w.write_u16::<BigEndian>(h.damage)?;
w.write_u16::<BigEndian>(h.hitstun)?;
w.write_u16::<BigEndian>(h.blockstun)?;
w.write_f32::<BigEndian>(h.knockback_angle)?;
w.write_f32::<BigEndian>(h.knockback_strength)?;
w.write_f32::<BigEndian>(h.rotation)?;
Ok(())
}
fn write_audio_cue<W: Write>(w: &mut W, c: &AudioCue) -> Result<(), UffError> {
write_str(w, &c.clip_id)?;
w.write_f32::<BigEndian>(c.volume)?;
w.write_f32::<BigEndian>(c.pitch)?;
Ok(())
}
fn write_frame_entry<W: Write>(w: &mut W, f: &HitboxFrame) -> Result<(), UffError> {
w.write_u16::<BigEndian>(f.id)?;
write_str(w, &f.label)?;
write_str(w, &f.program)?;
write_str(w, &f.tags)?;
w.write_u16::<BigEndian>(f.hitboxes.len() as u16)?;
for h in &f.hitboxes {
write_hitbox(w, h)?;
}
w.write_u16::<BigEndian>(f.audio_cues.len() as u16)?;
for c in &f.audio_cues {
write_audio_cue(w, c)?;
}
Ok(())
}
fn write_sprite_entry<W: Write>(w: &mut W, e: &SpriteEntry) -> Result<(), UffError> {
w.write_u16::<BigEndian>(e.src_x)?;
w.write_u16::<BigEndian>(e.src_y)?;
w.write_u16::<BigEndian>(e.src_w)?;
w.write_u16::<BigEndian>(e.src_h)?;
w.write_i16::<BigEndian>(e.pivot_x)?;
w.write_i16::<BigEndian>(e.pivot_y)?;
w.write_u8(e.entry_flags)?;
w.write_u8(e.tag)?;
w.write_u16::<BigEndian>(e.duration_ms)?;
Ok(())
}
fn serialise_anim(anim: &UffAnimation) -> Result<Vec<u8>, UffError> {
let mut buf: Vec<u8> = Vec::new();
let hitbox_buf: Vec<u8> = if !anim.frames.is_empty() {
let mut hb: Vec<u8> = Vec::new();
for f in &anim.frames {
write_frame_entry(&mut hb, f)?;
}
hb
} else {
Vec::new()
};
let pixel_size = anim.pixel_data.as_ref().map_or(0u32, |d| d.len() as u32);
let name_bytes = anim.name.as_bytes();
buf.write_u16::<BigEndian>(name_bytes.len() as u16)?;
buf.write_u16::<BigEndian>(anim.sprite_table.len() as u16)?;
buf.write_u16::<BigEndian>(anim.default_fps)?;
buf.write_u8(anim.loop_mode as u8)?;
buf.write_u8(anim.pixel_flags)?;
buf.write_u16::<BigEndian>(anim.sheet_w)?;
buf.write_u16::<BigEndian>(anim.sheet_h)?;
buf.write_u16::<BigEndian>(anim.frame_w)?;
buf.write_u16::<BigEndian>(anim.frame_h)?;
buf.write_u32::<BigEndian>(hitbox_buf.len() as u32)?;
buf.write_u32::<BigEndian>(pixel_size)?;
buf.write_u16::<BigEndian>(anim.floor_y_override)?;
buf.write_all(name_bytes)?;
for e in &anim.sprite_table {
write_sprite_entry(&mut buf, e)?;
}
if !hitbox_buf.is_empty() {
buf.write_all(&hitbox_buf)?;
}
if let Some(px) = &anim.pixel_data {
buf.write_all(px)?;
}
Ok(buf)
}
pub fn write_uff(pkg: &UffPackage) -> Result<Vec<u8>, UffError> {
let anim_bufs: Vec<Vec<u8>> = pkg.animations.iter()
.map(serialise_anim)
.collect::<Result<_, _>>()?;
let char_name_bytes = pkg.char_name.as_bytes();
let anim_count = pkg.animations.len() as u16;
let dir_offset: u32 = 24 + 2 + char_name_bytes.len() as u32;
let first_anim_offset: u32 = dir_offset + anim_count as u32 * 4;
let mut offsets: Vec<u32> = Vec::with_capacity(anim_count as usize);
let mut cursor = first_anim_offset;
for buf in &anim_bufs {
offsets.push(cursor);
cursor += buf.len() as u32;
}
let mut out: Vec<u8> = Vec::new();
out.write_all(&MAGIC)?;
out.write_u8(1)?; out.write_u8(0)?; out.write_u16::<BigEndian>(anim_count)?;
out.write_u32::<BigEndian>(dir_offset)?;
out.write_u32::<BigEndian>(0)?; out.write_u16::<BigEndian>(pkg.floor_y)?;
out.write_all(&[0u8; 6])?;
out.write_u16::<BigEndian>(char_name_bytes.len() as u16)?;
out.write_all(char_name_bytes)?;
for off in &offsets {
out.write_u32::<BigEndian>(*off)?;
}
for buf in &anim_bufs {
out.write_all(buf)?;
}
Ok(out)
}