wip osu pp update

This commit is contained in:
MaxOhn
2022-10-07 18:46:17 +02:00
parent fae3a0359c
commit 5eb8dab588
34 changed files with 2415 additions and 1931 deletions
+69 -36
View File
@@ -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;
+1 -3
View File
@@ -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
View File
@@ -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.
+90
View File
@@ -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);
}
}
}