mania convert fixes
This commit is contained in:
+35
-126
@@ -1,6 +1,4 @@
|
|||||||
use std::{cmp::Ordering, iter::Copied, slice::Iter};
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use crate::Beatmap;
|
|
||||||
|
|
||||||
/// New rhythm speed change.
|
/// New rhythm speed change.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
@@ -9,24 +7,27 @@ pub struct TimingPoint {
|
|||||||
pub beat_len: f64,
|
pub beat_len: f64,
|
||||||
/// The start time of this timing section
|
/// The start time of this timing section
|
||||||
pub time: f64,
|
pub time: f64,
|
||||||
/// Whether the section between this and the
|
}
|
||||||
/// next timing points is a kiai section
|
|
||||||
pub kiai: bool,
|
impl TimingPoint {
|
||||||
|
/// Create a new [`TimingPoint`].
|
||||||
|
#[inline]
|
||||||
|
pub fn new(time: f64, beat_len: f64) -> Self {
|
||||||
|
Self { time, beat_len }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for TimingPoint {
|
impl PartialOrd for TimingPoint {
|
||||||
|
#[inline]
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
self.time.partial_cmp(&other.time)
|
self.time.partial_cmp(&other.time)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TimingPoint {
|
impl Default for TimingPoint {
|
||||||
|
#[inline]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self::new(0.0, 60_000.0 / 60.0)
|
||||||
beat_len: 60_000.0 / 60.0,
|
|
||||||
time: 0.0,
|
|
||||||
kiai: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,9 +38,6 @@ pub struct DifficultyPoint {
|
|||||||
pub time: f64,
|
pub time: f64,
|
||||||
/// The slider velocity at this control point.
|
/// The slider velocity at this control point.
|
||||||
pub slider_vel: f64,
|
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.
|
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
|
||||||
pub bpm_mult: f64,
|
pub bpm_mult: f64,
|
||||||
/// Whether or not slider ticks should be generated at this control point.
|
/// Whether or not slider ticks should be generated at this control point.
|
||||||
@@ -56,12 +54,13 @@ impl DifficultyPoint {
|
|||||||
/// The default for generating ticks of a [`DifficultyPoint`]
|
/// The default for generating ticks of a [`DifficultyPoint`]
|
||||||
pub const DEFAULT_GENERATE_TICKS: bool = true;
|
pub const DEFAULT_GENERATE_TICKS: bool = true;
|
||||||
|
|
||||||
/// Create a new [`DifficultyPoint`]
|
/// Create a new [`DifficultyPoint`].
|
||||||
pub fn new(time: f64, beat_len: f64, speed_multiplier: f64, kiai: bool) -> Self {
|
#[inline]
|
||||||
|
pub fn new(time: f64, beat_len: f64, speed_multiplier: f64) -> Self {
|
||||||
// * Note: In stable, the division occurs on floats, but with compiler optimisations
|
// * 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?).
|
// * turned on actually seems to occur on doubles via some .NET black magic (possibly inlining?).
|
||||||
let bpm_multiplier = if beat_len < 0.0 {
|
let bpm_multiplier = if beat_len < 0.0 {
|
||||||
((-beat_len) as f32).clamp(10.0, 10_000.0)
|
((-beat_len) as f32).clamp(10.0, 10_000.0) as f64 / 100.0
|
||||||
} else {
|
} else {
|
||||||
1.0
|
1.0
|
||||||
};
|
};
|
||||||
@@ -69,7 +68,6 @@ impl DifficultyPoint {
|
|||||||
Self {
|
Self {
|
||||||
time,
|
time,
|
||||||
slider_vel: speed_multiplier.clamp(0.1, 10.0),
|
slider_vel: speed_multiplier.clamp(0.1, 10.0),
|
||||||
kiai,
|
|
||||||
bpm_mult: bpm_multiplier as f64,
|
bpm_mult: bpm_multiplier as f64,
|
||||||
generate_ticks: !beat_len.is_nan(),
|
generate_ticks: !beat_len.is_nan(),
|
||||||
}
|
}
|
||||||
@@ -82,16 +80,17 @@ impl DifficultyPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for DifficultyPoint {
|
impl PartialOrd for DifficultyPoint {
|
||||||
|
#[inline]
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
self.time.partial_cmp(&other.time)
|
self.time.partial_cmp(&other.time)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DifficultyPoint {
|
impl Default for DifficultyPoint {
|
||||||
|
#[inline]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
time: 0.0,
|
time: 0.0,
|
||||||
kiai: false,
|
|
||||||
slider_vel: Self::DEFAULT_SLIDER_VEL,
|
slider_vel: Self::DEFAULT_SLIDER_VEL,
|
||||||
bpm_mult: Self::DEFAULT_BPM_MULT,
|
bpm_mult: Self::DEFAULT_BPM_MULT,
|
||||||
generate_ticks: Self::DEFAULT_GENERATE_TICKS,
|
generate_ticks: Self::DEFAULT_GENERATE_TICKS,
|
||||||
@@ -99,119 +98,29 @@ impl Default for DifficultyPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Control point for a [`Beatmap`].
|
/// Control point storing effects and their timestamps.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum ControlPoint {
|
pub struct EffectPoint {
|
||||||
/// A timing point containing the current beat length.
|
/// The time at which the control point takes effect.
|
||||||
Timing(TimingPoint),
|
pub time: f64,
|
||||||
/// A difficulty point containing the current speed multiplier.
|
/// Whether this control point enables Kiai mode.
|
||||||
Difficulty(DifficultyPoint),
|
pub kiai: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControlPoint {
|
impl EffectPoint {
|
||||||
/// Provides the timestamp of the control point.
|
/// The default slider velocity for a [`DifficultyPoint`]
|
||||||
|
pub const DEFAULT_KIAI: bool = true;
|
||||||
|
|
||||||
|
/// Create a new [`EffectPoint`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn time(&self) -> f64 {
|
pub fn new(time: f64, kiai: bool) -> Self {
|
||||||
match self {
|
Self { time, kiai }
|
||||||
Self::Timing(point) => point.time,
|
|
||||||
Self::Difficulty(point) => point.time,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator for a [`Beatmap`]'s timing- and difficulty points sorted by timestamp
|
impl Default for EffectPoint {
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ControlPointIter<'p> {
|
|
||||||
timing_points: Copied<Iter<'p, TimingPoint>>,
|
|
||||||
difficulty_points: Copied<Iter<'p, DifficultyPoint>>,
|
|
||||||
|
|
||||||
next_timing: Option<TimingPoint>,
|
|
||||||
next_difficulty: Option<DifficultyPoint>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'p> ControlPointIter<'p> {
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn new(map: &'p Beatmap) -> Self {
|
fn default() -> Self {
|
||||||
let mut timing_points = map.timing_points.iter().copied();
|
Self::new(0.0, Self::DEFAULT_KIAI)
|
||||||
let mut difficulty_points = map.difficulty_points.iter().copied();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
next_timing: timing_points.next(),
|
|
||||||
next_difficulty: difficulty_points.next(),
|
|
||||||
|
|
||||||
timing_points,
|
|
||||||
difficulty_points,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'p> Iterator for ControlPointIter<'p> {
|
|
||||||
type Item = ControlPoint;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
match (self.next_timing, self.next_difficulty) {
|
|
||||||
(Some(timing), Some(difficulty)) if timing.time <= difficulty.time => {
|
|
||||||
self.next_timing = self.timing_points.next();
|
|
||||||
|
|
||||||
Some(ControlPoint::Timing(timing))
|
|
||||||
}
|
|
||||||
(_, Some(point)) => {
|
|
||||||
self.next_difficulty = self.difficulty_points.next();
|
|
||||||
|
|
||||||
Some(ControlPoint::Difficulty(point))
|
|
||||||
}
|
|
||||||
(Some(point), None) => {
|
|
||||||
self.next_timing = self.timing_points.next();
|
|
||||||
|
|
||||||
Some(ControlPoint::Timing(point))
|
|
||||||
}
|
|
||||||
(None, None) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::{
|
|
||||||
beatmap::{ControlPoint, ControlPointIter, DifficultyPoint, TimingPoint},
|
|
||||||
Beatmap,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn control_point_iter() {
|
|
||||||
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);
|
|
||||||
|
|
||||||
assert!(matches!(iter.next(), Some(ControlPoint::Timing(_))));
|
|
||||||
assert!(matches!(iter.next(), Some(ControlPoint::Difficulty(_))));
|
|
||||||
assert!(matches!(iter.next(), Some(ControlPoint::Timing(_))));
|
|
||||||
assert!(matches!(iter.next(), Some(ControlPoint::Timing(_))));
|
|
||||||
assert!(matches!(iter.next(), Some(ControlPoint::Difficulty(_))));
|
|
||||||
assert!(matches!(iter.next(), None));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ impl Hasher for ByteHasher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write(&mut self, bytes: &[u8]) {
|
fn write(&mut self, _: &[u8]) {
|
||||||
// Only use this hasher for single bytes
|
unreachable!()
|
||||||
debug_assert_eq!(bytes.len(), 1);
|
|
||||||
self.byte = bytes[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ impl Beatmap {
|
|||||||
.count();
|
.count();
|
||||||
|
|
||||||
let percent_slider_or_spinner =
|
let percent_slider_or_spinner =
|
||||||
slider_or_spinner_count as f32 / self.hit_objects.len() as f32;
|
(slider_or_spinner_count as f32 / self.hit_objects.len() as f32) as f64;
|
||||||
|
|
||||||
let target_columns = if percent_slider_or_spinner < 0.2 {
|
let target_columns = if percent_slider_or_spinner < 0.2 {
|
||||||
7.0
|
7.0
|
||||||
} else if percent_slider_or_spinner < 0.3 || rounded_cs >= 5.0 {
|
} else if percent_slider_or_spinner < 0.3 || rounded_cs >= 5.0 {
|
||||||
(6 + (rounded_od > 5.0) as u8) as f32
|
(6 + (rounded_od > 5.0) as u8) as f32
|
||||||
} else if percent_slider_or_spinner > 0.6 {
|
} else if percent_slider_or_spinner as f64 > 0.6 {
|
||||||
(4 + (rounded_od > 4.0) as u8) as f32
|
(4 + (rounded_od > 4.0) as u8) as f32
|
||||||
} else {
|
} else {
|
||||||
(rounded_od + 1.0).clamp(4.0, 7.0)
|
(rounded_od + 1.0).clamp(4.0, 7.0)
|
||||||
@@ -104,8 +104,8 @@ impl Beatmap {
|
|||||||
last_values.time = obj.start_time;
|
last_values.time = obj.start_time;
|
||||||
last_values.pos = obj.pos;
|
last_values.pos = obj.pos;
|
||||||
|
|
||||||
map.hit_objects
|
let new_hit_objects = new_pattern.hit_objects.iter().cloned();
|
||||||
.extend(new_pattern.hit_objects.iter().cloned());
|
map.hit_objects.extend(new_hit_objects);
|
||||||
|
|
||||||
n_circles += new_pattern.hit_objects.len();
|
n_circles += new_pattern.hit_objects.len();
|
||||||
last_values.pattern = new_pattern;
|
last_values.pattern = new_pattern;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
beatmap::converts::mania::{
|
beatmap::{
|
||||||
legacy_random::Random, pattern::Pattern, pattern_type::PatternType,
|
converts::mania::{legacy_random::Random, pattern::Pattern, pattern_type::PatternType},
|
||||||
|
EffectPoint,
|
||||||
},
|
},
|
||||||
curve::Curve,
|
curve::Curve,
|
||||||
mania::ManiaObject,
|
mania::ManiaObject,
|
||||||
@@ -44,11 +45,9 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
|||||||
.difficulty_point_at(hit_object.start_time)
|
.difficulty_point_at(hit_object.start_time)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let kiai = if timing_point.time < difficulty_point.time {
|
let kiai = orig
|
||||||
difficulty_point.kiai
|
.effect_point_at(hit_object.start_time)
|
||||||
} else {
|
.map_or(EffectPoint::DEFAULT_KIAI, |point| point.kiai);
|
||||||
timing_point.kiai
|
|
||||||
};
|
|
||||||
|
|
||||||
let convert_type = if kiai {
|
let convert_type = if kiai {
|
||||||
PatternType::default()
|
PatternType::default()
|
||||||
@@ -56,8 +55,6 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
|||||||
PatternType::LOW_PROBABILITY
|
PatternType::LOW_PROBABILITY
|
||||||
};
|
};
|
||||||
|
|
||||||
// ! 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.bpm_mult;
|
let beat_len = timing_point.beat_len * difficulty_point.bpm_mult;
|
||||||
|
|
||||||
let span_count = (repeats + 1) as i32;
|
let span_count = (repeats + 1) as i32;
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
beatmap::converts::mania::{
|
beatmap::{
|
||||||
|
converts::mania::{
|
||||||
legacy_random::Random, pattern::Pattern, pattern_type::PatternType, PrevValues,
|
legacy_random::Random, pattern::Pattern, pattern_type::PatternType, PrevValues,
|
||||||
},
|
},
|
||||||
|
EffectPoint,
|
||||||
|
},
|
||||||
mania::ManiaObject,
|
mania::ManiaObject,
|
||||||
parse::{HitObject, HitSound},
|
parse::{HitObject, HitSound},
|
||||||
Beatmap,
|
Beatmap,
|
||||||
@@ -61,18 +64,9 @@ impl<'h> HitObjectPatternGenerator<'h> {
|
|||||||
} else if density < timing_point.beat_len / 2.5 {
|
} else if density < timing_point.beat_len / 2.5 {
|
||||||
// * High density
|
// * High density
|
||||||
} else {
|
} else {
|
||||||
let difficulty_point = orig.difficulty_point_at(hit_object.start_time);
|
let kiai = orig
|
||||||
|
.effect_point_at(hit_object.start_time)
|
||||||
let kiai = match difficulty_point {
|
.map_or(EffectPoint::DEFAULT_KIAI, |point| point.kiai);
|
||||||
Some(difficulty_point) => {
|
|
||||||
if timing_point.time < difficulty_point.time {
|
|
||||||
difficulty_point.kiai
|
|
||||||
} else {
|
|
||||||
timing_point.kiai
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => timing_point.kiai,
|
|
||||||
};
|
|
||||||
|
|
||||||
if kiai {
|
if kiai {
|
||||||
// * High density
|
// * High density
|
||||||
|
|||||||
+16
-7
@@ -5,7 +5,7 @@ use crate::parse::HitObject;
|
|||||||
pub use self::{
|
pub use self::{
|
||||||
attributes::{BeatmapAttributes, BeatmapAttributesBuilder, BeatmapHitWindows},
|
attributes::{BeatmapAttributes, BeatmapAttributesBuilder, BeatmapHitWindows},
|
||||||
breaks::Break,
|
breaks::Break,
|
||||||
control_points::{ControlPoint, ControlPointIter, DifficultyPoint, TimingPoint},
|
control_points::{DifficultyPoint, EffectPoint, TimingPoint},
|
||||||
mode::GameMode,
|
mode::GameMode,
|
||||||
sorted_vec::SortedVec,
|
sorted_vec::SortedVec,
|
||||||
};
|
};
|
||||||
@@ -57,6 +57,9 @@ pub struct Beatmap {
|
|||||||
/// Timing point for the current timing section.
|
/// Timing point for the current timing section.
|
||||||
pub difficulty_points: SortedVec<DifficultyPoint>,
|
pub difficulty_points: SortedVec<DifficultyPoint>,
|
||||||
|
|
||||||
|
/// Control points for effect sections.
|
||||||
|
pub effect_points: SortedVec<EffectPoint>,
|
||||||
|
|
||||||
/// The stack leniency that is used to calculate
|
/// The stack leniency that is used to calculate
|
||||||
/// the stack offset for stacked positions.
|
/// the stack offset for stacked positions.
|
||||||
pub stack_leniency: f32,
|
pub stack_leniency: f32,
|
||||||
@@ -81,12 +84,6 @@ impl Beatmap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an iterator over the map's timing- and difficulty points sorted by timestamp.
|
|
||||||
#[inline]
|
|
||||||
pub fn control_points(&self) -> ControlPointIter<'_> {
|
|
||||||
ControlPointIter::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sum up the duration of all breaks (in milliseconds).
|
/// Sum up the duration of all breaks (in milliseconds).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn total_break_time(&self) -> f64 {
|
pub fn total_break_time(&self) -> f64 {
|
||||||
@@ -120,6 +117,17 @@ impl Beatmap {
|
|||||||
.map(|i| self.difficulty_points[i])
|
.map(|i| self.difficulty_points[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the [`EffectPoint`] for the given timestamp.
|
||||||
|
///
|
||||||
|
/// If `time` is before the first effect point, `None` is returned.
|
||||||
|
#[inline]
|
||||||
|
pub fn effect_point_at(&self, time: f64) -> Option<EffectPoint> {
|
||||||
|
self.effect_points
|
||||||
|
.binary_search_by(|probe| probe.time.partial_cmp(&time).unwrap_or(Ordering::Less))
|
||||||
|
.map_or_else(|i| i.checked_sub(1), Some)
|
||||||
|
.map(|i| self.effect_points[i])
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert a [`Beatmap`] of some mode into a different mode.
|
/// Convert a [`Beatmap`] of some mode into a different mode.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
@@ -157,6 +165,7 @@ impl Beatmap {
|
|||||||
sounds: Vec::with_capacity((with_sounds as usize) * self.sounds.len()),
|
sounds: Vec::with_capacity((with_sounds as usize) * self.sounds.len()),
|
||||||
timing_points: self.timing_points.clone(),
|
timing_points: self.timing_points.clone(),
|
||||||
difficulty_points: self.difficulty_points.clone(),
|
difficulty_points: self.difficulty_points.clone(),
|
||||||
|
effect_points: self.effect_points.clone(),
|
||||||
stack_leniency: self.stack_leniency,
|
stack_leniency: self.stack_leniency,
|
||||||
breaks: self.breaks.clone(),
|
breaks: self.breaks.clone(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::{
|
|||||||
ops::Deref,
|
ops::Deref,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{DifficultyPoint, TimingPoint};
|
use super::{control_points::EffectPoint, DifficultyPoint, TimingPoint};
|
||||||
|
|
||||||
/// A [`Vec`] whose elements are guaranteed to be in order based on the given comparator.
|
/// A [`Vec`] whose elements are guaranteed to be in order based on the given comparator.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -76,6 +76,16 @@ impl Default for SortedVec<DifficultyPoint> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SortedVec<EffectPoint> {
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Vec::new(),
|
||||||
|
cmp: |a, b| a.time.partial_cmp(&b.time).unwrap_or(Ordering::Equal),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SortedVec<DifficultyPoint> {
|
impl SortedVec<DifficultyPoint> {
|
||||||
pub(crate) fn push_if_not_redundant(&mut self, value: 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)) {
|
let is_redundant = match self.find(&value).map_err(|idx| idx.checked_sub(1)) {
|
||||||
|
|||||||
+9
-19
@@ -516,36 +516,26 @@ compile_error!("Only one of the features `async_tokio` and `async_std` should be
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Beatmap, OsuPP};
|
use crate::{Beatmap, GameMode, OsuPP, PerformanceAttributes};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn custom() {
|
fn custom() {
|
||||||
let path = "F:\\osu!\\beatmaps\\1529760.osu";
|
let path = "F:/osu!/beatmaps/1000168.osu";
|
||||||
let map = Beatmap::from_path(path).unwrap();
|
let map = Beatmap::from_path(path).unwrap();
|
||||||
|
|
||||||
let attrs = OsuPP::new(&map).mods(16).calculate();
|
let attrs = match OsuPP::new(&map).mode(GameMode::Mania).mods(0).calculate() {
|
||||||
|
PerformanceAttributes::Mania(attrs) => attrs,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"difficulty:\n\
|
"difficulty:\n\
|
||||||
aim={}\n\
|
|
||||||
speed={}\n\
|
|
||||||
flashlight={}\n\
|
|
||||||
stars={}\n\
|
stars={}\n\
|
||||||
|
max_combo={}\n\
|
||||||
performance:\n\
|
performance:\n\
|
||||||
aim={}\n\
|
difficulty={}\n\
|
||||||
speed={}\n\
|
|
||||||
acc={}\n\
|
|
||||||
flashlight={}\n\
|
|
||||||
pp={}\n",
|
pp={}\n",
|
||||||
attrs.difficulty.aim,
|
attrs.difficulty.stars, attrs.difficulty.max_combo, attrs.pp_difficulty, attrs.pp,
|
||||||
attrs.difficulty.speed,
|
|
||||||
attrs.difficulty.flashlight,
|
|
||||||
attrs.difficulty.stars,
|
|
||||||
attrs.pp_aim,
|
|
||||||
attrs.pp_speed,
|
|
||||||
attrs.pp_acc,
|
|
||||||
attrs.pp_flashlight,
|
|
||||||
attrs.pp,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -226,7 +226,7 @@ impl<'map> OsuPP<'map> {
|
|||||||
|
|
||||||
match priority {
|
match priority {
|
||||||
HitResultPriority::BestCase => n300 += remaining,
|
HitResultPriority::BestCase => n300 += remaining,
|
||||||
HitResultPriority::WorstCase => n100 += remaining,
|
HitResultPriority::WorstCase => n50 += remaining,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Some(_), Some(_), None) => n50 = n_objects.saturating_sub(n300 + n100 + n_misses),
|
(Some(_), Some(_), None) => n50 = n_objects.saturating_sub(n300 + n100 + n_misses),
|
||||||
@@ -771,7 +771,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "async_tokio", feature = "async_str"))]
|
#[cfg(any(feature = "async_tokio", feature = "async_str"))]
|
||||||
async fn test_map() -> Beatmap {
|
async fn test_map() -> (Beatmap, OsuDifficultyAttributes) {
|
||||||
let path = "./maps/2785319.osu";
|
let path = "./maps/2785319.osu";
|
||||||
let map = Beatmap::from_path(path).await.unwrap();
|
let map = Beatmap::from_path(path).await.unwrap();
|
||||||
|
|
||||||
|
|||||||
+7
-8
@@ -28,7 +28,7 @@ use std::path::Path;
|
|||||||
#[cfg(feature = "async_std")]
|
#[cfg(feature = "async_std")]
|
||||||
use async_std::{fs::File, io::Read as AsyncRead, path::Path};
|
use async_std::{fs::File, io::Read as AsyncRead, path::Path};
|
||||||
|
|
||||||
use crate::beatmap::{Beatmap, Break, DifficultyPoint, GameMode, TimingPoint};
|
use crate::beatmap::{Beatmap, Break, DifficultyPoint, EffectPoint, GameMode, TimingPoint};
|
||||||
|
|
||||||
fn sort_unstable<T: PartialOrd>(slice: &mut [T]) {
|
fn sort_unstable<T: PartialOrd>(slice: &mut [T]) {
|
||||||
slice.sort_unstable_by(|p1, p2| p1.partial_cmp(p2).unwrap_or(Ordering::Equal));
|
slice.sort_unstable_by(|p1, p2| p1.partial_cmp(p2).unwrap_or(Ordering::Equal));
|
||||||
@@ -374,20 +374,18 @@ macro_rules! parse_timingpoints_body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if timing_change {
|
if timing_change {
|
||||||
let point = TimingPoint {
|
let point = TimingPoint::new(time, beat_len.clamp(6.0, 60_000.0));
|
||||||
time,
|
|
||||||
beat_len: beat_len.clamp(6.0, 60_000.0),
|
|
||||||
kiai,
|
|
||||||
};
|
|
||||||
|
|
||||||
$self.timing_points.push(point);
|
$self.timing_points.push(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !timing_change || pending_diff_point.is_none() {
|
if !timing_change || pending_diff_point.is_none() {
|
||||||
pending_diff_point =
|
pending_diff_point = Some(DifficultyPoint::new(time, beat_len, speed_multiplier));
|
||||||
Some(DifficultyPoint::new(time, beat_len, speed_multiplier, kiai));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let effect_point = EffectPoint::new(time, kiai);
|
||||||
|
$self.effect_points.push(effect_point);
|
||||||
|
|
||||||
pending_diff_points_time = time;
|
pending_diff_points_time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,6 +395,7 @@ macro_rules! parse_timingpoints_body {
|
|||||||
|
|
||||||
$self.timing_points.dedup_by_key(|point| point.time);
|
$self.timing_points.dedup_by_key(|point| point.time);
|
||||||
$self.difficulty_points.dedup_by_key(|point| point.time);
|
$self.difficulty_points.dedup_by_key(|point| point.time);
|
||||||
|
$self.effect_points.dedup_by_key(|point| point.time);
|
||||||
|
|
||||||
Ok(empty)
|
Ok(empty)
|
||||||
}};
|
}};
|
||||||
|
|||||||
Reference in New Issue
Block a user