mania fixes

This commit is contained in:
MaxOhn
2022-10-15 10:19:48 +02:00
parent a091d30e1e
commit fa91703a2a
7 changed files with 87 additions and 27 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ pub enum GradualDifficultyAttributes<'map> {
/// Gradual osu!catch difficulty attributes. /// Gradual osu!catch difficulty attributes.
Catch(CatchGradualDifficultyAttributes<'map>), Catch(CatchGradualDifficultyAttributes<'map>),
/// Gradual osu!mania difficulty attributes. /// Gradual osu!mania difficulty attributes.
Mania(ManiaGradualDifficultyAttributes), Mania(ManiaGradualDifficultyAttributes<'map>),
/// Gradual osu!standard difficulty attributes. /// Gradual osu!standard difficulty attributes.
Osu(OsuGradualDifficultyAttributes), Osu(OsuGradualDifficultyAttributes),
/// Gradual osu!taiko difficulty attributes. /// Gradual osu!taiko difficulty attributes.
+33 -8
View File
@@ -1,4 +1,4 @@
use crate::{beatmap::BeatmapHitWindows, Beatmap, Mods}; use crate::{beatmap::BeatmapHitWindows, parse::HitObjectKind, Beatmap, Mods};
use super::{ use super::{
difficulty_object::ManiaDifficultyObject, difficulty_object::ManiaDifficultyObject,
@@ -37,16 +37,18 @@ use super::{
/// } /// }
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ManiaGradualDifficultyAttributes { pub struct ManiaGradualDifficultyAttributes<'map> {
pub(crate) idx: usize, pub(crate) idx: usize,
map: &'map Beatmap,
hit_window: f64, hit_window: f64,
strain: Strain, strain: Strain,
diff_objects: Vec<ManiaDifficultyObject>, diff_objects: Vec<ManiaDifficultyObject>,
curr_combo: usize,
} }
impl ManiaGradualDifficultyAttributes { impl<'map> ManiaGradualDifficultyAttributes<'map> {
/// Create a new difficulty attributes iterator for osu!mania maps. /// Create a new difficulty attributes iterator for osu!mania maps.
pub fn new(map: &Beatmap, mods: u32) -> Self { pub fn new(map: &'map Beatmap, mods: u32) -> Self {
let total_columns = map.cs.round().max(1.0); let total_columns = map.cs.round().max(1.0);
let clock_rate = mods.clock_rate(); let clock_rate = mods.clock_rate();
@@ -55,8 +57,8 @@ impl ManiaGradualDifficultyAttributes {
let BeatmapHitWindows { od: hit_window, .. } = map let BeatmapHitWindows { od: hit_window, .. } = map
.attributes() .attributes()
.mods(mods) .mods(mods)
// TODO: allow converts // TODO: allow converts in gradual calc
// .converted(is_convert) .converted(false)
.clock_rate(clock_rate) .clock_rate(clock_rate)
.hit_windows(); .hit_windows();
@@ -71,30 +73,53 @@ impl ManiaGradualDifficultyAttributes {
ManiaDifficultyObject::new(base, prev, clock_rate, total_columns, i) ManiaDifficultyObject::new(base, prev, clock_rate, total_columns, i)
}); });
let curr_combo = if let Some(h) = map.hit_objects.first() {
match &h.kind {
HitObjectKind::Hold { end_time } => {
1 + ((*end_time - h.start_time) / 100.0) as usize
}
_ => 1,
}
} else {
0
};
let mut diff_objects = Vec::with_capacity(map.hit_objects.len().saturating_sub(1)); let mut diff_objects = Vec::with_capacity(map.hit_objects.len().saturating_sub(1));
diff_objects.extend(diff_objects_iter); diff_objects.extend(diff_objects_iter);
Self { Self {
idx: 0, idx: 0,
map,
hit_window, hit_window,
strain, strain,
diff_objects, diff_objects,
curr_combo,
} }
} }
} }
impl Iterator for ManiaGradualDifficultyAttributes { impl Iterator for ManiaGradualDifficultyAttributes<'_> {
type Item = ManiaDifficultyAttributes; type Item = ManiaDifficultyAttributes;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let curr = self.diff_objects.get(self.idx)?; let curr = self.diff_objects.get(self.idx)?;
self.idx += 1; self.idx += 1;
if let Some(h) = self.map.hit_objects.get(self.idx) {
match &h.kind {
HitObjectKind::Hold { end_time } => {
self.curr_combo += 1 + ((*end_time - h.start_time) / 100.0) as usize
}
_ => self.curr_combo += 1,
}
}
self.strain.process(curr, &self.diff_objects); self.strain.process(curr, &self.diff_objects);
Some(ManiaDifficultyAttributes { Some(ManiaDifficultyAttributes {
stars: self.strain.clone().difficulty_value() * STAR_SCALING_FACTOR, stars: self.strain.clone().difficulty_value() * STAR_SCALING_FACTOR,
hit_window: self.hit_window, hit_window: self.hit_window,
max_combo: self.curr_combo,
}) })
} }
@@ -106,7 +131,7 @@ impl Iterator for ManiaGradualDifficultyAttributes {
} }
} }
impl ExactSizeIterator for ManiaGradualDifficultyAttributes { impl ExactSizeIterator for ManiaGradualDifficultyAttributes<'_> {
#[inline] #[inline]
fn len(&self) -> usize { fn len(&self) -> usize {
self.diff_objects.len() - self.idx self.diff_objects.len() - self.idx
+1 -1
View File
@@ -101,7 +101,7 @@ impl ManiaScoreState {
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ManiaGradualPerformanceAttributes<'map> { pub struct ManiaGradualPerformanceAttributes<'map> {
difficulty: ManiaGradualDifficultyAttributes, difficulty: ManiaGradualDifficultyAttributes<'map>,
performance: ManiaPP<'map>, performance: ManiaPP<'map>,
} }
+34 -6
View File
@@ -7,7 +7,7 @@ mod skills;
use std::borrow::Cow; use std::borrow::Cow;
use crate::{beatmap::BeatmapHitWindows, Beatmap, GameMode, Mods, OsuStars}; use crate::{beatmap::BeatmapHitWindows, parse::HitObjectKind, Beatmap, GameMode, Mods, OsuStars};
pub use self::{gradual_difficulty::*, gradual_performance::*, pp::*}; pub use self::{gradual_difficulty::*, gradual_performance::*, pp::*};
@@ -45,6 +45,7 @@ pub struct ManiaStars<'map> {
mods: u32, mods: u32,
passed_objects: Option<usize>, passed_objects: Option<usize>,
clock_rate: Option<f64>, clock_rate: Option<f64>,
is_convert: bool,
} }
impl<'map> ManiaStars<'map> { impl<'map> ManiaStars<'map> {
@@ -56,6 +57,7 @@ impl<'map> ManiaStars<'map> {
mods: 0, mods: 0,
passed_objects: None, passed_objects: None,
clock_rate: None, clock_rate: None,
is_convert: false,
} }
} }
@@ -91,10 +93,19 @@ impl<'map> ManiaStars<'map> {
self self
} }
/// Specify whether the map is a convert i.e. an osu!standard map.
#[inline]
pub fn is_convert(mut self, is_convert: bool) -> Self {
self.is_convert = is_convert;
self
}
/// Calculate all difficulty related values, including stars. /// Calculate all difficulty related values, including stars.
#[inline] #[inline]
pub fn calculate(self) -> ManiaDifficultyAttributes { pub fn calculate(self) -> ManiaDifficultyAttributes {
let is_convert = matches!(self.map, Cow::Owned(_)); let is_convert = self.is_convert || matches!(self.map, Cow::Owned(_));
let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.clock_rate()); let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.clock_rate());
let BeatmapHitWindows { od: hit_window, .. } = self let BeatmapHitWindows { od: hit_window, .. } = self
@@ -105,11 +116,12 @@ impl<'map> ManiaStars<'map> {
.clock_rate(clock_rate) .clock_rate(clock_rate)
.hit_windows(); .hit_windows();
let strain = calculate_strain(self); let ManiaResult { strain, max_combo } = calculate_result(self);
ManiaDifficultyAttributes { ManiaDifficultyAttributes {
stars: strain.difficulty_value() * STAR_SCALING_FACTOR, stars: strain.difficulty_value() * STAR_SCALING_FACTOR,
hit_window, hit_window,
max_combo,
} }
} }
@@ -119,7 +131,7 @@ impl<'map> ManiaStars<'map> {
#[inline] #[inline]
pub fn strains(self) -> ManiaStrains { pub fn strains(self) -> ManiaStrains {
let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.clock_rate()); let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.clock_rate());
let strain = calculate_strain(self); let ManiaResult { strain, .. } = calculate_result(self);
ManiaStrains { ManiaStrains {
section_len: SECTION_LEN * clock_rate, // TODO: clock_rate correct here? section_len: SECTION_LEN * clock_rate, // TODO: clock_rate correct here?
@@ -147,12 +159,13 @@ impl ManiaStrains {
} }
} }
fn calculate_strain(params: ManiaStars<'_>) -> Strain { fn calculate_result(params: ManiaStars<'_>) -> ManiaResult {
let ManiaStars { let ManiaStars {
map, map,
mods, mods,
passed_objects, passed_objects,
clock_rate, clock_rate,
is_convert: _,
} = params; } = params;
let take = passed_objects.unwrap_or(map.hit_objects.len()); let take = passed_objects.unwrap_or(map.hit_objects.len());
@@ -160,11 +173,18 @@ fn calculate_strain(params: ManiaStars<'_>) -> Strain {
let clock_rate = clock_rate.unwrap_or_else(|| mods.clock_rate()); let clock_rate = clock_rate.unwrap_or_else(|| mods.clock_rate());
let mut strain = Strain::new(total_columns as usize); let mut strain = Strain::new(total_columns as usize);
let mut max_combo = 0;
let diff_objects_iter = map let diff_objects_iter = map
.hit_objects .hit_objects
.iter() .iter()
.take(take) .take(take)
.inspect(|h| match &h.kind {
HitObjectKind::Hold { end_time } => {
max_combo += 1 + ((*end_time - h.start_time) / 100.0) as usize
}
_ => max_combo += 1,
})
.skip(1) .skip(1)
.map(ManiaObject::new) .map(ManiaObject::new)
.enumerate() .enumerate()
@@ -180,7 +200,12 @@ fn calculate_strain(params: ManiaStars<'_>) -> Strain {
strain.process(curr, &diff_objects); strain.process(curr, &diff_objects);
} }
strain ManiaResult { strain, max_combo }
}
struct ManiaResult {
strain: Strain,
max_combo: usize,
} }
/// The result of a difficulty calculation on an osu!mania map. /// The result of a difficulty calculation on an osu!mania map.
@@ -190,6 +215,8 @@ pub struct ManiaDifficultyAttributes {
pub stars: f64, pub stars: f64,
/// The perceived hit window for an n300 inclusive of rate-adjusting mods (DT/HT/etc). /// The perceived hit window for an n300 inclusive of rate-adjusting mods (DT/HT/etc).
pub hit_window: f64, pub hit_window: f64,
/// The maximum achievable combo.
pub max_combo: usize,
} }
/// The result of a performance calculation on an osu!mania map. /// The result of a performance calculation on an osu!mania map.
@@ -239,6 +266,7 @@ impl<'map> From<OsuStars<'map>> for ManiaStars<'map> {
mods, mods,
passed_objects, passed_objects,
clock_rate, clock_rate,
is_convert: true,
} }
} }
} }
+13 -6
View File
@@ -212,8 +212,9 @@ impl<'map> ManiaPP<'map> {
/// Calculate all performance related values, including pp and stars. /// Calculate all performance related values, including pp and stars.
pub fn calculate(self) -> ManiaPerformanceAttributes { pub fn calculate(self) -> ManiaPerformanceAttributes {
let attrs = self.attributes.unwrap_or_else(|| { let attrs = self.attributes.unwrap_or_else(|| {
// TODO: handle converts let mut calculator = ManiaStars::new(self.map.as_ref())
let mut calculator = ManiaStars::new(self.map.as_ref()).mods(self.mods); .mods(self.mods)
.is_convert(matches!(self.map, Cow::Owned(_)));
if let Some(passed_objects) = self.passed_objects { if let Some(passed_objects) = self.passed_objects {
calculator = calculator.passed_objects(passed_objects); calculator = calculator.passed_objects(passed_objects);
@@ -355,7 +356,7 @@ impl ManiaPpInner {
fn calculate(self) -> ManiaPerformanceAttributes { fn calculate(self) -> ManiaPerformanceAttributes {
// * Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // * Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
// * The specific number has no intrinsic meaning and can be adjusted as needed. // * The specific number has no intrinsic meaning and can be adjusted as needed.
let mut multiplier = 0.8; let mut multiplier = 8.0;
if self.mods.nf() { if self.mods.nf() {
multiplier *= 0.75; multiplier *= 0.75;
@@ -379,7 +380,7 @@ impl ManiaPpInner {
// Star rating to pp curve // Star rating to pp curve
(self.attrs.stars - 0.15).max(0.05).powf(2.2) (self.attrs.stars - 0.15).max(0.05).powf(2.2)
// From 80% accuracy, 1/20th of total pp is awarded per additional 1% accuracy // From 80% accuracy, 1/20th of total pp is awarded per additional 1% accuracy
* (5.0 * self.custom_accuracy() - 4.0).max(0.0) * (5.0 * self.calculate_custom_accuracy() - 4.0).max(0.0)
// Length bonus, capped at 1500 notes // Length bonus, capped at 1500 notes
* (1.0 + 0.1 * (self.total_hits() / 1500.0).min(1.0)) * (1.0 + 0.1 * (self.total_hits() / 1500.0).min(1.0))
} }
@@ -388,7 +389,7 @@ impl ManiaPpInner {
self.state.total_hits() as f64 self.state.total_hits() as f64
} }
fn custom_accuracy(&self) -> f64 { fn calculate_custom_accuracy(&self) -> f64 {
let ManiaScoreState { let ManiaScoreState {
n320, n320,
n300, n300,
@@ -398,8 +399,14 @@ impl ManiaPpInner {
n_misses: _, n_misses: _,
} = &self.state; } = &self.state;
let total_hits = self.state.total_hits();
if total_hits == 0 {
return 0.0;
}
let numerator = *n320 * 320 + *n300 * 300 + *n200 * 200 + *n100 * 100 + *n50 * 50; let numerator = *n320 * 320 + *n300 * 300 + *n200 * 200 + *n100 * 100 + *n50 * 50;
let denominator = self.total_hits() * 320.0; let denominator = total_hits as f64 * 320.0;
numerator as f64 / denominator numerator as f64 / denominator
} }
+4 -4
View File
@@ -25,9 +25,9 @@ impl Strain {
pub(crate) fn new(total_columns: usize) -> Self { pub(crate) fn new(total_columns: usize) -> Self {
Self { Self {
start_times: Vec::with_capacity(total_columns), start_times: vec![0.0; total_columns],
end_times: Vec::with_capacity(total_columns), end_times: vec![0.0; total_columns],
individual_strains: Vec::with_capacity(total_columns), individual_strains: vec![0.0; total_columns],
individual_strain: 0.0, individual_strain: 0.0,
overall_strain: 1.0, overall_strain: 1.0,
curr_strain: 0.0, curr_strain: 0.0,
@@ -194,6 +194,6 @@ impl StrainDecaySkill for Strain {
Self::OVERALL_DECAY_BASE, Self::OVERALL_DECAY_BASE,
); );
individual_decay * overall_decay individual_decay + overall_decay
} }
} }
+1 -1
View File
@@ -209,7 +209,7 @@ fn calculate_skills(params: TaikoStars<'_>) -> (Peaks, usize) {
mods, mods,
passed_objects, passed_objects,
clock_rate, clock_rate,
.. is_convert: _,
} = params; } = params;
let take = passed_objects.unwrap_or(map.hit_objects.len()); let take = passed_objects.unwrap_or(map.hit_objects.len());