use teleia::*; use crate::overlay; use rand::Rng; use std::{collections::HashMap, io::{Seek, SeekFrom}}; use byteorder::{LE, ReadBytesExt}; struct Bitstream { bytes: Vec, idx: usize, bidx: usize, } impl Bitstream { pub fn new(bytes: Vec) -> Self { Self { bytes, idx: 0, bidx: 0 } } fn pop_bit(&mut self) -> Option { let w = self.bytes.get(self.idx)?; let ret = (w >> self.bidx) & 0b1; self.bidx += 1; if self.bidx > 7 { self.bidx = 0; self.idx += 1; } Some(ret == 1) } fn pop_bits(&mut self, count: usize) -> Option { let mut ret = 0; for shift in 0..count { ret |= (self.pop_bit()? as u32) << shift; } Some(ret) } fn pop_byte(&mut self) -> Option { Some(self.pop_bits(8)? as u8) } } fn decompress(bytes: Vec) -> Option> { let mut ret = Vec::new(); let mut bits = Bitstream::new(bytes); if bits.pop_byte()? != 0 { panic!("no leading 0 byte"); } while let Some(bty) = bits.pop_bit() { match bty { false => ret.push(bits.pop_byte()?), true => { let mut bytes_to_read = 2; let mut off_sequential_ones = 0; for _ in 0..3 { if bits.pop_bit()? { off_sequential_ones += 1; } else { break; } } let (bitcount, addend) = match off_sequential_ones { 0 => (6, 1), 1 => (9, 65), 2 => (12, 577), 3 => (20, 4673), _ => unreachable!("bad sequential bits"), }; let mut num = bits.pop_bits(bitcount)?; if bitcount == 20 { if num == 0x000FFFFF { break; } else { bytes_to_read += 1; } } num += addend; let idx = ret.len() - num as usize; let mut sequential_ones = 0; for i in 0..12 { if i == 11 { if bits.pop_bit()? { panic!("malformed data! expected 0-bit terminator!"); } break; } else { if bits.pop_bit()? { sequential_ones += 1; } else { break; } } } bytes_to_read += (1 << sequential_ones) - 1; bytes_to_read += bits.pop_bits(sequential_ones)? as usize; for i in 0..bytes_to_read { ret.push(ret[idx + i]); } }, }} Some(ret) } fn parse_string(r: &mut R) -> Erm where R: ReadBytesExt + Seek { let len = r.read_u32::()?; // account for null terminator if len == 0 { return Ok(String::new()) } let mut bs = vec![0; (len * 2 + 2) as usize]; r.read_exact(&mut bs)?; Ok(String::from_utf16le(&bs[0..bs.len() - 2])?) } type RGB = [u8; 3]; fn parse_rgb(r: &mut R) -> Erm where R: ReadBytesExt + Seek { let cb = r.read_u8()?; let cg = r.read_u8()?; let cr = r.read_u8()?; let _ = r.read_u8()?; Ok([cr, cg, cb]) } #[derive(Debug)] struct Locator { offset: u32, size: u32, } impl Locator { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { Ok(Self { offset: r.read_u32::()?, size: r.read_u32::()?, }) } fn seek(&self, r: &mut R) -> Erm<()> where R: Seek { r.seek(SeekFrom::Start(self.offset as _))?; Ok(()) } } #[derive(Debug)] struct GUID(u32, u16, u16, [u8; 8]); impl GUID { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { Ok(Self( r.read_u32::()?, r.read_u16::()?, r.read_u16::()?, [ r.read_u8()?, r.read_u8()?, r.read_u8()?, r.read_u8()?, r.read_u8()?, r.read_u8()?, r.read_u8()?, r.read_u8()? ] )) } } #[derive(Debug)] struct VoiceInfoExtra { lang_id: u16, dialect: String, gender: u16, age: u16, style: String, } impl VoiceInfoExtra { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { Ok(Self { lang_id: r.read_u16::()?, dialect: parse_string(r)?, gender: r.read_u16::()?, age: r.read_u16::()?, style: parse_string(r)?, }) } } #[derive(Debug)] struct VoiceInfo { engine_id: GUID, mode_id: GUID, speed: u32, pitch: u16, extra: Option, } impl VoiceInfo { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { let engine_id = GUID::parse(r)?; let mode_id = GUID::parse(r)?; let speed = r.read_u32::()?; let pitch = r.read_u16::()?; let is_extra = r.read_u8()? == 1; let extra = if is_extra { Some(VoiceInfoExtra::parse(r)?) } else { None }; Ok(Self { engine_id, mode_id, speed, pitch, extra }) } } #[derive(Debug)] struct BalloonInfo { lines: u8, chars_per_line: u8, foreground: RGB, background: RGB, border: RGB, font_name: String, font_height: i32, font_weight: i32, font_italic: bool, font_underline: bool, } impl BalloonInfo { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { Ok(Self { lines: r.read_u8()?, chars_per_line: r.read_u8()?, foreground: parse_rgb(r)?, background: parse_rgb(r)?, border: parse_rgb(r)?, font_name: parse_string(r)?, font_height: r.read_i32::()?, font_weight: r.read_i32::()?, font_italic: r.read_u8()? == 1, font_underline: r.read_u8()? == 1, }) } } #[derive(Debug)] struct CharacterInfo { minor_version: u16, major_version: u16, localized_info: Locator, id: GUID, width: u16, height: u16, transparent_index: u8, flags: u32, animation_major_version: u16, animation_minor_version: u16, voice_info: Option, balloon_info: Option, palette: Vec, } impl CharacterInfo { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { let minor_version = r.read_u16::()?; let major_version = r.read_u16::()?; let localized_info = Locator::parse(r)?; let id = GUID::parse(r)?; let width = r.read_u16::()?; let height = r.read_u16::()?; let transparent_index = r.read_u8()?; let flags = r.read_u32::()?; let animation_major_version = r.read_u16::()?; let animation_minor_version = r.read_u16::()?; let voice_info = if flags >> 4 & 1 == 1 { None } else { Some(VoiceInfo::parse(r)?) }; let balloon_info = if flags >> 8 & 1 == 1 { None } else { Some(BalloonInfo::parse(r)?) }; let palette = { let len = r.read_u32::()?; (0..len).map(|_| parse_rgb(r)).collect::>>()? }; Ok(Self { minor_version, major_version, localized_info, id, width, height, transparent_index, flags, animation_major_version, animation_minor_version, voice_info, balloon_info, palette, }) } } #[derive(Debug)] struct FrameImage { index: u32, x_off: i16, y_off: i16, } impl FrameImage { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { Ok(Self { index: r.read_u32::()?, x_off: r.read_i16::()?, y_off: r.read_i16::()?, }) } fn parse_list(r: &mut R) -> Erm> where R: ReadBytesExt + Seek { let len = r.read_u16::()?; (0..len).map(|_| Self::parse(r)).collect() } } #[derive(Debug)] struct BranchInfo { index: u16, probability: u16, } impl BranchInfo { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { Ok(Self { index: r.read_u16::()?, probability: r.read_u16::()?, }) } fn parse_list(r: &mut R) -> Erm> where R: ReadBytesExt + Seek { let len = r.read_u8()?; (0..len).map(|_| Self::parse(r)).collect() } } #[derive(Debug)] enum OverlayType { MouthClosed, MouthWideOpen1, MouthWideOpen2, MouthWideOpen3, MouthWideOpen4, MouthMedium, MouthNarrow, } #[derive(Debug)] struct OverlayInfo { ty: OverlayType, replace_top_image: bool, index: u16, x_off: i16, y_off: i16, width: u16, height: u16, data: Option>, } impl OverlayInfo { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { let ty = match r.read_u8()? { 0 => OverlayType::MouthClosed, 1 => OverlayType::MouthWideOpen1, 2 => OverlayType::MouthWideOpen2, 3 => OverlayType::MouthWideOpen3, 4 => OverlayType::MouthWideOpen4, 5 => OverlayType::MouthMedium, 6 => OverlayType::MouthNarrow, _ => { return utils::erm_msg("invalid overlay type"); } }; let replace_top_image = r.read_u8()? == 1; let index = r.read_u16::()?; let _ = r.read_u8()?; let has_data = r.read_u8()? == 1; let x_off = r.read_i16::()?; let y_off = r.read_i16::()?; let width = r.read_u16::()?; let height = r.read_u16::()?; let data = if has_data { let len = r.read_u32::()?; let mut buf = vec![0; len as usize]; r.read_exact(&mut buf)?; Some(buf) } else { None }; Ok(Self { ty, replace_top_image, index, x_off, y_off, width, height, data, }) } fn parse_list(r: &mut R) -> Erm> where R: ReadBytesExt + Seek { let len = r.read_u8()?; (0..len).map(|_| Self::parse(r)).collect() } } #[derive(Debug)] struct FrameInfo { images: Vec, audio_index: u16, duration: u16, exit_frame: i16, branches: Vec, mouth_overlays: Vec, } impl FrameInfo { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { Ok(Self { images: FrameImage::parse_list(r)?, audio_index: r.read_u16::()?, duration: r.read_u16::()?, exit_frame: r.read_i16::()?, branches: BranchInfo::parse_list(r)?, mouth_overlays: OverlayInfo::parse_list(r)?, }) } fn parse_list(r: &mut R) -> Erm> where R: ReadBytesExt + Seek { let len = r.read_u16::()?; (0..len).map(|_| Self::parse(r)).collect() } } #[derive(Debug)] enum AnimationTransition {Return, ExitBranch, None} #[derive(Debug)] struct AnimationInfo { name: String, transition: AnimationTransition, return_name: String, frames: Vec } impl AnimationInfo { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { let _name = parse_string(r)?; let loc = Locator::parse(r)?; let pos = r.stream_position()?; loc.seek(r)?; let name = parse_string(r)?; let transition = match r.read_u8()? { 0 => AnimationTransition::Return, 1 => AnimationTransition::ExitBranch, 2 => AnimationTransition::None, _ => { return utils::erm_msg("invalid animation transition type"); } }; let return_name = parse_string(r)?; let frames = FrameInfo::parse_list(r)?; r.seek(SeekFrom::Start(pos))?; Ok(Self { name, transition, return_name, frames }) } fn parse_map(r: &mut R) -> Erm> where R: ReadBytesExt + Seek { let mut ret = HashMap::new(); let len = r.read_u32::()?; for _ in 0..len { let v = Self::parse(r)?; ret.insert(v.name.clone(), v); } Ok(ret) } } struct ImageInfo { width: u16, height: u16, image: Vec, } impl std::fmt::Debug for ImageInfo { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { fmt.write_fmt(format_args!("", self.width, self.height))?; Ok(()) } } impl ImageInfo { fn size() -> u32 { 3 } fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { let loc = Locator::parse(r)?; let _ = r.read_u32::()?; let pos = r.stream_position()?; loc.seek(r)?; let _ = r.read_u8()?; let width = r.read_u16::()?; let height = r.read_u16::()?; let compressed = r.read_u8()? == 1; let img_len = r.read_u32::()?; let mut img_bytes = vec![0; img_len as usize]; r.read_exact(&mut img_bytes)?; r.seek(SeekFrom::Start(pos))?; Ok(Self { width, height, image: if compressed { decompress(img_bytes).ok_or_else( || utils::Error { msg: "failed to decompress".to_owned()} )? } else { img_bytes } }) } fn parse_list(r: &mut R) -> Erm> where R: ReadBytesExt + Seek { let len = r.read_u32::()?; (0..len).map(|_| Self::parse(r)).collect() } } #[derive(Debug)] struct ACS { character_info: CharacterInfo, animation_info: HashMap, image_info: Vec, audio_info: Locator, } impl ACS { fn parse(r: &mut R) -> Erm where R: ReadBytesExt + Seek { if r.read_u32::()? != 0xabcdabc3 { return utils::erm_msg("invalid magic bytes"); } let character_info_loc = Locator::parse(r)?; let animation_info_loc = Locator::parse(r)?; let image_info_loc = Locator::parse(r)?; let audio_info = Locator::parse(r)?; Ok(Self { character_info: { character_info_loc.seek(r)?; CharacterInfo::parse(r)? }, animation_info: { animation_info_loc.seek(r)?; AnimationInfo::parse_map(r)? }, image_info: { image_info_loc.seek(r)?; ImageInfo::parse_list(r)? }, audio_info, }) } } pub struct Overlay { tex: texture::Texture, acs: ACS, anim: Option, anim_idx: usize, border: bool, } impl Overlay { pub fn new(ctx: &context::Context) -> Self { let tex = texture::Texture::new_empty(ctx); let bytes = include_bytes!("../assets/acs/clippy.acs"); let mut r = std::io::Cursor::new(bytes); let acs = ACS::parse(&mut r).unwrap(); log::info!("{:#?}", acs.animation_info.keys().collect::>()); Self { tex, acs, anim: Some("EMPTYTRASH".to_owned()), anim_idx: 0, border: false, } } pub fn reset_animation(&mut self) { self.anim = None; self.anim_idx = 0; } pub fn play_animation(&mut self, anim: &str) { self.anim = Some(anim.to_owned()); self.anim_idx = 0; } pub fn play_idle_animation(&mut self) { let idle = [ "IDLEEYEBROWRAISE", "IDLE1_1", "IDLESIDETOSIDE", "IDLEFINGERTAP", "IDLEHEADSCRATCH", ]; let idx = rand::thread_rng().gen_range(0..idle.len()); self.play_animation(idle[idx]); } } impl overlay::Overlay for Overlay { fn handle_binary(&mut self, ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State, msg: &net::fig::BinaryMessage) -> Erm<()> { match &*msg.event { b"overlay clippy animate" => { let res: Erm<()> = try { let mut reader = std::io::Cursor::new(&msg.data); let anim = net::fig::read_length_prefixed_utf8(&mut reader)?; self.play_animation(&anim); () }; if let Err(e) = res { log::warn!("malformed clippy update: {}", e); } }, b"overlay clippy border on" => self.border = true, b"overlay clippy border off" => self.border = false, _ => {}, } Ok(()) } fn update(&mut self, ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State) -> Erm<()> { let anim = self.anim.as_deref().unwrap_or("RESTPOSE"); if let Some(ainfo) = &self.acs.animation_info.get(anim) { let frames = &ainfo.frames; let f = &frames[self.anim_idx]; if f.images.len() > 0 { let idx = f.images[0].index as usize; let colors: Vec = self.acs.image_info[idx].image.iter().flat_map(|idx| { if *idx == self.acs.character_info.transparent_index { [0, 0, 0, 0] } else { let c = self.acs.character_info.palette[*idx as usize]; [c[0], c[1], c[2], 255] } }).collect(); self.tex.upload_rgba8(ctx, self.acs.image_info[idx].width as _, self.acs.image_info[idx].height as _, &colors ); } if st.tick % 6 == 0 { if self.anim_idx < frames.len() - 1 { self.anim_idx += 1; } else { self.reset_animation(); } } } else { self.reset_animation(); } if self.anim.is_none() { if rand::thread_rng().gen_ratio(1, 10 * 60) { self.play_idle_animation(); } } Ok(()) } fn render(&mut self, ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State) -> Erm<()> { st.bind_2d(ctx, &ost.assets.shader_acs); let w = 3.0 * self.acs.character_info.width as f32; let h = 3.0 * self.acs.character_info.height as f32; ost.assets.shader_acs.set_position_2d_helper(ctx, st, &(st.render_dims - glam::Vec2::new(w, h)), &glam::Vec2::new(w, h), // &glam::Quat::from_rotation_z(st.tick as f32 / 100.0), &glam::Quat::IDENTITY, ); self.tex.bind(ctx); st.mesh_square.render(ctx); if self.border { st.bind_2d(ctx, &ost.assets.shader_flat); ost.assets.shader_flat.set_position_2d_helper(ctx, st, &glam::Vec2::new(1590.0, 541.0), &glam::Vec2::new(295.0, 238.0), &glam::Quat::IDENTITY, ); ost.assets.texture_clippyborder.bind(ctx); st.mesh_square.render(ctx); } Ok(()) } }