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) prev: Option<(f32, f32)>, // (jump_dist, strain_time)
|
||||||
|
|
||||||
pub(crate) jump_dist: f32,
|
pub(crate) jump_dist: f32,
|
||||||
|
pub(crate) travel_dist: f32,
|
||||||
pub(crate) angle: Option<f32>,
|
pub(crate) angle: Option<f32>,
|
||||||
|
|
||||||
pub(crate) delta: f32,
|
pub(crate) delta: f32,
|
||||||
@@ -20,19 +21,20 @@ impl<'h> DifficultyObject<'h> {
|
|||||||
scaling_factor: f32,
|
scaling_factor: f32,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delta = base.time - prev.time;
|
let delta = base.time - prev.time;
|
||||||
|
let travel_dist = prev.travel_dist();
|
||||||
|
|
||||||
// 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(25.0);
|
let strain_time = delta.max(25.0);
|
||||||
|
|
||||||
// We don't need to calculate either angle or distance
|
// We don't need to calculate either angle or distance
|
||||||
// when one of the last->curr objects is a spinner
|
// 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)
|
(0.0, None)
|
||||||
} else {
|
} 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 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 v2 = base.pos - prev.pos;
|
||||||
|
|
||||||
let dot = v1.dot(v2);
|
let dot = v1.dot(v2);
|
||||||
@@ -49,6 +51,7 @@ impl<'h> DifficultyObject<'h> {
|
|||||||
prev: prev_vals,
|
prev: prev_vals,
|
||||||
|
|
||||||
jump_dist,
|
jump_dist,
|
||||||
|
travel_dist,
|
||||||
angle,
|
angle,
|
||||||
|
|
||||||
delta,
|
delta,
|
||||||
|
|||||||
+58
-8
@@ -76,19 +76,44 @@ pub fn stars(
|
|||||||
HitObjectKind::Circle => {
|
HitObjectKind::Circle => {
|
||||||
max_combo += 1;
|
max_combo += 1;
|
||||||
|
|
||||||
OsuObject::from(h, clock_rate)
|
Some(OsuObject::circle(h, clock_rate))
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "sliders")]
|
||||||
HitObjectKind::Slider {
|
HitObjectKind::Slider {
|
||||||
pixel_len, repeats, ..
|
pixel_len,
|
||||||
|
repeats,
|
||||||
|
control_points,
|
||||||
} => {
|
} => {
|
||||||
max_combo += state.count_ticks(h.start_time, *pixel_len, *repeats, map);
|
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 { .. } => {
|
HitObjectKind::Spinner { .. } => {
|
||||||
max_combo += 1;
|
max_combo += 1;
|
||||||
|
|
||||||
OsuObject::from(h, clock_rate)
|
Some(OsuObject::spinner(h, clock_rate))
|
||||||
}
|
}
|
||||||
HitObjectKind::Hold { .. } => None,
|
HitObjectKind::Hold { .. } => None,
|
||||||
});
|
});
|
||||||
@@ -236,10 +261,35 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
|
|
||||||
let clock_rate = attributes.clock_rate;
|
let clock_rate = attributes.clock_rate;
|
||||||
|
|
||||||
let mut hit_objects = map
|
let mut hit_objects = map.hit_objects.iter().filter_map(|h| match &h.kind {
|
||||||
.hit_objects
|
HitObjectKind::Circle => Some(OsuObject::circle(h, clock_rate)),
|
||||||
.iter()
|
#[cfg(feature = "sliders")]
|
||||||
.filter_map(|h| OsuObject::from(h, clock_rate));
|
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 fl = mods.fl();
|
||||||
let mut skills = Vec::with_capacity(2 + fl as usize);
|
let mut skills = Vec::with_capacity(2 + fl as usize);
|
||||||
|
|||||||
+109
-19
@@ -1,35 +1,125 @@
|
|||||||
use crate::parse::{HitObject, HitObjectKind, Pos2};
|
use crate::parse::{HitObject, Pos2};
|
||||||
|
|
||||||
pub(crate) struct OsuObject {
|
pub(crate) struct OsuObject {
|
||||||
pub(crate) pos: Pos2,
|
pub(crate) pos: Pos2,
|
||||||
pub(crate) time: f32,
|
pub(crate) time: f32,
|
||||||
pub(crate) is_spinner: bool,
|
kind: OsuObjectKind,
|
||||||
pub(crate) is_slider: bool,
|
}
|
||||||
|
|
||||||
|
pub(crate) enum OsuObjectKind {
|
||||||
|
Circle,
|
||||||
|
Slider { end_pos: Pos2, travel_dist: f32 },
|
||||||
|
Spinner,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OsuObject {
|
impl OsuObject {
|
||||||
#[inline]
|
pub(crate) fn circle(h: &HitObject, clock_rate: f32) -> Self {
|
||||||
pub(crate) fn from(h: &HitObject, clock_rate: f32) -> Option<Self> {
|
Self {
|
||||||
match &h.kind {
|
|
||||||
HitObjectKind::Circle => Some(Self {
|
|
||||||
pos: h.pos,
|
pos: h.pos,
|
||||||
time: h.start_time / clock_rate,
|
time: h.start_time / clock_rate,
|
||||||
is_spinner: false,
|
kind: OsuObjectKind::Circle,
|
||||||
is_slider: false,
|
}
|
||||||
}),
|
}
|
||||||
HitObjectKind::Slider { .. } => Some(Self {
|
|
||||||
|
#[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,
|
pos: h.pos,
|
||||||
time: h.start_time / clock_rate,
|
time: h.start_time / clock_rate,
|
||||||
is_spinner: false,
|
kind: OsuObjectKind::Slider {
|
||||||
is_slider: true,
|
end_pos,
|
||||||
}),
|
travel_dist,
|
||||||
HitObjectKind::Spinner { .. } => Some(Self {
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,
|
pos: h.pos,
|
||||||
time: h.start_time / clock_rate,
|
time: h.start_time / clock_rate,
|
||||||
is_spinner: true,
|
kind: OsuObjectKind::Slider {
|
||||||
is_slider: false,
|
end_pos,
|
||||||
}),
|
travel_dist,
|
||||||
HitObjectKind::Hold { .. } => None,
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl From<&DifficultyObject<'_>> for FlashlightHistoryEntry {
|
||||||
fn from(h: &DifficultyObject<'_>) -> Self {
|
fn from(h: &DifficultyObject<'_>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
end_pos: h.base.pos,
|
end_pos: h.base.end_pos(),
|
||||||
is_spinner: h.base.is_spinner,
|
is_spinner: h.base.is_spinner(),
|
||||||
jump_dist: h.jump_dist,
|
jump_dist: h.jump_dist,
|
||||||
strain_time: h.strain_time,
|
strain_time: h.strain_time,
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ pub(crate) struct SpeedHistoryEntry {
|
|||||||
impl From<&DifficultyObject<'_>> for SpeedHistoryEntry {
|
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,
|
||||||
strain_time: h.strain_time,
|
strain_time: h.strain_time,
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ impl SkillKind {
|
|||||||
pub(crate) fn strain_value_of(&self, curr: &DifficultyObject<'_>) -> f32 {
|
pub(crate) fn strain_value_of(&self, curr: &DifficultyObject<'_>) -> f32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Aim => {
|
Self::Aim => {
|
||||||
if curr.base.is_spinner {
|
if curr.base.is_spinner() {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,15 +140,19 @@ impl SkillKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let jump_dist_exp = apply_diminishing_exp(curr.jump_dist);
|
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))
|
let dist_exp =
|
||||||
.max(jump_dist_exp / curr.strain_time)
|
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 {
|
Self::Flashlight {
|
||||||
history,
|
history,
|
||||||
scaling_factor,
|
scaling_factor,
|
||||||
} => {
|
} => {
|
||||||
if curr.base.is_spinner {
|
if curr.base.is_spinner() {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,13 +167,11 @@ impl SkillKind {
|
|||||||
let jump_dist = (curr.base.pos - prev.end_pos).length();
|
let jump_dist = (curr.base.pos - prev.end_pos).length();
|
||||||
cumulative_strain_time += prev.strain_time;
|
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);
|
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
|
// * 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);
|
||||||
let stack_nerf =
|
|
||||||
((prev.jump_dist / scaling_factor) / 25.0).min(1.0).max(0.2);
|
|
||||||
|
|
||||||
result += stack_nerf * scaling_factor * jump_dist / cumulative_strain_time;
|
result += stack_nerf * scaling_factor * jump_dist / cumulative_strain_time;
|
||||||
}
|
}
|
||||||
@@ -180,8 +182,9 @@ impl SkillKind {
|
|||||||
if !prev.is_spinner {
|
if !prev.is_spinner {
|
||||||
let jump_dist = (curr.base.pos - prev.end_pos).length();
|
let jump_dist = (curr.base.pos - prev.end_pos).length();
|
||||||
cumulative_strain_time += prev.strain_time;
|
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
|
result += factor * stack_nerf * scaling_factor * jump_dist
|
||||||
/ cumulative_strain_time;
|
/ cumulative_strain_time;
|
||||||
@@ -198,7 +201,7 @@ impl SkillKind {
|
|||||||
hit_window,
|
hit_window,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if curr.base.is_spinner {
|
if curr.base.is_spinner() {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +210,7 @@ impl SkillKind {
|
|||||||
let speed_window_ratio = strain_time / hit_window_full;
|
let speed_window_ratio = strain_time / hit_window_full;
|
||||||
let prev = history.front();
|
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) =
|
if let Some(prev) =
|
||||||
prev.filter(|p| strain_time < hit_window_full && p.strain_time > strain_time)
|
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);
|
math_util::lerp(prev.strain_time, strain_time, speed_window_ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cap delta time to the OD 300 hit window
|
// * Cap delta time to the OD 300 hit window
|
||||||
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly,
|
// * 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly,
|
||||||
// whilst 0.92 limits the effect of the cap
|
// * whilst 0.92 limits the effect of the cap
|
||||||
strain_time /= (strain_time / hit_window_full / 0.93).clamp(0.92, 1.0);
|
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;
|
let mut speed_bonus = 1.0;
|
||||||
|
|
||||||
if strain_time < MIN_SPEED_BONUS {
|
if strain_time < MIN_SPEED_BONUS {
|
||||||
@@ -228,7 +231,7 @@ impl SkillKind {
|
|||||||
speed_bonus = 1.0 + 0.75 * base * base;
|
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))
|
(speed_bonus + speed_bonus * (dist / SINGLE_SPACING_TRESHOLD).powf(3.5))
|
||||||
/ strain_time
|
/ strain_time
|
||||||
@@ -286,7 +289,7 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
|||||||
history: &VecDeque<SpeedHistoryEntry>,
|
history: &VecDeque<SpeedHistoryEntry>,
|
||||||
hit_window: f32,
|
hit_window: f32,
|
||||||
) -> f32 {
|
) -> f32 {
|
||||||
if current.base.is_spinner {
|
if current.base.is_spinner() {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +300,7 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
|||||||
let adjusted_hit_window = hit_window * 0.6;
|
let adjusted_hit_window = hit_window * 0.6;
|
||||||
let history_len = history.len() as f32;
|
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 mut start_ratio = 0.0;
|
||||||
|
|
||||||
let currs = history.iter();
|
let currs = history.iter();
|
||||||
@@ -310,14 +313,14 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
|||||||
/ SPEED_HISTORY_TIME_MAX;
|
/ SPEED_HISTORY_TIME_MAX;
|
||||||
|
|
||||||
if curr_historical_decay.abs() > f32::EPSILON {
|
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);
|
curr_historical_decay = curr_historical_decay.min(i as f32 / history_len);
|
||||||
|
|
||||||
let curr_delta = curr.strain_time;
|
let curr_delta = curr.strain_time;
|
||||||
let prev_delta = prev.strain_time;
|
let prev_delta = prev.strain_time;
|
||||||
let last_delta = last.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 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);
|
let curr_ratio = 1.0 + 6.0 * (base * base).min(0.5);
|
||||||
|
|
||||||
@@ -333,27 +336,27 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if curr.is_slider {
|
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;
|
effective_ratio *= 0.125;
|
||||||
}
|
}
|
||||||
|
|
||||||
if prev.is_slider {
|
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;
|
effective_ratio *= 0.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
if prev_island_size == island_size {
|
if prev_island_size == island_size {
|
||||||
// repeated island size (ex: triplet -> triplet)
|
// * repeated island size (ex: triplet -> triplet)
|
||||||
effective_ratio *= 0.25;
|
effective_ratio *= 0.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
if prev_island_size % 2 == island_size % 2 {
|
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;
|
effective_ratio *= 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
if last_delta > prev_delta + 10.0 && prev_delta > curr_delta + 10.0 {
|
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;
|
effective_ratio *= 0.125;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,15 +370,15 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
|||||||
prev_island_size = island_size;
|
prev_island_size = island_size;
|
||||||
island_size = 1;
|
island_size = 1;
|
||||||
|
|
||||||
// we're slowing down, stop counting
|
// * we're slowing down, stop counting
|
||||||
if prev_delta * 1.25 < curr_delta {
|
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;
|
first_delta_switch = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if prev_delta > 1.25 * curr_delta {
|
} else if prev_delta > 1.25 * curr_delta {
|
||||||
// we want to be speeding up
|
// * we want to be speeding up
|
||||||
// begin counting island until we change speed again
|
// * begin counting island until we change speed again
|
||||||
first_delta_switch = true;
|
first_delta_switch = true;
|
||||||
start_ratio = effective_ratio;
|
start_ratio = effective_ratio;
|
||||||
island_size = 1;
|
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
|
(4.0 + rhythm_complexity_sum * SPEED_RHYTHM_MULTIPLIER).sqrt() / 2.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ impl<'p> SliderState<'p> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
time: f32,
|
time: f32,
|
||||||
pixel_len: f32,
|
pixel_len: f32,
|
||||||
repeats: usize,
|
span_count: usize,
|
||||||
map: &Beatmap,
|
map: &Beatmap,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
while time >= self.next_time {
|
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 beats = pixel_len * spans / self.px_per_beat;
|
||||||
let ticks = ((beats - 0.1) / spans * map.tick_rate).ceil() as usize;
|
let ticks = ((beats - 0.1) / spans * map.tick_rate).ceil() as usize;
|
||||||
|
|
||||||
ticks
|
ticks
|
||||||
.checked_sub(1)
|
.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]
|
#[test]
|
||||||
#[ignore]
|
// #[ignore]
|
||||||
fn custom_osu() {
|
fn custom_osu() {
|
||||||
use std::{fs::File, time::Instant};
|
use std::{fs::File, time::Instant};
|
||||||
|
|
||||||
use crate::{Beatmap, OsuPP};
|
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 file = File::open(path).unwrap();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
@@ -94,7 +94,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(66).calculate();
|
let result = OsuPP::new(&map).mods(1024).calculate();
|
||||||
|
|
||||||
let iters = 100;
|
let iters = 100;
|
||||||
let accum = start.elapsed();
|
let accum = start.elapsed();
|
||||||
|
|||||||
@@ -171,7 +171,6 @@ impl SkillKind {
|
|||||||
small_dist_nerf = (jump_dist / 75.0).min(1.0);
|
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
|
// * 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);
|
let stack_nerf = ((prev.jump_dist / scaling_factor) / 25.0).min(1.0);
|
||||||
|
|
||||||
result += stack_nerf * scaling_factor * jump_dist / cumulative_strain_time;
|
result += stack_nerf * scaling_factor * jump_dist / cumulative_strain_time;
|
||||||
@@ -183,6 +182,8 @@ impl SkillKind {
|
|||||||
if !prev.is_spinner {
|
if !prev.is_spinner {
|
||||||
let jump_dist = (curr.base.pos - prev.end_pos).length();
|
let jump_dist = (curr.base.pos - prev.end_pos).length();
|
||||||
cumulative_strain_time += prev.strain_time;
|
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);
|
let stack_nerf = ((prev.jump_dist / scaling_factor) / 25.0).min(1.0);
|
||||||
|
|
||||||
result += factor * stack_nerf * scaling_factor * jump_dist
|
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 adjusted_hit_window = hit_window * 0.6;
|
||||||
let history_len = history.len() as f32;
|
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 mut start_ratio = 0.0;
|
||||||
|
|
||||||
let currs = history.iter();
|
let currs = history.iter();
|
||||||
@@ -312,14 +313,14 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
|||||||
/ SPEED_HISTORY_TIME_MAX;
|
/ SPEED_HISTORY_TIME_MAX;
|
||||||
|
|
||||||
if curr_historical_decay.abs() > f32::EPSILON {
|
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);
|
curr_historical_decay = curr_historical_decay.min(i as f32 / history_len);
|
||||||
|
|
||||||
let curr_delta = curr.strain_time;
|
let curr_delta = curr.strain_time;
|
||||||
let prev_delta = prev.strain_time;
|
let prev_delta = prev.strain_time;
|
||||||
let last_delta = last.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 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);
|
let curr_ratio = 1.0 + 6.0 * (base * base).min(0.5);
|
||||||
|
|
||||||
@@ -335,27 +336,27 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if curr.is_slider {
|
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;
|
effective_ratio *= 0.125;
|
||||||
}
|
}
|
||||||
|
|
||||||
if prev.is_slider {
|
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;
|
effective_ratio *= 0.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
if prev_island_size == island_size {
|
if prev_island_size == island_size {
|
||||||
// repeated island size (ex: triplet -> triplet)
|
// * repeated island size (ex: triplet -> triplet)
|
||||||
effective_ratio *= 0.25;
|
effective_ratio *= 0.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
if prev_island_size % 2 == island_size % 2 {
|
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;
|
effective_ratio *= 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
if last_delta > prev_delta + 10.0 && prev_delta > curr_delta + 10.0 {
|
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;
|
effective_ratio *= 0.125;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,15 +370,15 @@ pub(crate) fn calculate_speed_rhythm_bonus(
|
|||||||
prev_island_size = island_size;
|
prev_island_size = island_size;
|
||||||
island_size = 1;
|
island_size = 1;
|
||||||
|
|
||||||
// we're slowing down, stop counting
|
// * we're slowing down, stop counting
|
||||||
if prev_delta * 1.25 < curr_delta {
|
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;
|
first_delta_switch = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if prev_delta > 1.25 * curr_delta {
|
} else if prev_delta > 1.25 * curr_delta {
|
||||||
// we want to be speeding up
|
// * we want to be speeding up
|
||||||
// begin counting island until we change speed again
|
// * begin counting island until we change speed again
|
||||||
first_delta_switch = true;
|
first_delta_switch = true;
|
||||||
start_ratio = effective_ratio;
|
start_ratio = effective_ratio;
|
||||||
island_size = 1;
|
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
|
(4.0 + rhythm_complexity_sum * SPEED_RHYTHM_MULTIPLIER).sqrt() / 2.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ pub enum HitObjectKind {
|
|||||||
#[cfg(not(feature = "sliders"))]
|
#[cfg(not(feature = "sliders"))]
|
||||||
Slider {
|
Slider {
|
||||||
pixel_len: f32,
|
pixel_len: f32,
|
||||||
repeats: usize,
|
span_count: usize,
|
||||||
|
last_control_point: Pos2,
|
||||||
},
|
},
|
||||||
Spinner {
|
Spinner {
|
||||||
end_time: f32,
|
end_time: f32,
|
||||||
|
|||||||
+22
-2
@@ -533,10 +533,30 @@ macro_rules! parse_hitobjects_body {
|
|||||||
|
|
||||||
#[cfg(not(feature = "sliders"))]
|
#[cfg(not(feature = "sliders"))]
|
||||||
{
|
{
|
||||||
let repeats = split.nth(1).next_field("repeats")?.parse()?;
|
let last_control_point = split
|
||||||
|
.next()
|
||||||
|
.next_field("control points")?
|
||||||
|
.split('|')
|
||||||
|
.next_back();
|
||||||
|
|
||||||
|
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()?;
|
let pixel_len = split.next().next_field("pixel len")?.parse()?;
|
||||||
|
|
||||||
HitObjectKind::Slider { repeats, pixel_len }
|
HitObjectKind::Slider {
|
||||||
|
span_count,
|
||||||
|
pixel_len,
|
||||||
|
last_control_point,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => HitObjectKind::Circle,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if kind & Self::SPINNER_FLAG > 0 {
|
} else if kind & Self::SPINNER_FLAG > 0 {
|
||||||
$self.n_spinners += 1;
|
$self.n_spinners += 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user