initial stacking port
This commit is contained in:
parent
ff36531a62
commit
5684670344
5 changed files with 155 additions and 24 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1038,7 +1038,7 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libosu"
|
name = "libosu"
|
||||||
version = "0.0.12"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
|
|
@ -20,4 +20,4 @@ ordered-float = "2.0.1"
|
||||||
|
|
||||||
[dependencies.libosu]
|
[dependencies.libosu]
|
||||||
git = "https://github.com/iptq/libosu"
|
git = "https://github.com/iptq/libosu"
|
||||||
rev = "f15cfd0c6331ba7ad76cc0acc0eb5c11cb145cb0"
|
rev = "332b7a55559c0257cc4d39577240b1cab5ed3d07"
|
||||||
|
|
148
src/beatmap.rs
148
src/beatmap.rs
|
@ -2,7 +2,7 @@ use libosu::{Beatmap, HitObjectKind, Point};
|
||||||
|
|
||||||
use crate::hit_object::HitObjectExt;
|
use crate::hit_object::HitObjectExt;
|
||||||
|
|
||||||
const STACK_DISTANCE: f64 = 3.0;
|
pub const STACK_DISTANCE: f64 = 3.0;
|
||||||
|
|
||||||
pub struct BeatmapExt {
|
pub struct BeatmapExt {
|
||||||
pub inner: Beatmap,
|
pub inner: Beatmap,
|
||||||
|
@ -21,7 +21,11 @@ impl BeatmapExt {
|
||||||
BeatmapExt { inner, hit_objects }
|
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;
|
let mut extended_end_idx = end_idx;
|
||||||
|
|
||||||
if end_idx < self.hit_objects.len() - 1 {
|
if end_idx < self.hit_objects.len() - 1 {
|
||||||
|
@ -51,18 +55,142 @@ impl BeatmapExt {
|
||||||
|
|
||||||
let stack_base_pos: Point<f64> = stack_base_obj.inner.pos.to_float().unwrap();
|
let stack_base_pos: Point<f64> = stack_base_obj.inner.pos.to_float().unwrap();
|
||||||
let object_n_pos: Point<f64> = object_n.inner.pos.to_float().unwrap();
|
let object_n_pos: Point<f64> = object_n.inner.pos.to_float().unwrap();
|
||||||
// if stack_base_pos.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.kind.is_slider()
|
||||||
// && self
|
&& stack_base_obj
|
||||||
// .inner
|
.inner
|
||||||
// .get_hitobject_end_pos(stack_base_obj)
|
.end_pos()
|
||||||
// .distance(object_n_pos)
|
.unwrap()
|
||||||
// < STACK_DISTANCE)
|
.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;
|
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::<f64>()
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
src/game.rs
25
src/game.rs
|
@ -16,7 +16,8 @@ use ggez::{
|
||||||
use libosu::{Beatmap, HitObject, HitObjectKind, Point, SpinnerInfo, Spline};
|
use libosu::{Beatmap, HitObject, HitObjectKind, Point, SpinnerInfo, Spline};
|
||||||
|
|
||||||
use crate::audio::{AudioEngine, Sound};
|
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::skin::Skin;
|
||||||
use crate::slider_render::render_slider;
|
use crate::slider_render::render_slider;
|
||||||
|
|
||||||
|
@ -60,6 +61,7 @@ impl Game {
|
||||||
|
|
||||||
let beatmap = Beatmap::from_osz(&contents)?;
|
let beatmap = Beatmap::from_osz(&contents)?;
|
||||||
self.beatmap = BeatmapExt::new(beatmap);
|
self.beatmap = BeatmapExt::new(beatmap);
|
||||||
|
self.beatmap.compute_stacking();
|
||||||
|
|
||||||
let dir = path.parent().unwrap();
|
let dir = path.parent().unwrap();
|
||||||
|
|
||||||
|
@ -102,7 +104,7 @@ impl Game {
|
||||||
graphics::draw_queued_text(ctx, DrawParam::default(), None, FilterMode::Linear)?;
|
graphics::draw_queued_text(ctx, DrawParam::default(), None, FilterMode::Linear)?;
|
||||||
|
|
||||||
struct DrawInfo<'a> {
|
struct DrawInfo<'a> {
|
||||||
hit_object: &'a HitObject,
|
hit_object: &'a HitObjectExt,
|
||||||
opacity: f64,
|
opacity: f64,
|
||||||
end_time: 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
|
// 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
|
// playfield hitobjects rather than looping through the entire beatmap, better yet, just
|
||||||
// keeping track of the old index will probably be much faster
|
// keeping track of the old index will probably be much faster
|
||||||
for ho in self.beatmap.inner.hit_objects.iter().rev() {
|
for ho in self.beatmap.hit_objects.iter().rev() {
|
||||||
let ho_time = (ho.start_time.0 as f64) / 1000.0;
|
let ho_time = (ho.inner.start_time.0 as f64) / 1000.0;
|
||||||
|
|
||||||
// draw in timeline
|
// draw in timeline
|
||||||
if ho_time >= timeline_left && ho_time <= timeline_right {
|
if ho_time >= timeline_left && ho_time <= timeline_right {
|
||||||
|
@ -172,10 +174,10 @@ impl Game {
|
||||||
// TODO: calculate ease
|
// TODO: calculate ease
|
||||||
(time - (ho_time - preempt)) / fade_in
|
(time - (ho_time - preempt)) / fade_in
|
||||||
};
|
};
|
||||||
match ho.kind {
|
match ho.inner.kind {
|
||||||
HitObjectKind::Circle => end_time = ho_time,
|
HitObjectKind::Circle => end_time = ho_time,
|
||||||
HitObjectKind::Slider(_) => {
|
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;
|
end_time = ho_time + duration / 1000.0;
|
||||||
}
|
}
|
||||||
HitObjectKind::Spinner(SpinnerInfo {
|
HitObjectKind::Spinner(SpinnerInfo {
|
||||||
|
@ -199,21 +201,22 @@ impl Game {
|
||||||
|
|
||||||
for draw_info in playfield_hitobjects.iter() {
|
for draw_info in playfield_hitobjects.iter() {
|
||||||
let ho = draw_info.hit_object;
|
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 = [
|
let pos = [
|
||||||
PLAYFIELD_BOUNDS.x + osupx_scale_x * ho.pos.0 as f32,
|
PLAYFIELD_BOUNDS.x + osupx_scale_x * ho.inner.pos.0 as f32 - stacking,
|
||||||
PLAYFIELD_BOUNDS.y + osupx_scale_y * ho.pos.1 as f32,
|
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);
|
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 color = graphics::Color::new(1.0, 1.0, 1.0, 0.6 * draw_info.opacity as f32);
|
||||||
let spline = render_slider(
|
let spline = render_slider(
|
||||||
&mut self.slider_cache,
|
&mut self.slider_cache,
|
||||||
ctx,
|
ctx,
|
||||||
PLAYFIELD_BOUNDS,
|
PLAYFIELD_BOUNDS,
|
||||||
&self.beatmap.inner,
|
&self.beatmap.inner,
|
||||||
ho,
|
&ho.inner,
|
||||||
color,
|
color,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub fn render_slider<'a>(
|
||||||
HitObjectKind::Slider(info) => info,
|
HitObjectKind::Slider(info) => info,
|
||||||
_ => unreachable!("retard"),
|
_ => unreachable!("retard"),
|
||||||
};
|
};
|
||||||
control_points.extend(&slider_info.control);
|
control_points.extend(&slider_info.control_points);
|
||||||
|
|
||||||
let spline = if slider_cache.contains_key(&control_points) {
|
let spline = if slider_cache.contains_key(&control_points) {
|
||||||
slider_cache.get(&control_points).unwrap()
|
slider_cache.get(&control_points).unwrap()
|
||||||
|
|
Loading…
Reference in a new issue