mania fixes
This commit is contained in:
+1
-1
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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());
|
||||||
|
|||||||
Reference in New Issue
Block a user