mania fixes
This commit is contained in:
+1
-1
@@ -42,7 +42,7 @@ pub enum GradualDifficultyAttributes<'map> {
|
||||
/// Gradual osu!catch difficulty attributes.
|
||||
Catch(CatchGradualDifficultyAttributes<'map>),
|
||||
/// Gradual osu!mania difficulty attributes.
|
||||
Mania(ManiaGradualDifficultyAttributes),
|
||||
Mania(ManiaGradualDifficultyAttributes<'map>),
|
||||
/// Gradual osu!standard difficulty attributes.
|
||||
Osu(OsuGradualDifficultyAttributes),
|
||||
/// 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::{
|
||||
difficulty_object::ManiaDifficultyObject,
|
||||
@@ -37,16 +37,18 @@ use super::{
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ManiaGradualDifficultyAttributes {
|
||||
pub struct ManiaGradualDifficultyAttributes<'map> {
|
||||
pub(crate) idx: usize,
|
||||
map: &'map Beatmap,
|
||||
hit_window: f64,
|
||||
strain: Strain,
|
||||
diff_objects: Vec<ManiaDifficultyObject>,
|
||||
curr_combo: usize,
|
||||
}
|
||||
|
||||
impl ManiaGradualDifficultyAttributes {
|
||||
impl<'map> ManiaGradualDifficultyAttributes<'map> {
|
||||
/// 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 clock_rate = mods.clock_rate();
|
||||
@@ -55,8 +57,8 @@ impl ManiaGradualDifficultyAttributes {
|
||||
let BeatmapHitWindows { od: hit_window, .. } = map
|
||||
.attributes()
|
||||
.mods(mods)
|
||||
// TODO: allow converts
|
||||
// .converted(is_convert)
|
||||
// TODO: allow converts in gradual calc
|
||||
.converted(false)
|
||||
.clock_rate(clock_rate)
|
||||
.hit_windows();
|
||||
|
||||
@@ -71,30 +73,53 @@ impl ManiaGradualDifficultyAttributes {
|
||||
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));
|
||||
diff_objects.extend(diff_objects_iter);
|
||||
|
||||
Self {
|
||||
idx: 0,
|
||||
map,
|
||||
hit_window,
|
||||
strain,
|
||||
diff_objects,
|
||||
curr_combo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ManiaGradualDifficultyAttributes {
|
||||
impl Iterator for ManiaGradualDifficultyAttributes<'_> {
|
||||
type Item = ManiaDifficultyAttributes;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let curr = self.diff_objects.get(self.idx)?;
|
||||
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);
|
||||
|
||||
Some(ManiaDifficultyAttributes {
|
||||
stars: self.strain.clone().difficulty_value() * STAR_SCALING_FACTOR,
|
||||
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]
|
||||
fn len(&self) -> usize {
|
||||
self.diff_objects.len() - self.idx
|
||||
|
||||
@@ -101,7 +101,7 @@ impl ManiaScoreState {
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ManiaGradualPerformanceAttributes<'map> {
|
||||
difficulty: ManiaGradualDifficultyAttributes,
|
||||
difficulty: ManiaGradualDifficultyAttributes<'map>,
|
||||
performance: ManiaPP<'map>,
|
||||
}
|
||||
|
||||
|
||||
+34
-6
@@ -7,7 +7,7 @@ mod skills;
|
||||
|
||||
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::*};
|
||||
|
||||
@@ -45,6 +45,7 @@ pub struct ManiaStars<'map> {
|
||||
mods: u32,
|
||||
passed_objects: Option<usize>,
|
||||
clock_rate: Option<f64>,
|
||||
is_convert: bool,
|
||||
}
|
||||
|
||||
impl<'map> ManiaStars<'map> {
|
||||
@@ -56,6 +57,7 @@ impl<'map> ManiaStars<'map> {
|
||||
mods: 0,
|
||||
passed_objects: None,
|
||||
clock_rate: None,
|
||||
is_convert: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,10 +93,19 @@ impl<'map> ManiaStars<'map> {
|
||||
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.
|
||||
#[inline]
|
||||
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 BeatmapHitWindows { od: hit_window, .. } = self
|
||||
@@ -105,11 +116,12 @@ impl<'map> ManiaStars<'map> {
|
||||
.clock_rate(clock_rate)
|
||||
.hit_windows();
|
||||
|
||||
let strain = calculate_strain(self);
|
||||
let ManiaResult { strain, max_combo } = calculate_result(self);
|
||||
|
||||
ManiaDifficultyAttributes {
|
||||
stars: strain.difficulty_value() * STAR_SCALING_FACTOR,
|
||||
hit_window,
|
||||
max_combo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +131,7 @@ impl<'map> ManiaStars<'map> {
|
||||
#[inline]
|
||||
pub fn strains(self) -> ManiaStrains {
|
||||
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 {
|
||||
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 {
|
||||
map,
|
||||
mods,
|
||||
passed_objects,
|
||||
clock_rate,
|
||||
is_convert: _,
|
||||
} = params;
|
||||
|
||||
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 mut strain = Strain::new(total_columns as usize);
|
||||
let mut max_combo = 0;
|
||||
|
||||
let diff_objects_iter = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.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)
|
||||
.map(ManiaObject::new)
|
||||
.enumerate()
|
||||
@@ -180,7 +200,12 @@ fn calculate_strain(params: ManiaStars<'_>) -> Strain {
|
||||
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.
|
||||
@@ -190,6 +215,8 @@ pub struct ManiaDifficultyAttributes {
|
||||
pub stars: f64,
|
||||
/// The perceived hit window for an n300 inclusive of rate-adjusting mods (DT/HT/etc).
|
||||
pub hit_window: f64,
|
||||
/// The maximum achievable combo.
|
||||
pub max_combo: usize,
|
||||
}
|
||||
|
||||
/// The result of a performance calculation on an osu!mania map.
|
||||
@@ -239,6 +266,7 @@ impl<'map> From<OsuStars<'map>> for ManiaStars<'map> {
|
||||
mods,
|
||||
passed_objects,
|
||||
clock_rate,
|
||||
is_convert: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-6
@@ -212,8 +212,9 @@ impl<'map> ManiaPP<'map> {
|
||||
/// Calculate all performance related values, including pp and stars.
|
||||
pub fn calculate(self) -> ManiaPerformanceAttributes {
|
||||
let attrs = self.attributes.unwrap_or_else(|| {
|
||||
// TODO: handle converts
|
||||
let mut calculator = ManiaStars::new(self.map.as_ref()).mods(self.mods);
|
||||
let mut calculator = ManiaStars::new(self.map.as_ref())
|
||||
.mods(self.mods)
|
||||
.is_convert(matches!(self.map, Cow::Owned(_)));
|
||||
|
||||
if let Some(passed_objects) = self.passed_objects {
|
||||
calculator = calculator.passed_objects(passed_objects);
|
||||
@@ -355,7 +356,7 @@ impl ManiaPpInner {
|
||||
fn calculate(self) -> ManiaPerformanceAttributes {
|
||||
// * 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.
|
||||
let mut multiplier = 0.8;
|
||||
let mut multiplier = 8.0;
|
||||
|
||||
if self.mods.nf() {
|
||||
multiplier *= 0.75;
|
||||
@@ -379,7 +380,7 @@ impl ManiaPpInner {
|
||||
// Star rating to pp curve
|
||||
(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
|
||||
* (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
|
||||
* (1.0 + 0.1 * (self.total_hits() / 1500.0).min(1.0))
|
||||
}
|
||||
@@ -388,7 +389,7 @@ impl ManiaPpInner {
|
||||
self.state.total_hits() as f64
|
||||
}
|
||||
|
||||
fn custom_accuracy(&self) -> f64 {
|
||||
fn calculate_custom_accuracy(&self) -> f64 {
|
||||
let ManiaScoreState {
|
||||
n320,
|
||||
n300,
|
||||
@@ -398,8 +399,14 @@ impl ManiaPpInner {
|
||||
n_misses: _,
|
||||
} = &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 denominator = self.total_hits() * 320.0;
|
||||
let denominator = total_hits as f64 * 320.0;
|
||||
|
||||
numerator as f64 / denominator
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ impl Strain {
|
||||
|
||||
pub(crate) fn new(total_columns: usize) -> Self {
|
||||
Self {
|
||||
start_times: Vec::with_capacity(total_columns),
|
||||
end_times: Vec::with_capacity(total_columns),
|
||||
individual_strains: Vec::with_capacity(total_columns),
|
||||
start_times: vec![0.0; total_columns],
|
||||
end_times: vec![0.0; total_columns],
|
||||
individual_strains: vec![0.0; total_columns],
|
||||
individual_strain: 0.0,
|
||||
overall_strain: 1.0,
|
||||
curr_strain: 0.0,
|
||||
@@ -194,6 +194,6 @@ impl StrainDecaySkill for Strain {
|
||||
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,
|
||||
passed_objects,
|
||||
clock_rate,
|
||||
..
|
||||
is_convert: _,
|
||||
} = params;
|
||||
|
||||
let take = passed_objects.unwrap_or(map.hit_objects.len());
|
||||
|
||||
Reference in New Issue
Block a user