osu: bugfixes
This commit is contained in:
+28
-20
@@ -1,12 +1,18 @@
|
|||||||
#![cfg(any(
|
#[cfg(any(
|
||||||
feature = "fruits",
|
feature = "fruits",
|
||||||
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
|
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
|
||||||
))]
|
))]
|
||||||
|
pub(crate) use fruits_osu::*;
|
||||||
|
|
||||||
use crate::parse::Pos2;
|
#[cfg(any(
|
||||||
|
feature = "fruits",
|
||||||
|
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
|
||||||
|
))]
|
||||||
|
mod fruits_osu {
|
||||||
|
use crate::parse::Pos2;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn cpn(mut p: i32, n: i32) -> f32 {
|
pub(crate) fn cpn(mut p: i32, n: i32) -> f32 {
|
||||||
if p < 0 || p > n {
|
if p < 0 || p > n {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
@@ -20,9 +26,9 @@ pub(crate) fn cpn(mut p: i32, n: i32) -> f32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn point_at_distance(points: &[Pos2], dist: f32) -> Pos2 {
|
pub(crate) fn point_at_distance(points: &[Pos2], dist: f32) -> Pos2 {
|
||||||
if points.len() < 2 {
|
if points.len() < 2 {
|
||||||
return Pos2::zero();
|
return Pos2::zero();
|
||||||
} else if dist.abs() <= f32::EPSILON {
|
} else if dist.abs() <= f32::EPSILON {
|
||||||
@@ -56,9 +62,9 @@ pub(crate) fn point_at_distance(points: &[Pos2], dist: f32) -> Pos2 {
|
|||||||
let last = points[points.len() - 1];
|
let last = points[points.len() - 1];
|
||||||
|
|
||||||
pre_last + (last - pre_last) * (remaining_dist / new_dist)
|
pre_last + (last - pre_last) * (remaining_dist / new_dist)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_circum_circle(p0: Pos2, p1: Pos2, p2: Pos2) -> (Pos2, f32) {
|
pub(crate) fn get_circum_circle(p0: Pos2, p1: Pos2, p2: Pos2) -> (Pos2, f32) {
|
||||||
let a = 2.0 * (p0.x * (p1.y - p2.y) - p0.y * (p1.x - p2.x) + p1.x * p2.y - p2.x * p1.y);
|
let a = 2.0 * (p0.x * (p1.y - p2.y) - p0.y * (p1.x - p2.x) + p1.x * p2.y - p2.x * p1.y);
|
||||||
|
|
||||||
let q0 = p0.length_squared();
|
let q0 = p0.length_squared();
|
||||||
@@ -71,20 +77,20 @@ pub(crate) fn get_circum_circle(p0: Pos2, p1: Pos2, p2: Pos2) -> (Pos2, f32) {
|
|||||||
let r = (cx - p0.x).hypot(cy - p0.y);
|
let r = (cx - p0.x).hypot(cy - p0.y);
|
||||||
|
|
||||||
(Pos2 { x: cx, y: cy }, r)
|
(Pos2 { x: cx, y: cy }, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_left(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
|
pub(crate) fn is_left(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
|
||||||
((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)) < 0.0
|
((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)) < 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_linear(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
|
pub(crate) fn is_linear(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
|
||||||
((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)).abs() <= f32::EPSILON
|
((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)).abs() <= f32::EPSILON
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn valid_linear(points: &[Pos2]) -> bool {
|
pub(crate) fn valid_linear(points: &[Pos2]) -> bool {
|
||||||
for (curr, next) in points.iter().skip(1).zip(points.iter().skip(2)).step_by(2) {
|
for (curr, next) in points.iter().skip(1).zip(points.iter().skip(2)).step_by(2) {
|
||||||
if curr != next {
|
if curr != next {
|
||||||
return false;
|
return false;
|
||||||
@@ -92,10 +98,10 @@ pub(crate) fn valid_linear(points: &[Pos2]) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn rotate(center: Pos2, origin: Pos2, theta: f32) -> Pos2 {
|
pub(crate) fn rotate(center: Pos2, origin: Pos2, theta: f32) -> Pos2 {
|
||||||
let (sin, cos) = theta.sin_cos();
|
let (sin, cos) = theta.sin_cos();
|
||||||
let diff = origin - center;
|
let diff = origin - center;
|
||||||
|
|
||||||
@@ -105,8 +111,10 @@ pub(crate) fn rotate(center: Pos2, origin: Pos2, theta: f32) -> Pos2 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
center + offset
|
center + offset
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "osu")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn lerp(start: f32, end: f32, percent: f32) -> f32 {
|
pub(crate) fn lerp(start: f32, end: f32, percent: f32) -> f32 {
|
||||||
start + (end - start) * percent
|
start + (end - start) * percent
|
||||||
|
|||||||
@@ -53,9 +53,10 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut raw_ar = map.ar;
|
let mut raw_ar = map.ar;
|
||||||
|
let hr = mods.hr();
|
||||||
|
|
||||||
if mods.hr() {
|
if hr {
|
||||||
raw_ar *= 1.4;
|
raw_ar = (raw_ar * 1.4).min(10.0);
|
||||||
} else if mods.ez() {
|
} else if mods.ez() {
|
||||||
raw_ar *= 0.5;
|
raw_ar *= 0.5;
|
||||||
}
|
}
|
||||||
@@ -85,6 +86,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
|
|||||||
map,
|
map,
|
||||||
radius,
|
radius,
|
||||||
scaling_factor,
|
scaling_factor,
|
||||||
|
hr,
|
||||||
&mut ticks_buf,
|
&mut ticks_buf,
|
||||||
&mut diff_attributes,
|
&mut diff_attributes,
|
||||||
&mut slider_state,
|
&mut slider_state,
|
||||||
@@ -100,8 +102,10 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
|
|||||||
old_stacking(&mut hit_objects, stack_threshold);
|
old_stacking(&mut hit_objects, stack_threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let scale_factor = scale * -6.4;
|
||||||
|
|
||||||
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 = h.stack_height * scale * -6.4;
|
let stack_offset = h.stack_height * scale_factor;
|
||||||
|
|
||||||
h.pos += Pos2 {
|
h.pos += Pos2 {
|
||||||
x: stack_offset,
|
x: stack_offset,
|
||||||
@@ -175,16 +179,16 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
|
|||||||
aim.save_current_peak();
|
aim.save_current_peak();
|
||||||
speed.save_current_peak();
|
speed.save_current_peak();
|
||||||
|
|
||||||
let aim_strain = aim.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
let aim_rating = aim.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
let speed_strain = speed.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
let speed_rating = speed.difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||||
|
|
||||||
let stars = aim_strain + speed_strain + (aim_strain - speed_strain).abs() / 2.0;
|
let stars = aim_rating + speed_rating + (aim_rating - speed_rating).abs() / 2.0;
|
||||||
|
|
||||||
diff_attributes.n_circles = map.n_circles as usize;
|
diff_attributes.n_circles = map.n_circles as usize;
|
||||||
diff_attributes.n_spinners = map.n_spinners as usize;
|
diff_attributes.n_spinners = map.n_spinners as usize;
|
||||||
diff_attributes.stars = stars;
|
diff_attributes.stars = stars;
|
||||||
diff_attributes.speed_strain = speed_strain;
|
diff_attributes.speed_strain = speed_rating;
|
||||||
diff_attributes.aim_strain = aim_strain;
|
diff_attributes.aim_strain = aim_rating;
|
||||||
|
|
||||||
StarResult::Osu(diff_attributes)
|
StarResult::Osu(diff_attributes)
|
||||||
}
|
}
|
||||||
@@ -209,8 +213,9 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut raw_ar = map.ar;
|
let mut raw_ar = map.ar;
|
||||||
|
let hr = mods.hr();
|
||||||
|
|
||||||
if mods.hr() {
|
if hr {
|
||||||
raw_ar *= 1.4;
|
raw_ar *= 1.4;
|
||||||
} else if mods.ez() {
|
} else if mods.ez() {
|
||||||
raw_ar *= 0.5;
|
raw_ar *= 0.5;
|
||||||
@@ -240,6 +245,7 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
map,
|
map,
|
||||||
radius,
|
radius,
|
||||||
scaling_factor,
|
scaling_factor,
|
||||||
|
hr,
|
||||||
&mut ticks_buf,
|
&mut ticks_buf,
|
||||||
&mut diff_attributes,
|
&mut diff_attributes,
|
||||||
&mut slider_state,
|
&mut slider_state,
|
||||||
@@ -255,8 +261,10 @@ 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 = scale * -6.4;
|
||||||
|
|
||||||
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 = h.stack_height * scale * -6.4;
|
let stack_offset = h.stack_height * scale_factor;
|
||||||
|
|
||||||
h.pos += Pos2 {
|
h.pos += Pos2 {
|
||||||
x: stack_offset,
|
x: stack_offset,
|
||||||
@@ -350,7 +358,7 @@ fn stacking(hit_objects: &mut [OsuObject], stack_threshold: f32) {
|
|||||||
for mut i in (1..=extended_end_idx).rev() {
|
for mut i in (1..=extended_end_idx).rev() {
|
||||||
let mut n = i;
|
let mut n = i;
|
||||||
|
|
||||||
if hit_objects[i].stack_height != 0.0 || !hit_objects[i].is_slider() {
|
if hit_objects[i].stack_height.abs() > 0.0 || hit_objects[i].is_spinner() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,6 +370,8 @@ fn stacking(hit_objects: &mut [OsuObject], stack_threshold: f32) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if hit_objects[n].is_spinner() {
|
if hit_objects[n].is_spinner() {
|
||||||
|
continue;
|
||||||
|
} else if hit_objects[i].time - hit_objects[n].end_time() > stack_threshold {
|
||||||
break;
|
break;
|
||||||
} else if n < extended_start_idx {
|
} else if n < extended_start_idx {
|
||||||
hit_objects[n].stack_height = 0.0;
|
hit_objects[n].stack_height = 0.0;
|
||||||
|
|||||||
@@ -30,23 +30,29 @@ enum OsuObjectKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OsuObject {
|
impl OsuObject {
|
||||||
|
#[allow(clippy::clippy::too_many_arguments)]
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
h: &HitObject,
|
h: &HitObject,
|
||||||
map: &Beatmap,
|
map: &Beatmap,
|
||||||
radius: f32,
|
radius: f32,
|
||||||
scaling_factor: f32,
|
scaling_factor: f32,
|
||||||
|
hr: bool,
|
||||||
ticks: &mut Vec<f32>,
|
ticks: &mut Vec<f32>,
|
||||||
attributes: &mut DifficultyAttributes,
|
attributes: &mut DifficultyAttributes,
|
||||||
slider_state: &mut SliderState,
|
slider_state: &mut SliderState,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
attributes.max_combo += 1; // hitcircle, slider head, or spinner
|
attributes.max_combo += 1; // hitcircle, slider head, or spinner
|
||||||
let stack_height = 0.0;
|
let mut pos = h.pos;
|
||||||
|
|
||||||
|
if hr {
|
||||||
|
pos.y = 384.0 - pos.y;
|
||||||
|
}
|
||||||
|
|
||||||
let obj = match &h.kind {
|
let obj = match &h.kind {
|
||||||
HitObjectKind::Circle => Self {
|
HitObjectKind::Circle => Self {
|
||||||
time: h.start_time,
|
time: h.start_time,
|
||||||
pos: h.pos,
|
pos,
|
||||||
stack_height,
|
stack_height: 0.0,
|
||||||
kind: OsuObjectKind::Circle,
|
kind: OsuObjectKind::Circle,
|
||||||
},
|
},
|
||||||
HitObjectKind::Slider {
|
HitObjectKind::Slider {
|
||||||
@@ -56,7 +62,7 @@ impl OsuObject {
|
|||||||
path_type,
|
path_type,
|
||||||
} => {
|
} => {
|
||||||
// Key values which are computed here
|
// Key values which are computed here
|
||||||
let mut lazy_end_pos = h.pos;
|
let mut lazy_end_pos = pos;
|
||||||
let mut travel_dist = 0.0;
|
let mut travel_dist = 0.0;
|
||||||
|
|
||||||
// Responsible for timing point values
|
// Responsible for timing point values
|
||||||
@@ -93,7 +99,11 @@ impl OsuObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let curr_dist = pixel_len * progress;
|
let curr_dist = pixel_len * progress;
|
||||||
let curr_pos = curve.point_at_distance(curr_dist);
|
let mut curr_pos = curve.point_at_distance(curr_dist);
|
||||||
|
|
||||||
|
if hr {
|
||||||
|
curr_pos.y = 384.0 - curr_pos.y;
|
||||||
|
}
|
||||||
|
|
||||||
let diff = curr_pos - lazy_end_pos;
|
let diff = curr_pos - lazy_end_pos;
|
||||||
let mut dist = diff.length();
|
let mut dist = diff.length();
|
||||||
@@ -153,12 +163,16 @@ impl OsuObject {
|
|||||||
|
|
||||||
travel_dist *= scaling_factor;
|
travel_dist *= scaling_factor;
|
||||||
|
|
||||||
let end_pos = curve.point_at_distance(*pixel_len);
|
let mut end_pos = curve.point_at_distance(*pixel_len);
|
||||||
|
|
||||||
|
if hr {
|
||||||
|
end_pos.y = 384.0 - end_pos.y;
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
time: h.start_time,
|
time: h.start_time,
|
||||||
pos: h.pos,
|
pos,
|
||||||
stack_height,
|
stack_height: 0.0,
|
||||||
kind: OsuObjectKind::Slider {
|
kind: OsuObjectKind::Slider {
|
||||||
end_time: final_span_end_time,
|
end_time: final_span_end_time,
|
||||||
end_pos,
|
end_pos,
|
||||||
@@ -169,8 +183,8 @@ impl OsuObject {
|
|||||||
}
|
}
|
||||||
HitObjectKind::Spinner { end_time } => Self {
|
HitObjectKind::Spinner { end_time } => Self {
|
||||||
time: h.start_time,
|
time: h.start_time,
|
||||||
pos: h.pos,
|
pos,
|
||||||
stack_height,
|
stack_height: 0.0,
|
||||||
kind: OsuObjectKind::Spinner {
|
kind: OsuObjectKind::Spinner {
|
||||||
end_time: *end_time,
|
end_time: *end_time,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ use std::cmp::Ordering;
|
|||||||
|
|
||||||
const SPEED_SKILL_MULTIPLIER: f32 = 1400.0;
|
const SPEED_SKILL_MULTIPLIER: f32 = 1400.0;
|
||||||
const SPEED_STRAIN_DECAY_BASE: f32 = 0.3;
|
const SPEED_STRAIN_DECAY_BASE: f32 = 0.3;
|
||||||
const REDUCED_SECTION_COUNT: f32 = 10.0;
|
|
||||||
const REDUCED_STRAIN_BASELINE: f32 = 0.75;
|
const REDUCED_STRAIN_BASELINE: f32 = 0.75;
|
||||||
const DIFFICULTY_MULTIPLIER: f32 = 1.06;
|
|
||||||
|
|
||||||
const AIM_SKILL_MULTIPLIER: f32 = 26.25;
|
const AIM_SKILL_MULTIPLIER: f32 = 26.25;
|
||||||
const AIM_STRAIN_DECAY_BASE: f32 = 0.15;
|
const AIM_STRAIN_DECAY_BASE: f32 = 0.15;
|
||||||
@@ -61,11 +59,19 @@ impl Skill {
|
|||||||
let mut difficulty = 0.0;
|
let mut difficulty = 0.0;
|
||||||
let mut weight = 1.0;
|
let mut weight = 1.0;
|
||||||
|
|
||||||
|
let (reduced_section_count, difficulty_multiplier) = self.kind.difficulty_values();
|
||||||
|
let reduced_section_count_f32 = reduced_section_count as f32;
|
||||||
|
|
||||||
self.strain_peaks
|
self.strain_peaks
|
||||||
.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||||
|
|
||||||
for (i, strain) in self.strain_peaks.iter_mut().enumerate() {
|
for (i, strain) in self
|
||||||
let clamped = (i as f32 / REDUCED_SECTION_COUNT).clamp(0.0, 1.0);
|
.strain_peaks
|
||||||
|
.iter_mut()
|
||||||
|
.take(reduced_section_count)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let clamped = (i as f32 / reduced_section_count_f32).clamp(0.0, 1.0);
|
||||||
let scale = (math_util::lerp(1.0, 10.0, clamped)).log10();
|
let scale = (math_util::lerp(1.0, 10.0, clamped)).log10();
|
||||||
*strain *= math_util::lerp(REDUCED_STRAIN_BASELINE, 1.0, scale);
|
*strain *= math_util::lerp(REDUCED_STRAIN_BASELINE, 1.0, scale);
|
||||||
}
|
}
|
||||||
@@ -78,7 +84,7 @@ impl Skill {
|
|||||||
weight *= DECAY_WEIGHT;
|
weight *= DECAY_WEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
difficulty * DIFFICULTY_MULTIPLIER
|
difficulty * difficulty_multiplier
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ const SPEED_BALANCING_FACTOR: f32 = 40.0;
|
|||||||
const AIM_ANGLE_BONUS_BEGIN: f32 = std::f32::consts::FRAC_PI_3;
|
const AIM_ANGLE_BONUS_BEGIN: f32 = std::f32::consts::FRAC_PI_3;
|
||||||
const TIMING_THRESHOLD: f32 = 107.0;
|
const TIMING_THRESHOLD: f32 = 107.0;
|
||||||
|
|
||||||
|
const AIM_REDUCED_SECTION_COUNT: usize = 10;
|
||||||
|
const SPEED_REDUCED_SECTION_COUNT: usize = 5;
|
||||||
|
|
||||||
|
const AIM_DIFFICULTY_MULTIPLIER: f32 = 1.06;
|
||||||
|
const SPEED_DIFFICULTY_MULTIPLIER: f32 = 1.04;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) enum SkillKind {
|
pub(crate) enum SkillKind {
|
||||||
Aim,
|
Aim,
|
||||||
@@ -92,6 +98,14 @@ impl SkillKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn difficulty_values(&self) -> (usize, f32) {
|
||||||
|
match self {
|
||||||
|
Self::Aim => (AIM_REDUCED_SECTION_COUNT, AIM_DIFFICULTY_MULTIPLIER),
|
||||||
|
Self::Speed => (SPEED_REDUCED_SECTION_COUNT, SPEED_DIFFICULTY_MULTIPLIER),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -273,20 +273,3 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
strains,
|
strains,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn custom() {
|
|
||||||
let map_id = 1579923;
|
|
||||||
let file = std::fs::File::open(format!(
|
|
||||||
"C:/Users/Max/Desktop/Coding/C#/osu-tools/cache/{}_.osu",
|
|
||||||
map_id
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
let map = crate::Beatmap::parse(file).unwrap();
|
|
||||||
let result = crate::OsuPP::new(&map).mods((1 << 10) + 24).calculate();
|
|
||||||
|
|
||||||
println!("Stars: {} | PP: {}", result.stars(), result.pp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use std::cmp::Ordering;
|
|||||||
const SPEED_SKILL_MULTIPLIER: f32 = 1400.0;
|
const SPEED_SKILL_MULTIPLIER: f32 = 1400.0;
|
||||||
const SPEED_STRAIN_DECAY_BASE: f32 = 0.3;
|
const SPEED_STRAIN_DECAY_BASE: f32 = 0.3;
|
||||||
const REDUCED_STRAIN_BASELINE: f32 = 0.75;
|
const REDUCED_STRAIN_BASELINE: f32 = 0.75;
|
||||||
const DIFFICULTY_MULTIPLIER: f32 = 1.06;
|
|
||||||
|
|
||||||
const AIM_SKILL_MULTIPLIER: f32 = 26.25;
|
const AIM_SKILL_MULTIPLIER: f32 = 26.25;
|
||||||
const AIM_STRAIN_DECAY_BASE: f32 = 0.15;
|
const AIM_STRAIN_DECAY_BASE: f32 = 0.15;
|
||||||
@@ -60,7 +59,7 @@ impl Skill {
|
|||||||
let mut difficulty = 0.0;
|
let mut difficulty = 0.0;
|
||||||
let mut weight = 1.0;
|
let mut weight = 1.0;
|
||||||
|
|
||||||
let reduced_section_count = self.kind.reduced_section_count();
|
let (reduced_section_count, difficulty_multiplier) = self.kind.difficulty_values();
|
||||||
let reduced_section_count_f32 = reduced_section_count as f32;
|
let reduced_section_count_f32 = reduced_section_count as f32;
|
||||||
|
|
||||||
self.strain_peaks
|
self.strain_peaks
|
||||||
@@ -85,7 +84,7 @@ impl Skill {
|
|||||||
weight *= DECAY_WEIGHT;
|
weight *= DECAY_WEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
difficulty * DIFFICULTY_MULTIPLIER
|
difficulty * difficulty_multiplier
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ const TIMING_THRESHOLD: f32 = 107.0;
|
|||||||
const AIM_REDUCED_SECTION_COUNT: usize = 10;
|
const AIM_REDUCED_SECTION_COUNT: usize = 10;
|
||||||
const SPEED_REDUCED_SECTION_COUNT: usize = 5;
|
const SPEED_REDUCED_SECTION_COUNT: usize = 5;
|
||||||
|
|
||||||
|
const AIM_DIFFICULTY_MULTIPLIER: f32 = 1.06;
|
||||||
|
const SPEED_DIFFICULTY_MULTIPLIER: f32 = 1.04;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) enum SkillKind {
|
pub(crate) enum SkillKind {
|
||||||
Aim,
|
Aim,
|
||||||
@@ -97,10 +100,10 @@ impl SkillKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn reduced_section_count(&self) -> usize {
|
pub(crate) fn difficulty_values(&self) -> (usize, f32) {
|
||||||
match self {
|
match self {
|
||||||
Self::Aim => AIM_REDUCED_SECTION_COUNT,
|
Self::Aim => (AIM_REDUCED_SECTION_COUNT, AIM_DIFFICULTY_MULTIPLIER),
|
||||||
Self::Speed => SPEED_REDUCED_SECTION_COUNT,
|
Self::Speed => (SPEED_REDUCED_SECTION_COUNT, SPEED_DIFFICULTY_MULTIPLIER),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ use std::cmp::Ordering;
|
|||||||
|
|
||||||
const SPEED_SKILL_MULTIPLIER: f32 = 1400.0;
|
const SPEED_SKILL_MULTIPLIER: f32 = 1400.0;
|
||||||
const SPEED_STRAIN_DECAY_BASE: f32 = 0.3;
|
const SPEED_STRAIN_DECAY_BASE: f32 = 0.3;
|
||||||
const REDUCED_SECTION_COUNT: f32 = 10.0;
|
|
||||||
const REDUCED_STRAIN_BASELINE: f32 = 0.75;
|
const REDUCED_STRAIN_BASELINE: f32 = 0.75;
|
||||||
const DIFFICULTY_MULTIPLIER: f32 = 1.06;
|
|
||||||
|
|
||||||
const AIM_SKILL_MULTIPLIER: f32 = 26.25;
|
const AIM_SKILL_MULTIPLIER: f32 = 26.25;
|
||||||
const AIM_STRAIN_DECAY_BASE: f32 = 0.15;
|
const AIM_STRAIN_DECAY_BASE: f32 = 0.15;
|
||||||
@@ -61,11 +59,19 @@ impl Skill {
|
|||||||
let mut difficulty = 0.0;
|
let mut difficulty = 0.0;
|
||||||
let mut weight = 1.0;
|
let mut weight = 1.0;
|
||||||
|
|
||||||
|
let (reduced_section_count, difficulty_multiplier) = self.kind.difficulty_values();
|
||||||
|
let reduced_section_count_f32 = reduced_section_count as f32;
|
||||||
|
|
||||||
self.strain_peaks
|
self.strain_peaks
|
||||||
.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||||
|
|
||||||
for (i, strain) in self.strain_peaks.iter_mut().enumerate() {
|
for (i, strain) in self
|
||||||
let clamped = (i as f32 / REDUCED_SECTION_COUNT).clamp(0.0, 1.0);
|
.strain_peaks
|
||||||
|
.iter_mut()
|
||||||
|
.take(reduced_section_count)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let clamped = (i as f32 / reduced_section_count_f32).clamp(0.0, 1.0);
|
||||||
let scale = (math_util::lerp(1.0, 10.0, clamped)).log10();
|
let scale = (math_util::lerp(1.0, 10.0, clamped)).log10();
|
||||||
*strain *= math_util::lerp(REDUCED_STRAIN_BASELINE, 1.0, scale);
|
*strain *= math_util::lerp(REDUCED_STRAIN_BASELINE, 1.0, scale);
|
||||||
}
|
}
|
||||||
@@ -78,7 +84,7 @@ impl Skill {
|
|||||||
weight *= DECAY_WEIGHT;
|
weight *= DECAY_WEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
difficulty * DIFFICULTY_MULTIPLIER
|
difficulty * difficulty_multiplier
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ const SPEED_BALANCING_FACTOR: f32 = 40.0;
|
|||||||
const AIM_ANGLE_BONUS_BEGIN: f32 = std::f32::consts::FRAC_PI_3;
|
const AIM_ANGLE_BONUS_BEGIN: f32 = std::f32::consts::FRAC_PI_3;
|
||||||
const TIMING_THRESHOLD: f32 = 107.0;
|
const TIMING_THRESHOLD: f32 = 107.0;
|
||||||
|
|
||||||
|
const AIM_REDUCED_SECTION_COUNT: usize = 10;
|
||||||
|
const SPEED_REDUCED_SECTION_COUNT: usize = 5;
|
||||||
|
|
||||||
|
const AIM_DIFFICULTY_MULTIPLIER: f32 = 1.06;
|
||||||
|
const SPEED_DIFFICULTY_MULTIPLIER: f32 = 1.04;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) enum SkillKind {
|
pub(crate) enum SkillKind {
|
||||||
Aim,
|
Aim,
|
||||||
@@ -88,6 +94,14 @@ impl SkillKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn difficulty_values(&self) -> (usize, f32) {
|
||||||
|
match self {
|
||||||
|
Self::Aim => (AIM_REDUCED_SECTION_COUNT, AIM_DIFFICULTY_MULTIPLIER),
|
||||||
|
Self::Speed => (SPEED_REDUCED_SECTION_COUNT, SPEED_DIFFICULTY_MULTIPLIER),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
Reference in New Issue
Block a user