summaryrefslogtreecommitdiff
path: root/crates/renderer/src/overlay/combo.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/renderer/src/overlay/combo.rs')
-rw-r--r--crates/renderer/src/overlay/combo.rs216
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(())
+ }
+}