diff options
Diffstat (limited to 'crates/renderer/src/overlay/combo.rs')
| -rw-r--r-- | crates/renderer/src/overlay/combo.rs | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/crates/renderer/src/overlay/combo.rs b/crates/renderer/src/overlay/combo.rs new file mode 100644 index 0000000..f200e22 --- /dev/null +++ b/crates/renderer/src/overlay/combo.rs @@ -0,0 +1,216 @@ +use std::collections::HashMap; + +use rand::Rng; +use teleia::*; + +use crate::overlay; + +use enum_map::{enum_map, Enum, EnumMap}; +use strum::{EnumIter, IntoEnumIterator}; + +const MAX_SCORE: f32 = 12800.0; + +const WIDTH: i64 = 250; +const HEIGHT: i64 = 400; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Enum, EnumIter)] +enum Rank {SSS, SS, S, A, B, C, D, None} +impl Rank { + fn threshold(self) -> f32 { + match self { + Self::SSS => 2800.0, + Self::SS => 2100.0, + Self::S => 1500.0, + Self::A => 1000.0, + Self::B => 600.0, + Self::C => 300.0, + Self::D => 100.0, + Self::None => 0.0, + } + } + fn multiplier(self) -> f32 { + match self { + Self::SSS => 16.0, + Self::SS => 6.0, + Self::S => 5.0, + Self::A => 4.0, + Self::B => 3.0, + Self::C => 2.0, + Self::D => 1.5, + Self::None => 1.0, + } + } +} + +pub struct Overlay { + font: font::Bitmap, + fb: framebuffer::Framebuffer, + rank_textures: EnumMap<Rank, texture::Texture>, + shake: u64, + score: f32, + tags: HashMap<String, u64>, +} +impl Overlay { + pub fn new(ctx: &context::Context) -> Self { + let fb = framebuffer::Framebuffer::new(ctx, &glam::Vec2::new(WIDTH as f32, HEIGHT as f32), &glam::Vec2::new(0.0, 0.0)); + Self { + fb, + font: font::Bitmap::from_image(ctx, 6, 12, 96, 72, include_bytes!("../assets/fonts/terminus.png")), + rank_textures: enum_map! { + Rank::SSS => texture::Texture::new(ctx, include_bytes!("../assets/textures/combo/sss.webp")), + Rank::SS => texture::Texture::new(ctx, include_bytes!("../assets/textures/combo/ss.webp")), + Rank::S => texture::Texture::new(ctx, include_bytes!("../assets/textures/combo/s.webp")), + Rank::A => texture::Texture::new(ctx, include_bytes!("../assets/textures/combo/a.webp")), + Rank::B => texture::Texture::new(ctx, include_bytes!("../assets/textures/combo/b.webp")), + Rank::C => texture::Texture::new(ctx, include_bytes!("../assets/textures/combo/c.webp")), + Rank::D => texture::Texture::new(ctx, include_bytes!("../assets/textures/combo/d.webp")), + Rank::None => texture::Texture::new(ctx, include_bytes!("../assets/textures/test.png")), + }, + shake: 0, + score: 0.0, + tags: HashMap::new(), + } + } + fn rank(&self) -> (Rank, f32) { + let mut prev = MAX_SCORE; + for r in Rank::iter() { + let thresh = r.threshold(); + if self.score > thresh { + return (r, (self.score - thresh) / (prev - thresh)); + } + prev = thresh; + } + return (Rank::None, 0.0); + } + fn tag_multiplier(&self) -> f32 { + return 1.0 + self.tags.len() as f32; + } + fn shake_offset(&self, r: Rank) -> f32 { + rand::thread_rng().gen_range(0..10) as f32 / 100.0 * r.multiplier() + } +} +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 combo message" => { + let (oldrank, _) = self.rank(); + self.score += 267.0 * self.tag_multiplier(); + let (newrank, _) = self.rank(); + if oldrank != newrank { + self.shake = 20; + } + if let Ok(ts) = String::from_utf8(msg.data.clone()) { + for t in ts.split(",") { + if t.len() > 0 { + self.tags.entry(t.to_owned()).and_modify(|x| *x += 1).or_insert(1); + } + } + } + }, + _ => {}, + } + Ok(()) + } + fn update(&mut self, ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State) -> Erm<()> { + let (rank, _) = self.rank(); + self.score -= 2.0 * rank.multiplier(); + if self.score < 0.0 { + self.score = 0.0; + self.tags.clear(); + } + if self.score > MAX_SCORE { self.score = MAX_SCORE; } + if self.shake > 0 { self.shake -= 1; } + Ok(()) + } + fn render(&mut self, ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State) -> Erm<()> { + let (rank, meter) = self.rank(); + if rank == Rank::None { return Ok(()); } + + st.bind_framebuffer(ctx, &self.fb); + ctx.clear_color(glam::Vec4::new(0.0, 0.0, 0.0, 0.8)); + ctx.clear(); + + // rank name + st.bind_2d(ctx, &ost.assets.shader_flat); + ost.assets.shader_flat.set_position_2d( + ctx, st, + &glam::Vec2::new(-4.0, 4.0), + &glam::Vec2::new(WIDTH as f32, WIDTH as f32 / 3.0), + ); + self.rank_textures[rank].bind(ctx); + st.mesh_square.render(ctx); + + // meter + st.bind_2d(ctx, &ost.assets.shader_color); + ost.assets.shader_color.set_position_2d( + ctx, st, + &glam::Vec2::new(0.0, 100.0), + &glam::Vec2::new(WIDTH as f32, 16.0), + ); + ost.assets.shader_color.set_vec4(ctx, "color", &glam::Vec4::new(0.0, 0.0, 0.0, 1.0)); + st.mesh_square.render(ctx); + ost.assets.shader_color.set_position_2d( + ctx, st, + &glam::Vec2::new(0.0, 100.0), + &glam::Vec2::new(WIDTH as f32 * meter, 16.0), + ); + ost.assets.shader_color.set_vec4(ctx, "color", &glam::Vec4::new(1.0, 1.0, 1.0, 1.0)); + st.mesh_square.render(ctx); + + // tags + let mut tagy = 124.0; + for (t, count) in self.tags.iter() { + self.font.render_text_parameterized( + ctx, st, + &glam::Vec2::new(4.0, tagy), + &format!("+ {t} (x{count})"), + font::BitmapParams { + color: &[glam::Vec3::new(1.0, 1.0, 1.0)], + scale: glam::Vec2::new(2.0, 2.0), + }, + ); + tagy += 24.0; + } + + // multiplier + self.font.render_text_parameterized( + ctx, st, + &glam::Vec2::new(4.0, HEIGHT as f32 - 50.0), + &format!("MULT:"), + font::BitmapParams { + color: &[glam::Vec3::new(1.0, 1.0, 1.0)], + scale: glam::Vec2::new(4.0, 4.0), + }, + ); + self.font.render_text_parameterized( + ctx, st, + &glam::Vec2::new(128.0, HEIGHT as f32 - 50.0), + &format!("{}x", self.tag_multiplier()), + font::BitmapParams { + color: &[glam::Vec3::new(1.0, 0.7, 0.0)], + scale: glam::Vec2::new(4.0, 4.0), + }, + ); + + // actually draw box to screen + st.bind_render_framebuffer(ctx); + ctx.clear_depth(); + st.bind_3d(ctx, &ost.assets.shader_flat_noflip); + let shake_offset = if self.shake > 0 { + glam::Vec3::new(self.shake_offset(rank), 0.0, self.shake_offset(rank)) + } else { + glam::Vec3::ZERO + }; + ost.assets.shader_flat_noflip.set_position_3d( + ctx, st, + &glam::Mat4::from_scale_rotation_translation( + glam::Vec3::new(WIDTH as f32 / HEIGHT as f32, 1.0, 1.0), + glam::Quat::from_rotation_y(std::f32::consts::PI + 0.7), + glam::Vec3::new(-5.5, 1.0, -8.0) + shake_offset, + ), + ); + self.fb.bind_texture(ctx); + st.mesh_square.render(ctx); + Ok(()) + } +} |
