store osu skills in struct instead of array
This commit is contained in:
@@ -6,12 +6,11 @@ use std::{
|
|||||||
use crate::{curve::CurveBuffers, Beatmap, Mods};
|
use crate::{curve::CurveBuffers, Beatmap, Mods};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
create_skills,
|
|
||||||
difficulty_object::{Distances, OsuDifficultyObject},
|
difficulty_object::{Distances, OsuDifficultyObject},
|
||||||
old_stacking,
|
old_stacking,
|
||||||
osu_object::{ObjectParameters, OsuObject, OsuObjectKind},
|
osu_object::{ObjectParameters, OsuObject, OsuObjectKind},
|
||||||
scaling_factor::ScalingFactor,
|
scaling_factor::ScalingFactor,
|
||||||
skills::{Aim, Flashlight, Skill, Speed},
|
skills::{Skill, Skills},
|
||||||
stacking, OsuDifficultyAttributes, DIFFICULTY_MULTIPLIER, FADE_IN_DURATION_MULTIPLIER,
|
stacking, OsuDifficultyAttributes, DIFFICULTY_MULTIPLIER, FADE_IN_DURATION_MULTIPLIER,
|
||||||
PERFORMANCE_BASE_MULTIPLIER, PREEMPT_MIN,
|
PERFORMANCE_BASE_MULTIPLIER, PREEMPT_MIN,
|
||||||
};
|
};
|
||||||
@@ -46,6 +45,7 @@ use super::{
|
|||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct OsuGradualDifficultyAttributes {
|
pub struct OsuGradualDifficultyAttributes {
|
||||||
pub(crate) idx: usize,
|
pub(crate) idx: usize,
|
||||||
mods: u32,
|
mods: u32,
|
||||||
@@ -54,7 +54,7 @@ pub struct OsuGradualDifficultyAttributes {
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
hit_objects: Vec<OsuObject>,
|
hit_objects: Vec<OsuObject>,
|
||||||
diff_objects: Vec<OsuDifficultyObject<'static>>,
|
diff_objects: Vec<OsuDifficultyObject<'static>>,
|
||||||
skills: [Box<dyn Skill>; 4],
|
skills: Skills,
|
||||||
hit_window: f64,
|
hit_window: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ impl Debug for OsuGradualDifficultyAttributes {
|
|||||||
.field("idx", &self.idx)
|
.field("idx", &self.idx)
|
||||||
.field("attrs", &self.attrs)
|
.field("attrs", &self.attrs)
|
||||||
.field("diff_objects", &self.diff_objects)
|
.field("diff_objects", &self.diff_objects)
|
||||||
.field("skills", &"<cannot be displayed>")
|
.field("skills", &self.skills)
|
||||||
.field("hit_window", &self.hit_window)
|
.field("hit_window", &self.hit_window)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ impl OsuGradualDifficultyAttributes {
|
|||||||
h
|
h
|
||||||
});
|
});
|
||||||
|
|
||||||
let skills = create_skills(mods, scaling_factor.radius, time_preempt, time_fade_in);
|
let skills = Skills::new(mods, scaling_factor.radius, time_preempt, time_fade_in);
|
||||||
|
|
||||||
let last = match hit_objects_iter.next() {
|
let last = match hit_objects_iter.next() {
|
||||||
Some(prev) => prev,
|
Some(prev) => prev,
|
||||||
@@ -222,36 +222,25 @@ impl Iterator for OsuGradualDifficultyAttributes {
|
|||||||
let curr = self.diff_objects.get(self.idx)?;
|
let curr = self.diff_objects.get(self.idx)?;
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
|
|
||||||
for skill in self.skills.iter_mut() {
|
self.skills
|
||||||
skill.process(curr, &self.diff_objects, self.hit_window);
|
.process(curr, &self.diff_objects, self.hit_window);
|
||||||
}
|
|
||||||
|
|
||||||
Self::increment_combo(curr.base, &mut self.attrs);
|
Self::increment_combo(curr.base, &mut self.attrs);
|
||||||
|
|
||||||
let [aim, aim_no_sliders, speed, flashlight] = &self.skills;
|
let Skills {
|
||||||
|
mut aim,
|
||||||
let mut aim = aim.as_any().downcast_ref::<Aim>().unwrap().clone();
|
mut aim_no_sliders,
|
||||||
|
mut speed,
|
||||||
let mut aim_no_sliders = aim_no_sliders
|
mut flashlight,
|
||||||
.as_any()
|
} = self.skills.clone();
|
||||||
.downcast_ref::<Aim>()
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let mut aim_rating = aim.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
let mut aim_rating = aim.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
let aim_rating_no_sliders =
|
let aim_rating_no_sliders =
|
||||||
aim_no_sliders.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
aim_no_sliders.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
|
|
||||||
let mut speed = speed.as_any().downcast_ref::<Speed>().unwrap().clone();
|
|
||||||
let speed_notes = speed.relevant_note_count();
|
let speed_notes = speed.relevant_note_count();
|
||||||
let mut speed_rating = speed.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
let mut speed_rating = speed.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
|
|
||||||
let mut flashlight = flashlight
|
|
||||||
.as_any()
|
|
||||||
.downcast_ref::<Flashlight>()
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let mut flashlight_rating = flashlight.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
let mut flashlight_rating = flashlight.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
|
|
||||||
let slider_factor = if aim_rating > 0.0 {
|
let slider_factor = if aim_rating > 0.0 {
|
||||||
@@ -319,9 +308,8 @@ impl Iterator for OsuGradualDifficultyAttributes {
|
|||||||
let curr = self.diff_objects.get(self.idx)?;
|
let curr = self.diff_objects.get(self.idx)?;
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
|
|
||||||
for skill in self.skills.iter_mut() {
|
self.skills
|
||||||
skill.process(curr, &self.diff_objects, self.hit_window);
|
.process(curr, &self.diff_objects, self.hit_window);
|
||||||
}
|
|
||||||
|
|
||||||
Self::increment_combo(curr.base, &mut self.attrs);
|
Self::increment_combo(curr.base, &mut self.attrs);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-35
@@ -12,7 +12,7 @@ use self::{
|
|||||||
difficulty_object::{Distances, OsuDifficultyObject},
|
difficulty_object::{Distances, OsuDifficultyObject},
|
||||||
osu_object::{ObjectParameters, OsuObject},
|
osu_object::{ObjectParameters, OsuObject},
|
||||||
scaling_factor::ScalingFactor,
|
scaling_factor::ScalingFactor,
|
||||||
skills::{Aim, Flashlight, Skill, Speed},
|
skills::{Skill, Skills},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use self::{gradual_difficulty::*, gradual_performance::*, pp::*};
|
pub use self::{gradual_difficulty::*, gradual_performance::*, pp::*};
|
||||||
@@ -116,21 +116,19 @@ impl<'map> OsuStars<'map> {
|
|||||||
|
|
||||||
let (skills, mut attrs) = calculate_skills(self);
|
let (skills, mut attrs) = calculate_skills(self);
|
||||||
|
|
||||||
let [mut aim, mut aim_no_sliders, mut speed, mut flashlight] = skills;
|
let Skills {
|
||||||
|
mut aim,
|
||||||
|
mut aim_no_sliders,
|
||||||
|
mut speed,
|
||||||
|
mut flashlight,
|
||||||
|
} = skills;
|
||||||
|
|
||||||
let mut aim_rating = aim.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
let mut aim_rating = aim.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
let aim_rating_no_sliders =
|
let aim_rating_no_sliders =
|
||||||
aim_no_sliders.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
aim_no_sliders.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
|
|
||||||
let (mut speed_rating, speed_notes) =
|
let speed_notes = speed.relevant_note_count();
|
||||||
if let Some(speed) = speed.as_any_mut().downcast_mut::<Speed>() {
|
let mut speed_rating = speed.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
let notes = speed.relevant_note_count();
|
|
||||||
let rating = speed.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
|
||||||
|
|
||||||
(rating, notes)
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut flashlight_rating = flashlight.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
let mut flashlight_rating = flashlight.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
|
|
||||||
@@ -192,14 +190,19 @@ impl<'map> OsuStars<'map> {
|
|||||||
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 (skills, _) = calculate_skills(self);
|
let (skills, _) = calculate_skills(self);
|
||||||
|
|
||||||
let [mut aim, mut aim_no_sliders, mut speed, mut flashlight] = skills;
|
let Skills {
|
||||||
|
aim,
|
||||||
|
aim_no_sliders,
|
||||||
|
speed,
|
||||||
|
flashlight,
|
||||||
|
} = skills;
|
||||||
|
|
||||||
OsuStrains {
|
OsuStrains {
|
||||||
section_len: SECTION_LEN * clock_rate,
|
section_len: SECTION_LEN * clock_rate,
|
||||||
aim: aim.take_strain_peaks(),
|
aim: aim.strain_peaks,
|
||||||
aim_no_sliders: aim_no_sliders.take_strain_peaks(),
|
aim_no_sliders: aim_no_sliders.strain_peaks,
|
||||||
speed: speed.take_strain_peaks(),
|
speed: speed.strain_peaks,
|
||||||
flashlight: flashlight.take_strain_peaks(),
|
flashlight: flashlight.strain_peaks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,7 +232,7 @@ impl OsuStrains {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_skills(params: OsuStars<'_>) -> ([Box<dyn Skill>; 4], OsuDifficultyAttributes) {
|
fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||||
let OsuStars {
|
let OsuStars {
|
||||||
map,
|
map,
|
||||||
mods,
|
mods,
|
||||||
@@ -295,7 +298,7 @@ fn calculate_skills(params: OsuStars<'_>) -> ([Box<dyn Skill>; 4], OsuDifficulty
|
|||||||
h
|
h
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut skills = create_skills(mods, scaling_factor.radius, time_preempt, time_fade_in);
|
let mut skills = Skills::new(mods, scaling_factor.radius, time_preempt, time_fade_in);
|
||||||
|
|
||||||
let last = match hit_objects.next() {
|
let last = match hit_objects.next() {
|
||||||
Some(prev) => prev,
|
Some(prev) => prev,
|
||||||
@@ -333,9 +336,7 @@ fn calculate_skills(params: OsuStars<'_>) -> ([Box<dyn Skill>; 4], OsuDifficulty
|
|||||||
}
|
}
|
||||||
|
|
||||||
for curr in diff_objects.iter() {
|
for curr in diff_objects.iter() {
|
||||||
for skill in skills.iter_mut() {
|
skills.process(curr, &diff_objects, hit_window);
|
||||||
skill.process(curr, &diff_objects, hit_window);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(skills, attrs)
|
(skills, attrs)
|
||||||
@@ -491,20 +492,6 @@ fn old_stacking(hit_objects: &mut [OsuObject], stack_threshold: f64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_skills(
|
|
||||||
mods: u32,
|
|
||||||
radius: f32,
|
|
||||||
time_preempt: f64,
|
|
||||||
time_fade_in: f64,
|
|
||||||
) -> [Box<dyn Skill>; 4] {
|
|
||||||
[
|
|
||||||
Box::new(Aim::new(true)) as Box<dyn Skill>,
|
|
||||||
Box::new(Aim::new(false)) as Box<dyn Skill>,
|
|
||||||
Box::new(Speed::new()) as Box<dyn Skill>,
|
|
||||||
Box::new(Flashlight::new(mods, radius, time_preempt, time_fade_in)) as Box<dyn Skill>,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of a difficulty calculation on an osu!standard map.
|
/// The result of a difficulty calculation on an osu!standard map.
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct OsuDifficultyAttributes {
|
pub struct OsuDifficultyAttributes {
|
||||||
|
|||||||
+3
-22
@@ -1,19 +1,15 @@
|
|||||||
use std::{
|
use std::f64::consts::{FRAC_PI_2, PI};
|
||||||
any::Any,
|
|
||||||
f64::consts::{FRAC_PI_2, PI},
|
|
||||||
mem,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::osu::difficulty_object::OsuDifficultyObject;
|
use crate::osu::difficulty_object::OsuDifficultyObject;
|
||||||
|
|
||||||
use super::{previous, previous_start_time, OsuStrainSkill, Skill, StrainSkill};
|
use super::{previous, previous_start_time, OsuStrainSkill, Skill, StrainSkill};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct Aim {
|
pub(crate) struct Aim {
|
||||||
curr_strain: f64,
|
curr_strain: f64,
|
||||||
curr_section_peak: f64,
|
curr_section_peak: f64,
|
||||||
curr_section_end: f64,
|
curr_section_end: f64,
|
||||||
strain_peaks: Vec<f64>,
|
pub(crate) strain_peaks: Vec<f64>,
|
||||||
with_sliders: bool,
|
with_sliders: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,21 +47,6 @@ impl Skill for Aim {
|
|||||||
fn difficulty_value(&mut self) -> f64 {
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
<Self as OsuStrainSkill>::difficulty_value(self)
|
<Self as OsuStrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
|
||||||
mem::take(&mut self.strain_peaks)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StrainSkill for Aim {
|
impl StrainSkill for Aim {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::{any::Any, mem};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
osu::{difficulty_object::OsuDifficultyObject, osu_object::OsuObjectKind},
|
osu::{difficulty_object::OsuDifficultyObject, osu_object::OsuObjectKind},
|
||||||
Mods,
|
Mods,
|
||||||
@@ -7,12 +5,12 @@ use crate::{
|
|||||||
|
|
||||||
use super::{previous, previous_start_time, OsuStrainSkill, Skill, StrainSkill};
|
use super::{previous, previous_start_time, OsuStrainSkill, Skill, StrainSkill};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct Flashlight {
|
pub(crate) struct Flashlight {
|
||||||
curr_strain: f64,
|
curr_strain: f64,
|
||||||
curr_section_peak: f64,
|
curr_section_peak: f64,
|
||||||
curr_section_end: f64,
|
curr_section_end: f64,
|
||||||
strain_peaks: Vec<f64>,
|
pub(crate) strain_peaks: Vec<f64>,
|
||||||
has_hidden_mod: bool,
|
has_hidden_mod: bool,
|
||||||
scaling_factor: f64,
|
scaling_factor: f64,
|
||||||
time_preempt: f64,
|
time_preempt: f64,
|
||||||
@@ -56,21 +54,6 @@ impl Skill for Flashlight {
|
|||||||
fn difficulty_value(&mut self) -> f64 {
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
<Self as StrainSkill>::difficulty_value(self)
|
<Self as StrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
|
||||||
mem::take(&mut self.strain_peaks)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StrainSkill for Flashlight {
|
impl StrainSkill for Flashlight {
|
||||||
|
|||||||
+28
-128
@@ -1,145 +1,45 @@
|
|||||||
mod aim;
|
mod aim;
|
||||||
mod flashlight;
|
mod flashlight;
|
||||||
mod speed;
|
mod speed;
|
||||||
|
mod traits;
|
||||||
|
|
||||||
use std::{any::Any, cmp::Ordering, mem};
|
use crate::osu::difficulty_object::OsuDifficultyObject;
|
||||||
|
|
||||||
pub(crate) use self::{aim::Aim, flashlight::Flashlight, speed::Speed};
|
pub(crate) use self::{
|
||||||
|
aim::Aim,
|
||||||
|
flashlight::Flashlight,
|
||||||
|
speed::Speed,
|
||||||
|
traits::{OsuStrainSkill, Skill, StrainSkill},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{difficulty_object::OsuDifficultyObject, SECTION_LEN};
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Skills {
|
||||||
pub(crate) trait Skill {
|
pub aim: Aim,
|
||||||
fn process(
|
pub aim_no_sliders: Aim,
|
||||||
&mut self,
|
pub speed: Speed,
|
||||||
curr: &OsuDifficultyObject<'_>,
|
pub flashlight: Flashlight,
|
||||||
diff_objects: &[OsuDifficultyObject<'_>],
|
|
||||||
hit_window: f64,
|
|
||||||
);
|
|
||||||
fn difficulty_value(&mut self) -> f64;
|
|
||||||
fn as_any(&self) -> &dyn Any;
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
|
||||||
fn take_strain_peaks(&mut self) -> Vec<f64>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait StrainSkill: Skill + Sized {
|
impl Skills {
|
||||||
const DECAY_WEIGHT: f64 = 0.9;
|
pub(crate) fn new(mods: u32, radius: f32, time_preempt: f64, time_fade_in: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
aim: Aim::new(true),
|
||||||
|
aim_no_sliders: Aim::new(false),
|
||||||
|
speed: Speed::new(),
|
||||||
|
flashlight: Flashlight::new(mods, radius, time_preempt, time_fade_in),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64>;
|
pub(crate) fn process(
|
||||||
fn curr_section_peak(&mut self) -> &mut f64;
|
|
||||||
fn curr_section_end(&mut self) -> &mut f64;
|
|
||||||
|
|
||||||
fn strain_value_at(
|
|
||||||
&mut self,
|
|
||||||
curr: &OsuDifficultyObject<'_>,
|
|
||||||
diff_objects: &[OsuDifficultyObject<'_>],
|
|
||||||
hit_window: f64,
|
|
||||||
) -> f64;
|
|
||||||
|
|
||||||
fn calculate_initial_strain(
|
|
||||||
&self,
|
|
||||||
time: f64,
|
|
||||||
curr: &OsuDifficultyObject<'_>,
|
|
||||||
diff_objects: &[OsuDifficultyObject<'_>],
|
|
||||||
) -> f64;
|
|
||||||
|
|
||||||
fn process(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
curr: &OsuDifficultyObject<'_>,
|
curr: &OsuDifficultyObject<'_>,
|
||||||
diff_objects: &[OsuDifficultyObject<'_>],
|
diff_objects: &[OsuDifficultyObject<'_>],
|
||||||
hit_window: f64,
|
hit_window: f64,
|
||||||
) {
|
) {
|
||||||
// * The first object doesn't generate a strain, so we begin with an incremented section end
|
<Aim as Skill>::process(&mut self.aim, curr, diff_objects, hit_window);
|
||||||
if curr.idx == 0 {
|
<Aim as Skill>::process(&mut self.aim_no_sliders, curr, diff_objects, hit_window);
|
||||||
let section_len = SECTION_LEN as f64;
|
<Speed as Skill>::process(&mut self.speed, curr, diff_objects, hit_window);
|
||||||
*self.curr_section_end() = (curr.start_time / section_len).ceil() * section_len;
|
<Flashlight as Skill>::process(&mut self.flashlight, curr, diff_objects, hit_window);
|
||||||
}
|
|
||||||
|
|
||||||
while curr.start_time > *self.curr_section_end() {
|
|
||||||
self.save_curr_peak();
|
|
||||||
|
|
||||||
{
|
|
||||||
let section_end = *self.curr_section_end();
|
|
||||||
self.start_new_section_from(section_end, curr, diff_objects);
|
|
||||||
}
|
|
||||||
|
|
||||||
*self.curr_section_end() += SECTION_LEN as f64;
|
|
||||||
}
|
|
||||||
|
|
||||||
*self.curr_section_peak() = self
|
|
||||||
.strain_value_at(curr, diff_objects, hit_window)
|
|
||||||
.max(*self.curr_section_peak());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn save_curr_peak(&mut self) {
|
|
||||||
let peak = *self.curr_section_peak();
|
|
||||||
self.strain_peaks_mut().push(peak);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn start_new_section_from(
|
|
||||||
&mut self,
|
|
||||||
time: f64,
|
|
||||||
curr: &OsuDifficultyObject<'_>,
|
|
||||||
diff_objects: &[OsuDifficultyObject<'_>],
|
|
||||||
) {
|
|
||||||
// * The maximum strain of the new section is not zero by default
|
|
||||||
// * This means we need to capture the strain level at the beginning of the new section,
|
|
||||||
// * and use that as the initial peak level.
|
|
||||||
*self.curr_section_peak() = self.calculate_initial_strain(time, curr, diff_objects);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn difficulty_value(&mut self) -> f64;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get_curr_strain_peaks(&mut self) -> Vec<f64> {
|
|
||||||
let curr_peak = *self.curr_section_peak();
|
|
||||||
let mut strain_peaks = mem::take(self.strain_peaks_mut());
|
|
||||||
strain_peaks.push(curr_peak);
|
|
||||||
|
|
||||||
strain_peaks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait OsuStrainSkill: StrainSkill + Sized {
|
|
||||||
const REDUCED_SECTION_COUNT: usize = 10;
|
|
||||||
const REDUCED_STRAIN_BASELINE: f64 = 0.75;
|
|
||||||
const DIFFICULTY_MULTIPLER: f64 = 1.06;
|
|
||||||
|
|
||||||
fn difficulty_value(&mut self) -> f64 {
|
|
||||||
let mut difficulty = 0.0;
|
|
||||||
let mut weight = 1.0;
|
|
||||||
|
|
||||||
// * Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
|
||||||
// * These sections will not contribute to the difficulty.
|
|
||||||
let mut peaks = self.get_curr_strain_peaks();
|
|
||||||
|
|
||||||
peaks.retain(|&peak| peak > 0.0);
|
|
||||||
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
|
||||||
|
|
||||||
let peak_iter = peaks.iter_mut().take(Self::REDUCED_SECTION_COUNT);
|
|
||||||
|
|
||||||
fn lerp(start: f64, end: f64, amount: f64) -> f64 {
|
|
||||||
start + (end - start) * amount
|
|
||||||
}
|
|
||||||
|
|
||||||
// * We are reducing the highest strains first to account for extreme difficulty spikes
|
|
||||||
for (i, strain) in peak_iter.enumerate() {
|
|
||||||
let clamped = (i as f32 / Self::REDUCED_SECTION_COUNT as f32).clamp(0.0, 1.0) as f64;
|
|
||||||
let scale = (lerp(1.0, 10.0, clamped)).log10();
|
|
||||||
*strain *= lerp(Self::REDUCED_STRAIN_BASELINE, 1.0, scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
|
||||||
|
|
||||||
// * Difficulty is the weighted sum of the highest strains from every section.
|
|
||||||
// * We're sorting from highest to lowest strain.
|
|
||||||
for strain in peaks {
|
|
||||||
difficulty += strain * weight;
|
|
||||||
weight *= Self::DECAY_WEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
difficulty * Self::DIFFICULTY_MULTIPLER
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-18
@@ -1,16 +1,16 @@
|
|||||||
use std::{any::Any, cmp::Ordering, f64::consts::PI, mem};
|
use std::{cmp::Ordering, f64::consts::PI};
|
||||||
|
|
||||||
use crate::osu::difficulty_object::OsuDifficultyObject;
|
use crate::osu::difficulty_object::OsuDifficultyObject;
|
||||||
|
|
||||||
use super::{next, previous, previous_start_time, OsuStrainSkill, Skill, StrainSkill};
|
use super::{next, previous, previous_start_time, OsuStrainSkill, Skill, StrainSkill};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct Speed {
|
pub(crate) struct Speed {
|
||||||
curr_strain: f64,
|
curr_strain: f64,
|
||||||
curr_section_peak: f64,
|
curr_section_peak: f64,
|
||||||
curr_section_end: f64,
|
curr_section_end: f64,
|
||||||
curr_rhythm: f64,
|
curr_rhythm: f64,
|
||||||
strain_peaks: Vec<f64>,
|
pub(crate) strain_peaks: Vec<f64>,
|
||||||
object_strains: Vec<f64>,
|
object_strains: Vec<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,21 +62,6 @@ impl Skill for Speed {
|
|||||||
fn difficulty_value(&mut self) -> f64 {
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
<Self as OsuStrainSkill>::difficulty_value(self)
|
<Self as OsuStrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
|
||||||
mem::take(&mut self.strain_peaks)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StrainSkill for Speed {
|
impl StrainSkill for Speed {
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
use std::{cmp::Ordering, mem};
|
||||||
|
|
||||||
|
use crate::osu::{difficulty_object::OsuDifficultyObject, SECTION_LEN};
|
||||||
|
|
||||||
|
pub(crate) trait Skill {
|
||||||
|
fn process(
|
||||||
|
&mut self,
|
||||||
|
curr: &OsuDifficultyObject<'_>,
|
||||||
|
diff_objects: &[OsuDifficultyObject<'_>],
|
||||||
|
hit_window: f64,
|
||||||
|
);
|
||||||
|
fn difficulty_value(&mut self) -> f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait StrainSkill: Skill + Sized {
|
||||||
|
const DECAY_WEIGHT: f64 = 0.9;
|
||||||
|
|
||||||
|
fn strain_peaks_mut(&mut self) -> &mut Vec<f64>;
|
||||||
|
fn curr_section_peak(&mut self) -> &mut f64;
|
||||||
|
fn curr_section_end(&mut self) -> &mut f64;
|
||||||
|
|
||||||
|
fn strain_value_at(
|
||||||
|
&mut self,
|
||||||
|
curr: &OsuDifficultyObject<'_>,
|
||||||
|
diff_objects: &[OsuDifficultyObject<'_>],
|
||||||
|
hit_window: f64,
|
||||||
|
) -> f64;
|
||||||
|
|
||||||
|
fn calculate_initial_strain(
|
||||||
|
&self,
|
||||||
|
time: f64,
|
||||||
|
curr: &OsuDifficultyObject<'_>,
|
||||||
|
diff_objects: &[OsuDifficultyObject<'_>],
|
||||||
|
) -> f64;
|
||||||
|
|
||||||
|
fn process(
|
||||||
|
&mut self,
|
||||||
|
curr: &OsuDifficultyObject<'_>,
|
||||||
|
diff_objects: &[OsuDifficultyObject<'_>],
|
||||||
|
hit_window: f64,
|
||||||
|
) {
|
||||||
|
// * The first object doesn't generate a strain, so we begin with an incremented section end
|
||||||
|
if curr.idx == 0 {
|
||||||
|
let section_len = SECTION_LEN as f64;
|
||||||
|
*self.curr_section_end() = (curr.start_time / section_len).ceil() * section_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
while curr.start_time > *self.curr_section_end() {
|
||||||
|
self.save_curr_peak();
|
||||||
|
|
||||||
|
{
|
||||||
|
let section_end = *self.curr_section_end();
|
||||||
|
self.start_new_section_from(section_end, curr, diff_objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
*self.curr_section_end() += SECTION_LEN as f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
*self.curr_section_peak() = self
|
||||||
|
.strain_value_at(curr, diff_objects, hit_window)
|
||||||
|
.max(*self.curr_section_peak());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn save_curr_peak(&mut self) {
|
||||||
|
let peak = *self.curr_section_peak();
|
||||||
|
self.strain_peaks_mut().push(peak);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn start_new_section_from(
|
||||||
|
&mut self,
|
||||||
|
time: f64,
|
||||||
|
curr: &OsuDifficultyObject<'_>,
|
||||||
|
diff_objects: &[OsuDifficultyObject<'_>],
|
||||||
|
) {
|
||||||
|
// * The maximum strain of the new section is not zero by default
|
||||||
|
// * This means we need to capture the strain level at the beginning of the new section,
|
||||||
|
// * and use that as the initial peak level.
|
||||||
|
*self.curr_section_peak() = self.calculate_initial_strain(time, curr, diff_objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn difficulty_value(&mut self) -> f64;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_curr_strain_peaks(&mut self) -> Vec<f64> {
|
||||||
|
let curr_peak = *self.curr_section_peak();
|
||||||
|
let mut strain_peaks = mem::take(self.strain_peaks_mut());
|
||||||
|
strain_peaks.push(curr_peak);
|
||||||
|
|
||||||
|
strain_peaks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait OsuStrainSkill: StrainSkill + Sized {
|
||||||
|
const REDUCED_SECTION_COUNT: usize = 10;
|
||||||
|
const REDUCED_STRAIN_BASELINE: f64 = 0.75;
|
||||||
|
const DIFFICULTY_MULTIPLER: f64 = 1.06;
|
||||||
|
|
||||||
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
|
let mut difficulty = 0.0;
|
||||||
|
let mut weight = 1.0;
|
||||||
|
|
||||||
|
// * Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
||||||
|
// * These sections will not contribute to the difficulty.
|
||||||
|
let mut peaks = self.get_curr_strain_peaks();
|
||||||
|
|
||||||
|
peaks.retain(|&peak| peak > 0.0);
|
||||||
|
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||||
|
|
||||||
|
let peak_iter = peaks.iter_mut().take(Self::REDUCED_SECTION_COUNT);
|
||||||
|
|
||||||
|
fn lerp(start: f64, end: f64, amount: f64) -> f64 {
|
||||||
|
start + (end - start) * amount
|
||||||
|
}
|
||||||
|
|
||||||
|
// * We are reducing the highest strains first to account for extreme difficulty spikes
|
||||||
|
for (i, strain) in peak_iter.enumerate() {
|
||||||
|
let clamped = (i as f32 / Self::REDUCED_SECTION_COUNT as f32).clamp(0.0, 1.0) as f64;
|
||||||
|
let scale = (lerp(1.0, 10.0, clamped)).log10();
|
||||||
|
*strain *= lerp(Self::REDUCED_STRAIN_BASELINE, 1.0, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||||
|
|
||||||
|
// * Difficulty is the weighted sum of the highest strains from every section.
|
||||||
|
// * We're sorting from highest to lowest strain.
|
||||||
|
for strain in peaks {
|
||||||
|
difficulty += strain * weight;
|
||||||
|
weight *= Self::DECAY_WEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
difficulty * Self::DIFFICULTY_MULTIPLER
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user