implemented hotfix & various bug fixes
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
- [BREAKING] Instead of returning `StarResult`, difficulty calculations now return `DifficultyAttributes` depending on the mode.
|
- [BREAKING] Instead of returning `StarResult`, difficulty calculations now return `DifficultyAttributes` depending on the mode.
|
||||||
- [BREAKING] Various fields and methods now include `f64` instead of `f32` to stay true to osu!'s original code
|
- [BREAKING] Various fields and methods now include `f64` instead of `f32` to stay true to osu!'s original code
|
||||||
- added internal binary crate `pp-gen` to calculate difficulty & pp values via `PerformanceCalculator.dll`
|
- added internal binary crate `pp-gen` to calculate difficulty & pp values via `PerformanceCalculator.dll`
|
||||||
- osu: Updated up to commit [baa5285b59911efa1433a298f365133254a96874](https://github.com/ppy/osu/commit/baa5285b59911efa1433a298f365133254a96874) (2021-11-09)
|
- osu: Updated up to commit [9fb2402781ad91c197d51aeec716b0000f52c4d1](https://github.com/ppy/osu/commit/9fb2402781ad91c197d51aeec716b0000f52c4d1) (2021-11-12)
|
||||||
|
|
||||||
# v0.2.3
|
# v0.2.3
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ use super::{OsuObject, ScalingFactor, NORMALIZED_RADIUS};
|
|||||||
|
|
||||||
const MIN_DELTA_TIME: f64 = 25.0;
|
const MIN_DELTA_TIME: f64 = 25.0;
|
||||||
const MAXIMUM_SLIDER_RADIUS: f32 = NORMALIZED_RADIUS * 2.4;
|
const MAXIMUM_SLIDER_RADIUS: f32 = NORMALIZED_RADIUS * 2.4;
|
||||||
const ASSUMED_SLIDER_RADIUS: f32 = NORMALIZED_RADIUS * 1.65;
|
const ASSUMED_SLIDER_RADIUS: f32 = NORMALIZED_RADIUS * 1.8;
|
||||||
|
|
||||||
pub(crate) struct DifficultyObject<'h> {
|
pub(crate) struct DifficultyObject<'h> {
|
||||||
pub(crate) base: &'h OsuObject,
|
pub(crate) base: &'h OsuObject,
|
||||||
|
pub(crate) clock_rate: f64,
|
||||||
|
|
||||||
pub(crate) delta: f64,
|
pub(crate) delta: f64,
|
||||||
pub(crate) strain_time: f64,
|
pub(crate) strain_time: f64,
|
||||||
@@ -33,7 +34,7 @@ impl<'h> DifficultyObject<'h> {
|
|||||||
scaling_factor: &ScalingFactor,
|
scaling_factor: &ScalingFactor,
|
||||||
clock_rate: f64,
|
clock_rate: f64,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delta = base.time - prev.time;
|
let delta = (base.time - prev.time) / clock_rate;
|
||||||
|
|
||||||
// * Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects
|
// * Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects
|
||||||
let strain_time = delta.max(MIN_DELTA_TIME);
|
let strain_time = delta.max(MIN_DELTA_TIME);
|
||||||
@@ -95,8 +96,21 @@ impl<'h> DifficultyObject<'h> {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ? Common values to debug
|
||||||
|
// println!("travel_dist={} | travel_time={}", travel_dist, travel_time);
|
||||||
|
// println!(
|
||||||
|
// "movement_dist={} | movement_time={}",
|
||||||
|
// movement_dist, movement_time
|
||||||
|
// );
|
||||||
|
// println!(
|
||||||
|
// "jump_dist={} | strain_time={} | angle={:?}",
|
||||||
|
// jump_dist, strain_time, angle
|
||||||
|
// );
|
||||||
|
// println!("--");
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
|
clock_rate,
|
||||||
delta,
|
delta,
|
||||||
strain_time,
|
strain_time,
|
||||||
jump_dist,
|
jump_dist,
|
||||||
@@ -188,9 +202,9 @@ impl<'h> DifficultyObject<'h> {
|
|||||||
|
|
||||||
let lazy_travel_time = nested_objects
|
let lazy_travel_time = nested_objects
|
||||||
.last()
|
.last()
|
||||||
.map_or(0.0, |nested| nested.time / clock_rate - prev_time);
|
.map_or(0.0, |nested| nested.time - prev_time);
|
||||||
|
|
||||||
let travel_time = MIN_DELTA_TIME.max(lazy_travel_time);
|
let travel_time = MIN_DELTA_TIME.max(lazy_travel_time / clock_rate);
|
||||||
|
|
||||||
(travel_dist, travel_time)
|
(travel_dist, travel_time)
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-27
@@ -16,8 +16,6 @@ use skill::Skill;
|
|||||||
use skill_kind::SkillKind;
|
use skill_kind::SkillKind;
|
||||||
use slider_state::SliderState;
|
use slider_state::SliderState;
|
||||||
|
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
use crate::{curve::CurveBuffers, Beatmap, Mods, Strains};
|
use crate::{curve::CurveBuffers, Beatmap, Mods, Strains};
|
||||||
|
|
||||||
const SECTION_LEN: f64 = 400.0;
|
const SECTION_LEN: f64 = 400.0;
|
||||||
@@ -88,13 +86,9 @@ pub fn stars(
|
|||||||
old_stacking(&mut hit_objects, stack_threshold);
|
old_stacking(&mut hit_objects, stack_threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
// let scale_factor = (scaling_factor.scale * -6.4) as f32;
|
|
||||||
|
|
||||||
let mut hit_objects = hit_objects.into_iter().map(|mut h| {
|
let mut hit_objects = hit_objects.into_iter().map(|mut h| {
|
||||||
// let stack_offset = Pos2::new(h.stack_height * scale_factor);
|
|
||||||
let stack_offset = scaling_factor.stack_offset(h.stack_height);
|
let stack_offset = scaling_factor.stack_offset(h.stack_height);
|
||||||
h.pos += stack_offset;
|
h.pos += stack_offset;
|
||||||
h.time /= map_attributes.clock_rate;
|
|
||||||
|
|
||||||
h
|
h
|
||||||
});
|
});
|
||||||
@@ -102,7 +96,8 @@ pub fn stars(
|
|||||||
let fl = mods.fl();
|
let fl = mods.fl();
|
||||||
let mut skills = Vec::with_capacity(2 + fl as usize);
|
let mut skills = Vec::with_capacity(2 + fl as usize);
|
||||||
|
|
||||||
skills.push(Skill::aim());
|
skills.push(Skill::aim(true));
|
||||||
|
skills.push(Skill::aim(false));
|
||||||
skills.push(Skill::speed(hit_window));
|
skills.push(Skill::speed(hit_window));
|
||||||
|
|
||||||
if fl {
|
if fl {
|
||||||
@@ -114,7 +109,8 @@ pub fn stars(
|
|||||||
let mut prev = hit_objects.next().unwrap();
|
let mut prev = hit_objects.next().unwrap();
|
||||||
|
|
||||||
// First object has no predecessor and thus no strain, handle distinctly
|
// First object has no predecessor and thus no strain, handle distinctly
|
||||||
let mut current_section_end = (prev.time / SECTION_LEN).ceil() * SECTION_LEN;
|
let mut current_section_end =
|
||||||
|
(prev.time / map_attributes.clock_rate / SECTION_LEN).ceil() * SECTION_LEN;
|
||||||
|
|
||||||
// Handle second object separately to remove later if-branching
|
// Handle second object separately to remove later if-branching
|
||||||
let curr = hit_objects.next().unwrap();
|
let curr = hit_objects.next().unwrap();
|
||||||
@@ -126,7 +122,9 @@ pub fn stars(
|
|||||||
map_attributes.clock_rate,
|
map_attributes.clock_rate,
|
||||||
);
|
);
|
||||||
|
|
||||||
while h.base.time > current_section_end {
|
let base_time = h.base.time / map_attributes.clock_rate;
|
||||||
|
|
||||||
|
while base_time > current_section_end {
|
||||||
for skill in skills.iter_mut() {
|
for skill in skills.iter_mut() {
|
||||||
skill.start_new_section_from(current_section_end);
|
skill.start_new_section_from(current_section_end);
|
||||||
}
|
}
|
||||||
@@ -151,7 +149,9 @@ pub fn stars(
|
|||||||
map_attributes.clock_rate,
|
map_attributes.clock_rate,
|
||||||
);
|
);
|
||||||
|
|
||||||
while h.base.time > current_section_end {
|
let base_time = h.base.time / map_attributes.clock_rate;
|
||||||
|
|
||||||
|
while base_time > current_section_end {
|
||||||
for skill in skills.iter_mut() {
|
for skill in skills.iter_mut() {
|
||||||
skill.save_current_peak();
|
skill.save_current_peak();
|
||||||
skill.start_new_section_from(current_section_end);
|
skill.start_new_section_from(current_section_end);
|
||||||
@@ -174,13 +174,21 @@ pub fn stars(
|
|||||||
|
|
||||||
let aim_rating = skills[0].difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
let aim_rating = skills[0].difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
|
|
||||||
|
let slider_factor = if aim_rating > 0.0 {
|
||||||
|
let aim_rating_no_sliders = skills[1].difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
|
|
||||||
|
aim_rating_no_sliders / aim_rating
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
|
||||||
let speed_rating = if mods.rx() {
|
let speed_rating = if mods.rx() {
|
||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
skills[1].difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER
|
skills[2].difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER
|
||||||
};
|
};
|
||||||
|
|
||||||
let flashlight_rating = skills.get_mut(2).map_or(0.0, |skill| {
|
let flashlight_rating = skills.get_mut(3).map_or(0.0, |skill| {
|
||||||
skill.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER
|
skill.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -222,6 +230,7 @@ pub fn stars(
|
|||||||
aim_strain: aim_rating,
|
aim_strain: aim_rating,
|
||||||
speed_strain: speed_rating,
|
speed_strain: speed_rating,
|
||||||
flashlight_rating,
|
flashlight_rating,
|
||||||
|
slider_factor,
|
||||||
n_circles: map.n_circles as usize,
|
n_circles: map.n_circles as usize,
|
||||||
n_sliders: map.n_sliders as usize,
|
n_sliders: map.n_sliders as usize,
|
||||||
n_spinners: map.n_spinners as usize,
|
n_spinners: map.n_spinners as usize,
|
||||||
@@ -278,13 +287,9 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
old_stacking(&mut hit_objects, stack_threshold);
|
old_stacking(&mut hit_objects, stack_threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
// let scale_factor = (scaling_factor.scale * -6.4) as f32;
|
|
||||||
|
|
||||||
let mut hit_objects = hit_objects.into_iter().map(|mut h| {
|
let mut hit_objects = hit_objects.into_iter().map(|mut h| {
|
||||||
// let stack_offset = Pos2::new(h.stack_height * scale_factor);
|
|
||||||
let stack_offset = scaling_factor.stack_offset(h.stack_height);
|
let stack_offset = scaling_factor.stack_offset(h.stack_height);
|
||||||
h.pos += stack_offset;
|
h.pos += stack_offset;
|
||||||
h.time /= map_attributes.clock_rate;
|
|
||||||
|
|
||||||
h
|
h
|
||||||
});
|
});
|
||||||
@@ -292,7 +297,8 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
let fl = mods.fl();
|
let fl = mods.fl();
|
||||||
let mut skills = Vec::with_capacity(2 + fl as usize);
|
let mut skills = Vec::with_capacity(2 + fl as usize);
|
||||||
|
|
||||||
skills.push(Skill::aim());
|
skills.push(Skill::aim(true));
|
||||||
|
skills.push(Skill::aim(false));
|
||||||
skills.push(Skill::speed(hit_window));
|
skills.push(Skill::speed(hit_window));
|
||||||
|
|
||||||
if fl {
|
if fl {
|
||||||
@@ -304,7 +310,8 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
let mut prev = hit_objects.next().unwrap();
|
let mut prev = hit_objects.next().unwrap();
|
||||||
|
|
||||||
// First object has no predecessor and thus no strain, handle distinctly
|
// First object has no predecessor and thus no strain, handle distinctly
|
||||||
let mut current_section_end = (prev.time / SECTION_LEN).ceil() * SECTION_LEN;
|
let mut current_section_end =
|
||||||
|
(prev.time / map_attributes.clock_rate / SECTION_LEN).ceil() * SECTION_LEN;
|
||||||
|
|
||||||
// Handle second object separately to remove later if-branching
|
// Handle second object separately to remove later if-branching
|
||||||
let curr = hit_objects.next().unwrap();
|
let curr = hit_objects.next().unwrap();
|
||||||
@@ -316,7 +323,9 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
map_attributes.clock_rate,
|
map_attributes.clock_rate,
|
||||||
);
|
);
|
||||||
|
|
||||||
while h.base.time > current_section_end {
|
let base_time = h.base.time / map_attributes.clock_rate;
|
||||||
|
|
||||||
|
while base_time > current_section_end {
|
||||||
for skill in skills.iter_mut() {
|
for skill in skills.iter_mut() {
|
||||||
skill.start_new_section_from(current_section_end);
|
skill.start_new_section_from(current_section_end);
|
||||||
}
|
}
|
||||||
@@ -341,7 +350,9 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
map_attributes.clock_rate,
|
map_attributes.clock_rate,
|
||||||
);
|
);
|
||||||
|
|
||||||
while h.base.time > current_section_end {
|
let base_time = h.base.time / map_attributes.clock_rate;
|
||||||
|
|
||||||
|
while base_time > current_section_end {
|
||||||
for skill in skills.iter_mut() {
|
for skill in skills.iter_mut() {
|
||||||
skill.save_current_peak();
|
skill.save_current_peak();
|
||||||
skill.start_new_section_from(current_section_end);
|
skill.start_new_section_from(current_section_end);
|
||||||
@@ -362,13 +373,13 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
skill.save_current_peak();
|
skill.save_current_peak();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut speed_strains = skills.pop().unwrap().strain_peaks;
|
skills.reverse();
|
||||||
let mut aim_strains = skills.pop().unwrap().strain_peaks;
|
|
||||||
|
|
||||||
let strains = if let Some(mut flashlight_strains) = skills.pop().map(|s| s.strain_peaks) {
|
let _ = skills.pop();
|
||||||
mem::swap(&mut speed_strains, &mut aim_strains);
|
let aim_strains = skills.pop().unwrap().strain_peaks; // no sliders
|
||||||
mem::swap(&mut aim_strains, &mut flashlight_strains);
|
let speed_strains = skills.pop().unwrap().strain_peaks;
|
||||||
|
|
||||||
|
let strains = if let Some(flashlight_strains) = skills.pop().map(|s| s.strain_peaks) {
|
||||||
aim_strains
|
aim_strains
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(speed_strains)
|
.zip(speed_strains)
|
||||||
@@ -540,6 +551,7 @@ pub struct DifficultyAttributes {
|
|||||||
pub aim_strain: f64,
|
pub aim_strain: f64,
|
||||||
pub speed_strain: f64,
|
pub speed_strain: f64,
|
||||||
pub flashlight_rating: f64,
|
pub flashlight_rating: f64,
|
||||||
|
pub slider_factor: f64,
|
||||||
pub ar: f64,
|
pub ar: f64,
|
||||||
pub od: f64,
|
pub od: f64,
|
||||||
pub hp: f64,
|
pub hp: f64,
|
||||||
@@ -587,7 +599,7 @@ fn custom_osu() {
|
|||||||
|
|
||||||
use crate::{Beatmap, OsuPP};
|
use crate::{Beatmap, OsuPP};
|
||||||
|
|
||||||
let path = "E:Games/osu!/beatmaps/70090_.osu";
|
let path = "E:Games/osu!/beatmaps/116169_.osu";
|
||||||
let file = File::open(path).unwrap();
|
let file = File::open(path).unwrap();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
@@ -609,7 +621,7 @@ fn custom_osu() {
|
|||||||
println!("Parsing average: {:?}", accum / iters);
|
println!("Parsing average: {:?}", accum / iters);
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let result = OsuPP::new(&map).mods(16 + 64).calculate();
|
let result = OsuPP::new(&map).mods(2 + 64).calculate();
|
||||||
|
|
||||||
let iters = 100;
|
let iters = 100;
|
||||||
let accum = start.elapsed();
|
let accum = start.elapsed();
|
||||||
|
|||||||
+22
-2
@@ -1,3 +1,5 @@
|
|||||||
|
use std::{cmp::Ordering, convert::identity};
|
||||||
|
|
||||||
use super::slider_state::SliderState;
|
use super::slider_state::SliderState;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -16,6 +18,7 @@ pub(crate) struct OsuObject {
|
|||||||
pub(crate) kind: OsuObjectKind,
|
pub(crate) kind: OsuObjectKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) enum OsuObjectKind {
|
pub(crate) enum OsuObjectKind {
|
||||||
Circle,
|
Circle,
|
||||||
Slider {
|
Slider {
|
||||||
@@ -226,7 +229,24 @@ impl OsuObject {
|
|||||||
kind: NestedObjectKind::Tail,
|
kind: NestedObjectKind::Tail,
|
||||||
};
|
};
|
||||||
|
|
||||||
nested_objects.push(legacy_last_tick);
|
// On very short buzz sliders it can happen that the
|
||||||
|
// legacy last tick is not the last object time-wise
|
||||||
|
match nested_objects.last() {
|
||||||
|
Some(last) if last.time > final_span_end_time => {
|
||||||
|
let idx = nested_objects
|
||||||
|
.binary_search_by(|nested| {
|
||||||
|
nested
|
||||||
|
.time
|
||||||
|
.partial_cmp(&final_span_end_time)
|
||||||
|
.unwrap_or(Ordering::Equal)
|
||||||
|
})
|
||||||
|
.map_or_else(identity, identity);
|
||||||
|
|
||||||
|
nested_objects.insert(idx, legacy_last_tick);
|
||||||
|
}
|
||||||
|
_ => nested_objects.push(legacy_last_tick),
|
||||||
|
};
|
||||||
|
|
||||||
*max_combo += nested_objects.len();
|
*max_combo += nested_objects.len();
|
||||||
|
|
||||||
let lazy_travel_time = final_span_end_time - h.start_time;
|
let lazy_travel_time = final_span_end_time - h.start_time;
|
||||||
@@ -250,7 +270,7 @@ impl OsuObject {
|
|||||||
pos,
|
pos,
|
||||||
stack_height: 0.0,
|
stack_height: 0.0,
|
||||||
kind: OsuObjectKind::Slider {
|
kind: OsuObjectKind::Slider {
|
||||||
end_time: final_span_end_time,
|
end_time,
|
||||||
end_pos,
|
end_pos,
|
||||||
lazy_end_pos,
|
lazy_end_pos,
|
||||||
nested_objects,
|
nested_objects,
|
||||||
|
|||||||
@@ -400,6 +400,25 @@ impl OsuPPInner {
|
|||||||
aim_value *= 1.0 + 0.04 * (12.0 - attributes.ar);
|
aim_value *= 1.0 + 0.04 * (12.0 - attributes.ar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if attributes.n_sliders > 0 {
|
||||||
|
// * We assume 15% of sliders in a map are difficult since
|
||||||
|
// * there's no way to tell from the performance calculator.
|
||||||
|
let estimate_difficult_sliders = attributes.n_sliders as f64 * 0.15;
|
||||||
|
|
||||||
|
let non_300s = self.total_hits - self.n300 as f64;
|
||||||
|
let missing_combo = attributes.max_combo - self.combo.unwrap_or(attributes.max_combo);
|
||||||
|
|
||||||
|
let estimate_slider_ends_dropped = non_300s
|
||||||
|
.min(missing_combo as f64)
|
||||||
|
.clamp(0.0, estimate_difficult_sliders);
|
||||||
|
|
||||||
|
let base = 1.0 - estimate_slider_ends_dropped / estimate_difficult_sliders;
|
||||||
|
let slider_nerf_factor =
|
||||||
|
(1.0 - attributes.slider_factor) * base * base * base + attributes.slider_factor;
|
||||||
|
|
||||||
|
aim_value *= slider_nerf_factor;
|
||||||
|
}
|
||||||
|
|
||||||
aim_value *= self.acc;
|
aim_value *= self.acc;
|
||||||
aim_value *= 0.98 + attributes.od * attributes.od / 2500.0;
|
aim_value *= 0.98 + attributes.od * attributes.od / 2500.0;
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -18,8 +18,8 @@ pub(crate) struct Skill {
|
|||||||
|
|
||||||
impl Skill {
|
impl Skill {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn aim() -> Self {
|
pub(crate) fn aim(with_sliders: bool) -> Self {
|
||||||
Self::new(SkillKind::aim())
|
Self::new(SkillKind::aim(with_sliders))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -49,7 +49,7 @@ impl Skill {
|
|||||||
pub(crate) fn process(&mut self, curr: &DifficultyObject<'_>) {
|
pub(crate) fn process(&mut self, curr: &DifficultyObject<'_>) {
|
||||||
self.kind.pre_process();
|
self.kind.pre_process();
|
||||||
self.curr_section_peak = self.strain_value_at(curr).max(self.curr_section_peak);
|
self.curr_section_peak = self.strain_value_at(curr).max(self.curr_section_peak);
|
||||||
self.prev_time = Some(curr.base.time);
|
self.prev_time = Some(curr.base.time / curr.clock_rate);
|
||||||
self.kind.post_process(curr);
|
self.kind.post_process(curr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +65,13 @@ impl Skill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn difficulty_value(&mut self) -> f64 {
|
pub(crate) fn difficulty_value(&mut self) -> f64 {
|
||||||
|
// ? Common values to debug
|
||||||
|
// println!("---");
|
||||||
|
|
||||||
|
// for (i, strain) in self.strain_peaks.iter().enumerate() {
|
||||||
|
// println!("[{}] {}", i, strain);
|
||||||
|
// }
|
||||||
|
|
||||||
let mut difficulty = 0.0;
|
let mut difficulty = 0.0;
|
||||||
let mut weight = 1.0;
|
let mut weight = 1.0;
|
||||||
let decay_weight = self.kind.decay_weight();
|
let decay_weight = self.kind.decay_weight();
|
||||||
|
|||||||
+18
-9
@@ -89,6 +89,7 @@ impl From<&DifficultyObject<'_>> for FlashlightHistoryEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct SpeedHistoryEntry {
|
pub(crate) struct SpeedHistoryEntry {
|
||||||
is_slider: bool,
|
is_slider: bool,
|
||||||
start_time: f64,
|
start_time: f64,
|
||||||
@@ -99,7 +100,7 @@ impl From<&DifficultyObject<'_>> for SpeedHistoryEntry {
|
|||||||
fn from(h: &DifficultyObject<'_>) -> Self {
|
fn from(h: &DifficultyObject<'_>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
is_slider: h.base.is_slider(),
|
is_slider: h.base.is_slider(),
|
||||||
start_time: h.base.time,
|
start_time: h.base.time / h.clock_rate,
|
||||||
strain_time: h.strain_time,
|
strain_time: h.strain_time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,6 +109,7 @@ impl From<&DifficultyObject<'_>> for SpeedHistoryEntry {
|
|||||||
pub(crate) enum SkillKind {
|
pub(crate) enum SkillKind {
|
||||||
Aim {
|
Aim {
|
||||||
history: VecDeque<AimHistoryEntry>,
|
history: VecDeque<AimHistoryEntry>,
|
||||||
|
with_sliders: bool,
|
||||||
},
|
},
|
||||||
Flashlight {
|
Flashlight {
|
||||||
history: VecDeque<FlashlightHistoryEntry>,
|
history: VecDeque<FlashlightHistoryEntry>,
|
||||||
@@ -121,9 +123,10 @@ pub(crate) enum SkillKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SkillKind {
|
impl SkillKind {
|
||||||
pub(crate) fn aim() -> Self {
|
pub(crate) fn aim(with_sliders: bool) -> Self {
|
||||||
Self::Aim {
|
Self::Aim {
|
||||||
history: VecDeque::with_capacity(AIM_HISTORY_LENGTH + 1),
|
history: VecDeque::with_capacity(AIM_HISTORY_LENGTH + 1),
|
||||||
|
with_sliders,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +147,7 @@ impl SkillKind {
|
|||||||
|
|
||||||
pub(crate) fn pre_process(&mut self) {
|
pub(crate) fn pre_process(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
Self::Aim { history } => history.truncate(AIM_HISTORY_LENGTH),
|
Self::Aim { history, .. } => history.truncate(AIM_HISTORY_LENGTH),
|
||||||
Self::Flashlight { history, .. } => history.truncate(FLASHLIGHT_HISTORY_LENGTH),
|
Self::Flashlight { history, .. } => history.truncate(FLASHLIGHT_HISTORY_LENGTH),
|
||||||
Self::Speed { history, .. } => history.truncate(SPEED_HISTORY_LENGTH),
|
Self::Speed { history, .. } => history.truncate(SPEED_HISTORY_LENGTH),
|
||||||
}
|
}
|
||||||
@@ -152,7 +155,7 @@ impl SkillKind {
|
|||||||
|
|
||||||
pub(crate) fn post_process(&mut self, current: &DifficultyObject<'_>) {
|
pub(crate) fn post_process(&mut self, current: &DifficultyObject<'_>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Aim { history } => history.push_front(current.into()),
|
Self::Aim { history, .. } => history.push_front(current.into()),
|
||||||
Self::Flashlight { history, .. } => history.push_front(current.into()),
|
Self::Flashlight { history, .. } => history.push_front(current.into()),
|
||||||
Self::Speed { history, .. } => history.push_front(current.into()),
|
Self::Speed { history, .. } => history.push_front(current.into()),
|
||||||
}
|
}
|
||||||
@@ -160,7 +163,10 @@ impl SkillKind {
|
|||||||
|
|
||||||
pub(crate) fn strain_value_of(&self, curr: &DifficultyObject<'_>) -> f64 {
|
pub(crate) fn strain_value_of(&self, curr: &DifficultyObject<'_>) -> f64 {
|
||||||
match self {
|
match self {
|
||||||
Self::Aim { history } => {
|
Self::Aim {
|
||||||
|
history,
|
||||||
|
with_sliders,
|
||||||
|
} => {
|
||||||
if curr.base.is_spinner() || history.len() < 2 || history[0].is_spinner {
|
if curr.base.is_spinner() || history.len() < 2 || history[0].is_spinner {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
@@ -174,7 +180,7 @@ impl SkillKind {
|
|||||||
|
|
||||||
// * But if the last object is a slider, then we extend the
|
// * But if the last object is a slider, then we extend the
|
||||||
// * travel velocity through the slider into the current object.
|
// * travel velocity through the slider into the current object.
|
||||||
if prev.is_slider {
|
if prev.is_slider && *with_sliders {
|
||||||
// * calculate the movement velocity from slider end to current object
|
// * calculate the movement velocity from slider end to current object
|
||||||
let movement_velocity = curr.movement_dist / curr.movement_time;
|
let movement_velocity = curr.movement_dist / curr.movement_time;
|
||||||
|
|
||||||
@@ -188,7 +194,7 @@ impl SkillKind {
|
|||||||
// * As above, do the same for the previous hitobject.
|
// * As above, do the same for the previous hitobject.
|
||||||
let mut prev_velocity = prev.jump_dist / prev.strain_time;
|
let mut prev_velocity = prev.jump_dist / prev.strain_time;
|
||||||
|
|
||||||
if prev_prev.is_slider {
|
if prev_prev.is_slider && *with_sliders {
|
||||||
let movement_velocity = prev.movement_dist / prev.movement_time;
|
let movement_velocity = prev.movement_dist / prev.movement_time;
|
||||||
let travel_velocity = prev.travel_dist / prev.travel_time;
|
let travel_velocity = prev.travel_dist / prev.travel_time;
|
||||||
prev_velocity = prev_velocity.max(movement_velocity + travel_velocity);
|
prev_velocity = prev_velocity.max(movement_velocity + travel_velocity);
|
||||||
@@ -302,7 +308,9 @@ impl SkillKind {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// * Add in additional slider velocity bonus.
|
// * Add in additional slider velocity bonus.
|
||||||
|
if *with_sliders {
|
||||||
aim_strain += slider_bonus * AIM_SLIDER_MULTIPLIER;
|
aim_strain += slider_bonus * AIM_SLIDER_MULTIPLIER;
|
||||||
|
}
|
||||||
|
|
||||||
aim_strain
|
aim_strain
|
||||||
}
|
}
|
||||||
@@ -466,8 +474,9 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
|||||||
let lasts = history.iter().skip(2);
|
let lasts = history.iter().skip(2);
|
||||||
|
|
||||||
for (((prev, curr), last), i) in prevs.zip(currs).zip(lasts).rev().zip(2..) {
|
for (((prev, curr), last), i) in prevs.zip(currs).zip(lasts).rev().zip(2..) {
|
||||||
let mut curr_historical_decay =
|
let mut curr_historical_decay = (SPEED_HISTORY_TIME_MAX
|
||||||
(SPEED_HISTORY_TIME_MAX - (current.base.time - curr.start_time)).max(0.0)
|
- (current.base.time / current.clock_rate - curr.start_time))
|
||||||
|
.max(0.0)
|
||||||
/ SPEED_HISTORY_TIME_MAX;
|
/ SPEED_HISTORY_TIME_MAX;
|
||||||
|
|
||||||
if curr_historical_decay.abs() > f64::EPSILON {
|
if curr_historical_decay.abs() > f64::EPSILON {
|
||||||
|
|||||||
+1
-1
@@ -320,7 +320,7 @@ macro_rules! parse_timingpoints_body {
|
|||||||
if beat_len < 0.0 {
|
if beat_len < 0.0 {
|
||||||
let point = DifficultyPoint {
|
let point = DifficultyPoint {
|
||||||
time,
|
time,
|
||||||
speed_multiplier: -100.0 / beat_len,
|
speed_multiplier: (-100.0 / beat_len).max(0.1).min(10.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
$self.difficulty_points.push(point);
|
$self.difficulty_points.push(point);
|
||||||
|
|||||||
Reference in New Issue
Block a user