diff --git a/Cargo.lock b/Cargo.lock index bd4c437..cf9d38b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1037,8 +1037,8 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "libosu" -version = "0.0.12" -source = "git+https://github.com/iptq/libosu?rev=332b7a55559c0257cc4d39577240b1cab5ed3d07#332b7a55559c0257cc4d39577240b1cab5ed3d07" +version = "0.0.13" +source = "git+https://github.com/iptq/libosu?rev=dee4d05eeaba1313a17eefbbd7679a947b52fbc1#dee4d05eeaba1313a17eefbbd7679a947b52fbc1" dependencies = [ "anyhow", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index 5ee2e3d..96c6ccf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,4 @@ ordered-float = "2.0.1" [dependencies.libosu] git = "https://github.com/iptq/libosu" -rev = "332b7a55559c0257cc4d39577240b1cab5ed3d07" +rev = "dee4d05eeaba1313a17eefbbd7679a947b52fbc1" diff --git a/src/game/mod.rs b/src/game/mod.rs index ad39ae7..8d6c81d 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,5 +1,5 @@ -mod timeline; mod sliders; +mod timeline; use std::collections::HashMap; use std::fs::File; @@ -114,8 +114,6 @@ impl Game { color: Color, } - self.draw_timeline(ctx, time)?; - let mut playfield_hitobjects = Vec::new(); let preempt = (self.beatmap.inner.difficulty.approach_preempt() as f64) / 1000.0; let fade_in = (self.beatmap.inner.difficulty.approach_fade_time() as f64) / 1000.0; @@ -164,6 +162,8 @@ impl Game { } } + self.draw_timeline(ctx, time)?; + let cs_scale = PLAYFIELD_BOUNDS.w / 640.0; let osupx_scale_x = PLAYFIELD_BOUNDS.w / 512.0; let osupx_scale_y = PLAYFIELD_BOUNDS.h / 384.0; diff --git a/src/game/timeline.rs b/src/game/timeline.rs index e14a222..f2ad434 100644 --- a/src/game/timeline.rs +++ b/src/game/timeline.rs @@ -1,34 +1,108 @@ use anyhow::Result; use ggez::{ - graphics::{self, DrawParam, Mesh, Rect}, + graphics::{self, Color, DrawParam, Mesh, Rect, WHITE}, nalgebra::Point2, Context, }; +use libosu::TimingPointKind; use crate::hit_object::HitObjectExt; use super::Game; -pub const TIMELINE_BOUNDS: Rect = Rect::new(0.0, 0.0, 1024.0, 108.0); +pub const BOUNDS: Rect = Rect::new(0.0, 54.0, 768.0, 54.0); + +pub const RED: Color = Color::new(1.0, 0.0, 0.0, 1.0); +pub const BLUE: Color = Color::new(0.0, 0.0, 1.0, 1.0); +pub const TICKS: &[&[(Color, f32)]] = &[ + &[], + &[], + &[], + &[], + &[(WHITE, 1.0), (BLUE, 0.5), (RED, 0.5), (BLUE, 0.5)], +]; impl Game { pub(super) fn draw_timeline(&self, ctx: &mut Context, time: f64) -> Result<()> { let timeline_span = 6.0 / self.beatmap.inner.timeline_zoom; - let timeline_current_line_x = TIMELINE_BOUNDS.x + TIMELINE_BOUNDS.w * 0.5; + let timeline_left = time - timeline_span / 2.0; + let timeline_right = time + timeline_span / 2.0; + let timeline_current_line_x = BOUNDS.x + BOUNDS.w * 0.5; + + // the vertical line let current_line = Mesh::new_line( ctx, &[ - Point2::new(timeline_current_line_x, TIMELINE_BOUNDS.y), - Point2::new( - timeline_current_line_x, - TIMELINE_BOUNDS.y + TIMELINE_BOUNDS.h, - ), + Point2::new(timeline_current_line_x, BOUNDS.y), + Point2::new(timeline_current_line_x, BOUNDS.y + BOUNDS.h), ], 2.0, graphics::WHITE, )?; graphics::draw(ctx, ¤t_line, DrawParam::default())?; + // timing sections in this little span + let mut last_uninherited = None; + for timing_points in self.beatmap.inner.timing_points.windows(2) { + let (fst, snd) = (&timing_points[0], &timing_points[1]); + let fst_time = fst.time.as_seconds(); + let snd_time = snd.time.as_seconds(); + if let TimingPointKind::Uninherited(info) = &fst.kind { + last_uninherited = Some(info); + } + + if let Some(last_uninherited) = last_uninherited { + if (fst_time >= timeline_left && fst_time <= timeline_right) + || (snd_time >= timeline_left && snd_time <= timeline_right) + || (fst_time < timeline_left && snd_time > timeline_right) + { + // TODO: optimize this + let mut time = fst.time.as_seconds(); + let beat = last_uninherited.mpb / 1000.0; + let ticks = TICKS[last_uninherited.meter as usize]; + 'outer: loop { + for i in 0..last_uninherited.meter as usize { + let tick_time = time + beat * i as f64 / last_uninherited.meter as f64; + if tick_time > snd_time.min(timeline_right) { + break 'outer; + } + + let (color, height) = ticks[i]; + let percent = + (tick_time - timeline_left) / (timeline_right - timeline_left); + let x = percent as f32 * BOUNDS.w + BOUNDS.x; + let y2 = BOUNDS.y + BOUNDS.h; + let y1 = y2 - BOUNDS.h * 0.3 * height; + let tick = Mesh::new_line( + ctx, + &[Point2::new(x, y1), Point2::new(x, y2)], + 1.0, + color, + )?; + graphics::draw(ctx, &tick, DrawParam::default())?; + } + time += beat; + + if time >= snd_time.min(timeline_right) { + break; + } + } + } + } + } + + // draw a bottom line for the timeline + let bottom_line = Mesh::new_line( + ctx, + &[ + Point2::new(BOUNDS.x, BOUNDS.y + BOUNDS.h), + Point2::new(BOUNDS.x + BOUNDS.w, BOUNDS.y + BOUNDS.h), + ], + 2.0, + graphics::WHITE, + )?; + graphics::draw(ctx, &bottom_line, DrawParam::default())?; + Ok(()) } @@ -43,6 +117,8 @@ impl Game { let timeline_right = time + timeline_span / 2.0; let ho_time = (ho.inner.start_time.0 as f64) / 1000.0; + let end_time = (self.beatmap.inner.get_hitobject_end_time(&ho.inner).0 as f64) / 1000.0; + let color = self.beatmap.inner.colors[ho.color_idx]; let color = graphics::Color::new( color.red as f32 / 256.0, @@ -51,23 +127,23 @@ impl Game { 1.0, ); - if ho_time >= timeline_left && ho_time <= timeline_right { + if end_time >= timeline_left && ho_time <= timeline_right { let timeline_percent = (ho_time - timeline_left) / (timeline_right - timeline_left); - let timeline_x = timeline_percent as f32 * TIMELINE_BOUNDS.w + TIMELINE_BOUNDS.x; - let timeline_y = TIMELINE_BOUNDS.y; + let timeline_x = timeline_percent as f32 * BOUNDS.w + BOUNDS.x; + let timeline_y = BOUNDS.y; self.skin.hitcircle.draw( ctx, - (TIMELINE_BOUNDS.h, TIMELINE_BOUNDS.h), + (BOUNDS.h, BOUNDS.h), DrawParam::default() - .dest([timeline_x, timeline_y + TIMELINE_BOUNDS.h / 2.0]) + .dest([timeline_x, timeline_y + BOUNDS.h / 2.0]) .offset([0.5, 0.0]) .color(color), )?; self.skin.hitcircleoverlay.draw( ctx, - (TIMELINE_BOUNDS.h, TIMELINE_BOUNDS.h), + (BOUNDS.h, BOUNDS.h), DrawParam::default() - .dest([timeline_x, timeline_y + TIMELINE_BOUNDS.h / 2.0]) + .dest([timeline_x, timeline_y + BOUNDS.h / 2.0]) .offset([0.5, 0.0]), )?; }