use teleia::*; use crate::overlay; use crate::overlay::tcg; use std::{collections::VecDeque, io::Read}; use byteorder::{LE, ReadBytesExt}; const DUEL_DURATION: u64 = 5 * 60; const FLIP_START: u64 = 60; const FLIP_END: u64 = 90; fn read_length_prefixed(r: &mut impl Read) -> Erm> { let len = r.read_u32::()?; let mut bytes = vec![0; len as usize]; r.read_exact(&mut bytes)?; Ok(bytes) } fn extract_meta(png: &[u8]) -> Erm> { let cursor = std::io::Cursor::new(png); let decoder = png::Decoder::new(cursor); let mut reader = decoder.read_info()?; let mut buf = vec![0; reader.output_buffer_size().unwrap()]; while let Ok(f) = reader.next_frame(&mut buf) {} reader.finish()?; for tc in reader.info().uncompressed_latin1_text.iter() { if tc.keyword == "lcolonqtcg" { let (sig, meta) = if let Some(suffix) = tc.text.strip_prefix("+SIGNED+") { suffix.split_once("+").unwrap_or(("", suffix)) } else { ("", &*tc.text) }; return Ok(Some(tcg::Card::parse(meta)?)); } } Ok(None) } fn render_card_texture( ctx: &context::Context, st: &state::State, ost: &overlay::State, t: &texture::Texture, pos: Mat4 ) { st.bind_3d(ctx, &ost.assets.shader_tcg_screen); ost.assets.shader_tcg_screen.set_i32(ctx, "texture_front", 0); ost.assets.shader_tcg_screen.set_i32(ctx, "texture_back", 1); t.bind(ctx); ost.assets.texture_cardback.bind_index(ctx, 1); ost.assets.shader_tcg_screen.set_position_3d(ctx, st, &pos); st.mesh_square.render(ctx); } pub struct Duel { duelist1: String, duelist2: String, png1: Vec, png2: Vec, meta1: Option, meta2: Option, } pub struct Overlay { current: Option<(Duel, u64)>, queue: VecDeque, card1: texture::Texture, card2: texture::Texture, } impl Overlay { pub fn new(ctx: &context::Context) -> Self { Self { current: None, queue: VecDeque::new(), card1: texture::Texture::new_empty(ctx), card2: texture::Texture::new_empty(ctx), } } } 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<()> { #[allow(clippy::single_match)] match &*msg.event { b"overlay tcg duel" => { let mut reader = std::io::Cursor::new(&msg.data); let duelist1 = String::from_utf8(read_length_prefixed(&mut reader)?)?; let duelist2 = String::from_utf8(read_length_prefixed(&mut reader)?)?; let png1 = read_length_prefixed(&mut reader)?; let png2 = read_length_prefixed(&mut reader)?; let meta1 = extract_meta(&png1)?; let meta2 = extract_meta(&png2)?; self.queue.push_back(Duel { duelist1, duelist2, png1, png2, meta1, meta2 }); }, _ => {}, } Ok(()) } fn update(&mut self, ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State) -> Erm<()> { if let Some((_, start)) = &self.current { let elapsed = st.tick - start; if elapsed > DUEL_DURATION { self.current = None; } } else if let Some(d) = self.queue.pop_front() { self.card1.upload(ctx, &d.png1); self.card2.upload(ctx, &d.png2); self.current = Some((d, st.tick)); } Ok(()) } fn render(&mut self, ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State) -> Erm<()> { if let Some((d, start)) = &self.current { let elapsed = st.tick - start; let card_scale = glam::Vec3::new(0.7111, 1.0, 1.0); let card_rotation = glam::Quat::from_rotation_y( if elapsed < FLIP_START { 0.0 } else if elapsed < FLIP_END { utils::lerp(0.0, std::f32::consts::PI, (elapsed - FLIP_START) as f32 / (FLIP_END - FLIP_START) as f32 ) } else { std::f32::consts::PI } ); render_card_texture(ctx, st, ost, &self.card1, glam::Mat4::from_scale_rotation_translation( card_scale, card_rotation, glam::Vec3::new(3.0, 1.9, -8.0) ) ); render_card_texture(ctx, st, ost, &self.card2, glam::Mat4::from_scale_rotation_translation( card_scale, card_rotation, glam::Vec3::new(-3.0, 1.9, -8.0) ) ); let mut names_pos = glam::Vec2::new(0.0, 80.0); let names = format!("{} vs {}", d.duelist1, d.duelist2); let scale = 8.0; let width = names.len() as f32 * st.font_default.char_width as f32 * scale; names_pos += glam::Vec2::new( st.render_dims.x / 2.0 - width / 2.0, 0.0, ); st.font_default.render_text_at(ctx, st, names_pos, &names, font::BitmapParams { color: &[glam::Vec4::new(1.0, 1.0, 1.0, 1.0)], scale: glam::Vec2::new(scale, scale), offset: glam::Vec2::ZERO, }, ); } Ok(()) } }