feat: added struct CatchOwnedGradualDifficulty
This commit is contained in:
+1
-2
@@ -137,7 +137,6 @@ impl BeatmapExt for Beatmap {
|
|||||||
curve_bufs: CurveBuffers::default(),
|
curve_bufs: CurveBuffers::default(),
|
||||||
last_pos: None,
|
last_pos: None,
|
||||||
last_time: 0.0,
|
last_time: 0.0,
|
||||||
map: self,
|
|
||||||
ticks: Vec::new(),
|
ticks: Vec::new(),
|
||||||
with_hr: mods.hr(),
|
with_hr: mods.hr(),
|
||||||
};
|
};
|
||||||
@@ -145,7 +144,7 @@ impl BeatmapExt for Beatmap {
|
|||||||
let mut hit_objects: Vec<_> = self
|
let mut hit_objects: Vec<_> = self
|
||||||
.hit_objects
|
.hit_objects
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|h| FruitOrJuice::new(h, &mut params))
|
.filter_map(|h| FruitOrJuice::new(h, &mut params, self))
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ impl CatchObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_hr(mut self, params: &mut FruitParams<'_>) -> Self {
|
pub(crate) fn with_hr(mut self, params: &mut FruitParams) -> Self {
|
||||||
let mut offset_pos = self.pos;
|
let mut offset_pos = self.pos;
|
||||||
let time_diff = self.time - params.last_time;
|
let time_diff = self.time - params.last_time;
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,11 @@ const LEGACY_LAST_TICK_OFFSET: f64 = 36.0;
|
|||||||
const BASE_SCORING_DISTANCE: f64 = 100.0;
|
const BASE_SCORING_DISTANCE: f64 = 100.0;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct FruitParams<'a> {
|
pub(crate) struct FruitParams {
|
||||||
pub(crate) attributes: CatchDifficultyAttributes,
|
pub(crate) attributes: CatchDifficultyAttributes,
|
||||||
pub(crate) curve_bufs: CurveBuffers,
|
pub(crate) curve_bufs: CurveBuffers,
|
||||||
pub(crate) last_pos: Option<f32>,
|
pub(crate) last_pos: Option<f32>,
|
||||||
pub(crate) last_time: f64,
|
pub(crate) last_time: f64,
|
||||||
pub(crate) map: &'a Beatmap,
|
|
||||||
pub(crate) ticks: Vec<(Pos2, f64)>,
|
pub(crate) ticks: Vec<(Pos2, f64)>,
|
||||||
pub(crate) with_hr: bool,
|
pub(crate) with_hr: bool,
|
||||||
}
|
}
|
||||||
@@ -31,7 +30,7 @@ pub(crate) enum FruitOrJuice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FruitOrJuice {
|
impl FruitOrJuice {
|
||||||
pub(crate) fn new(h: &HitObject, params: &mut FruitParams<'_>) -> Option<Self> {
|
pub(crate) fn new(h: &HitObject, params: &mut FruitParams, map: &Beatmap) -> Option<Self> {
|
||||||
match &h.kind {
|
match &h.kind {
|
||||||
HitObjectKind::Circle => {
|
HitObjectKind::Circle => {
|
||||||
let mut h = CatchObject::new((h.pos, h.start_time));
|
let mut h = CatchObject::new((h.pos, h.start_time));
|
||||||
@@ -54,17 +53,12 @@ impl FruitOrJuice {
|
|||||||
params.last_pos = Some(h.pos.x + control_points[control_points.len() - 1].pos.x);
|
params.last_pos = Some(h.pos.x + control_points[control_points.len() - 1].pos.x);
|
||||||
params.last_time = h.start_time;
|
params.last_time = h.start_time;
|
||||||
|
|
||||||
let timing_point = params.map.timing_point_at(h.start_time);
|
let timing_point = map.timing_point_at(h.start_time);
|
||||||
|
|
||||||
let difficulty_point = params
|
let difficulty_point = map.difficulty_point_at(h.start_time).unwrap_or_default();
|
||||||
.map
|
|
||||||
.difficulty_point_at(h.start_time)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let vel_factor =
|
let vel_factor = BASE_SCORING_DISTANCE * map.slider_mult / timing_point.beat_len;
|
||||||
BASE_SCORING_DISTANCE * params.map.slider_mult / timing_point.beat_len;
|
let tick_dist_factor = BASE_SCORING_DISTANCE * map.slider_mult / map.tick_rate;
|
||||||
let tick_dist_factor =
|
|
||||||
BASE_SCORING_DISTANCE * params.map.slider_mult / params.map.tick_rate;
|
|
||||||
|
|
||||||
let vel = vel_factor * difficulty_point.slider_vel;
|
let vel = vel_factor * difficulty_point.slider_vel;
|
||||||
|
|
||||||
|
|||||||
+110
-68
@@ -1,9 +1,9 @@
|
|||||||
use std::{iter, slice::Iter};
|
use std::iter;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
catch::{difficulty_object::DifficultyObject, SECTION_LENGTH, STAR_SCALING_FACTOR},
|
catch::{difficulty_object::DifficultyObject, SECTION_LENGTH, STAR_SCALING_FACTOR},
|
||||||
curve::CurveBuffers,
|
curve::CurveBuffers,
|
||||||
parse::{HitObject, Pos2},
|
parse::Pos2,
|
||||||
Beatmap, Mods,
|
Beatmap, Mods,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,9 +50,111 @@ use super::{
|
|||||||
#[cfg_attr(docsrs, doc(cfg(feature = "gradual")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "gradual")))]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CatchGradualDifficulty<'map> {
|
pub struct CatchGradualDifficulty<'map> {
|
||||||
|
map: &'map Beatmap,
|
||||||
|
inner: CatchGradualDifficultyInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'map> CatchGradualDifficulty<'map> {
|
||||||
|
/// Create a new difficulty attributes iterator for osu!catch maps.
|
||||||
|
pub fn new(map: &'map Beatmap, mods: u32) -> Self {
|
||||||
|
let inner = CatchGradualDifficultyInner::new(map, mods);
|
||||||
|
|
||||||
|
Self { map, inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn idx(&self) -> usize {
|
||||||
|
self.inner.idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for CatchGradualDifficulty<'_> {
|
||||||
|
type Item = CatchDifficultyAttributes;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.inner.next(self.map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gradually calculate the difficulty attributes of an osu!catch map.
|
||||||
|
///
|
||||||
|
/// Check [`CatchGradualDifficulty`] for more information. This struct does the same
|
||||||
|
/// but includes an up-front allocation to avoid being bound to a lifetime.
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "gradual")))]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CatchOwnedGradualDifficulty {
|
||||||
|
map: Box<Beatmap>,
|
||||||
|
inner: CatchGradualDifficultyInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CatchOwnedGradualDifficulty {
|
||||||
|
/// Create a new difficulty attributes iterator for osu!catch maps.
|
||||||
|
pub fn new(map: Beatmap, mods: u32) -> Self {
|
||||||
|
let inner = CatchGradualDifficultyInner::new(&map, mods);
|
||||||
|
let map = Box::new(map);
|
||||||
|
|
||||||
|
Self { map, inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn idx(&self) -> usize {
|
||||||
|
self.inner.idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for CatchOwnedGradualDifficulty {
|
||||||
|
type Item = CatchDifficultyAttributes;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.inner.next(&self.map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct CatchObjectIter {
|
||||||
|
last_object: Option<FruitOrJuice>,
|
||||||
|
params: FruitParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CatchObjectIter {
|
||||||
|
fn new(mods: impl Mods, attributes: CatchDifficultyAttributes) -> Self {
|
||||||
|
let params = FruitParams {
|
||||||
|
attributes,
|
||||||
|
curve_bufs: CurveBuffers::default(),
|
||||||
|
last_pos: None,
|
||||||
|
last_time: 0.0,
|
||||||
|
ticks: Vec::new(),
|
||||||
|
with_hr: mods.hr(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
last_object: None,
|
||||||
|
params,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attributes(&self) -> CatchDifficultyAttributes {
|
||||||
|
self.params.attributes.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self, map: &Beatmap, idx: usize) -> Option<CatchObject> {
|
||||||
|
if let opt @ Some(_) = self.last_object.as_mut().and_then(Iterator::next) {
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.hit_objects[idx..]
|
||||||
|
.iter()
|
||||||
|
.find_map(|h| FruitOrJuice::new(h, &mut self.params, map))
|
||||||
|
.and_then(|h| self.last_object.insert(h).next())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct CatchGradualDifficultyInner {
|
||||||
pub(crate) idx: usize,
|
pub(crate) idx: usize,
|
||||||
clock_rate: f64,
|
clock_rate: f64,
|
||||||
hit_objects: CatchObjectIter<'map>,
|
hit_objects: CatchObjectIter,
|
||||||
movement: Movement,
|
movement: Movement,
|
||||||
prev: CatchObject,
|
prev: CatchObject,
|
||||||
half_catcher_width: f64,
|
half_catcher_width: f64,
|
||||||
@@ -62,9 +164,8 @@ pub struct CatchGradualDifficulty<'map> {
|
|||||||
strain_peak_buf: Vec<f64>,
|
strain_peak_buf: Vec<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'map> CatchGradualDifficulty<'map> {
|
impl CatchGradualDifficultyInner {
|
||||||
/// Create a new difficulty attributes iterator for osu!catch maps.
|
fn new(map: &Beatmap, mods: u32) -> Self {
|
||||||
pub fn new(map: &'map Beatmap, mods: u32) -> Self {
|
|
||||||
let map_attributes = map.attributes().mods(mods).build();
|
let map_attributes = map.attributes().mods(mods).build();
|
||||||
|
|
||||||
let attributes = CatchDifficultyAttributes {
|
let attributes = CatchDifficultyAttributes {
|
||||||
@@ -72,7 +173,7 @@ impl<'map> CatchGradualDifficulty<'map> {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let hit_objects = CatchObjectIter::new(map, mods, attributes);
|
let hit_objects = CatchObjectIter::new(mods, attributes);
|
||||||
|
|
||||||
let half_catcher_width =
|
let half_catcher_width =
|
||||||
(calculate_catch_width(map_attributes.cs as f32) / 2.0 / ALLOWED_CATCH_RANGE) as f64;
|
(calculate_catch_width(map_attributes.cs as f32) / 2.0 / ALLOWED_CATCH_RANGE) as f64;
|
||||||
@@ -104,13 +205,9 @@ impl<'map> CatchGradualDifficulty<'map> {
|
|||||||
&mut self.last_excess,
|
&mut self.last_excess,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for CatchGradualDifficulty<'_> {
|
fn next(&mut self, map: &Beatmap) -> Option<CatchDifficultyAttributes> {
|
||||||
type Item = CatchDifficultyAttributes;
|
let curr = self.hit_objects.next(map, self.idx)?;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let curr = self.hit_objects.next()?;
|
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
|
|
||||||
if self.idx == 1 {
|
if self.idx == 1 {
|
||||||
@@ -165,58 +262,3 @@ impl Iterator for CatchGradualDifficulty<'_> {
|
|||||||
Some(attrs)
|
Some(attrs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct CatchObjectIter<'map> {
|
|
||||||
last_object: Option<FruitOrJuice>,
|
|
||||||
hit_objects: Iter<'map, HitObject>,
|
|
||||||
params: FruitParams<'map>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'map> CatchObjectIter<'map> {
|
|
||||||
fn new(map: &'map Beatmap, mods: impl Mods, attributes: CatchDifficultyAttributes) -> Self {
|
|
||||||
let params = FruitParams {
|
|
||||||
attributes,
|
|
||||||
curve_bufs: CurveBuffers::default(),
|
|
||||||
last_pos: None,
|
|
||||||
last_time: 0.0,
|
|
||||||
map,
|
|
||||||
ticks: Vec::new(),
|
|
||||||
with_hr: mods.hr(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
last_object: None,
|
|
||||||
hit_objects: map.hit_objects.iter(),
|
|
||||||
params,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn attributes(&self) -> CatchDifficultyAttributes {
|
|
||||||
self.params.attributes.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for CatchObjectIter<'_> {
|
|
||||||
type Item = CatchObject;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if let opt @ Some(_) = self.last_object.as_mut().and_then(Iterator::next) {
|
|
||||||
return opt;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.hit_objects
|
|
||||||
.find_map(|h| FruitOrJuice::new(h, &mut self.params))
|
|
||||||
.and_then(|h| self.last_object.insert(h).next())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
||||||
let min = self
|
|
||||||
.last_object
|
|
||||||
.as_ref()
|
|
||||||
.map(ExactSizeIterator::len)
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
(min, None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ impl<'map> CatchGradualPerformance<'map> {
|
|||||||
.clone()
|
.clone()
|
||||||
.attributes(difficulty)
|
.attributes(difficulty)
|
||||||
.state(state)
|
.state(state)
|
||||||
.passed_objects(self.difficulty.idx)
|
.passed_objects(self.difficulty.idx())
|
||||||
.calculate();
|
.calculate();
|
||||||
|
|
||||||
Some(performance)
|
Some(performance)
|
||||||
|
|||||||
+3
-3
@@ -17,7 +17,8 @@ pub use self::{catch_object::CatchObject, pp::*, score_state::CatchScoreState};
|
|||||||
|
|
||||||
#[cfg(feature = "gradual")]
|
#[cfg(feature = "gradual")]
|
||||||
pub use self::{
|
pub use self::{
|
||||||
gradual_difficulty::CatchGradualDifficulty, gradual_performance::CatchGradualPerformance,
|
gradual_difficulty::{CatchGradualDifficulty, CatchOwnedGradualDifficulty},
|
||||||
|
gradual_performance::CatchGradualPerformance,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use self::fruit_or_juice::{FruitOrJuice, FruitParams};
|
pub(crate) use self::fruit_or_juice::{FruitOrJuice, FruitParams};
|
||||||
@@ -165,7 +166,6 @@ fn calculate_movement(params: CatchStars<'_>) -> (Movement, CatchDifficultyAttri
|
|||||||
curve_bufs: CurveBuffers::default(),
|
curve_bufs: CurveBuffers::default(),
|
||||||
last_pos: None,
|
last_pos: None,
|
||||||
last_time: 0.0,
|
last_time: 0.0,
|
||||||
map,
|
|
||||||
ticks: Vec::new(), // using the same buffer for all sliders
|
ticks: Vec::new(), // using the same buffer for all sliders
|
||||||
with_hr: mods.hr(),
|
with_hr: mods.hr(),
|
||||||
};
|
};
|
||||||
@@ -174,7 +174,7 @@ fn calculate_movement(params: CatchStars<'_>) -> (Movement, CatchDifficultyAttri
|
|||||||
let mut hit_objects = map
|
let mut hit_objects = map
|
||||||
.hit_objects
|
.hit_objects
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|h| FruitOrJuice::new(h, &mut params))
|
.filter_map(|h| FruitOrJuice::new(h, &mut params, map))
|
||||||
.flatten()
|
.flatten()
|
||||||
.take(take);
|
.take(take);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user