From 5684670344519dbd9b74847e143438a24714420b Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Fri, 8 Jan 2021 18:21:52 -0600 Subject: [PATCH] initial stacking port --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/beatmap.rs | 148 ++++++++++++++++++++++++++++++++++++++++--- src/game.rs | 25 ++++---- src/slider_render.rs | 2 +- 5 files changed, 155 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d76acaf..bd4c437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1038,7 +1038,7 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "libosu" version = "0.0.12" -source = "git+https://github.com/iptq/libosu?rev=f15cfd0c6331ba7ad76cc0acc0eb5c11cb145cb0#f15cfd0c6331ba7ad76cc0acc0eb5c11cb145cb0" +source = "git+https://github.com/iptq/libosu?rev=332b7a55559c0257cc4d39577240b1cab5ed3d07#332b7a55559c0257cc4d39577240b1cab5ed3d07" dependencies = [ "anyhow", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index ac06798..5ee2e3d 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 = "f15cfd0c6331ba7ad76cc0acc0eb5c11cb145cb0" +rev = "332b7a55559c0257cc4d39577240b1cab5ed3d07" diff --git a/src/beatmap.rs b/src/beatmap.rs index f8f99e3..55149b3 100644 --- a/src/beatmap.rs +++ b/src/beatmap.rs @@ -2,7 +2,7 @@ use libosu::{Beatmap, HitObjectKind, Point}; use crate::hit_object::HitObjectExt; -const STACK_DISTANCE: f64 = 3.0; +pub const STACK_DISTANCE: f64 = 3.0; pub struct BeatmapExt { pub inner: Beatmap, @@ -21,7 +21,11 @@ impl BeatmapExt { BeatmapExt { inner, hit_objects } } - pub fn compute_stacking(&mut self, start_idx: usize, end_idx: usize) { + pub fn compute_stacking(&mut self) { + self.compute_stacking_inner(0, self.hit_objects.len() - 1) + } + + fn compute_stacking_inner(&mut self, start_idx: usize, end_idx: usize) { let mut extended_end_idx = end_idx; if end_idx < self.hit_objects.len() - 1 { @@ -51,18 +55,142 @@ impl BeatmapExt { let stack_base_pos: Point = stack_base_obj.inner.pos.to_float().unwrap(); let object_n_pos: Point = object_n.inner.pos.to_float().unwrap(); - // if stack_base_pos.distance(object_n_pos) < STACK_DISTANCE - // || (stack_base_obj.inner.kind.is_slider() - // && self - // .inner - // .get_hitobject_end_pos(stack_base_obj) - // .distance(object_n_pos) - // < STACK_DISTANCE) - // {} + if stack_base_pos.distance(object_n_pos) < STACK_DISTANCE + || (stack_base_obj.inner.kind.is_slider() + && stack_base_obj + .inner + .end_pos() + .unwrap() + .distance(object_n_pos) + < STACK_DISTANCE) + { + stack_base_idx = n; + self.hit_objects[n].stacking = 0; + } + } + + if stack_base_idx > extended_end_idx { + extended_end_idx = stack_base_idx; + if extended_end_idx == self.hit_objects.len() - 1 { + break; + } } } } + // Reverse pass for stack calculation. let mut extended_start_idx = start_idx; + + for i in (start_idx..=extended_end_idx).rev() { + let mut n = i; + + // We should check every note which has not yet got a stack. + // Consider the case we have two interwound stacks and this will make sense. + // o <-1 o <-2 + // o <-3 o <-4 + // We first process starting from 4 and handle 2, + // then we come backwards on the i loop iteration until we reach 3 and handle 1. + // 2 and 1 will be ignored in the i loop because they already have a stack value. + + let object_i = &self.hit_objects[i]; + let mut iidx = i; + let start_time = object_i.inner.start_time.0; + if object_i.stacking != 0 || object_i.inner.kind.is_spinner() { + continue; + } + + let stack_threshold = + self.inner.difficulty.approach_preempt() as f64 * self.inner.stack_leniency; + + match object_i.inner.kind { + HitObjectKind::Circle => { + for n in (0..n).rev() { + if self.hit_objects[n].inner.kind.is_spinner() { + continue; + } + + let end_time = self + .inner + .get_hitobject_end_time(&self.hit_objects[n].inner); + + if (self.hit_objects[iidx].inner.start_time.0 - end_time.0) as f64 + > stack_threshold + { + break; + } + + if n < extended_start_idx { + self.hit_objects[n].stacking = 0; + extended_start_idx = n; + } + + if self.hit_objects[n].inner.kind.is_slider() + && self.hit_objects[n] + .inner + .end_pos() + .unwrap() + .distance(self.hit_objects[iidx].inner.pos.to_float().unwrap()) + < STACK_DISTANCE + { + let offset = + self.hit_objects[iidx].stacking - self.hit_objects[n].stacking + 1; + + for j in n + 1..=i { + if self.hit_objects[n] + .inner + .end_pos() + .unwrap() + .distance(self.hit_objects[j].inner.pos.to_float().unwrap()) + < STACK_DISTANCE + { + self.hit_objects[j].stacking -= offset; + } + } + + break; + } + + if self.hit_objects[n] + .inner + .pos + .to_float::() + .unwrap() + .distance(self.hit_objects[iidx].inner.pos.to_float().unwrap()) + < STACK_DISTANCE + { + self.hit_objects[n].stacking = self.hit_objects[iidx].stacking + 1; + iidx = n; + } + } + } + HitObjectKind::Slider(_) => { + for n in (start_idx..n).rev() { + if self.hit_objects[n].inner.kind.is_spinner() { + continue; + } + + if (self.hit_objects[iidx].inner.start_time.0 + - self.hit_objects[n].inner.start_time.0) + as f64 + > stack_threshold + { + break; + } + + if self.hit_objects[n] + .inner + .end_pos() + .unwrap() + .distance(self.hit_objects[iidx].inner.pos.to_float().unwrap()) + < STACK_DISTANCE + { + self.hit_objects[n].stacking = self.hit_objects[iidx].stacking + 1; + iidx = n; + } + } + } + _ => {} + } + } } } diff --git a/src/game.rs b/src/game.rs index 088c5af..e29764f 100644 --- a/src/game.rs +++ b/src/game.rs @@ -16,7 +16,8 @@ use ggez::{ use libosu::{Beatmap, HitObject, HitObjectKind, Point, SpinnerInfo, Spline}; use crate::audio::{AudioEngine, Sound}; -use crate::beatmap::BeatmapExt; +use crate::hit_object::HitObjectExt; +use crate::beatmap::{BeatmapExt, STACK_DISTANCE}; use crate::skin::Skin; use crate::slider_render::render_slider; @@ -60,6 +61,7 @@ impl Game { let beatmap = Beatmap::from_osz(&contents)?; self.beatmap = BeatmapExt::new(beatmap); + self.beatmap.compute_stacking(); let dir = path.parent().unwrap(); @@ -102,7 +104,7 @@ impl Game { graphics::draw_queued_text(ctx, DrawParam::default(), None, FilterMode::Linear)?; struct DrawInfo<'a> { - hit_object: &'a HitObject, + hit_object: &'a HitObjectExt, opacity: f64, end_time: f64, } @@ -133,8 +135,8 @@ impl Game { // TODO: tighten this loop even more by binary searching for the start of the timeline and // playfield hitobjects rather than looping through the entire beatmap, better yet, just // keeping track of the old index will probably be much faster - for ho in self.beatmap.inner.hit_objects.iter().rev() { - let ho_time = (ho.start_time.0 as f64) / 1000.0; + for ho in self.beatmap.hit_objects.iter().rev() { + let ho_time = (ho.inner.start_time.0 as f64) / 1000.0; // draw in timeline if ho_time >= timeline_left && ho_time <= timeline_right { @@ -172,10 +174,10 @@ impl Game { // TODO: calculate ease (time - (ho_time - preempt)) / fade_in }; - match ho.kind { + match ho.inner.kind { HitObjectKind::Circle => end_time = ho_time, HitObjectKind::Slider(_) => { - let duration = self.beatmap.inner.get_slider_duration(ho).unwrap(); + let duration = self.beatmap.inner.get_slider_duration(&ho.inner).unwrap(); end_time = ho_time + duration / 1000.0; } HitObjectKind::Spinner(SpinnerInfo { @@ -199,21 +201,22 @@ impl Game { for draw_info in playfield_hitobjects.iter() { let ho = draw_info.hit_object; - let ho_time = (ho.start_time.0 as f64) / 1000.0; + let ho_time = (ho.inner.start_time.0 as f64) / 1000.0; + let stacking = ho.stacking as f32 * STACK_DISTANCE as f32; let pos = [ - PLAYFIELD_BOUNDS.x + osupx_scale_x * ho.pos.0 as f32, - PLAYFIELD_BOUNDS.y + osupx_scale_y * ho.pos.1 as f32, + PLAYFIELD_BOUNDS.x + osupx_scale_x * ho.inner.pos.0 as f32 - stacking, + PLAYFIELD_BOUNDS.y + osupx_scale_y * ho.inner.pos.1 as f32 - stacking, ]; let color = graphics::Color::new(1.0, 1.0, 1.0, draw_info.opacity as f32); - if let HitObjectKind::Slider(info) = &ho.kind { + if let HitObjectKind::Slider(info) = &ho.inner.kind { let color = graphics::Color::new(1.0, 1.0, 1.0, 0.6 * draw_info.opacity as f32); let spline = render_slider( &mut self.slider_cache, ctx, PLAYFIELD_BOUNDS, &self.beatmap.inner, - ho, + &ho.inner, color, )?; diff --git a/src/slider_render.rs b/src/slider_render.rs index 77a75e3..07770dd 100644 --- a/src/slider_render.rs +++ b/src/slider_render.rs @@ -23,7 +23,7 @@ pub fn render_slider<'a>( HitObjectKind::Slider(info) => info, _ => unreachable!("retard"), }; - control_points.extend(&slider_info.control); + control_points.extend(&slider_info.control_points); let spline = if slider_cache.contains_key(&control_points) { slider_cache.get(&control_points).unwrap()