fixes for osu_fast feature
This commit is contained in:
@@ -5,6 +5,7 @@ pub(crate) struct DifficultyObject<'h> {
|
||||
pub(crate) prev: Option<(f32, f32)>, // (jump_dist, strain_time)
|
||||
|
||||
pub(crate) jump_dist: f32,
|
||||
pub(crate) travel_dist: f32,
|
||||
pub(crate) angle: Option<f32>,
|
||||
|
||||
pub(crate) delta: f32,
|
||||
@@ -20,19 +21,20 @@ impl<'h> DifficultyObject<'h> {
|
||||
scaling_factor: f32,
|
||||
) -> Self {
|
||||
let delta = base.time - prev.time;
|
||||
let travel_dist = prev.travel_dist();
|
||||
|
||||
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects
|
||||
let strain_time = delta.max(25.0);
|
||||
|
||||
// We don't need to calculate either angle or distance
|
||||
// when one of the last->curr objects is a spinner
|
||||
let (jump_dist, angle) = if base.is_spinner {
|
||||
let (jump_dist, angle) = if base.is_spinner() || prev.is_spinner() {
|
||||
(0.0, None)
|
||||
} else {
|
||||
let jump_dist = ((base.pos - prev.pos) * scaling_factor).length();
|
||||
let jump_dist = ((base.pos - prev.end_pos()) * scaling_factor).length();
|
||||
|
||||
let angle = prev_prev.map(|prev_prev| {
|
||||
let v1 = prev_prev.pos - prev.pos;
|
||||
let v1 = prev_prev.end_pos() - prev.pos;
|
||||
let v2 = base.pos - prev.pos;
|
||||
|
||||
let dot = v1.dot(v2);
|
||||
@@ -49,6 +51,7 @@ impl<'h> DifficultyObject<'h> {
|
||||
prev: prev_vals,
|
||||
|
||||
jump_dist,
|
||||
travel_dist,
|
||||
angle,
|
||||
|
||||
delta,
|
||||
|
||||
+58
-8
@@ -76,19 +76,44 @@ pub fn stars(
|
||||
HitObjectKind::Circle => {
|
||||
max_combo += 1;
|
||||
|
||||
OsuObject::from(h, clock_rate)
|
||||
Some(OsuObject::circle(h, clock_rate))
|
||||
}
|
||||
#[cfg(feature = "sliders")]
|
||||
HitObjectKind::Slider {
|
||||
pixel_len, repeats, ..
|
||||
pixel_len,
|
||||
repeats,
|
||||
control_points,
|
||||
} => {
|
||||
max_combo += state.count_ticks(h.start_time, *pixel_len, *repeats, map);
|
||||
|
||||
OsuObject::from(h, clock_rate)
|
||||
Some(OsuObject::slider(
|
||||
h,
|
||||
clock_rate,
|
||||
radius,
|
||||
*repeats,
|
||||
control_points,
|
||||
))
|
||||
}
|
||||
#[cfg(not(feature = "sliders"))]
|
||||
HitObjectKind::Slider {
|
||||
pixel_len,
|
||||
span_count,
|
||||
last_control_point,
|
||||
} => {
|
||||
max_combo += state.count_ticks(h.start_time, *pixel_len, *span_count, map);
|
||||
|
||||
Some(OsuObject::slider(
|
||||
h,
|
||||
clock_rate,
|
||||
radius,
|
||||
*span_count,
|
||||
*last_control_point,
|
||||
))
|
||||
}
|
||||
HitObjectKind::Spinner { .. } => {
|
||||
max_combo += 1;
|
||||
|
||||
OsuObject::from(h, clock_rate)
|
||||
Some(OsuObject::spinner(h, clock_rate))
|
||||
}
|
||||
HitObjectKind::Hold { .. } => None,
|
||||
});
|
||||
@@ -236,10 +261,35 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
||||
|
||||
let clock_rate = attributes.clock_rate;
|
||||
|
||||
let mut hit_objects = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.filter_map(|h| OsuObject::from(h, clock_rate));
|
||||
let mut hit_objects = map.hit_objects.iter().filter_map(|h| match &h.kind {
|
||||
HitObjectKind::Circle => Some(OsuObject::circle(h, clock_rate)),
|
||||
#[cfg(feature = "sliders")]
|
||||
HitObjectKind::Slider {
|
||||
repeats,
|
||||
control_points,
|
||||
..
|
||||
} => Some(OsuObject::slider(
|
||||
h,
|
||||
clock_rate,
|
||||
radius,
|
||||
*repeats,
|
||||
control_points,
|
||||
)),
|
||||
#[cfg(not(feature = "sliders"))]
|
||||
HitObjectKind::Slider {
|
||||
span_count,
|
||||
last_control_point,
|
||||
..
|
||||
} => Some(OsuObject::slider(
|
||||
h,
|
||||
clock_rate,
|
||||
radius,
|
||||
*span_count,
|
||||
*last_control_point,
|
||||
)),
|
||||
HitObjectKind::Spinner { .. } => Some(OsuObject::spinner(h, clock_rate)),
|
||||
HitObjectKind::Hold { .. } => None,
|
||||
});
|
||||
|
||||
let fl = mods.fl();
|
||||
let mut skills = Vec::with_capacity(2 + fl as usize);
|
||||
|
||||
+115
-25
@@ -1,35 +1,125 @@
|
||||
use crate::parse::{HitObject, HitObjectKind, Pos2};
|
||||
use crate::parse::{HitObject, Pos2};
|
||||
|
||||
pub(crate) struct OsuObject {
|
||||
pub(crate) pos: Pos2,
|
||||
pub(crate) time: f32,
|
||||
pub(crate) is_spinner: bool,
|
||||
pub(crate) is_slider: bool,
|
||||
kind: OsuObjectKind,
|
||||
}
|
||||
|
||||
pub(crate) enum OsuObjectKind {
|
||||
Circle,
|
||||
Slider { end_pos: Pos2, travel_dist: f32 },
|
||||
Spinner,
|
||||
}
|
||||
|
||||
impl OsuObject {
|
||||
#[inline]
|
||||
pub(crate) fn from(h: &HitObject, clock_rate: f32) -> Option<Self> {
|
||||
match &h.kind {
|
||||
HitObjectKind::Circle => Some(Self {
|
||||
pos: h.pos,
|
||||
time: h.start_time / clock_rate,
|
||||
is_spinner: false,
|
||||
is_slider: false,
|
||||
}),
|
||||
HitObjectKind::Slider { .. } => Some(Self {
|
||||
pos: h.pos,
|
||||
time: h.start_time / clock_rate,
|
||||
is_spinner: false,
|
||||
is_slider: true,
|
||||
}),
|
||||
HitObjectKind::Spinner { .. } => Some(Self {
|
||||
pos: h.pos,
|
||||
time: h.start_time / clock_rate,
|
||||
is_spinner: true,
|
||||
is_slider: false,
|
||||
}),
|
||||
HitObjectKind::Hold { .. } => None,
|
||||
pub(crate) fn circle(h: &HitObject, clock_rate: f32) -> Self {
|
||||
Self {
|
||||
pos: h.pos,
|
||||
time: h.start_time / clock_rate,
|
||||
kind: OsuObjectKind::Circle,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sliders")]
|
||||
pub(crate) fn slider(
|
||||
h: &HitObject,
|
||||
clock_rate: f32,
|
||||
radius: f32,
|
||||
repeats: usize,
|
||||
control_points: &[crate::parse::PathControlPoint],
|
||||
) -> Self {
|
||||
match control_points.last() {
|
||||
Some(point) => {
|
||||
let travel_dist =
|
||||
Self::approximate_travel_dist(radius, repeats + 1, h.pos, point.pos + h.pos);
|
||||
|
||||
let mut end_pos = h.pos;
|
||||
|
||||
if repeats % 2 == 0 {
|
||||
end_pos += point.pos
|
||||
}
|
||||
|
||||
Self {
|
||||
pos: h.pos,
|
||||
time: h.start_time / clock_rate,
|
||||
kind: OsuObjectKind::Slider {
|
||||
end_pos,
|
||||
travel_dist,
|
||||
},
|
||||
}
|
||||
}
|
||||
None => Self::circle(h, clock_rate),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "sliders"))]
|
||||
pub(crate) fn slider(
|
||||
h: &HitObject,
|
||||
clock_rate: f32,
|
||||
radius: f32,
|
||||
span_count: usize,
|
||||
last_control_point: Pos2,
|
||||
) -> Self {
|
||||
let travel_dist = Self::approximate_travel_dist(radius, span_count, h.pos, point.pos);
|
||||
|
||||
let end_pos = if span_count % 2 == 1 {
|
||||
last_control_point
|
||||
} else {
|
||||
h.pos
|
||||
};
|
||||
|
||||
Self {
|
||||
pos: h.pos,
|
||||
time: h.start_time / clock_rate,
|
||||
kind: OsuObjectKind::Slider {
|
||||
end_pos,
|
||||
travel_dist,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn spinner(h: &HitObject, clock_rate: f32) -> Self {
|
||||
Self {
|
||||
pos: h.pos,
|
||||
time: h.start_time / clock_rate,
|
||||
kind: OsuObjectKind::Spinner,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_slider(&self) -> bool {
|
||||
matches!(self.kind, OsuObjectKind::Slider { .. })
|
||||
}
|
||||
|
||||
pub(crate) fn is_spinner(&self) -> bool {
|
||||
matches!(self.kind, OsuObjectKind::Spinner)
|
||||
}
|
||||
|
||||
pub(crate) fn end_pos(&self) -> Pos2 {
|
||||
match &self.kind {
|
||||
OsuObjectKind::Circle | OsuObjectKind::Spinner => self.pos,
|
||||
OsuObjectKind::Slider { end_pos, .. } => *end_pos,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn travel_dist(&self) -> f32 {
|
||||
match &self.kind {
|
||||
OsuObjectKind::Circle | OsuObjectKind::Spinner => 0.0,
|
||||
OsuObjectKind::Slider { travel_dist, .. } => *travel_dist,
|
||||
}
|
||||
}
|
||||
|
||||
// Approximating lower bound for lazy travel distance
|
||||
fn approximate_travel_dist(
|
||||
radius: f32,
|
||||
span_count: usize,
|
||||
pos: Pos2,
|
||||
last_control_point: Pos2,
|
||||
) -> f32 {
|
||||
let approx_follow_circle_radius = radius * 3.0;
|
||||
let lazy_end_point_dist = approx_follow_circle_radius * (span_count + 1) as f32;
|
||||
let dist = (pos - last_control_point).length();
|
||||
|
||||
(dist * span_count as f32 - lazy_end_point_dist).max(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
+38
-35
@@ -47,8 +47,8 @@ pub(crate) struct FlashlightHistoryEntry {
|
||||
impl From<&DifficultyObject<'_>> for FlashlightHistoryEntry {
|
||||
fn from(h: &DifficultyObject<'_>) -> Self {
|
||||
Self {
|
||||
end_pos: h.base.pos,
|
||||
is_spinner: h.base.is_spinner,
|
||||
end_pos: h.base.end_pos(),
|
||||
is_spinner: h.base.is_spinner(),
|
||||
jump_dist: h.jump_dist,
|
||||
strain_time: h.strain_time,
|
||||
}
|
||||
@@ -64,7 +64,7 @@ pub(crate) struct SpeedHistoryEntry {
|
||||
impl From<&DifficultyObject<'_>> for SpeedHistoryEntry {
|
||||
fn from(h: &DifficultyObject<'_>) -> Self {
|
||||
Self {
|
||||
is_slider: h.base.is_slider,
|
||||
is_slider: h.base.is_slider(),
|
||||
start_time: h.base.time,
|
||||
strain_time: h.strain_time,
|
||||
}
|
||||
@@ -119,7 +119,7 @@ impl SkillKind {
|
||||
pub(crate) fn strain_value_of(&self, curr: &DifficultyObject<'_>) -> f32 {
|
||||
match self {
|
||||
Self::Aim => {
|
||||
if curr.base.is_spinner {
|
||||
if curr.base.is_spinner() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@@ -140,15 +140,19 @@ impl SkillKind {
|
||||
}
|
||||
|
||||
let jump_dist_exp = apply_diminishing_exp(curr.jump_dist);
|
||||
let travel_dist_exp = apply_diminishing_exp(curr.travel_dist);
|
||||
|
||||
(aim_strain + jump_dist_exp / (curr.strain_time).max(TIMING_THRESHOLD))
|
||||
.max(jump_dist_exp / curr.strain_time)
|
||||
let dist_exp =
|
||||
jump_dist_exp + travel_dist_exp + (travel_dist_exp * jump_dist_exp).sqrt();
|
||||
|
||||
(aim_strain + dist_exp / (curr.strain_time).max(TIMING_THRESHOLD))
|
||||
.max(dist_exp / curr.strain_time)
|
||||
}
|
||||
Self::Flashlight {
|
||||
history,
|
||||
scaling_factor,
|
||||
} => {
|
||||
if curr.base.is_spinner {
|
||||
if curr.base.is_spinner() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@@ -163,13 +167,11 @@ impl SkillKind {
|
||||
let jump_dist = (curr.base.pos - prev.end_pos).length();
|
||||
cumulative_strain_time += prev.strain_time;
|
||||
|
||||
// We want to nerf objects that can be easily seen within the Flashlight circle radius
|
||||
// * We want to nerf objects that can be easily seen within the Flashlight circle radius
|
||||
small_dist_nerf = (jump_dist / 75.0).min(1.0);
|
||||
|
||||
// We also want to nerf stacks so that only the first object of the stack is accounted for
|
||||
// -- since jump distance is 0 on stacked notes in this version, approximate value as 0.2
|
||||
let stack_nerf =
|
||||
((prev.jump_dist / scaling_factor) / 25.0).min(1.0).max(0.2);
|
||||
// * We also want to nerf stacks so that only the first object of the stack is accounted for
|
||||
let stack_nerf = ((prev.jump_dist / scaling_factor) / 25.0).min(1.0);
|
||||
|
||||
result += stack_nerf * scaling_factor * jump_dist / cumulative_strain_time;
|
||||
}
|
||||
@@ -180,8 +182,9 @@ impl SkillKind {
|
||||
if !prev.is_spinner {
|
||||
let jump_dist = (curr.base.pos - prev.end_pos).length();
|
||||
cumulative_strain_time += prev.strain_time;
|
||||
let stack_nerf =
|
||||
((prev.jump_dist / scaling_factor) / 25.0).min(1.0).max(0.2);
|
||||
|
||||
// * We also want to nerf stacks so that only the first object of the stack is accounted for
|
||||
let stack_nerf = ((prev.jump_dist / scaling_factor) / 25.0).min(1.0);
|
||||
|
||||
result += factor * stack_nerf * scaling_factor * jump_dist
|
||||
/ cumulative_strain_time;
|
||||
@@ -198,7 +201,7 @@ impl SkillKind {
|
||||
hit_window,
|
||||
..
|
||||
} => {
|
||||
if curr.base.is_spinner {
|
||||
if curr.base.is_spinner() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@@ -207,7 +210,7 @@ impl SkillKind {
|
||||
let speed_window_ratio = strain_time / hit_window_full;
|
||||
let prev = history.front();
|
||||
|
||||
// Aim to nerf cheesy rhythms (very fast consecutive doubles with large delta times between)
|
||||
// * Aim to nerf cheesy rhythms (very fast consecutive doubles with large delta times between)
|
||||
if let Some(prev) =
|
||||
prev.filter(|p| strain_time < hit_window_full && p.strain_time > strain_time)
|
||||
{
|
||||
@@ -215,12 +218,12 @@ impl SkillKind {
|
||||
math_util::lerp(prev.strain_time, strain_time, speed_window_ratio);
|
||||
}
|
||||
|
||||
// Cap delta time to the OD 300 hit window
|
||||
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly,
|
||||
// whilst 0.92 limits the effect of the cap
|
||||
// * Cap delta time to the OD 300 hit window
|
||||
// * 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly,
|
||||
// * whilst 0.92 limits the effect of the cap
|
||||
strain_time /= (strain_time / hit_window_full / 0.93).clamp(0.92, 1.0);
|
||||
|
||||
// Derive speed bonus for calculation
|
||||
// * Derive speed bonus for calculation
|
||||
let mut speed_bonus = 1.0;
|
||||
|
||||
if strain_time < MIN_SPEED_BONUS {
|
||||
@@ -228,7 +231,7 @@ impl SkillKind {
|
||||
speed_bonus = 1.0 + 0.75 * base * base;
|
||||
}
|
||||
|
||||
let dist = SINGLE_SPACING_TRESHOLD.min(curr.jump_dist);
|
||||
let dist = SINGLE_SPACING_TRESHOLD.min(curr.travel_dist + curr.jump_dist);
|
||||
|
||||
(speed_bonus + speed_bonus * (dist / SINGLE_SPACING_TRESHOLD).powf(3.5))
|
||||
/ strain_time
|
||||
@@ -286,7 +289,7 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
history: &VecDeque<SpeedHistoryEntry>,
|
||||
hit_window: f32,
|
||||
) -> f32 {
|
||||
if current.base.is_spinner {
|
||||
if current.base.is_spinner() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@@ -297,7 +300,7 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
let adjusted_hit_window = hit_window * 0.6;
|
||||
let history_len = history.len() as f32;
|
||||
|
||||
// Store the ratio of the current start of an island to buff for tighter rhythms
|
||||
// * Store the ratio of the current start of an island to buff for tighter rhythms
|
||||
let mut start_ratio = 0.0;
|
||||
|
||||
let currs = history.iter();
|
||||
@@ -310,14 +313,14 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
/ SPEED_HISTORY_TIME_MAX;
|
||||
|
||||
if curr_historical_decay.abs() > f32::EPSILON {
|
||||
// Either we're limited by time or limited by object count
|
||||
// * Either we're limited by time or limited by object count
|
||||
curr_historical_decay = curr_historical_decay.min(i as f32 / history_len);
|
||||
|
||||
let curr_delta = curr.strain_time;
|
||||
let prev_delta = prev.strain_time;
|
||||
let last_delta = last.strain_time;
|
||||
|
||||
// Fancy function to calculate rhythm bonuses
|
||||
// * Fancy function to calculate rhythm bonuses
|
||||
let base = (PI / (prev_delta.min(curr_delta) / prev_delta.max(curr_delta))).sin();
|
||||
let curr_ratio = 1.0 + 6.0 * (base * base).min(0.5);
|
||||
|
||||
@@ -333,27 +336,27 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
}
|
||||
} else {
|
||||
if curr.is_slider {
|
||||
// bpm change is into slider, this is easy acc window
|
||||
// * bpm change is into slider, this is easy acc window
|
||||
effective_ratio *= 0.125;
|
||||
}
|
||||
|
||||
if prev.is_slider {
|
||||
// bpm change was from a slider, this is easier typically than circle -> circle
|
||||
// * bpm change was from a slider, this is easier typically than circle -> circle
|
||||
effective_ratio *= 0.25;
|
||||
}
|
||||
|
||||
if prev_island_size == island_size {
|
||||
// repeated island size (ex: triplet -> triplet)
|
||||
// * repeated island size (ex: triplet -> triplet)
|
||||
effective_ratio *= 0.25;
|
||||
}
|
||||
|
||||
if prev_island_size % 2 == island_size % 2 {
|
||||
// repeated island polarity (2 -> 4, 3 -> 5)
|
||||
// * repeated island polarity (2 -> 4, 3 -> 5)
|
||||
effective_ratio *= 0.5;
|
||||
}
|
||||
|
||||
if last_delta > prev_delta + 10.0 && prev_delta > curr_delta + 10.0 {
|
||||
// previous increase happened a note ago, 1/1 -> 1/2-1/4, don't want to buff this
|
||||
// * previous increase happened a note ago, 1/1 -> 1/2-1/4, don't want to buff this
|
||||
effective_ratio *= 0.125;
|
||||
}
|
||||
|
||||
@@ -367,15 +370,15 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
prev_island_size = island_size;
|
||||
island_size = 1;
|
||||
|
||||
// we're slowing down, stop counting
|
||||
// * we're slowing down, stop counting
|
||||
if prev_delta * 1.25 < curr_delta {
|
||||
// if we're speeding up, this stays true and we keep counting island size
|
||||
// * if we're speeding up, this stays true and we keep counting island size
|
||||
first_delta_switch = false;
|
||||
}
|
||||
}
|
||||
} else if prev_delta > 1.25 * curr_delta {
|
||||
// we want to be speeding up
|
||||
// begin counting island until we change speed again
|
||||
// * we want to be speeding up
|
||||
// * begin counting island until we change speed again
|
||||
first_delta_switch = true;
|
||||
start_ratio = effective_ratio;
|
||||
island_size = 1;
|
||||
@@ -383,7 +386,7 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
}
|
||||
}
|
||||
|
||||
// produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
||||
// * produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
||||
(4.0 + rhythm_complexity_sum * SPEED_RHYTHM_MULTIPLIER).sqrt() / 2.0
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ impl<'p> SliderState<'p> {
|
||||
&mut self,
|
||||
time: f32,
|
||||
pixel_len: f32,
|
||||
repeats: usize,
|
||||
span_count: usize,
|
||||
map: &Beatmap,
|
||||
) -> usize {
|
||||
while time >= self.next_time {
|
||||
@@ -46,12 +46,12 @@ impl<'p> SliderState<'p> {
|
||||
}
|
||||
}
|
||||
|
||||
let spans = repeats as f32;
|
||||
let spans = span_count as f32;
|
||||
let beats = pixel_len * spans / self.px_per_beat;
|
||||
let ticks = ((beats - 0.1) / spans * map.tick_rate).ceil() as usize;
|
||||
|
||||
ticks
|
||||
.checked_sub(1)
|
||||
.map_or(0, |ticks| ticks * repeats + repeats + 1)
|
||||
.map_or(0, |ticks| ticks * span_count + span_count + 1)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -66,13 +66,13 @@ fn difficulty_range_od(od: f32) -> f32 {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
// #[ignore]
|
||||
fn custom_osu() {
|
||||
use std::{fs::File, time::Instant};
|
||||
|
||||
use crate::{Beatmap, OsuPP};
|
||||
|
||||
let path = "E:Games/osu!/beatmaps/809469_.osu";
|
||||
let path = "E:Games/osu!/beatmaps/1402167_.osu";
|
||||
let file = File::open(path).unwrap();
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -94,7 +94,7 @@ fn custom_osu() {
|
||||
println!("Parsing average: {:?}", accum / iters);
|
||||
|
||||
let start = Instant::now();
|
||||
let result = OsuPP::new(&map).mods(66).calculate();
|
||||
let result = OsuPP::new(&map).mods(1024).calculate();
|
||||
|
||||
let iters = 100;
|
||||
let accum = start.elapsed();
|
||||
|
||||
@@ -171,7 +171,6 @@ impl SkillKind {
|
||||
small_dist_nerf = (jump_dist / 75.0).min(1.0);
|
||||
|
||||
// * We also want to nerf stacks so that only the first object of the stack is accounted for
|
||||
// -- since jump distance is 0 on stacked notes in this version, approximate value as 0.2
|
||||
let stack_nerf = ((prev.jump_dist / scaling_factor) / 25.0).min(1.0);
|
||||
|
||||
result += stack_nerf * scaling_factor * jump_dist / cumulative_strain_time;
|
||||
@@ -183,6 +182,8 @@ impl SkillKind {
|
||||
if !prev.is_spinner {
|
||||
let jump_dist = (curr.base.pos - prev.end_pos).length();
|
||||
cumulative_strain_time += prev.strain_time;
|
||||
|
||||
// * We also want to nerf stacks so that only the first object of the stack is accounted for
|
||||
let stack_nerf = ((prev.jump_dist / scaling_factor) / 25.0).min(1.0);
|
||||
|
||||
result += factor * stack_nerf * scaling_factor * jump_dist
|
||||
@@ -299,7 +300,7 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
let adjusted_hit_window = hit_window * 0.6;
|
||||
let history_len = history.len() as f32;
|
||||
|
||||
// Store the ratio of the current start of an island to buff for tighter rhythms
|
||||
// * Store the ratio of the current start of an island to buff for tighter rhythms
|
||||
let mut start_ratio = 0.0;
|
||||
|
||||
let currs = history.iter();
|
||||
@@ -312,14 +313,14 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
/ SPEED_HISTORY_TIME_MAX;
|
||||
|
||||
if curr_historical_decay.abs() > f32::EPSILON {
|
||||
// Either we're limited by time or limited by object count
|
||||
// * Either we're limited by time or limited by object count
|
||||
curr_historical_decay = curr_historical_decay.min(i as f32 / history_len);
|
||||
|
||||
let curr_delta = curr.strain_time;
|
||||
let prev_delta = prev.strain_time;
|
||||
let last_delta = last.strain_time;
|
||||
|
||||
// Fancy function to calculate rhythm bonuses
|
||||
// * Fancy function to calculate rhythm bonuses
|
||||
let base = (PI / (prev_delta.min(curr_delta) / prev_delta.max(curr_delta))).sin();
|
||||
let curr_ratio = 1.0 + 6.0 * (base * base).min(0.5);
|
||||
|
||||
@@ -335,27 +336,27 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
}
|
||||
} else {
|
||||
if curr.is_slider {
|
||||
// bpm change is into slider, this is easy acc window
|
||||
// * bpm change is into slider, this is easy acc window
|
||||
effective_ratio *= 0.125;
|
||||
}
|
||||
|
||||
if prev.is_slider {
|
||||
// bpm change was from a slider, this is easier typically than circle -> circle
|
||||
// * bpm change was from a slider, this is easier typically than circle -> circle
|
||||
effective_ratio *= 0.25;
|
||||
}
|
||||
|
||||
if prev_island_size == island_size {
|
||||
// repeated island size (ex: triplet -> triplet)
|
||||
// * repeated island size (ex: triplet -> triplet)
|
||||
effective_ratio *= 0.25;
|
||||
}
|
||||
|
||||
if prev_island_size % 2 == island_size % 2 {
|
||||
// repeated island polarity (2 -> 4, 3 -> 5)
|
||||
// * repeated island polarity (2 -> 4, 3 -> 5)
|
||||
effective_ratio *= 0.5;
|
||||
}
|
||||
|
||||
if last_delta > prev_delta + 10.0 && prev_delta > curr_delta + 10.0 {
|
||||
// previous increase happened a note ago, 1/1 -> 1/2-1/4, don't want to buff this
|
||||
// * previous increase happened a note ago, 1/1 -> 1/2-1/4, don't want to buff this
|
||||
effective_ratio *= 0.125;
|
||||
}
|
||||
|
||||
@@ -369,15 +370,15 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
prev_island_size = island_size;
|
||||
island_size = 1;
|
||||
|
||||
// we're slowing down, stop counting
|
||||
// * we're slowing down, stop counting
|
||||
if prev_delta * 1.25 < curr_delta {
|
||||
// if we're speeding up, this stays true and we keep counting island size
|
||||
// * if we're speeding up, this stays true and we keep counting island size
|
||||
first_delta_switch = false;
|
||||
}
|
||||
}
|
||||
} else if prev_delta > 1.25 * curr_delta {
|
||||
// we want to be speeding up
|
||||
// begin counting island until we change speed again
|
||||
// * we want to be speeding up
|
||||
// * begin counting island until we change speed again
|
||||
first_delta_switch = true;
|
||||
start_ratio = effective_ratio;
|
||||
island_size = 1;
|
||||
@@ -385,7 +386,7 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
||||
}
|
||||
}
|
||||
|
||||
// produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
||||
// * produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
||||
(4.0 + rhythm_complexity_sum * SPEED_RHYTHM_MULTIPLIER).sqrt() / 2.0
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ pub enum HitObjectKind {
|
||||
#[cfg(not(feature = "sliders"))]
|
||||
Slider {
|
||||
pixel_len: f32,
|
||||
repeats: usize,
|
||||
span_count: usize,
|
||||
last_control_point: Pos2,
|
||||
},
|
||||
Spinner {
|
||||
end_time: f32,
|
||||
|
||||
+23
-3
@@ -533,10 +533,30 @@ macro_rules! parse_hitobjects_body {
|
||||
|
||||
#[cfg(not(feature = "sliders"))]
|
||||
{
|
||||
let repeats = split.nth(1).next_field("repeats")?.parse()?;
|
||||
let pixel_len = split.next().next_field("pixel len")?.parse()?;
|
||||
let last_control_point = split
|
||||
.next()
|
||||
.next_field("control points")?
|
||||
.split('|')
|
||||
.next_back();
|
||||
|
||||
HitObjectKind::Slider { repeats, pixel_len }
|
||||
match last_control_point.map(|v| v.split(':').map(str::parse)) {
|
||||
Some(mut coords) => {
|
||||
let last_control_point = match (coords.next(), coords.next()) {
|
||||
(Some(Ok(x)), Some(Ok(y))) => Pos2 { x, y },
|
||||
_ => return Err(ParseError::InvalidCurvePoints),
|
||||
};
|
||||
|
||||
let span_count = split.next().next_field("repeats")?.parse()?;
|
||||
let pixel_len = split.next().next_field("pixel len")?.parse()?;
|
||||
|
||||
HitObjectKind::Slider {
|
||||
span_count,
|
||||
pixel_len,
|
||||
last_control_point,
|
||||
}
|
||||
}
|
||||
None => HitObjectKind::Circle,
|
||||
}
|
||||
}
|
||||
} else if kind & Self::SPINNER_FLAG > 0 {
|
||||
$self.n_spinners += 1;
|
||||
|
||||
Reference in New Issue
Block a user