diff options
Diffstat (limited to 'crates/renderer/src/overlay/duel.rs')
| -rw-r--r-- | crates/renderer/src/overlay/duel.rs | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/crates/renderer/src/overlay/duel.rs b/crates/renderer/src/overlay/duel.rs new file mode 100644 index 0000000..4f90062 --- /dev/null +++ b/crates/renderer/src/overlay/duel.rs @@ -0,0 +1,158 @@ +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<Vec<u8>> { + let len = r.read_u32::<LE>()?; + let mut bytes = vec![0; len as usize]; + r.read_exact(&mut bytes)?; + Ok(bytes) +} + +fn extract_meta(png: &[u8]) -> Erm<Option<tcg::Card>> { + 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<u8>, + png2: Vec<u8>, + meta1: Option<tcg::Card>, + meta2: Option<tcg::Card>, +} + +pub struct Overlay { + current: Option<(Duel, u64)>, + queue: VecDeque<Duel>, + 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(()) + } +} |
