wip osu pp update
This commit is contained in:
@@ -33,13 +33,52 @@ impl Default for TimingPoint {
|
||||
/// [`TimingPoint`] that depends on a previous one.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct DifficultyPoint {
|
||||
/// The start time for the current speed multiplier
|
||||
/// The time at which the control point takes effect.
|
||||
pub time: f64,
|
||||
/// The speed multiplier until the next timing point
|
||||
pub speed_multiplier: f64,
|
||||
/// The slider velocity at this control point.
|
||||
pub slider_vel: f64,
|
||||
/// Whether the section between this and the
|
||||
/// next timing points is a kiai section
|
||||
pub kiai: bool,
|
||||
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
|
||||
pub bpm_mult: f64,
|
||||
/// Whether or not slider ticks should be generated at this control point.
|
||||
/// This exists for backwards compatibility with maps that abuse NaN
|
||||
/// slider velocity behavior on osu!stable (e.g. /b/2628991).
|
||||
pub generate_ticks: bool,
|
||||
}
|
||||
|
||||
impl DifficultyPoint {
|
||||
/// The default slider velocity for a [`DifficultyPoint`]
|
||||
pub const DEFAULT_SLIDER_VEL: f64 = 1.0;
|
||||
/// The default BPM multipler for a [`DifficultyPoint`]
|
||||
pub const DEFAULT_BPM_MULT: f64 = 1.0;
|
||||
/// The default for generating ticks of a [`DifficultyPoint`]
|
||||
pub const DEFAULT_GENERATE_TICKS: bool = true;
|
||||
|
||||
/// Create a new [`DifficultyPoint`]
|
||||
pub fn new(time: f64, beat_len: f64, speed_multiplier: f64, kiai: bool) -> Self {
|
||||
// * Note: In stable, the division occurs on floats, but with compiler optimisations
|
||||
// * turned on actually seems to occur on doubles via some .NET black magic (possibly inlining?).
|
||||
let bpm_multiplier = if beat_len < 0.0 {
|
||||
((-beat_len) as f32).clamp(10.0, 10_000.0)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
Self {
|
||||
time,
|
||||
slider_vel: speed_multiplier.clamp(0.1, 10.0),
|
||||
kiai,
|
||||
bpm_mult: bpm_multiplier as f64,
|
||||
generate_ticks: !beat_len.is_nan(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_redundant(&self, existing: &DifficultyPoint) -> bool {
|
||||
(self.slider_vel - existing.slider_vel).abs() <= f64::EPSILON
|
||||
&& self.generate_ticks == existing.generate_ticks
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for DifficultyPoint {
|
||||
@@ -52,8 +91,10 @@ impl Default for DifficultyPoint {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
time: 0.0,
|
||||
speed_multiplier: 1.0,
|
||||
kiai: false,
|
||||
slider_vel: Self::DEFAULT_SLIDER_VEL,
|
||||
bpm_mult: Self::DEFAULT_BPM_MULT,
|
||||
generate_ticks: Self::DEFAULT_GENERATE_TICKS,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,38 +180,30 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn control_point_iter() {
|
||||
let map = Beatmap {
|
||||
timing_points: vec![
|
||||
TimingPoint {
|
||||
time: 1.0,
|
||||
beat_len: 10.0,
|
||||
kiai: false,
|
||||
},
|
||||
TimingPoint {
|
||||
time: 3.0,
|
||||
beat_len: 10.0,
|
||||
kiai: false,
|
||||
},
|
||||
TimingPoint {
|
||||
time: 4.0,
|
||||
beat_len: 10.0,
|
||||
kiai: false,
|
||||
},
|
||||
],
|
||||
difficulty_points: vec![
|
||||
DifficultyPoint {
|
||||
time: 2.0,
|
||||
speed_multiplier: 10.0,
|
||||
kiai: false,
|
||||
},
|
||||
DifficultyPoint {
|
||||
time: 5.0,
|
||||
speed_multiplier: 10.0,
|
||||
kiai: false,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
let mut map = Beatmap::default();
|
||||
|
||||
map.timing_points.push(TimingPoint {
|
||||
time: 1.0,
|
||||
beat_len: 10.0,
|
||||
kiai: false,
|
||||
});
|
||||
|
||||
map.timing_points.push(TimingPoint {
|
||||
time: 3.0,
|
||||
beat_len: 10.0,
|
||||
kiai: false,
|
||||
});
|
||||
|
||||
map.timing_points.push(TimingPoint {
|
||||
time: 4.0,
|
||||
beat_len: 10.0,
|
||||
kiai: false,
|
||||
});
|
||||
|
||||
map.difficulty_points
|
||||
.push(DifficultyPoint::new(2.0, 10.0, 10.0, false));
|
||||
map.difficulty_points
|
||||
.push(DifficultyPoint::new(5.0, 10.0, 10.0, false));
|
||||
|
||||
let mut iter = ControlPointIter::new(&map);
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
|
||||
// ! BUG: Since `LegacyDifficultyControlPoint` are not considered while parsing,
|
||||
// ! this value can be slightly off due to float arithmetics.
|
||||
let beat_len = timing_point.beat_len / difficulty_point.speed_multiplier;
|
||||
let beat_len = timing_point.beat_len * difficulty_point.bpm_mult;
|
||||
|
||||
let span_count = (repeats + 1) as i32;
|
||||
let start_time = hit_object.start_time.round() as i32;
|
||||
|
||||
@@ -111,9 +111,7 @@ impl Beatmap {
|
||||
let timing_point = self.timing_point_at(*start_time);
|
||||
let difficulty_point = self.difficulty_point_at(*start_time).unwrap_or_default();
|
||||
|
||||
// ! BUG: Since `LegacyDifficultyControlPoint` are not considered while parsing,
|
||||
// ! this value can be slightly off due to float arithmetics.
|
||||
let mut beat_len = timing_point.beat_len / difficulty_point.speed_multiplier;
|
||||
let mut beat_len = timing_point.beat_len * difficulty_point.bpm_mult;
|
||||
|
||||
let slider_scoring_point_dist =
|
||||
OSU_BASE_SCORING_DIST as f64 * self.slider_mult / self.tick_rate;
|
||||
|
||||
+4
-2
@@ -7,6 +7,7 @@ pub use self::{
|
||||
breaks::Break,
|
||||
control_points::{ControlPoint, ControlPointIter, DifficultyPoint, TimingPoint},
|
||||
mode::GameMode,
|
||||
sorted_vec::SortedVec,
|
||||
};
|
||||
|
||||
mod attributes;
|
||||
@@ -14,6 +15,7 @@ mod breaks;
|
||||
mod control_points;
|
||||
mod converts;
|
||||
mod mode;
|
||||
mod sorted_vec;
|
||||
|
||||
/// The main beatmap struct containing all data relevant
|
||||
/// for difficulty and performance calculation
|
||||
@@ -50,10 +52,10 @@ pub struct Beatmap {
|
||||
pub sounds: Vec<u8>,
|
||||
|
||||
/// Timing points that indicate a new timing section.
|
||||
pub timing_points: Vec<TimingPoint>,
|
||||
pub timing_points: SortedVec<TimingPoint>,
|
||||
|
||||
/// Timing point for the current timing section.
|
||||
pub difficulty_points: Vec<DifficultyPoint>,
|
||||
pub difficulty_points: SortedVec<DifficultyPoint>,
|
||||
|
||||
/// The stack leniency that is used to calculate
|
||||
/// the stack offset for stacked positions.
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
convert::identity,
|
||||
fmt::{Debug, Formatter, Result as FmtResult},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use super::{DifficultyPoint, TimingPoint};
|
||||
|
||||
/// A [`Vec`] whose elements are guaranteed to be in order based on the given comparator.
|
||||
#[derive(Clone)]
|
||||
pub struct SortedVec<T> {
|
||||
inner: Vec<T>,
|
||||
cmp: fn(&T, &T) -> Ordering,
|
||||
}
|
||||
|
||||
impl<T> SortedVec<T> {
|
||||
/// If the value is found then [`Result::Ok`] is returned, containing the
|
||||
/// index of the matching element. If there are multiple matches, then any
|
||||
/// one of the matches could be returned.
|
||||
/// If the value is not found then [`Result::Err`] is returned, containing
|
||||
/// the index where a matching element could be inserted while maintaining
|
||||
/// sorted order.
|
||||
pub fn find(&self, value: &T) -> Result<usize, usize> {
|
||||
self.inner
|
||||
.binary_search_by(|probe| (self.cmp)(probe, value))
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, value: T) {
|
||||
let idx = self.find(&value).map_or_else(identity, identity);
|
||||
|
||||
self.inner.insert(idx, value);
|
||||
}
|
||||
|
||||
pub(crate) fn dedup_by_key<F, K>(&mut self, mut key: F)
|
||||
where
|
||||
F: FnMut(&mut T) -> K,
|
||||
K: PartialEq,
|
||||
{
|
||||
self.inner.dedup_by(|a, b| key(a) == key(b))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for SortedVec<T> {
|
||||
type Target = Vec<T>;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for SortedVec<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
Debug::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SortedVec<TimingPoint> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Vec::new(),
|
||||
cmp: |a, b| a.time.partial_cmp(&b.time).unwrap_or(Ordering::Equal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SortedVec<DifficultyPoint> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Vec::new(),
|
||||
cmp: |a, b| a.time.partial_cmp(&b.time).unwrap_or(Ordering::Equal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SortedVec<DifficultyPoint> {
|
||||
pub(crate) fn push_if_not_redundant(&mut self, value: DifficultyPoint) {
|
||||
let is_redundant = match self.find(&value).map_err(|idx| idx.checked_sub(1)) {
|
||||
Ok(idx) | Err(Some(idx)) => value.is_redundant(&self[idx]),
|
||||
Err(None) => value.is_redundant(&DifficultyPoint::default()),
|
||||
};
|
||||
|
||||
if !is_redundant {
|
||||
self.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user