feat: adjusted lint restrictions (#31)
This commit is contained in:
@@ -137,21 +137,25 @@ impl BeatmapAttributesBuilder {
|
||||
};
|
||||
|
||||
let raw_ar = mod_mult(self.ar);
|
||||
let preempt = difficulty_range(raw_ar as f64, 1800.0, 1200.0, 450.0) / clock_rate;
|
||||
let preempt = difficulty_range(f64::from(raw_ar), 1800.0, 1200.0, 450.0) / clock_rate;
|
||||
|
||||
// OD
|
||||
let hit_window = match self.mode {
|
||||
GameMode::Osu | GameMode::Catch => {
|
||||
let raw_od = mod_mult(self.od);
|
||||
|
||||
difficulty_range(raw_od as f64, Self::OSU_MIN, Self::OSU_AVG, Self::OSU_MAX)
|
||||
/ clock_rate
|
||||
difficulty_range(
|
||||
f64::from(raw_od),
|
||||
Self::OSU_MIN,
|
||||
Self::OSU_AVG,
|
||||
Self::OSU_MAX,
|
||||
) / clock_rate
|
||||
}
|
||||
GameMode::Taiko => {
|
||||
let raw_od = mod_mult(self.od);
|
||||
|
||||
let diff_range = difficulty_range(
|
||||
raw_od as f64,
|
||||
f64::from(raw_od),
|
||||
Self::TAIKO_MIN,
|
||||
Self::TAIKO_AVG,
|
||||
Self::TAIKO_MAX,
|
||||
@@ -174,7 +178,7 @@ impl BeatmapAttributesBuilder {
|
||||
value *= 1.4;
|
||||
}
|
||||
|
||||
((value as f64 * clock_rate).floor() / clock_rate).ceil()
|
||||
((f64::from(value) * clock_rate).floor() / clock_rate).ceil()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,14 +219,14 @@ impl BeatmapAttributesBuilder {
|
||||
let od = match self.mode {
|
||||
GameMode::Osu => (Self::OSU_MIN - od) / 6.0,
|
||||
GameMode::Taiko => (Self::TAIKO_MIN - od) / (Self::TAIKO_MIN - Self::TAIKO_AVG) * 5.0,
|
||||
GameMode::Catch | GameMode::Mania => self.od as f64,
|
||||
GameMode::Catch | GameMode::Mania => f64::from(self.od),
|
||||
};
|
||||
|
||||
BeatmapAttributes {
|
||||
ar,
|
||||
od,
|
||||
cs: cs as f64,
|
||||
hp: hp as f64,
|
||||
cs: f64::from(cs),
|
||||
hp: f64::from(hp),
|
||||
clock_rate,
|
||||
hit_windows,
|
||||
}
|
||||
@@ -245,6 +249,7 @@ impl From<&Beatmap> for BeatmapAttributesBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::similar_names)]
|
||||
fn difficulty_range(difficulty: f64, min: f64, mid: f64, max: f64) -> f64 {
|
||||
if difficulty > 5.0 {
|
||||
mid + (max - mid) * (difficulty - 5.0) / 5.0
|
||||
|
||||
@@ -12,8 +12,8 @@ pub struct TimingPoint {
|
||||
impl TimingPoint {
|
||||
/// Create a new [`TimingPoint`].
|
||||
#[inline]
|
||||
pub fn new(time: f64, beat_len: f64) -> Self {
|
||||
Self { time, beat_len }
|
||||
pub const fn new(time: f64, beat_len: f64) -> Self {
|
||||
Self { beat_len, time }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ impl DifficultyPoint {
|
||||
// * Note: In stable, the division occurs on floats, but with compiler optimisations
|
||||
// * turned on actually seems to occur on doubles via some .NET black magic (possibly inlining?).
|
||||
let bpm_mult = if beat_len < 0.0 {
|
||||
((-beat_len) as f32).clamp(10.0, 10_000.0) as f64 / 100.0
|
||||
f64::from(((-beat_len) as f32).clamp(10.0, 10_000.0)) / 100.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
@@ -113,7 +113,7 @@ impl EffectPoint {
|
||||
|
||||
/// Create a new [`EffectPoint`].
|
||||
#[inline]
|
||||
pub fn new(time: f64, kiai: bool) -> Self {
|
||||
pub const fn new(time: f64, kiai: bool) -> Self {
|
||||
Self { time, kiai }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ pub(crate) struct Random {
|
||||
}
|
||||
|
||||
impl Random {
|
||||
pub(crate) fn new(seed: i32) -> Self {
|
||||
pub(crate) const fn new(seed: i32) -> Self {
|
||||
Self {
|
||||
x: seed as u32,
|
||||
y: 842_502_087,
|
||||
@@ -33,10 +33,10 @@ impl Random {
|
||||
}
|
||||
|
||||
pub(crate) fn gen_double(&mut self) -> f64 {
|
||||
INT_TO_REAL * self.gen_signed() as f64
|
||||
INT_TO_REAL * f64::from(self.gen_signed())
|
||||
}
|
||||
|
||||
pub(crate) fn gen_int_range(&mut self, min: i32, max: i32) -> i32 {
|
||||
(min as f64 + self.gen_double() * (max - min) as f64) as i32
|
||||
(f64::from(min) + self.gen_double() * f64::from(max - min)) as i32
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ mod pattern_type;
|
||||
const MAX_NOTES_FOR_DENSITY: usize = 7;
|
||||
|
||||
impl Beatmap {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(in crate::beatmap) fn convert_to_mania(&self) -> Self {
|
||||
let mut map = self.clone_without_hit_objects(false);
|
||||
|
||||
@@ -52,14 +53,14 @@ impl Beatmap {
|
||||
.count();
|
||||
|
||||
let percent_slider_or_spinner =
|
||||
(slider_or_spinner_count as f32 / self.hit_objects.len() as f32) as f64;
|
||||
f64::from(slider_or_spinner_count as f32 / self.hit_objects.len() as f32);
|
||||
|
||||
let target_columns = if percent_slider_or_spinner < 0.2 {
|
||||
7.0
|
||||
} else if percent_slider_or_spinner < 0.3 || rounded_cs >= 5.0 {
|
||||
(6 + (rounded_od > 5.0) as u8) as f32
|
||||
f32::from(6 + u8::from(rounded_od > 5.0))
|
||||
} else if percent_slider_or_spinner > 0.6 {
|
||||
(4 + (rounded_od > 4.0) as u8) as f32
|
||||
f32::from(4 + u8::from(rounded_od > 4.0))
|
||||
} else {
|
||||
(rounded_od + 1.0).clamp(4.0, 7.0)
|
||||
};
|
||||
@@ -67,7 +68,7 @@ impl Beatmap {
|
||||
map.cs = target_columns;
|
||||
|
||||
let mut prev_note_times: LimitedQueue<f64, MAX_NOTES_FOR_DENSITY> = LimitedQueue::new();
|
||||
let mut density = i32::MAX as f64;
|
||||
let mut density = f64::from(i32::MAX);
|
||||
|
||||
let mut compute_density = |new_note_time: f64, d: &mut f64| {
|
||||
prev_note_times.push(new_note_time);
|
||||
@@ -129,10 +130,10 @@ impl Beatmap {
|
||||
edge_sounds,
|
||||
);
|
||||
|
||||
let segment_duration = gen.segment_duration as f64;
|
||||
let segment_duration = f64::from(gen.segment_duration);
|
||||
|
||||
for i in 0..=repeats as i32 + 1 {
|
||||
let time = obj.start_time + segment_duration * i as f64;
|
||||
let time = obj.start_time + segment_duration * f64::from(i);
|
||||
|
||||
last_values.time = time;
|
||||
last_values.pos = obj.pos;
|
||||
|
||||
@@ -93,15 +93,15 @@ impl Pattern {
|
||||
let hit_object = if start_time == end_time {
|
||||
HitObject {
|
||||
pos,
|
||||
start_time: start_time as f64,
|
||||
start_time: f64::from(start_time),
|
||||
kind: HitObjectKind::Circle,
|
||||
}
|
||||
} else {
|
||||
HitObject {
|
||||
pos,
|
||||
start_time: start_time as f64,
|
||||
start_time: f64::from(start_time),
|
||||
kind: HitObjectKind::Hold {
|
||||
end_time: end_time as f64,
|
||||
end_time: f64::from(end_time),
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -121,15 +121,15 @@ impl Pattern {
|
||||
let hit_object = if start_time == end_time {
|
||||
HitObject {
|
||||
pos,
|
||||
start_time: start_time as f64,
|
||||
start_time: f64::from(start_time),
|
||||
kind: HitObjectKind::Circle,
|
||||
}
|
||||
} else {
|
||||
HitObject {
|
||||
pos,
|
||||
start_time: start_time as f64,
|
||||
start_time: f64::from(start_time),
|
||||
kind: HitObjectKind::Hold {
|
||||
end_time: end_time as f64,
|
||||
end_time: f64::from(end_time),
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -163,5 +163,5 @@ impl Pattern {
|
||||
fn column_to_pos(column: u8, total_columns: i32) -> f32 {
|
||||
let divisor = 512.0 / total_columns as f32;
|
||||
|
||||
(column as f32 * divisor).ceil()
|
||||
(f32::from(column) * divisor).ceil()
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
let start_time = hit_object.start_time.round_even() as i32;
|
||||
|
||||
// * This matches stable's calculation.
|
||||
let end_time = (start_time as f64
|
||||
+ curve.dist() * beat_len * span_count as f64 * 0.01 / orig.slider_mult)
|
||||
let end_time = (f64::from(start_time)
|
||||
+ curve.dist() * beat_len * f64::from(span_count) * 0.01 / orig.slider_mult)
|
||||
.floor() as i32;
|
||||
|
||||
let segment_duration = (end_time - start_time) / span_count;
|
||||
@@ -100,6 +100,8 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
for obj in orig_pattern.hit_objects {
|
||||
let col = ManiaObject::column(obj.pos.x, self.total_columns as f32) as u8;
|
||||
|
||||
// Keeping it in-sync with lazer
|
||||
#[allow(clippy::if_not_else)]
|
||||
if self.end_time != obj.end_time().round_even() as i32 {
|
||||
intermediate_pattern.add_object(obj, col);
|
||||
} else {
|
||||
@@ -142,7 +144,7 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
self.convert_type &= !PatternType::FORCE_NOT_STACK;
|
||||
}
|
||||
|
||||
let note_count = 1 + (self.segment_duration >= 80) as i32;
|
||||
let note_count = 1 + i32::from(self.segment_duration >= 80);
|
||||
|
||||
self.generate_random_notes(self.start_time, note_count)
|
||||
} else if conversion_diff > 6.5 {
|
||||
@@ -241,7 +243,7 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(&|c| c != last_column as i32),
|
||||
Some(&|c| c != i32::from(last_column)),
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -262,7 +264,7 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
// * - x - -
|
||||
// * x - - -
|
||||
|
||||
let mut column = self.get_column(Some(true)) as i32;
|
||||
let mut column = i32::from(self.get_column(Some(true)));
|
||||
let mut increasing = self.random.gen_double() > 0.5;
|
||||
let mut pattern = Pattern::with_capacity(self.span_count as usize + 1);
|
||||
|
||||
@@ -299,13 +301,13 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
let legacy = (4..=8).contains(&self.total_columns);
|
||||
let interval = self
|
||||
.random
|
||||
.gen_int_range(1, self.total_columns - (legacy as i32));
|
||||
.gen_int_range(1, self.total_columns - i32::from(legacy));
|
||||
|
||||
let mut next_column = self.get_column(Some(true)) as i32;
|
||||
let mut next_column = i32::from(self.get_column(Some(true)));
|
||||
let random_start = self.random_start();
|
||||
let not_2k = self.total_columns > 2;
|
||||
let mut pattern =
|
||||
Pattern::with_capacity((self.span_count as usize + 1) * (1 + not_2k as usize));
|
||||
Pattern::with_capacity((self.span_count as usize + 1) * (1 + usize::from(not_2k)));
|
||||
|
||||
for _ in 0..=self.span_count as usize {
|
||||
pattern.add_slider_note(self, next_column as u8, start_time, start_time);
|
||||
@@ -313,7 +315,7 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
next_column += interval;
|
||||
|
||||
if next_column >= self.total_columns - random_start {
|
||||
next_column = next_column - self.total_columns - random_start + (legacy as i32);
|
||||
next_column = next_column - self.total_columns - random_start + i32::from(legacy);
|
||||
}
|
||||
|
||||
next_column += random_start;
|
||||
@@ -323,7 +325,7 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
pattern.add_slider_note(self, next_column as u8, start_time, start_time);
|
||||
}
|
||||
|
||||
next_column = PatternGenerator::get_random_column(self, None, None) as i32;
|
||||
next_column = i32::from(PatternGenerator::get_random_column(self, None, None));
|
||||
start_time += self.segment_duration;
|
||||
}
|
||||
|
||||
@@ -476,7 +478,7 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
let ignore_head = !(sample.whistle() || sample.finish() || sample.clap());
|
||||
|
||||
let mut row_pattern = Pattern::default();
|
||||
let hold_column = hold_column as i32;
|
||||
let hold_column = i32::from(hold_column);
|
||||
|
||||
for _ in 0..=self.span_count as usize {
|
||||
if !(ignore_head && start_time == self.start_time) {
|
||||
|
||||
@@ -15,7 +15,7 @@ trait PatternGenerator {
|
||||
// ----------------------------------
|
||||
|
||||
fn random_start(&self) -> i32 {
|
||||
(self.total_columns() == 8) as i32
|
||||
i32::from(self.total_columns() == 8)
|
||||
}
|
||||
|
||||
fn get_column(&self, allow_special: Option<bool>) -> u8 {
|
||||
@@ -53,7 +53,7 @@ trait PatternGenerator {
|
||||
} else if val >= 1.0 - p3 {
|
||||
3
|
||||
} else {
|
||||
1 + (val >= 1.0 - p2) as i32
|
||||
1 + i32::from(val >= 1.0 - p2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ trait PatternGenerator {
|
||||
}
|
||||
|
||||
let mut conversion_difficulty = 0.0;
|
||||
conversion_difficulty += (orig.hp + orig.ar.clamp(4.0, 7.0)) as f64 / 1.5;
|
||||
conversion_difficulty += orig.hit_objects.len() as f64 / drain_time as f64 * 9.0;
|
||||
conversion_difficulty += f64::from(orig.hp + orig.ar.clamp(4.0, 7.0)) / 1.5;
|
||||
conversion_difficulty += orig.hit_objects.len() as f64 / f64::from(drain_time) * 9.0;
|
||||
conversion_difficulty /= 38.0;
|
||||
conversion_difficulty *= 5.0;
|
||||
conversion_difficulty /= 1.15;
|
||||
@@ -115,7 +115,7 @@ trait PatternGenerator {
|
||||
};
|
||||
|
||||
// * Check for the initial column
|
||||
if is_valid(initial_column as i32) {
|
||||
if is_valid(i32::from(initial_column)) {
|
||||
return initial_column;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ trait PatternGenerator {
|
||||
PatternGenerator::get_random_column(self, Some(lower), Some(upper))
|
||||
};
|
||||
|
||||
!is_valid(initial_column as i32)
|
||||
!is_valid(i32::from(initial_column))
|
||||
} {}
|
||||
|
||||
initial_column
|
||||
|
||||
@@ -66,7 +66,7 @@ impl fmt::Display for PatternType {
|
||||
}
|
||||
|
||||
impl PatternType {
|
||||
pub(crate) fn contains(self, other: Self) -> bool {
|
||||
pub(crate) const fn contains(self, other: Self) -> bool {
|
||||
self.0 & other.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
curve::{Curve, CurveBuffers},
|
||||
parse::{HitObject, HitObjectKind},
|
||||
parse::{HitObject, HitObjectKind, Pos2},
|
||||
util::TandemSorter,
|
||||
Beatmap, GameMode,
|
||||
};
|
||||
@@ -13,7 +13,7 @@ impl Beatmap {
|
||||
let mut map = self.clone_without_hit_objects(true);
|
||||
let mut curve_bufs = CurveBuffers::default();
|
||||
|
||||
map.slider_mult *= LEGACY_TAIKO_VELOCITY_MULTIPLIER as f64;
|
||||
map.slider_mult *= f64::from(LEGACY_TAIKO_VELOCITY_MULTIPLIER);
|
||||
|
||||
for (obj, sound) in self.hit_objects.iter().zip(self.sounds.iter()) {
|
||||
match obj.kind {
|
||||
@@ -38,10 +38,12 @@ impl Beatmap {
|
||||
let edge_sound_count = edge_sounds.len().max(1);
|
||||
|
||||
while j
|
||||
<= obj.start_time + params.duration as f64 + params.tick_spacing / 8.0
|
||||
<= obj.start_time
|
||||
+ f64::from(params.duration)
|
||||
+ params.tick_spacing / 8.0
|
||||
{
|
||||
let h = HitObject {
|
||||
pos: Default::default(),
|
||||
pos: Pos2::default(),
|
||||
start_time: j,
|
||||
kind: HitObjectKind::Circle,
|
||||
};
|
||||
@@ -107,7 +109,7 @@ impl Beatmap {
|
||||
|
||||
// * The true distance, accounting for any repeats. This ends up being the drum roll distance later
|
||||
let spans = (*repeats + 1) as f64;
|
||||
let dist = curve.dist() * spans * LEGACY_TAIKO_VELOCITY_MULTIPLIER as f64;
|
||||
let dist = curve.dist() * spans * f64::from(LEGACY_TAIKO_VELOCITY_MULTIPLIER);
|
||||
|
||||
let timing_point = self.timing_point_at(*start_time);
|
||||
let difficulty_point = self.difficulty_point_at(*start_time).unwrap_or_default();
|
||||
@@ -115,13 +117,13 @@ impl Beatmap {
|
||||
let mut beat_len = timing_point.beat_len * difficulty_point.bpm_mult;
|
||||
|
||||
let slider_scoring_point_dist =
|
||||
OSU_BASE_SCORING_DIST as f64 * self.slider_mult / self.tick_rate;
|
||||
f64::from(OSU_BASE_SCORING_DIST) * self.slider_mult / self.tick_rate;
|
||||
|
||||
// * The velocity and duration of the taiko hit object - calculated as the velocity of a drum roll.
|
||||
let taiko_vel = slider_scoring_point_dist * self.tick_rate;
|
||||
*duration = (dist / taiko_vel * beat_len) as u32;
|
||||
|
||||
let osu_vel = taiko_vel * (1000.0_f32 as f64 / beat_len);
|
||||
let osu_vel = taiko_vel * (f64::from(1000.0_f32) / beat_len);
|
||||
|
||||
// * osu-stable always uses the speed-adjusted beatlength to determine the osu! velocity, but only uses it for conversion if beatmap version < 8
|
||||
if self.version >= 8 {
|
||||
@@ -129,7 +131,7 @@ impl Beatmap {
|
||||
}
|
||||
|
||||
// * If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
|
||||
*tick_spacing = (beat_len / self.tick_rate).min(*duration as f64 / spans);
|
||||
*tick_spacing = (beat_len / self.tick_rate).min(f64::from(*duration) / spans);
|
||||
|
||||
*tick_spacing > 0.0 && dist / osu_vel * 1000.0 < 2.0 * beat_len
|
||||
}
|
||||
@@ -144,7 +146,7 @@ struct SliderParams<'c> {
|
||||
}
|
||||
|
||||
impl<'c> SliderParams<'c> {
|
||||
fn new(start_time: f64, repeats: usize, curve: &'c Curve<'c>) -> Self {
|
||||
const fn new(start_time: f64, repeats: usize, curve: &'c Curve<'c>) -> Self {
|
||||
Self {
|
||||
curve,
|
||||
repeats,
|
||||
|
||||
+2
-2
@@ -104,7 +104,7 @@ impl BeatmapExt for Beatmap {
|
||||
let attrs = self.attributes().mods(mods).build();
|
||||
let scaling_factor = ScalingFactor::new(attrs.cs);
|
||||
let hr = mods.hr();
|
||||
let time_preempt = (attrs.hit_windows.ar * attrs.clock_rate) as f32 as f64;
|
||||
let time_preempt = f64::from((attrs.hit_windows.ar * attrs.clock_rate) as f32);
|
||||
let mut attrs = OsuDifficultyAttributes::default();
|
||||
|
||||
crate::osu::create_osu_objects(
|
||||
@@ -149,7 +149,7 @@ impl BeatmapExt for Beatmap {
|
||||
.collect();
|
||||
|
||||
let half_catcher_width =
|
||||
(calculate_catch_width(attrs.cs as f32) / 2.0 / ALLOWED_CATCH_RANGE) as f64;
|
||||
f64::from(calculate_catch_width(attrs.cs as f32) / 2.0 / ALLOWED_CATCH_RANGE);
|
||||
let mut last_direction = 0;
|
||||
let mut last_excess = half_catcher_width;
|
||||
|
||||
|
||||
+2
-2
@@ -89,7 +89,7 @@ impl Beatmap {
|
||||
.or_else(|| self.timing_points.last().map(|t| t.time))
|
||||
.unwrap_or(0.0);
|
||||
|
||||
/// Maps beat_len to a cumulative duration
|
||||
/// Maps `beat_len` to a cumulative duration
|
||||
#[derive(Debug)]
|
||||
struct BeatLenDuration {
|
||||
last_time: f64,
|
||||
@@ -221,7 +221,7 @@ impl Beatmap {
|
||||
slider_mult: self.slider_mult,
|
||||
tick_rate: self.tick_rate,
|
||||
hit_objects: Vec::with_capacity(self.hit_objects.len()),
|
||||
sounds: Vec::with_capacity((with_sounds as usize) * self.sounds.len()),
|
||||
sounds: Vec::with_capacity((usize::from(with_sounds)) * self.sounds.len()),
|
||||
timing_points: self.timing_points.clone(),
|
||||
difficulty_points: self.difficulty_points.clone(),
|
||||
effect_points: self.effect_points.clone(),
|
||||
|
||||
@@ -15,6 +15,8 @@ pub enum GameMode {
|
||||
impl From<u8> for GameMode {
|
||||
#[inline]
|
||||
fn from(mode: u8) -> Self {
|
||||
// `0` will happen most commonly so it should be the first branch
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match mode {
|
||||
0 => Self::Osu,
|
||||
1 => Self::Taiko,
|
||||
|
||||
@@ -16,7 +16,7 @@ pub struct SortedVec<T> {
|
||||
impl<T> SortedVec<T> {
|
||||
/// Constructs a new, empty `SortedVec<T>`.
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
pub const fn new() -> Self {
|
||||
Self { inner: Vec::new() }
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ impl<T: Sortable> SortedVec<T> {
|
||||
|
||||
/// Push a new value into the sorted list based on [`<T as Sortable>::push`](Sortable::push).
|
||||
pub fn push(&mut self, value: T) {
|
||||
<T as Sortable>::push(value, self)
|
||||
<T as Sortable>::push(value, self);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ pub struct CatchObject {
|
||||
|
||||
impl CatchObject {
|
||||
#[inline]
|
||||
pub(crate) fn new((pos, time): (Pos2, f64)) -> Self {
|
||||
pub(crate) const fn new((pos, time): (Pos2, f64)) -> Self {
|
||||
Self {
|
||||
pos: pos.x,
|
||||
time,
|
||||
@@ -70,7 +70,7 @@ impl CatchObject {
|
||||
let next_x = next.pos;
|
||||
let curr_x = self.pos;
|
||||
|
||||
let this_direction = (next_x > curr_x) as i8 * 2 - 1;
|
||||
let this_direction = i8::from(next_x > curr_x) * 2 - 1;
|
||||
let time_to_next = next.time - self.time - 1000.0 / 60.0 / 4.0;
|
||||
|
||||
let sub = if *last_direction == this_direction {
|
||||
@@ -79,7 +79,7 @@ impl CatchObject {
|
||||
half_catcher_width
|
||||
};
|
||||
|
||||
let dist_to_next = (next_x - curr_x).abs() as f64 - sub;
|
||||
let dist_to_next = f64::from((next_x - curr_x).abs()) - sub;
|
||||
let hyper_dist = (time_to_next * BASE_SPEED - dist_to_next) as f32;
|
||||
|
||||
if hyper_dist < 0.0 {
|
||||
@@ -87,7 +87,7 @@ impl CatchObject {
|
||||
*last_excess = half_catcher_width;
|
||||
} else {
|
||||
self.hyper_dist = hyper_dist;
|
||||
*last_excess = (hyper_dist as f64).clamp(0.0, half_catcher_width);
|
||||
*last_excess = f64::from(hyper_dist).clamp(0.0, half_catcher_width);
|
||||
}
|
||||
|
||||
*last_direction = this_direction;
|
||||
|
||||
@@ -121,7 +121,7 @@ impl FruitOrJuice {
|
||||
slider_objects.extend(¶ms.ticks);
|
||||
|
||||
for span_idx in 1..=*repeats {
|
||||
let progress = (span_idx % 2 == 1) as u8 as f64;
|
||||
let progress = f64::from(u8::from(span_idx % 2 == 1));
|
||||
let pos = h.pos + curve.position_at(progress);
|
||||
let time_offset = span_duration * span_idx as f64;
|
||||
|
||||
@@ -153,11 +153,11 @@ impl FruitOrJuice {
|
||||
}
|
||||
|
||||
// Slider tail
|
||||
let progress = (*repeats % 2 == 0) as u8 as f64;
|
||||
let progress = f64::from(u8::from(*repeats % 2 == 0));
|
||||
let pos = h.pos + curve.position_at(progress);
|
||||
slider_objects.push((pos, h.start_time + total_duration));
|
||||
|
||||
let new_fruits = 2 + (tick_dist > 0.0) as usize * *repeats;
|
||||
let new_fruits = 2 + usize::from(tick_dist > 0.0) * *repeats;
|
||||
params.attributes.n_fruits += new_fruits;
|
||||
params.attributes.n_droplets += slider_objects.len() - new_fruits;
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ impl<'map> CatchGradualDifficulty<'map> {
|
||||
Self { map, inner }
|
||||
}
|
||||
|
||||
pub(crate) fn idx(&self) -> usize {
|
||||
pub(crate) const fn idx(&self) -> usize {
|
||||
self.inner.idx
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ impl CatchOwnedGradualDifficulty {
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn idx(&self) -> usize {
|
||||
pub(crate) const fn idx(&self) -> usize {
|
||||
self.inner.idx
|
||||
}
|
||||
}
|
||||
@@ -181,7 +181,7 @@ impl CatchGradualDifficultyInner {
|
||||
let hit_objects = CatchObjectIter::new(mods, attributes);
|
||||
|
||||
let half_catcher_width =
|
||||
(calculate_catch_width(map_attributes.cs as f32) / 2.0 / ALLOWED_CATCH_RANGE) as f64;
|
||||
f64::from(calculate_catch_width(map_attributes.cs as f32) / 2.0 / ALLOWED_CATCH_RANGE);
|
||||
let last_direction = 0;
|
||||
let last_excess = half_catcher_width;
|
||||
|
||||
|
||||
+12
-10
@@ -50,6 +50,7 @@ const CATCHER_SIZE: f32 = 106.75;
|
||||
/// println!("Stars: {}", difficulty_attrs.stars);
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct CatchStars<'map> {
|
||||
map: &'map Beatmap,
|
||||
mods: u32,
|
||||
@@ -60,7 +61,7 @@ pub struct CatchStars<'map> {
|
||||
impl<'map> CatchStars<'map> {
|
||||
/// Create a new difficulty calculator for osu!catch maps.
|
||||
#[inline]
|
||||
pub fn new(map: &'map Beatmap) -> Self {
|
||||
pub const fn new(map: &'map Beatmap) -> Self {
|
||||
Self {
|
||||
map,
|
||||
mods: 0,
|
||||
@@ -73,7 +74,7 @@ impl<'map> CatchStars<'map> {
|
||||
///
|
||||
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
|
||||
#[inline]
|
||||
pub fn mods(mut self, mods: u32) -> Self {
|
||||
pub const fn mods(mut self, mods: u32) -> Self {
|
||||
self.mods = mods;
|
||||
|
||||
self
|
||||
@@ -88,7 +89,7 @@ impl<'map> CatchStars<'map> {
|
||||
[`CatchGradualDifficultyAttributes`](crate::catch::CatchGradualDifficulty)."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
pub const fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
self.passed_objects = Some(passed_objects);
|
||||
|
||||
self
|
||||
@@ -98,7 +99,7 @@ impl<'map> CatchStars<'map> {
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
pub const fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
@@ -147,6 +148,7 @@ impl CatchStrains {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn calculate_movement(params: CatchStars<'_>) -> (Movement, CatchDifficultyAttributes) {
|
||||
let CatchStars {
|
||||
map,
|
||||
@@ -183,7 +185,7 @@ fn calculate_movement(params: CatchStars<'_>) -> (Movement, CatchDifficultyAttri
|
||||
|
||||
// Hyper dash business
|
||||
let half_catcher_width =
|
||||
(calculate_catch_width(map_attributes.cs as f32) / 2.0 / ALLOWED_CATCH_RANGE) as f64;
|
||||
f64::from(calculate_catch_width(map_attributes.cs as f32) / 2.0 / ALLOWED_CATCH_RANGE);
|
||||
let mut last_direction = 0;
|
||||
let mut last_excess = half_catcher_width;
|
||||
|
||||
@@ -192,7 +194,7 @@ fn calculate_movement(params: CatchStars<'_>) -> (Movement, CatchDifficultyAttri
|
||||
|
||||
let (mut prev, curr) = match (hit_objects.next(), hit_objects.next()) {
|
||||
(Some(prev), Some(curr)) => (prev, curr),
|
||||
(Some(_), None) | (None, None) => return (movement, params.attributes),
|
||||
(_, None) => return (movement, params.attributes),
|
||||
(None, Some(_)) => unreachable!(),
|
||||
};
|
||||
|
||||
@@ -274,7 +276,7 @@ pub struct CatchDifficultyAttributes {
|
||||
impl CatchDifficultyAttributes {
|
||||
/// Return the maximum combo.
|
||||
#[inline]
|
||||
pub fn max_combo(&self) -> usize {
|
||||
pub const fn max_combo(&self) -> usize {
|
||||
self.n_fruits + self.n_droplets
|
||||
}
|
||||
|
||||
@@ -297,19 +299,19 @@ pub struct CatchPerformanceAttributes {
|
||||
impl CatchPerformanceAttributes {
|
||||
/// Return the star value.
|
||||
#[inline]
|
||||
pub fn stars(&self) -> f64 {
|
||||
pub const fn stars(&self) -> f64 {
|
||||
self.difficulty.stars
|
||||
}
|
||||
|
||||
/// Return the performance point value.
|
||||
#[inline]
|
||||
pub fn pp(&self) -> f64 {
|
||||
pub const fn pp(&self) -> f64 {
|
||||
self.pp
|
||||
}
|
||||
|
||||
/// Return the maximum combo of the map.
|
||||
#[inline]
|
||||
pub fn max_combo(&self) -> usize {
|
||||
pub const fn max_combo(&self) -> usize {
|
||||
self.difficulty.max_combo()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,15 +90,15 @@ impl Movement {
|
||||
let dist_moved = pos - last_player_pos;
|
||||
let weighted_strain_time = current.strain_time + 13.0 + (3.0 / current.clock_rate);
|
||||
|
||||
let mut dist_addition = (dist_moved.abs().powf(1.3) / 510.0) as f64;
|
||||
let mut dist_addition = f64::from(dist_moved.abs().powf(1.3) / 510.0);
|
||||
|
||||
if dist_moved.abs() > 0.1 {
|
||||
if self.last_distance_moved.abs() > 0.1
|
||||
&& dist_moved.signum() != self.last_distance_moved.signum()
|
||||
{
|
||||
let bonus_factor = (dist_moved.abs().min(50.0) / 50.0) as f64;
|
||||
let bonus_factor = f64::from(dist_moved.abs().min(50.0) / 50.0);
|
||||
let anti_flow_factor =
|
||||
(self.last_distance_moved.abs().min(70.0) / 70.0).max(0.38) as f64;
|
||||
f64::from((self.last_distance_moved.abs().min(70.0) / 70.0).max(0.38));
|
||||
|
||||
dist_addition += DIRECTION_CHANGE_BONUS / (self.last_strain_time + 16.0).sqrt()
|
||||
* bonus_factor
|
||||
@@ -106,14 +106,16 @@ impl Movement {
|
||||
* (1.0 - (weighted_strain_time / 1000.0).powi(3)).max(0.0);
|
||||
}
|
||||
|
||||
dist_addition += (12.5 * dist_moved.abs().min(NORMALIZED_HITOBJECT_RADIUS * 2.0)
|
||||
/ (NORMALIZED_HITOBJECT_RADIUS * 6.0)) as f64
|
||||
/ weighted_strain_time.sqrt();
|
||||
dist_addition += f64::from(
|
||||
12.5 * dist_moved.abs().min(NORMALIZED_HITOBJECT_RADIUS * 2.0)
|
||||
/ (NORMALIZED_HITOBJECT_RADIUS * 6.0),
|
||||
) / weighted_strain_time.sqrt();
|
||||
}
|
||||
|
||||
let mut edge_dash_bonus = 0.0;
|
||||
|
||||
if current.last.hyper_dist <= 20.0 {
|
||||
#[allow(clippy::if_not_else)]
|
||||
if !current.last.hyper_dash {
|
||||
edge_dash_bonus += 5.7;
|
||||
} else {
|
||||
@@ -122,7 +124,7 @@ impl Movement {
|
||||
|
||||
dist_addition *= 1.0
|
||||
+ edge_dash_bonus
|
||||
* ((20.0 - current.last.hyper_dist) / 20.0) as f64
|
||||
* f64::from((20.0 - current.last.hyper_dist) / 20.0)
|
||||
* ((current.strain_time * current.clock_rate).min(265.0) / 265.0).powf(1.5);
|
||||
}
|
||||
|
||||
|
||||
+19
-15
@@ -36,6 +36,7 @@ use std::cmp::Ordering;
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[must_use]
|
||||
pub struct CatchPP<'map> {
|
||||
pub(crate) map_or_attrs: MapOrElse<MapRef<'map>, CatchDifficultyAttributes>,
|
||||
pub(crate) mods: u32,
|
||||
@@ -87,7 +88,7 @@ impl<'map> CatchPP<'map> {
|
||||
///
|
||||
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
|
||||
#[inline]
|
||||
pub fn mods(mut self, mods: u32) -> Self {
|
||||
pub const fn mods(mut self, mods: u32) -> Self {
|
||||
self.mods = mods;
|
||||
|
||||
self
|
||||
@@ -95,7 +96,7 @@ impl<'map> CatchPP<'map> {
|
||||
|
||||
/// Specify the max combo of the play.
|
||||
#[inline]
|
||||
pub fn combo(mut self, combo: usize) -> Self {
|
||||
pub const fn combo(mut self, combo: usize) -> Self {
|
||||
self.combo = Some(combo);
|
||||
|
||||
self
|
||||
@@ -103,7 +104,7 @@ impl<'map> CatchPP<'map> {
|
||||
|
||||
/// Specify the amount of fruits of a play i.e. n300.
|
||||
#[inline]
|
||||
pub fn fruits(mut self, n_fruits: usize) -> Self {
|
||||
pub const fn fruits(mut self, n_fruits: usize) -> Self {
|
||||
self.n_fruits = Some(n_fruits);
|
||||
|
||||
self
|
||||
@@ -111,7 +112,7 @@ impl<'map> CatchPP<'map> {
|
||||
|
||||
/// Specify the amount of droplets of a play i.e. n100.
|
||||
#[inline]
|
||||
pub fn droplets(mut self, n_droplets: usize) -> Self {
|
||||
pub const fn droplets(mut self, n_droplets: usize) -> Self {
|
||||
self.n_droplets = Some(n_droplets);
|
||||
|
||||
self
|
||||
@@ -119,15 +120,15 @@ impl<'map> CatchPP<'map> {
|
||||
|
||||
/// Specify the amount of tiny droplets of a play i.e. n50.
|
||||
#[inline]
|
||||
pub fn tiny_droplets(mut self, n_tiny_droplets: usize) -> Self {
|
||||
pub const fn tiny_droplets(mut self, n_tiny_droplets: usize) -> Self {
|
||||
self.n_tiny_droplets = Some(n_tiny_droplets);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the amount of tiny droplet misses of a play i.e. n_katu.
|
||||
/// Specify the amount of tiny droplet misses of a play i.e. `n_katu`.
|
||||
#[inline]
|
||||
pub fn tiny_droplet_misses(mut self, n_tiny_droplet_misses: usize) -> Self {
|
||||
pub const fn tiny_droplet_misses(mut self, n_tiny_droplet_misses: usize) -> Self {
|
||||
self.n_tiny_droplet_misses = Some(n_tiny_droplet_misses);
|
||||
|
||||
self
|
||||
@@ -135,7 +136,7 @@ impl<'map> CatchPP<'map> {
|
||||
|
||||
/// Specify the amount of fruit / droplet misses of the play.
|
||||
#[inline]
|
||||
pub fn misses(mut self, n_misses: usize) -> Self {
|
||||
pub const fn misses(mut self, n_misses: usize) -> Self {
|
||||
self.n_misses = Some(n_misses);
|
||||
|
||||
self
|
||||
@@ -150,7 +151,7 @@ impl<'map> CatchPP<'map> {
|
||||
[`CatchGradualPerformanceAttributes`](crate::catch::CatchGradualPerformance)."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
pub const fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
self.passed_objects = Some(passed_objects);
|
||||
|
||||
self
|
||||
@@ -160,7 +161,7 @@ impl<'map> CatchPP<'map> {
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
pub const fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
@@ -168,7 +169,8 @@ impl<'map> CatchPP<'map> {
|
||||
|
||||
/// Provide parameters through an [`CatchScoreState`].
|
||||
#[inline]
|
||||
pub fn state(mut self, state: CatchScoreState) -> Self {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub const fn state(mut self, state: CatchScoreState) -> Self {
|
||||
let CatchScoreState {
|
||||
max_combo,
|
||||
n_fruits,
|
||||
@@ -198,6 +200,7 @@ impl<'map> CatchPP<'map> {
|
||||
}
|
||||
|
||||
/// Create the [`CatchScoreState`] that will be used for performance calculation.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn generate_state(&mut self) -> CatchScoreState {
|
||||
let attrs = match self.map_or_attrs {
|
||||
MapOrElse::Map(ref map) => {
|
||||
@@ -297,6 +300,7 @@ impl<'map> CatchPP<'map> {
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(clippy::single_match_else)]
|
||||
match (self.n_tiny_droplets, self.n_tiny_droplet_misses) {
|
||||
(Some(n_tiny_droplets), Some(n_tiny_droplet_misses)) => match self.acc {
|
||||
Some(acc) => {
|
||||
@@ -376,7 +380,7 @@ impl<'map> CatchPP<'map> {
|
||||
///
|
||||
/// [`OsuDifficultyAttributes`]: crate::osu::OsuDifficultyAttributes
|
||||
#[inline]
|
||||
pub fn try_from_osu(osu: OsuPP<'map>) -> Option<Self> {
|
||||
pub const fn try_from_osu(osu: OsuPP<'map>) -> Option<Self> {
|
||||
let OsuPP {
|
||||
map_or_attrs,
|
||||
mods,
|
||||
@@ -449,7 +453,7 @@ impl CatchPPInner {
|
||||
// Longer maps are worth more
|
||||
let len_bonus = 0.95
|
||||
+ 0.3 * (combo_hits as f64 / 2500.0).min(1.0)
|
||||
+ (combo_hits > 2500) as u8 as f64 * (combo_hits as f64 / 2500.0).log10() * 0.475;
|
||||
+ f64::from(u8::from(combo_hits > 2500)) * (combo_hits as f64 / 2500.0).log10() * 0.475;
|
||||
|
||||
pp *= len_bonus;
|
||||
|
||||
@@ -467,7 +471,7 @@ impl CatchPPInner {
|
||||
let ar = attributes.ar;
|
||||
let mut ar_factor = 1.0;
|
||||
if ar > 9.0 {
|
||||
ar_factor += 0.1 * (ar - 9.0) + (ar > 10.0) as u8 as f64 * 0.1 * (ar - 10.0);
|
||||
ar_factor += 0.1 * (ar - 9.0) + f64::from(u8::from(ar > 10.0)) * 0.1 * (ar - 10.0);
|
||||
} else if ar < 8.0 {
|
||||
ar_factor += 0.025 * (8.0 - ar);
|
||||
}
|
||||
@@ -501,7 +505,7 @@ impl CatchPPInner {
|
||||
}
|
||||
}
|
||||
|
||||
fn combo_hits(&self) -> usize {
|
||||
const fn combo_hits(&self) -> usize {
|
||||
self.state.n_fruits + self.state.n_droplets + self.state.n_misses
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ impl CatchScoreState {
|
||||
|
||||
/// Return the total amount of hits by adding everything up.
|
||||
#[inline]
|
||||
pub fn total_hits(&self) -> usize {
|
||||
pub const fn total_hits(&self) -> usize {
|
||||
self.n_fruits
|
||||
+ self.n_droplets
|
||||
+ self.n_tiny_droplets
|
||||
|
||||
+10
-8
@@ -147,7 +147,7 @@ impl<'bufs> Curve<'bufs> {
|
||||
}
|
||||
|
||||
// * The current vertex ends the segment
|
||||
let segment_vertices = &vertices[start..i + 1];
|
||||
let segment_vertices = &vertices[start..=i];
|
||||
let segment_kind = points[start].kind.unwrap_or(PathType::Linear);
|
||||
|
||||
Self::calculate_subpath(path, segment_vertices, segment_kind, bezier);
|
||||
@@ -176,14 +176,16 @@ impl<'bufs> Curve<'bufs> {
|
||||
cumulative_len.push(0.0);
|
||||
|
||||
let length_iter = path.iter().zip(path.iter().skip(1)).map(|(&curr, &next)| {
|
||||
calculated_len += (next - curr).length() as f64;
|
||||
calculated_len += f64::from((next - curr).length());
|
||||
|
||||
calculated_len
|
||||
});
|
||||
|
||||
cumulative_len.extend(length_iter);
|
||||
|
||||
if let Some(expected_len) = expected_len.filter(|&len| calculated_len != len) {
|
||||
if let Some(expected_len) =
|
||||
expected_len.filter(|&len| (calculated_len - len).abs() >= f64::EPSILON)
|
||||
{
|
||||
// * In osu-stable, if the last two control points of a slider are equal, extension is not performed
|
||||
let condition_opt = points
|
||||
.len()
|
||||
@@ -254,7 +256,7 @@ impl<'bufs> Curve<'bufs> {
|
||||
}
|
||||
}
|
||||
|
||||
Self::approximate_bezier(path, sub_points, bufs)
|
||||
Self::approximate_bezier(path, sub_points, bufs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,7 +292,7 @@ impl<'bufs> Curve<'bufs> {
|
||||
}
|
||||
|
||||
fn approximate_linear(path: &mut Vec<Pos2>, points: &[Pos2]) {
|
||||
path.extend(points)
|
||||
path.extend(points);
|
||||
}
|
||||
|
||||
fn approximate_circular_arc(path: &mut Vec<Pos2>, a: Pos2, b: Pos2, c: Pos2) -> bool {
|
||||
@@ -313,7 +315,7 @@ impl<'bufs> Curve<'bufs> {
|
||||
if divisor.abs() <= f32::EPSILON {
|
||||
2
|
||||
} else {
|
||||
((pr.theta_range / divisor as f64).ceil() as usize).max(2)
|
||||
((pr.theta_range / f64::from(divisor)).ceil() as usize).max(2)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -509,8 +511,8 @@ impl<'bufs> Curve<'bufs> {
|
||||
|
||||
let radius = d_a.length();
|
||||
|
||||
let theta_start = (d_a.y as f64).atan2(d_a.x as f64);
|
||||
let mut theta_end = (d_c.y as f64).atan2(d_c.x as f64);
|
||||
let theta_start = f64::from(d_a.y).atan2(f64::from(d_a.x));
|
||||
let mut theta_end = f64::from(d_c.y).atan2(f64::from(d_c.x));
|
||||
|
||||
while theta_end < theta_start {
|
||||
theta_end += 2.0 * PI;
|
||||
|
||||
+21
-7
@@ -1,6 +1,8 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![deny(
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::pedantic,
|
||||
clippy::missing_const_for_fn,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
unused,
|
||||
@@ -9,6 +11,18 @@
|
||||
missing_docs,
|
||||
rustdoc::broken_intra_doc_links
|
||||
)]
|
||||
#![allow(
|
||||
clippy::must_use_candidate,
|
||||
clippy::inline_always,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_possible_wrap,
|
||||
clippy::explicit_iter_loop,
|
||||
clippy::items_after_statements,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::module_name_repetitions
|
||||
)]
|
||||
|
||||
//! A standalone crate to calculate star ratings and performance points for all [osu!](https://osu.ppy.sh/home) gamemodes.
|
||||
//!
|
||||
@@ -249,7 +263,7 @@ pub enum Strains {
|
||||
impl Strains {
|
||||
/// Time in ms inbetween two strains.
|
||||
#[inline]
|
||||
pub fn section_len(&self) -> f64 {
|
||||
pub const fn section_len(&self) -> f64 {
|
||||
match self {
|
||||
Strains::Osu(strains) => strains.section_len,
|
||||
Strains::Taiko(strains) => strains.section_len,
|
||||
@@ -287,7 +301,7 @@ pub enum DifficultyAttributes {
|
||||
impl DifficultyAttributes {
|
||||
/// The star value.
|
||||
#[inline]
|
||||
pub fn stars(&self) -> f64 {
|
||||
pub const fn stars(&self) -> f64 {
|
||||
match self {
|
||||
Self::Osu(attrs) => attrs.stars,
|
||||
Self::Taiko(attrs) => attrs.stars,
|
||||
@@ -298,7 +312,7 @@ impl DifficultyAttributes {
|
||||
|
||||
/// The maximum combo of the map.
|
||||
#[inline]
|
||||
pub fn max_combo(&self) -> usize {
|
||||
pub const fn max_combo(&self) -> usize {
|
||||
match self {
|
||||
Self::Osu(attrs) => attrs.max_combo,
|
||||
Self::Taiko(attrs) => attrs.max_combo,
|
||||
@@ -358,7 +372,7 @@ pub enum PerformanceAttributes {
|
||||
impl PerformanceAttributes {
|
||||
/// The pp value.
|
||||
#[inline]
|
||||
pub fn pp(&self) -> f64 {
|
||||
pub const fn pp(&self) -> f64 {
|
||||
match self {
|
||||
Self::Osu(attrs) => attrs.pp,
|
||||
Self::Taiko(attrs) => attrs.pp,
|
||||
@@ -369,7 +383,7 @@ impl PerformanceAttributes {
|
||||
|
||||
/// The star value.
|
||||
#[inline]
|
||||
pub fn stars(&self) -> f64 {
|
||||
pub const fn stars(&self) -> f64 {
|
||||
match self {
|
||||
Self::Osu(attrs) => attrs.stars(),
|
||||
Self::Taiko(attrs) => attrs.stars(),
|
||||
@@ -391,7 +405,7 @@ impl PerformanceAttributes {
|
||||
|
||||
#[inline]
|
||||
/// The maximum combo of the map.
|
||||
pub fn max_combo(&self) -> usize {
|
||||
pub const fn max_combo(&self) -> usize {
|
||||
match self {
|
||||
Self::Osu(attrs) => attrs.difficulty.max_combo,
|
||||
Self::Taiko(attrs) => attrs.difficulty.max_combo,
|
||||
|
||||
@@ -63,7 +63,7 @@ impl<'map> ManiaGradualDifficulty<'map> {
|
||||
Self { map, inner }
|
||||
}
|
||||
|
||||
pub(crate) fn idx(&self) -> usize {
|
||||
pub(crate) const fn idx(&self) -> usize {
|
||||
self.inner.idx
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ impl ManiaOwnedGradualDifficulty {
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn idx(&self) -> usize {
|
||||
pub(crate) const fn idx(&self) -> usize {
|
||||
self.inner.idx
|
||||
}
|
||||
}
|
||||
@@ -257,7 +257,7 @@ impl ManiaGradualDifficultyInner {
|
||||
})
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
const fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let len = self.len();
|
||||
|
||||
(len, Some(len))
|
||||
@@ -287,7 +287,7 @@ impl ManiaGradualDifficultyInner {
|
||||
self.next(hit_objects)
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
const fn len(&self) -> usize {
|
||||
self.diff_objects.len() + 1 - self.idx
|
||||
}
|
||||
}
|
||||
|
||||
+13
-12
@@ -50,6 +50,7 @@ const STAR_SCALING_FACTOR: f64 = 0.018;
|
||||
/// println!("Stars: {}", difficulty_attrs.stars);
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct ManiaStars<'map> {
|
||||
map: Cow<'map, Beatmap>,
|
||||
mods: u32,
|
||||
@@ -78,7 +79,7 @@ impl<'map> ManiaStars<'map> {
|
||||
///
|
||||
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
|
||||
#[inline]
|
||||
pub fn mods(mut self, mods: u32) -> Self {
|
||||
pub const fn mods(mut self, mods: u32) -> Self {
|
||||
self.mods = mods;
|
||||
|
||||
self
|
||||
@@ -93,7 +94,7 @@ impl<'map> ManiaStars<'map> {
|
||||
[`ManiaGradualDifficultyAttributes`](crate::mania::ManiaGradualDifficulty)."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
pub const fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
self.passed_objects = Some(passed_objects);
|
||||
|
||||
self
|
||||
@@ -103,7 +104,7 @@ impl<'map> ManiaStars<'map> {
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
pub const fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
@@ -113,7 +114,7 @@ impl<'map> ManiaStars<'map> {
|
||||
///
|
||||
/// This only needs to be specified if the map was converted manually beforehand.
|
||||
#[inline]
|
||||
pub fn is_convert(mut self, is_convert: bool) -> Self {
|
||||
pub const fn is_convert(mut self, is_convert: bool) -> Self {
|
||||
self.is_convert = is_convert;
|
||||
|
||||
self
|
||||
@@ -250,19 +251,19 @@ pub struct ManiaDifficultyAttributes {
|
||||
impl ManiaDifficultyAttributes {
|
||||
/// Return the maximum combo.
|
||||
#[inline]
|
||||
pub fn max_combo(&self) -> usize {
|
||||
pub const fn max_combo(&self) -> usize {
|
||||
self.max_combo
|
||||
}
|
||||
|
||||
/// Return the amount of hitobjects.
|
||||
#[inline]
|
||||
pub fn n_objects(&self) -> usize {
|
||||
pub const fn n_objects(&self) -> usize {
|
||||
self.n_objects
|
||||
}
|
||||
|
||||
/// Whether the [`Beatmap`] was a convert i.e. an osu!standard map.
|
||||
#[inline]
|
||||
pub fn is_convert(&self) -> bool {
|
||||
pub const fn is_convert(&self) -> bool {
|
||||
self.is_convert
|
||||
}
|
||||
|
||||
@@ -287,31 +288,31 @@ pub struct ManiaPerformanceAttributes {
|
||||
impl ManiaPerformanceAttributes {
|
||||
/// Return the star value.
|
||||
#[inline]
|
||||
pub fn stars(&self) -> f64 {
|
||||
pub const fn stars(&self) -> f64 {
|
||||
self.difficulty.stars
|
||||
}
|
||||
|
||||
/// Return the performance point value.
|
||||
#[inline]
|
||||
pub fn pp(&self) -> f64 {
|
||||
pub const fn pp(&self) -> f64 {
|
||||
self.pp
|
||||
}
|
||||
|
||||
/// Return the maximum combo of the map.
|
||||
#[inline]
|
||||
pub fn max_combo(&self) -> usize {
|
||||
pub const fn max_combo(&self) -> usize {
|
||||
self.difficulty.max_combo
|
||||
}
|
||||
|
||||
/// Return the amount of hitobjects.
|
||||
#[inline]
|
||||
pub fn n_objects(&self) -> usize {
|
||||
pub const fn n_objects(&self) -> usize {
|
||||
self.difficulty.n_objects
|
||||
}
|
||||
|
||||
/// Whether the [`Beatmap`] was a convert i.e. an osu!standard map.
|
||||
#[inline]
|
||||
pub fn is_convert(&self) -> bool {
|
||||
pub const fn is_convert(&self) -> bool {
|
||||
self.difficulty.is_convert
|
||||
}
|
||||
}
|
||||
|
||||
+41
-35
@@ -41,6 +41,7 @@ use crate::{
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[must_use]
|
||||
pub struct ManiaPP<'map> {
|
||||
map_or_attrs: MapOrElse<Cow<'map, Beatmap>, ManiaDifficultyAttributes>,
|
||||
is_convert_overwrite: Option<bool>,
|
||||
@@ -98,7 +99,7 @@ impl<'map> ManiaPP<'map> {
|
||||
///
|
||||
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
|
||||
#[inline]
|
||||
pub fn mods(mut self, mods: u32) -> Self {
|
||||
pub const fn mods(mut self, mods: u32) -> Self {
|
||||
self.mods = mods;
|
||||
|
||||
self
|
||||
@@ -113,7 +114,7 @@ impl<'map> ManiaPP<'map> {
|
||||
[`ManiaGradualPerformanceAttributes`](crate::mania::ManiaGradualPerformance)."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
pub const fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
self.passed_objects = Some(passed_objects);
|
||||
|
||||
self
|
||||
@@ -123,7 +124,7 @@ impl<'map> ManiaPP<'map> {
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
pub const fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
@@ -142,7 +143,7 @@ impl<'map> ManiaPP<'map> {
|
||||
///
|
||||
/// Defauls to [`HitResultPriority::BestCase`].
|
||||
#[inline]
|
||||
pub fn hitresult_priority(mut self, priority: HitResultPriority) -> Self {
|
||||
pub const fn hitresult_priority(mut self, priority: HitResultPriority) -> Self {
|
||||
self.hitresult_priority = Some(priority);
|
||||
|
||||
self
|
||||
@@ -150,7 +151,7 @@ impl<'map> ManiaPP<'map> {
|
||||
|
||||
/// Specify the amount of 320s of a play.
|
||||
#[inline]
|
||||
pub fn n320(mut self, n320: usize) -> Self {
|
||||
pub const fn n320(mut self, n320: usize) -> Self {
|
||||
self.n320 = Some(n320);
|
||||
|
||||
self
|
||||
@@ -158,7 +159,7 @@ impl<'map> ManiaPP<'map> {
|
||||
|
||||
/// Specify the amount of 300s of a play.
|
||||
#[inline]
|
||||
pub fn n300(mut self, n300: usize) -> Self {
|
||||
pub const fn n300(mut self, n300: usize) -> Self {
|
||||
self.n300 = Some(n300);
|
||||
|
||||
self
|
||||
@@ -166,7 +167,7 @@ impl<'map> ManiaPP<'map> {
|
||||
|
||||
/// Specify the amount of 200s of a play.
|
||||
#[inline]
|
||||
pub fn n200(mut self, n200: usize) -> Self {
|
||||
pub const fn n200(mut self, n200: usize) -> Self {
|
||||
self.n200 = Some(n200);
|
||||
|
||||
self
|
||||
@@ -174,7 +175,7 @@ impl<'map> ManiaPP<'map> {
|
||||
|
||||
/// Specify the amount of 100s of a play.
|
||||
#[inline]
|
||||
pub fn n100(mut self, n100: usize) -> Self {
|
||||
pub const fn n100(mut self, n100: usize) -> Self {
|
||||
self.n100 = Some(n100);
|
||||
|
||||
self
|
||||
@@ -182,7 +183,7 @@ impl<'map> ManiaPP<'map> {
|
||||
|
||||
/// Specify the amount of 50s of a play.
|
||||
#[inline]
|
||||
pub fn n50(mut self, n50: usize) -> Self {
|
||||
pub const fn n50(mut self, n50: usize) -> Self {
|
||||
self.n50 = Some(n50);
|
||||
|
||||
self
|
||||
@@ -190,7 +191,7 @@ impl<'map> ManiaPP<'map> {
|
||||
|
||||
/// Specify the amount of misses of a play.
|
||||
#[inline]
|
||||
pub fn n_misses(mut self, n_misses: usize) -> Self {
|
||||
pub const fn n_misses(mut self, n_misses: usize) -> Self {
|
||||
self.n_misses = Some(n_misses);
|
||||
|
||||
self
|
||||
@@ -200,7 +201,7 @@ impl<'map> ManiaPP<'map> {
|
||||
///
|
||||
/// This only needs to be specified if the map was converted manually beforehand.
|
||||
#[inline]
|
||||
pub fn is_convert(mut self, is_convert: bool) -> Self {
|
||||
pub const fn is_convert(mut self, is_convert: bool) -> Self {
|
||||
self.is_convert_overwrite = Some(is_convert);
|
||||
|
||||
self
|
||||
@@ -208,7 +209,8 @@ impl<'map> ManiaPP<'map> {
|
||||
|
||||
/// Provide parameters through an [`ManiaScoreState`].
|
||||
#[inline]
|
||||
pub fn state(mut self, state: ManiaScoreState) -> Self {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub const fn state(mut self, state: ManiaScoreState) -> Self {
|
||||
let ManiaScoreState {
|
||||
n320,
|
||||
n300,
|
||||
@@ -229,21 +231,21 @@ impl<'map> ManiaPP<'map> {
|
||||
}
|
||||
|
||||
/// Create the [`ManiaScoreState`] that will be used for performance calculation.
|
||||
#[allow(clippy::too_many_lines, clippy::similar_names)]
|
||||
pub fn generate_state(&mut self) -> ManiaScoreState {
|
||||
let n_objects = match self.passed_objects {
|
||||
Some(passed) => passed,
|
||||
None => {
|
||||
let attrs = match self.map_or_attrs {
|
||||
MapOrElse::Map(ref map) => {
|
||||
let attrs = self.generate_attributes(map);
|
||||
let n_objects = if let Some(passed) = self.passed_objects {
|
||||
passed
|
||||
} else {
|
||||
let attrs = match self.map_or_attrs {
|
||||
MapOrElse::Map(ref map) => {
|
||||
let attrs = self.generate_attributes(map);
|
||||
|
||||
self.map_or_attrs.else_or_insert(attrs)
|
||||
}
|
||||
MapOrElse::Else(ref attrs) => attrs,
|
||||
};
|
||||
self.map_or_attrs.else_or_insert(attrs)
|
||||
}
|
||||
MapOrElse::Else(ref attrs) => attrs,
|
||||
};
|
||||
|
||||
attrs.n_objects
|
||||
}
|
||||
attrs.n_objects
|
||||
};
|
||||
|
||||
let priority = self.hitresult_priority.unwrap_or_default();
|
||||
@@ -274,16 +276,16 @@ impl<'map> ManiaPP<'map> {
|
||||
|
||||
// All but one hitresults given
|
||||
(None, Some(_), Some(_), Some(_), Some(_)) => {
|
||||
n320 = n_objects.saturating_sub(n300 + n200 + n100 + n50 + n_misses)
|
||||
n320 = n_objects.saturating_sub(n300 + n200 + n100 + n50 + n_misses);
|
||||
}
|
||||
(Some(_), None, Some(_), Some(_), Some(_)) => {
|
||||
n300 = n_objects.saturating_sub(n320 + n200 + n100 + n50 + n_misses)
|
||||
n300 = n_objects.saturating_sub(n320 + n200 + n100 + n50 + n_misses);
|
||||
}
|
||||
(Some(_), Some(_), None, Some(_), Some(_)) => {
|
||||
n200 = n_objects.saturating_sub(n320 + n300 + n100 + n50 + n_misses)
|
||||
n200 = n_objects.saturating_sub(n320 + n300 + n100 + n50 + n_misses);
|
||||
}
|
||||
(Some(_), Some(_), Some(_), None, Some(_)) => {
|
||||
n100 = n_objects.saturating_sub(n320 + n300 + n200 + n50 + n_misses)
|
||||
n100 = n_objects.saturating_sub(n320 + n300 + n200 + n50 + n_misses);
|
||||
}
|
||||
(Some(_), Some(_), Some(_), Some(_), None) => {
|
||||
n50 = n_objects.saturating_sub(n320 + n300 + n200 + n100 + n_misses);
|
||||
@@ -855,7 +857,7 @@ impl ManiaPpInner {
|
||||
* (1.0 + 0.1 * (self.total_hits() / 1500.0).min(1.0))
|
||||
}
|
||||
|
||||
fn total_hits(&self) -> f64 {
|
||||
const fn total_hits(&self) -> f64 {
|
||||
self.state.total_hits() as f64
|
||||
}
|
||||
|
||||
@@ -1010,7 +1012,11 @@ mod tests {
|
||||
///
|
||||
/// Very slow but accurate.
|
||||
/// Only slight optimizations have been applied so that it doesn't run unreasonably long.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(
|
||||
clippy::too_many_arguments,
|
||||
clippy::too_many_lines,
|
||||
clippy::similar_names
|
||||
)]
|
||||
fn brute_force_best(
|
||||
acc: f64,
|
||||
n320: Option<usize>,
|
||||
@@ -1033,11 +1039,11 @@ mod tests {
|
||||
|
||||
let n_remaining = N_OBJECTS - n_misses;
|
||||
|
||||
let multiple_given = (n320.is_some() as usize
|
||||
+ n300.is_some() as usize
|
||||
+ n200.is_some() as usize
|
||||
+ n100.is_some() as usize
|
||||
+ n50.is_some() as usize)
|
||||
let multiple_given = (usize::from(n320.is_some())
|
||||
+ usize::from(n300.is_some())
|
||||
+ usize::from(n200.is_some())
|
||||
+ usize::from(n100.is_some())
|
||||
+ usize::from(n50.is_some()))
|
||||
> 1;
|
||||
|
||||
let max_left = N_OBJECTS
|
||||
|
||||
@@ -24,7 +24,7 @@ impl ManiaScoreState {
|
||||
|
||||
/// Return the total amount of hits by adding everything up.
|
||||
#[inline]
|
||||
pub fn total_hits(&self) -> usize {
|
||||
pub const fn total_hits(&self) -> usize {
|
||||
self.n320 + self.n300 + self.n200 + self.n100 + self.n50 + self.n_misses
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::{mania::difficulty_object::ManiaDifficultyObject, util::CompactVec};
|
||||
use super::{previous, Skill, StrainDecaySkill, StrainSkill};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub(crate) struct Strain {
|
||||
start_times: Vec<f64>,
|
||||
end_times: Vec<f64>,
|
||||
@@ -45,7 +46,7 @@ impl Strain {
|
||||
impl Skill for Strain {
|
||||
#[inline]
|
||||
fn process(&mut self, curr: &ManiaDifficultyObject, diff_objects: &[ManiaDifficultyObject]) {
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects)
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -30,7 +30,7 @@ impl<'h> OsuDifficultyObject<'h> {
|
||||
let delta_time = (base.start_time - last.start_time) / clock_rate;
|
||||
|
||||
// * Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||
let strain_time = delta_time.max(Self::MIN_DELTA_TIME as f64);
|
||||
let strain_time = delta_time.max(f64::from(Self::MIN_DELTA_TIME));
|
||||
|
||||
Self {
|
||||
start_time,
|
||||
@@ -94,6 +94,7 @@ impl Distances {
|
||||
///
|
||||
/// By taking in [`Pin<&mut OsuObject>`](Pin), we imply that the argument will be
|
||||
/// modified but it won't be moved.
|
||||
#[allow(clippy::similar_names)]
|
||||
pub(crate) fn new(
|
||||
base: &mut Pin<&mut OsuObject>,
|
||||
last: &OsuObject,
|
||||
@@ -113,11 +114,11 @@ impl Distances {
|
||||
|
||||
Self {
|
||||
// * Bonus for repeat sliders until a better per nested object strain system can be achieved.
|
||||
travel_dist: (lazy_travel_dist
|
||||
* (1.0 + repeat_count as f64 / 2.5).powf(1.0 / 2.5) as f32)
|
||||
as f64,
|
||||
travel_dist: f64::from(
|
||||
lazy_travel_dist * (1.0 + repeat_count as f64 / 2.5).powf(1.0 / 2.5) as f32,
|
||||
),
|
||||
travel_time: (base.lazy_travel_time() / clock_rate)
|
||||
.max(OsuDifficultyObject::MIN_DELTA_TIME as f64),
|
||||
.max(f64::from(OsuDifficultyObject::MIN_DELTA_TIME)),
|
||||
lazy_travel_dist,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -136,17 +137,17 @@ impl Distances {
|
||||
|
||||
let last_cursor_pos = Self::get_end_cursor_pos(last);
|
||||
|
||||
this.lazy_jump_dist = (base.stacked_pos() * scaling_factor
|
||||
- last_cursor_pos * scaling_factor)
|
||||
.length() as f64;
|
||||
this.lazy_jump_dist = f64::from(
|
||||
(base.stacked_pos() * scaling_factor - last_cursor_pos * scaling_factor).length(),
|
||||
);
|
||||
this.min_jump_time = strain_time;
|
||||
this.min_jump_dist = this.lazy_jump_dist;
|
||||
|
||||
if let OsuObjectKind::Slider(slider) = &last.kind {
|
||||
let last_travel_time = (last.lazy_travel_time() / clock_rate)
|
||||
.max(OsuDifficultyObject::MIN_DELTA_TIME as f64);
|
||||
this.min_jump_time =
|
||||
(strain_time - last_travel_time).max(OsuDifficultyObject::MIN_DELTA_TIME as f64);
|
||||
.max(f64::from(OsuDifficultyObject::MIN_DELTA_TIME));
|
||||
this.min_jump_time = (strain_time - last_travel_time)
|
||||
.max(f64::from(OsuDifficultyObject::MIN_DELTA_TIME));
|
||||
|
||||
// * There are two types of slider-to-object patterns to consider in order
|
||||
// * to better approximate the real movement a player will take to jump between the hitobjects.
|
||||
@@ -176,8 +177,8 @@ impl Distances {
|
||||
|
||||
let tail_jump_dist = (stacked_tail_pos - base.stacked_pos()).length() * scaling_factor;
|
||||
|
||||
let diff = (Self::MAXIMUM_SLIDER_RADIUS - Self::ASSUMED_SLIDER_RADIUS) as f64;
|
||||
let min = (tail_jump_dist - Self::MAXIMUM_SLIDER_RADIUS) as f64;
|
||||
let diff = f64::from(Self::MAXIMUM_SLIDER_RADIUS - Self::ASSUMED_SLIDER_RADIUS);
|
||||
let min = f64::from(tail_jump_dist - Self::MAXIMUM_SLIDER_RADIUS);
|
||||
|
||||
// "attributes on expressions are experimental see issue #15701 https://github.com/rust-lang/rust/issues/15701"
|
||||
// rust pls...
|
||||
@@ -192,8 +193,8 @@ impl Distances {
|
||||
let v1 = last_last_cursor_pos - last.stacked_pos();
|
||||
let v2 = base.stacked_pos() - last_cursor_pos;
|
||||
|
||||
let dot = v1.dot(v2) as f64;
|
||||
let det = (v1.x * v2.y - v1.y * v2.x) as f64;
|
||||
let dot = f64::from(v1.dot(v2));
|
||||
let det = f64::from(v1.x * v2.y - v1.y * v2.x);
|
||||
|
||||
this.angle = Some(det.atan2(dot).abs());
|
||||
}
|
||||
@@ -208,16 +209,16 @@ impl Distances {
|
||||
scaling_factor_: &ScalingFactor,
|
||||
) -> f32 {
|
||||
let mut curr_cursor_pos = pos + stack_offset;
|
||||
let scaling_factor = Self::NORMALISED_RADIUS as f64 / scaling_factor_.radius as f64;
|
||||
let scaling_factor = f64::from(Self::NORMALISED_RADIUS) / f64::from(scaling_factor_.radius);
|
||||
|
||||
let mut lazy_travel_dist: f32 = 0.0;
|
||||
|
||||
for (curr_movement_obj, i) in slider.nested_objects.iter().zip(1..) {
|
||||
let mut curr_movement = (curr_movement_obj.pos + stack_offset) - curr_cursor_pos;
|
||||
let mut curr_movement_len = scaling_factor * curr_movement.length() as f64;
|
||||
let mut curr_movement_len = scaling_factor * f64::from(curr_movement.length());
|
||||
|
||||
// * Amount of movement required so that the cursor position needs to be updated.
|
||||
let mut required_movement = Self::ASSUMED_SLIDER_RADIUS as f64;
|
||||
let mut required_movement = f64::from(Self::ASSUMED_SLIDER_RADIUS);
|
||||
|
||||
if i == slider.nested_objects.len() {
|
||||
// * The end of a slider has special aim rules due
|
||||
@@ -234,10 +235,10 @@ impl Distances {
|
||||
curr_movement = lazy_movement;
|
||||
}
|
||||
|
||||
curr_movement_len = scaling_factor * curr_movement.length() as f64;
|
||||
curr_movement_len = scaling_factor * f64::from(curr_movement.length());
|
||||
} else if let NestedObjectKind::Repeat = curr_movement_obj.kind {
|
||||
// * For a slider repeat, assume a tighter movement threshold to better assess repeat sliders.
|
||||
required_movement = Self::NORMALISED_RADIUS as f64;
|
||||
required_movement = f64::from(Self::NORMALISED_RADIUS);
|
||||
}
|
||||
|
||||
if curr_movement_len > required_movement {
|
||||
|
||||
@@ -70,10 +70,13 @@ struct NotClonable;
|
||||
impl Debug for OsuGradualDifficulty {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
f.debug_struct("OsuGradualDifficulty")
|
||||
.field("mods", &self.mods)
|
||||
.field("idx", &self.idx)
|
||||
.field("attrs", &self.attrs)
|
||||
.field("diff_objects", &self.diff_objects)
|
||||
.field("osu_objects", &"...")
|
||||
.field("skills", &self.skills)
|
||||
.field("_not_clonable", &"...")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -86,7 +89,7 @@ impl OsuGradualDifficulty {
|
||||
let scaling_factor = ScalingFactor::new(map_attrs.cs);
|
||||
let hr = mods.hr();
|
||||
let hit_window = 2.0 * map_attrs.hit_windows.od;
|
||||
let time_preempt = (map_attrs.hit_windows.ar * clock_rate) as f32 as f64;
|
||||
let time_preempt = f64::from((map_attrs.hit_windows.ar * clock_rate) as f32);
|
||||
|
||||
// * Preempt time can go below 450ms. Normally, this is achieved via the DT mod
|
||||
// * which uniformly speeds up all animations game wide regardless of AR.
|
||||
@@ -171,7 +174,7 @@ impl OsuGradualDifficulty {
|
||||
let delta_time = (curr.start_time - last.start_time) / clock_rate;
|
||||
|
||||
// * Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||
let strain_time = delta_time.max(OsuDifficultyObject::MIN_DELTA_TIME as f64);
|
||||
let strain_time = delta_time.max(f64::from(OsuDifficultyObject::MIN_DELTA_TIME));
|
||||
|
||||
let dists = Distances::new(
|
||||
&mut curr,
|
||||
@@ -361,7 +364,7 @@ mod osu_objects {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
pub(super) const fn is_empty(&self) -> bool {
|
||||
self.objects.is_empty()
|
||||
}
|
||||
|
||||
|
||||
+18
-16
@@ -58,6 +58,7 @@ const PLAYFIELD_BASE_SIZE: Pos2 = Pos2 { x: 512.0, y: 384.0 };
|
||||
/// println!("Stars: {}", difficulty_attrs.stars);
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct OsuStars<'map> {
|
||||
pub(crate) map: &'map Beatmap,
|
||||
pub(crate) mods: u32,
|
||||
@@ -68,7 +69,7 @@ pub struct OsuStars<'map> {
|
||||
impl<'map> OsuStars<'map> {
|
||||
/// Create a new difficulty calculator for osu!standard maps.
|
||||
#[inline]
|
||||
pub fn new(map: &'map Beatmap) -> Self {
|
||||
pub const fn new(map: &'map Beatmap) -> Self {
|
||||
Self {
|
||||
map,
|
||||
mods: 0,
|
||||
@@ -92,7 +93,7 @@ impl<'map> OsuStars<'map> {
|
||||
///
|
||||
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
|
||||
#[inline]
|
||||
pub fn mods(mut self, mods: u32) -> Self {
|
||||
pub const fn mods(mut self, mods: u32) -> Self {
|
||||
self.mods = mods;
|
||||
|
||||
self
|
||||
@@ -107,7 +108,7 @@ impl<'map> OsuStars<'map> {
|
||||
[`OsuGradualDifficulty`]."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
pub const fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
self.passed_objects = Some(passed_objects);
|
||||
|
||||
self
|
||||
@@ -117,7 +118,7 @@ impl<'map> OsuStars<'map> {
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
pub const fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
@@ -245,6 +246,7 @@ impl OsuStrains {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
let OsuStars {
|
||||
map,
|
||||
@@ -260,7 +262,7 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
let scaling_factor = ScalingFactor::new(map_attrs.cs);
|
||||
let hr = mods.hr();
|
||||
let hit_window = 2.0 * map_attrs.hit_windows.od;
|
||||
let time_preempt = (map_attrs.hit_windows.ar * clock_rate) as f32 as f64;
|
||||
let time_preempt = f64::from((map_attrs.hit_windows.ar * clock_rate) as f32);
|
||||
|
||||
// * Preempt time can go below 450ms. Normally, this is achieved via the DT mod
|
||||
// * which uniformly speeds up all animations game wide regardless of AR.
|
||||
@@ -316,7 +318,7 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
let delta_time = (curr.start_time - last.start_time) / clock_rate;
|
||||
|
||||
// * Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||
let strain_time = delta_time.max(OsuDifficultyObject::MIN_DELTA_TIME as f64);
|
||||
let strain_time = delta_time.max(f64::from(OsuDifficultyObject::MIN_DELTA_TIME));
|
||||
|
||||
let dists = Distances::new(
|
||||
&mut curr,
|
||||
@@ -389,7 +391,7 @@ pub(crate) fn create_osu_objects(
|
||||
hit_objects.extend(iter.map(|h| OsuObject::new(h, &mut params)));
|
||||
}
|
||||
|
||||
let stack_threshold = time_preempt * map.stack_leniency as f64;
|
||||
let stack_threshold = time_preempt * f64::from(map.stack_leniency);
|
||||
|
||||
if map.version >= 6 {
|
||||
stacking(&mut hit_objects, stack_threshold);
|
||||
@@ -443,9 +445,9 @@ fn stacking(hit_objects: &mut [OsuObject], stack_threshold: f64) {
|
||||
|
||||
if hit_objects[n].is_spinner() {
|
||||
continue;
|
||||
} else if hit_objects[obj_i_idx].start_time - hit_objects[n].end_time()
|
||||
> stack_threshold
|
||||
{
|
||||
}
|
||||
|
||||
if hit_objects[obj_i_idx].start_time - hit_objects[n].end_time() > stack_threshold {
|
||||
break; // * We are no longer within stacking range of the previous object.
|
||||
}
|
||||
|
||||
@@ -589,13 +591,13 @@ pub struct OsuDifficultyAttributes {
|
||||
impl OsuDifficultyAttributes {
|
||||
/// Return the maximum combo.
|
||||
#[inline]
|
||||
pub fn max_combo(&self) -> usize {
|
||||
pub const fn max_combo(&self) -> usize {
|
||||
self.max_combo
|
||||
}
|
||||
|
||||
/// Return the amount of hitobjects.
|
||||
#[inline]
|
||||
pub fn n_objects(&self) -> usize {
|
||||
pub const fn n_objects(&self) -> usize {
|
||||
self.n_circles + self.n_sliders + self.n_spinners
|
||||
}
|
||||
|
||||
@@ -628,24 +630,24 @@ pub struct OsuPerformanceAttributes {
|
||||
impl OsuPerformanceAttributes {
|
||||
/// Return the star value.
|
||||
#[inline]
|
||||
pub fn stars(&self) -> f64 {
|
||||
pub const fn stars(&self) -> f64 {
|
||||
self.difficulty.stars
|
||||
}
|
||||
|
||||
/// Return the performance point value.
|
||||
#[inline]
|
||||
pub fn pp(&self) -> f64 {
|
||||
pub const fn pp(&self) -> f64 {
|
||||
self.pp
|
||||
}
|
||||
|
||||
/// Return the maximum combo of the map.
|
||||
#[inline]
|
||||
pub fn max_combo(&self) -> usize {
|
||||
pub const fn max_combo(&self) -> usize {
|
||||
self.difficulty.max_combo
|
||||
}
|
||||
/// Return the amount of hitobjects.
|
||||
#[inline]
|
||||
pub fn n_objects(&self) -> usize {
|
||||
pub const fn n_objects(&self) -> usize {
|
||||
self.difficulty.n_objects()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ impl OsuSlider {
|
||||
/// The amount of repeat points.
|
||||
pub fn repeat_count(&self) -> usize {
|
||||
self.nested_objects.iter().fold(0, |count, nested| {
|
||||
count + matches!(nested.kind, NestedObjectKind::Repeat) as usize
|
||||
count + usize::from(matches!(nested.kind, NestedObjectKind::Repeat))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ pub(crate) struct ObjectParameters<'a> {
|
||||
}
|
||||
|
||||
impl OsuObject {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) fn new(h: &HitObject, params: &mut ObjectParameters<'_>) -> Self {
|
||||
let ObjectParameters {
|
||||
map,
|
||||
@@ -195,6 +196,7 @@ impl OsuObject {
|
||||
|
||||
ticks.clear();
|
||||
|
||||
#[allow(clippy::if_not_else)]
|
||||
let mut nested_objects = if tick_dist != 0.0 {
|
||||
ticks.reserve((len / tick_dist) as usize);
|
||||
let mut nested_objects =
|
||||
@@ -221,7 +223,7 @@ impl OsuObject {
|
||||
|
||||
// Other spans
|
||||
for span_idx in 1..=*repeats {
|
||||
let progress = (span_idx % 2 == 1) as u8 as f64;
|
||||
let progress = f64::from(u8::from(span_idx % 2 == 1));
|
||||
let span_idx_f64 = span_idx as f64;
|
||||
|
||||
// Repeat point
|
||||
@@ -269,7 +271,7 @@ impl OsuObject {
|
||||
let final_span_end_time = (h.start_time + total_duration / 2.0)
|
||||
.max(final_span_start_time + span_duration - LEGACY_LAST_TICK_OFFSET);
|
||||
|
||||
let progress = (*repeats % 2 == 0) as u8 as f64;
|
||||
let progress = f64::from(u8::from(*repeats % 2 == 0));
|
||||
let end_pos = curve.position_at(progress);
|
||||
|
||||
// * we need to use the LegacyLastTick here for compatibility reasons (difficulty).
|
||||
@@ -347,7 +349,7 @@ impl OsuObject {
|
||||
}
|
||||
|
||||
/// Endtime of the object.
|
||||
pub fn end_time(&self) -> f64 {
|
||||
pub const fn end_time(&self) -> f64 {
|
||||
match &self.kind {
|
||||
OsuObjectKind::Circle => self.start_time,
|
||||
OsuObjectKind::Slider(slider) => slider.end_time,
|
||||
@@ -445,7 +447,7 @@ impl OsuObject {
|
||||
}
|
||||
|
||||
/// Applies stack offset, flips playfield for HR,
|
||||
/// and adjusts slider tails and lazy_end_positions.
|
||||
/// and adjusts slider tails and lazy end positions.
|
||||
pub(crate) fn post_process(&mut self, hr: bool, scaling_factor: &ScalingFactor) {
|
||||
self.stack_offset = scaling_factor.stack_offset(self.stack_height);
|
||||
let pos = self.pos();
|
||||
@@ -501,7 +503,7 @@ impl OsuObject {
|
||||
}
|
||||
|
||||
if hr {
|
||||
self.pos.y = PLAYFIELD_BASE_SIZE.y - pos.y
|
||||
self.pos.y = PLAYFIELD_BASE_SIZE.y - pos.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-15
@@ -38,6 +38,7 @@ use crate::{
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[must_use]
|
||||
pub struct OsuPP<'map> {
|
||||
pub(crate) map_or_attrs: MapOrElse<MapRef<'map>, OsuDifficultyAttributes>,
|
||||
pub(crate) mods: u32,
|
||||
@@ -104,7 +105,7 @@ impl<'map> OsuPP<'map> {
|
||||
///
|
||||
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
|
||||
#[inline]
|
||||
pub fn mods(mut self, mods: u32) -> Self {
|
||||
pub const fn mods(mut self, mods: u32) -> Self {
|
||||
self.mods = mods;
|
||||
|
||||
self
|
||||
@@ -112,7 +113,7 @@ impl<'map> OsuPP<'map> {
|
||||
|
||||
/// Specify the max combo of the play.
|
||||
#[inline]
|
||||
pub fn combo(mut self, combo: usize) -> Self {
|
||||
pub const fn combo(mut self, combo: usize) -> Self {
|
||||
self.combo = Some(combo);
|
||||
|
||||
self
|
||||
@@ -122,7 +123,7 @@ impl<'map> OsuPP<'map> {
|
||||
///
|
||||
/// Defauls to [`HitResultPriority::BestCase`].
|
||||
#[inline]
|
||||
pub fn hitresult_priority(mut self, priority: HitResultPriority) -> Self {
|
||||
pub const fn hitresult_priority(mut self, priority: HitResultPriority) -> Self {
|
||||
self.hitresult_priority = Some(priority);
|
||||
|
||||
self
|
||||
@@ -130,7 +131,7 @@ impl<'map> OsuPP<'map> {
|
||||
|
||||
/// Specify the amount of 300s of a play.
|
||||
#[inline]
|
||||
pub fn n300(mut self, n300: usize) -> Self {
|
||||
pub const fn n300(mut self, n300: usize) -> Self {
|
||||
self.n300 = Some(n300);
|
||||
|
||||
self
|
||||
@@ -138,7 +139,7 @@ impl<'map> OsuPP<'map> {
|
||||
|
||||
/// Specify the amount of 100s of a play.
|
||||
#[inline]
|
||||
pub fn n100(mut self, n100: usize) -> Self {
|
||||
pub const fn n100(mut self, n100: usize) -> Self {
|
||||
self.n100 = Some(n100);
|
||||
|
||||
self
|
||||
@@ -146,7 +147,7 @@ impl<'map> OsuPP<'map> {
|
||||
|
||||
/// Specify the amount of 50s of a play.
|
||||
#[inline]
|
||||
pub fn n50(mut self, n50: usize) -> Self {
|
||||
pub const fn n50(mut self, n50: usize) -> Self {
|
||||
self.n50 = Some(n50);
|
||||
|
||||
self
|
||||
@@ -154,7 +155,7 @@ impl<'map> OsuPP<'map> {
|
||||
|
||||
/// Specify the amount of misses of a play.
|
||||
#[inline]
|
||||
pub fn n_misses(mut self, n_misses: usize) -> Self {
|
||||
pub const fn n_misses(mut self, n_misses: usize) -> Self {
|
||||
self.n_misses = Some(n_misses);
|
||||
|
||||
self
|
||||
@@ -169,7 +170,7 @@ impl<'map> OsuPP<'map> {
|
||||
[`OsuGradualPerformanceAttributes`](crate::osu::OsuGradualPerformance)."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
pub const fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
self.passed_objects = Some(passed_objects);
|
||||
|
||||
self
|
||||
@@ -179,7 +180,7 @@ impl<'map> OsuPP<'map> {
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
pub const fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
@@ -187,7 +188,8 @@ impl<'map> OsuPP<'map> {
|
||||
|
||||
/// Provide parameters through an [`OsuScoreState`].
|
||||
#[inline]
|
||||
pub fn state(mut self, state: OsuScoreState) -> Self {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub const fn state(mut self, state: OsuScoreState) -> Self {
|
||||
let OsuScoreState {
|
||||
max_combo,
|
||||
n300,
|
||||
@@ -215,6 +217,7 @@ impl<'map> OsuPP<'map> {
|
||||
}
|
||||
|
||||
/// Create the [`OsuScoreState`] that will be used for performance calculation.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn generate_state(&mut self) -> OsuScoreState {
|
||||
let attrs = match self.map_or_attrs {
|
||||
MapOrElse::Map(ref map) => {
|
||||
@@ -527,7 +530,7 @@ impl OsuPpInner {
|
||||
|
||||
let len_bonus = 0.95
|
||||
+ 0.4 * (total_hits / 2000.0).min(1.0)
|
||||
+ (total_hits > 2000.0) as u8 as f64 * (total_hits / 2000.0).log10() * 0.5;
|
||||
+ f64::from(u8::from(total_hits > 2000.0)) * (total_hits / 2000.0).log10() * 0.5;
|
||||
|
||||
aim_value *= len_bonus;
|
||||
|
||||
@@ -594,7 +597,7 @@ impl OsuPpInner {
|
||||
|
||||
let len_bonus = 0.95
|
||||
+ 0.4 * (total_hits / 2000.0).min(1.0)
|
||||
+ (total_hits > 2000.0) as u8 as f64 * (total_hits / 2000.0).log10() * 0.5;
|
||||
+ f64::from(u8::from(total_hits > 2000.0)) * (total_hits / 2000.0).log10() * 0.5;
|
||||
|
||||
speed_value *= len_bonus;
|
||||
|
||||
@@ -646,7 +649,7 @@ impl OsuPpInner {
|
||||
|
||||
// * Scale the speed value with # of 50s to punish doubletapping.
|
||||
speed_value *= 0.99_f64.powf(
|
||||
(self.state.n50 as f64 >= total_hits / 500.0) as u8 as f64
|
||||
f64::from(u8::from(self.state.n50 as f64 >= total_hits / 500.0))
|
||||
* (self.state.n50 as f64 - total_hits / 500.0),
|
||||
);
|
||||
|
||||
@@ -718,7 +721,9 @@ impl OsuPpInner {
|
||||
// * Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius.
|
||||
flashlight_value *= 0.7
|
||||
+ 0.1 * (total_hits / 200.0).min(1.0)
|
||||
+ (total_hits > 200.0) as u8 as f64 * 0.2 * ((total_hits - 200.0) / 200.0).min(1.0);
|
||||
+ f64::from(u8::from(total_hits > 200.0))
|
||||
* 0.2
|
||||
* ((total_hits - 200.0) / 200.0).min(1.0);
|
||||
|
||||
// * Scale the flashlight value with accuracy _slightly_.
|
||||
flashlight_value *= 0.5 + self.acc / 2.0;
|
||||
@@ -737,7 +742,7 @@ impl OsuPpInner {
|
||||
}
|
||||
}
|
||||
|
||||
fn total_hits(&self) -> f64 {
|
||||
const fn total_hits(&self) -> f64 {
|
||||
self.state.total_hits() as f64
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ impl OsuScoreState {
|
||||
|
||||
/// Return the total amount of hits by adding everything up.
|
||||
#[inline]
|
||||
pub fn total_hits(&self) -> usize {
|
||||
pub const fn total_hits(&self) -> usize {
|
||||
self.n300 + self.n100 + self.n50 + self.n_misses
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Skill for Aim {
|
||||
curr: &OsuDifficultyObject<'_>,
|
||||
diff_objects: &[OsuDifficultyObject<'_>],
|
||||
) {
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects)
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -110,13 +110,10 @@ impl AimEvaluator {
|
||||
) -> f64 {
|
||||
let osu_curr_obj = curr;
|
||||
|
||||
let (osu_last_last_obj, osu_last_obj) = if let Some(tuple) =
|
||||
previous(diff_objects, curr.idx, 1)
|
||||
.zip(previous(diff_objects, curr.idx, 0))
|
||||
.filter(|(_, last)| !(curr.base.is_spinner() || last.base.is_spinner()))
|
||||
{
|
||||
tuple
|
||||
} else {
|
||||
let Some((osu_last_last_obj, osu_last_obj)) = previous(diff_objects, curr.idx, 1)
|
||||
.zip(previous(diff_objects, curr.idx, 0))
|
||||
.filter(|(_, last)| !(curr.base.is_spinner() || last.base.is_spinner()))
|
||||
else {
|
||||
return 0.0;
|
||||
};
|
||||
|
||||
@@ -234,7 +231,7 @@ impl AimEvaluator {
|
||||
|
||||
if osu_last_obj.base.is_slider() {
|
||||
// * Reward sliders based on velocity.
|
||||
slider_bonus = osu_last_obj.dists.travel_dist / osu_last_obj.dists.travel_time
|
||||
slider_bonus = osu_last_obj.dists.travel_dist / osu_last_obj.dists.travel_time;
|
||||
}
|
||||
|
||||
// * Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger.
|
||||
|
||||
@@ -29,7 +29,7 @@ impl Flashlight {
|
||||
curr_section_end: 0.0,
|
||||
strain_peaks: CompactVec::new(),
|
||||
has_hidden_mod: mods.hd(),
|
||||
scaling_factor: 52.0 / radius as f64,
|
||||
scaling_factor: 52.0 / f64::from(radius),
|
||||
time_preempt,
|
||||
time_fade_in,
|
||||
}
|
||||
@@ -47,7 +47,7 @@ impl Skill for Flashlight {
|
||||
curr: &OsuDifficultyObject<'_>,
|
||||
diff_objects: &[OsuDifficultyObject<'_>],
|
||||
) {
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects)
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -155,8 +155,9 @@ impl FlashlightEvaluator {
|
||||
let curr_hit_obj = curr_obj.base;
|
||||
|
||||
if !curr_obj.base.is_spinner() {
|
||||
let jump_dist =
|
||||
(osu_hit_obj.stacked_pos() - curr_hit_obj.stacked_end_pos()).length() as f64;
|
||||
let jump_dist = f64::from(
|
||||
(osu_hit_obj.stacked_pos() - curr_hit_obj.stacked_end_pos()).length(),
|
||||
);
|
||||
cumulative_strain_time += last_obj.strain_time;
|
||||
|
||||
// * We want to nerf objects that can be easily seen within the Flashlight circle radius.
|
||||
@@ -210,7 +211,7 @@ impl FlashlightEvaluator {
|
||||
|
||||
if let OsuObjectKind::Slider(slider) = &osu_curr.base.kind {
|
||||
// * Invert the scaling factor to determine the true travel distance independent of circle size.
|
||||
let pixel_travel_dist = osu_curr.dists.lazy_travel_dist as f64 / scaling_factor;
|
||||
let pixel_travel_dist = f64::from(osu_curr.dists.lazy_travel_dist) / scaling_factor;
|
||||
|
||||
// * Reward sliders based on velocity.
|
||||
slider_bonus = ((pixel_travel_dist / osu_curr.dists.travel_time - Self::MIN_VELOCITY)
|
||||
|
||||
+13
-14
@@ -56,7 +56,7 @@ impl Skill for Speed {
|
||||
curr: &OsuDifficultyObject<'_>,
|
||||
diff_objects: &[OsuDifficultyObject<'_>],
|
||||
) {
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects)
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -210,7 +210,7 @@ impl RhythmEvaluator {
|
||||
while previous(diff_objects, curr.idx, rhythm_start)
|
||||
.filter(|prev| {
|
||||
rhythm_start + 2 < historical_note_count
|
||||
&& curr.start_time - prev.start_time < Self::HISTORY_TIME_MAX as f64
|
||||
&& curr.start_time - prev.start_time < f64::from(Self::HISTORY_TIME_MAX)
|
||||
})
|
||||
.is_some()
|
||||
{
|
||||
@@ -218,20 +218,17 @@ impl RhythmEvaluator {
|
||||
}
|
||||
|
||||
for i in (1..=rhythm_start).rev() {
|
||||
let (curr_obj, prev_obj, last_obj) = if let Some(((curr, prev), last)) =
|
||||
previous(diff_objects, curr.idx, i - 1)
|
||||
.zip(previous(diff_objects, curr.idx, i))
|
||||
.zip(previous(diff_objects, curr.idx, i + 1))
|
||||
{
|
||||
(curr, prev, last)
|
||||
} else {
|
||||
let Some(((curr_obj, prev_obj), last_obj)) = previous(diff_objects, curr.idx, i - 1)
|
||||
.zip(previous(diff_objects, curr.idx, i))
|
||||
.zip(previous(diff_objects, curr.idx, i + 1))
|
||||
else {
|
||||
break;
|
||||
};
|
||||
|
||||
// * scales note 0 to 1 from history to now
|
||||
let mut curr_historical_decay = (Self::HISTORY_TIME_MAX as f64
|
||||
let mut curr_historical_decay = (f64::from(Self::HISTORY_TIME_MAX)
|
||||
- (curr.start_time - curr_obj.start_time))
|
||||
/ Self::HISTORY_TIME_MAX as f64;
|
||||
/ f64::from(Self::HISTORY_TIME_MAX);
|
||||
|
||||
// * either we're limited by time or limited by object count.
|
||||
curr_historical_decay = curr_historical_decay
|
||||
@@ -245,7 +242,7 @@ impl RhythmEvaluator {
|
||||
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 hit_window = !curr_obj.base.is_spinner() as u64 as f64 * hit_window;
|
||||
let hit_window = u64::from(!curr_obj.base.is_spinner()) as f64 * hit_window;
|
||||
|
||||
let mut window_penalty = ((((prev_delta - curr_delta).abs() - hit_window * 0.3)
|
||||
.max(0.0))
|
||||
@@ -257,6 +254,8 @@ impl RhythmEvaluator {
|
||||
let mut effective_ratio = window_penalty * curr_ratio;
|
||||
|
||||
if first_delta_switch {
|
||||
// Keep in-sync with lazer
|
||||
#[allow(clippy::if_not_else)]
|
||||
if !(prev_delta > 1.25 * curr_delta || prev_delta * 1.25 < curr_delta) {
|
||||
if island_size < 7 {
|
||||
// * island is still progressing, count size.
|
||||
@@ -290,9 +289,9 @@ impl RhythmEvaluator {
|
||||
|
||||
rhythm_complexity_sum += (effective_ratio * start_ratio).sqrt()
|
||||
* curr_historical_decay
|
||||
* ((4 + island_size) as f64).sqrt()
|
||||
* f64::from(4 + island_size).sqrt()
|
||||
/ 2.0
|
||||
* ((4 + prev_island_size) as f64).sqrt()
|
||||
* f64::from(4 + prev_island_size).sqrt()
|
||||
/ 2.0;
|
||||
|
||||
start_ratio = effective_ratio;
|
||||
|
||||
@@ -125,7 +125,8 @@ pub(crate) trait OsuStrainSkill: StrainSkill + Sized {
|
||||
|
||||
// * We are reducing the highest strains first to account for extreme difficulty spikes
|
||||
for (i, strain) in peak_iter.enumerate() {
|
||||
let clamped = (i as f32 / Self::REDUCED_SECTION_COUNT as f32).clamp(0.0, 1.0) as f64;
|
||||
let clamped =
|
||||
f64::from((i as f32 / Self::REDUCED_SECTION_COUNT as f32).clamp(0.0, 1.0));
|
||||
let scale = (lerp(1.0, 10.0, clamped)).log10();
|
||||
*strain *= lerp(Self::REDUCED_STRAIN_BASELINE, 1.0, scale);
|
||||
}
|
||||
|
||||
@@ -16,31 +16,29 @@ pub struct HitObject {
|
||||
|
||||
impl HitObject {
|
||||
/// The end time of the object.
|
||||
pub(crate) fn end_time(&self) -> f64 {
|
||||
pub(crate) const fn end_time(&self) -> f64 {
|
||||
match &self.kind {
|
||||
HitObjectKind::Circle => self.start_time,
|
||||
// incorrect, only called in mania which has no sliders though
|
||||
HitObjectKind::Slider { .. } => self.start_time,
|
||||
HitObjectKind::Spinner { end_time } => *end_time,
|
||||
HitObjectKind::Hold { end_time, .. } => *end_time,
|
||||
// incorrect for sliders, only called in mania which has nono though
|
||||
HitObjectKind::Circle | HitObjectKind::Slider { .. } => self.start_time,
|
||||
HitObjectKind::Spinner { end_time } | HitObjectKind::Hold { end_time, .. } => *end_time,
|
||||
}
|
||||
}
|
||||
|
||||
/// If the object is a circle.
|
||||
#[inline]
|
||||
pub fn is_circle(&self) -> bool {
|
||||
pub const fn is_circle(&self) -> bool {
|
||||
matches!(self.kind, HitObjectKind::Circle)
|
||||
}
|
||||
|
||||
/// If the object is a slider.
|
||||
#[inline]
|
||||
pub fn is_slider(&self) -> bool {
|
||||
pub const fn is_slider(&self) -> bool {
|
||||
matches!(self.kind, HitObjectKind::Slider { .. })
|
||||
}
|
||||
|
||||
/// If the object is a spinner.
|
||||
#[inline]
|
||||
pub fn is_spinner(&self) -> bool {
|
||||
pub const fn is_spinner(&self) -> bool {
|
||||
matches!(self.kind, HitObjectKind::Spinner { .. })
|
||||
}
|
||||
}
|
||||
|
||||
+20
-25
@@ -115,9 +115,8 @@ macro_rules! parse_general_body {
|
||||
break;
|
||||
}
|
||||
|
||||
let (key, value) = match $reader.split_colon() {
|
||||
Some(tuple) => tuple,
|
||||
None => continue,
|
||||
let Some((key, value)) = $reader.split_colon() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if key == b"Mode" {
|
||||
@@ -162,9 +161,8 @@ macro_rules! parse_difficulty_body {
|
||||
break;
|
||||
}
|
||||
|
||||
let (key, value) = match $reader.split_colon() {
|
||||
Some(tuple) => tuple,
|
||||
None => continue,
|
||||
let Some((key, value)) = $reader.split_colon() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match key {
|
||||
@@ -268,9 +266,8 @@ macro_rules! parse_timingpoints_body {
|
||||
let line = $reader.get_line()?;
|
||||
let mut split = line.split(',');
|
||||
|
||||
let time = match split.next().map(str::trim).and_then(f64::parse_in_range) {
|
||||
Some(time) => time,
|
||||
None => continue,
|
||||
let Some(time) = split.next().map(str::trim).and_then(f64::parse_in_range) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// * beatLength is allowed to be NaN to handle an edge case in which
|
||||
@@ -353,7 +350,7 @@ macro_rules! parse_timingpoints_body {
|
||||
1.0
|
||||
};
|
||||
|
||||
if time != pending_diff_points_time {
|
||||
if (time - pending_diff_points_time).abs() >= f64::EPSILON {
|
||||
if let Some(point) = pending_diff_point.take() {
|
||||
$self.difficulty_points.push(point);
|
||||
}
|
||||
@@ -425,23 +422,20 @@ macro_rules! parse_hitobjects_body {
|
||||
continue 'next_line;
|
||||
};
|
||||
|
||||
let time = match split.next().map(str::trim).and_then(f64::parse_in_range) {
|
||||
Some(time) => time,
|
||||
None => continue 'next_line,
|
||||
let Some(time) = split.next().map(str::trim).and_then(f64::parse_in_range) else {
|
||||
continue 'next_line;
|
||||
};
|
||||
|
||||
if !$self.hit_objects.is_empty() && time < prev_time {
|
||||
unsorted = true;
|
||||
}
|
||||
|
||||
let kind = match split.next().map(str::parse::<u8>) {
|
||||
Some(Ok(kind)) => kind,
|
||||
_ => continue 'next_line,
|
||||
let Some(Ok(kind)) = split.next().map(str::parse::<u8>) else {
|
||||
continue 'next_line;
|
||||
};
|
||||
|
||||
let mut sound = match split.next().map(str::parse::<u8>) {
|
||||
Some(Ok(sound)) => sound,
|
||||
_ => continue 'next_line,
|
||||
let Some(Ok(mut sound)) = split.next().map(str::parse::<u8>) else {
|
||||
continue 'next_line;
|
||||
};
|
||||
|
||||
fn has_custom_sound_file(bank_info: Option<&str>) -> Option<bool> {
|
||||
@@ -502,8 +496,9 @@ macro_rules! parse_hitobjects_body {
|
||||
let mut first = true;
|
||||
|
||||
// SAFETY: `Vec<(usize, usize)>` and `Vec<&str>` have the same size and layout.
|
||||
let point_split: &mut Vec<&str> =
|
||||
unsafe { std::mem::transmute(&mut point_split_raw) };
|
||||
let point_split = unsafe {
|
||||
&mut *((&mut point_split_raw as *mut Vec<(usize, usize)>).cast::<Vec<&str>>())
|
||||
};
|
||||
|
||||
point_split.clear();
|
||||
point_split.extend(control_point_iter);
|
||||
@@ -765,9 +760,9 @@ mod slider_parsing {
|
||||
) -> ConvertStatus {
|
||||
let mut path_kind = PathType::from_str(points[0]);
|
||||
|
||||
let read_offset = first as usize;
|
||||
let read_offset = usize::from(first);
|
||||
let readable_points = points.len() - 1;
|
||||
let end_point_len = end_point.is_some() as usize;
|
||||
let end_point_len = usize::from(end_point.is_some());
|
||||
|
||||
vertices.clear();
|
||||
vertices.reserve(read_offset + readable_points + end_point_len);
|
||||
@@ -862,7 +857,7 @@ mod slider_parsing {
|
||||
pub(super) fn read_point(value: &str, start_pos: Pos2) -> Option<PathControlPoint> {
|
||||
let mut v = value
|
||||
.split(':')
|
||||
.flat_map(|s| f64::parse_in_custom_range(s, MAX_COORDINATE_VALUE as f64))
|
||||
.filter_map(|s| f64::parse_in_custom_range(s, f64::from(MAX_COORDINATE_VALUE)))
|
||||
.map(|n| n as i32 as f32);
|
||||
|
||||
v.next()
|
||||
@@ -1056,7 +1051,7 @@ enum Section {
|
||||
}
|
||||
|
||||
impl Section {
|
||||
fn from_bytes(bytes: &[u8]) -> Self {
|
||||
const fn from_bytes(bytes: &[u8]) -> Self {
|
||||
match bytes {
|
||||
b"General" => Self::General,
|
||||
b"Difficulty" => Self::Difficulty,
|
||||
|
||||
+3
-2
@@ -3,6 +3,7 @@ use std::ops;
|
||||
|
||||
/// Simple (x, y) coordinate / vector
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
#[must_use]
|
||||
pub struct Pos2 {
|
||||
/// Position on the x-axis.
|
||||
pub x: f32,
|
||||
@@ -19,7 +20,7 @@ impl Pos2 {
|
||||
|
||||
/// Return a position with both coordinates on the given value.
|
||||
#[inline]
|
||||
pub fn new(value: f32) -> Self {
|
||||
pub const fn new(value: f32) -> Self {
|
||||
Self { x: value, y: value }
|
||||
}
|
||||
|
||||
@@ -32,7 +33,7 @@ impl Pos2 {
|
||||
/// Return the position's length.
|
||||
#[inline]
|
||||
pub fn length(&self) -> f32 {
|
||||
((self.x * self.x + self.y * self.y) as f64).sqrt() as f32
|
||||
(f64::from(self.x * self.x + self.y * self.y)).sqrt() as f32
|
||||
}
|
||||
|
||||
/// Return the dot product.
|
||||
|
||||
+6
-6
@@ -118,8 +118,9 @@ impl<R> FileReader<R> {
|
||||
if self.buf.len() >= 3 {
|
||||
// truncate one `0` that was left after truncating `\n` when reading the line.
|
||||
// additionally truncate `0\r` if possible.
|
||||
sub += 1 + 2
|
||||
* (self.buf.len() >= 4 && self.buf[self.buf.len() - 2] == b'\r') as usize;
|
||||
sub += 1 + 2 * usize::from(
|
||||
self.buf.len() >= 4 && self.buf[self.buf.len() - 2] == b'\r',
|
||||
);
|
||||
}
|
||||
|
||||
self.buf.rotate_left(1);
|
||||
@@ -159,7 +160,7 @@ impl<R> FileReader<R> {
|
||||
.ok_or(ParseError::IncorrectFileHeader)
|
||||
}
|
||||
|
||||
/// Returns the bytes inbetween '[' and ']'.
|
||||
/// Returns the bytes inbetween `[` and `]`.
|
||||
pub(crate) fn get_section(&self) -> Option<&[u8]> {
|
||||
if self.buf[0] == b'[' {
|
||||
if let Some(end) = self.buf[1..].iter().position(|&byte| byte == b']') {
|
||||
@@ -191,9 +192,8 @@ impl<R> FileReader<R> {
|
||||
///
|
||||
/// Returns `None` if the second half is invalid UTF-8.
|
||||
pub(crate) fn split_colon(&self) -> Option<(&[u8], &str)> {
|
||||
let idx = match self.buf.iter().position(|&byte| byte == b':') {
|
||||
Some(idx) => idx,
|
||||
None => return Some((&self.buf, "")),
|
||||
let Some(idx) = self.buf.iter().position(|&byte| byte == b':') else {
|
||||
return Some((&self.buf, ""));
|
||||
};
|
||||
|
||||
let back = std::str::from_utf8(&self.buf[idx + 1..]).ok()?;
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use std::cmp::Ordering;
|
||||
|
||||
const QUICK_SORT_DEPTH_THRESHOLD: usize = 32;
|
||||
|
||||
/// Algorithm from https://github.com/ppy/osu/blob/master/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs#L21
|
||||
/// Algorithm from <https://github.com/ppy/osu/blob/master/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs#L21>
|
||||
pub(crate) fn legacy_sort(keys: &mut [HitObject]) {
|
||||
if keys.is_empty() {
|
||||
return;
|
||||
|
||||
@@ -37,6 +37,7 @@ use crate::{
|
||||
/// ```
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub enum AnyPP<'map> {
|
||||
/// osu!standard performance calculator
|
||||
Osu(OsuPP<'map>),
|
||||
@@ -266,8 +267,7 @@ impl<'map> AnyPP<'map> {
|
||||
#[inline]
|
||||
pub fn n_katu(self, n_katu: usize) -> Self {
|
||||
match self {
|
||||
Self::Osu(_) => self,
|
||||
Self::Taiko(_) => self,
|
||||
Self::Osu(_) | Self::Taiko(_) => self,
|
||||
Self::Catch(f) => Self::Catch(f.tiny_droplet_misses(n_katu)),
|
||||
Self::Mania(m) => Self::Mania(m.n200(n_katu)),
|
||||
}
|
||||
@@ -280,9 +280,7 @@ impl<'map> AnyPP<'map> {
|
||||
#[inline]
|
||||
pub fn n_geki(self, n_geki: usize) -> Self {
|
||||
match self {
|
||||
Self::Osu(_) => self,
|
||||
Self::Taiko(_) => self,
|
||||
Self::Catch(_) => self,
|
||||
Self::Osu(_) | Self::Taiko(_) | Self::Catch(_) => self,
|
||||
Self::Mania(m) => Self::Mania(m.n320(n_geki)),
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ impl ScoreState {
|
||||
|
||||
if mode != GameMode::Osu {
|
||||
amount += self.n_katu;
|
||||
amount += (mode != GameMode::Catch) as usize * self.n_geki;
|
||||
amount += usize::from(mode != GameMode::Catch) * self.n_geki;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::{
|
||||
/// println!("Stars: {}", difficulty_attrs.stars());
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub enum AnyStars<'map> {
|
||||
/// osu!standard difficulty calculator
|
||||
Osu(OsuStars<'map>),
|
||||
|
||||
@@ -80,7 +80,7 @@ impl ColourDifficultyPreprocessor {
|
||||
|
||||
fn encode(data: &mut ObjectLists) -> Vec<Rc<RefCell<RepeatingHitPatterns>>> {
|
||||
let mono_streaks = Self::encode_mono_streak(data);
|
||||
let alternating_mono_patterns = Self::encode_alternating_mono_pattern(mono_streaks);
|
||||
let alternating_mono_patterns = Self::encode_alternating_mono_pattern(&mono_streaks);
|
||||
|
||||
Self::encode_repeating_hit_pattern(alternating_mono_patterns)
|
||||
}
|
||||
@@ -120,7 +120,7 @@ impl ColourDifficultyPreprocessor {
|
||||
}
|
||||
|
||||
fn encode_alternating_mono_pattern(
|
||||
data: Vec<Rc<RefCell<MonoStreak>>>,
|
||||
data: &[Rc<RefCell<MonoStreak>>],
|
||||
) -> VecDeque<Rc<RefCell<AlternatingMonoPattern>>> {
|
||||
let mut mono_patterns = VecDeque::new();
|
||||
mono_patterns.push_back(AlternatingMonoPattern::new());
|
||||
|
||||
@@ -109,7 +109,7 @@ impl TaikoGradualDifficulty {
|
||||
let mut diff_objects = ObjectLists::with_capacity(map.hit_objects.len().saturating_sub(2));
|
||||
|
||||
map.taiko_objects()
|
||||
.inspect(|(h, _)| total_hits += h.is_hit as usize)
|
||||
.inspect(|(h, _)| total_hits += usize::from(h.is_hit))
|
||||
.skip(2)
|
||||
.zip(map.hit_objects.iter().skip(1))
|
||||
.zip(map.hit_objects.iter())
|
||||
@@ -240,10 +240,8 @@ impl Iterator for TaikoGradualDifficulty {
|
||||
self.idx += 1;
|
||||
|
||||
match self.first_combos {
|
||||
FirstTwoCombos::None => {}
|
||||
FirstTwoCombos::OnlyFirst => self.attrs.max_combo = 1,
|
||||
FirstTwoCombos::OnlySecond => {}
|
||||
FirstTwoCombos::Both => self.attrs.max_combo = 1,
|
||||
FirstTwoCombos::None | FirstTwoCombos::OnlySecond => {}
|
||||
FirstTwoCombos::OnlyFirst | FirstTwoCombos::Both => self.attrs.max_combo = 1,
|
||||
}
|
||||
}
|
||||
(_, 0) => {
|
||||
@@ -252,8 +250,9 @@ impl Iterator for TaikoGradualDifficulty {
|
||||
|
||||
match self.first_combos {
|
||||
FirstTwoCombos::None => {}
|
||||
FirstTwoCombos::OnlyFirst => self.attrs.max_combo = 1,
|
||||
FirstTwoCombos::OnlySecond => self.attrs.max_combo = 1,
|
||||
FirstTwoCombos::OnlyFirst | FirstTwoCombos::OnlySecond => {
|
||||
self.attrs.max_combo = 1;
|
||||
}
|
||||
FirstTwoCombos::Both => self.attrs.max_combo = 2,
|
||||
}
|
||||
}
|
||||
@@ -263,8 +262,9 @@ impl Iterator for TaikoGradualDifficulty {
|
||||
|
||||
match self.first_combos {
|
||||
FirstTwoCombos::None => {}
|
||||
FirstTwoCombos::OnlyFirst => self.attrs.max_combo = 1,
|
||||
FirstTwoCombos::OnlySecond => self.attrs.max_combo = 1,
|
||||
FirstTwoCombos::OnlyFirst | FirstTwoCombos::OnlySecond => {
|
||||
self.attrs.max_combo = 1;
|
||||
}
|
||||
FirstTwoCombos::Both => self.attrs.max_combo = 2,
|
||||
}
|
||||
}
|
||||
|
||||
+14
-13
@@ -54,6 +54,7 @@ const DIFFICULTY_MULTIPLIER: f64 = 1.35;
|
||||
/// println!("Stars: {}", difficulty_attrs.stars);
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct TaikoStars<'map> {
|
||||
map: Cow<'map, Beatmap>,
|
||||
mods: u32,
|
||||
@@ -82,7 +83,7 @@ impl<'map> TaikoStars<'map> {
|
||||
///
|
||||
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
|
||||
#[inline]
|
||||
pub fn mods(mut self, mods: u32) -> Self {
|
||||
pub const fn mods(mut self, mods: u32) -> Self {
|
||||
self.mods = mods;
|
||||
|
||||
self
|
||||
@@ -97,7 +98,7 @@ impl<'map> TaikoStars<'map> {
|
||||
[`TaikoGradualDifficultyAttributes`](crate::taiko::TaikoGradualDifficulty)."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
pub const fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
self.passed_objects = Some(passed_objects);
|
||||
|
||||
self
|
||||
@@ -107,7 +108,7 @@ impl<'map> TaikoStars<'map> {
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
pub const fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
@@ -117,7 +118,7 @@ impl<'map> TaikoStars<'map> {
|
||||
///
|
||||
/// This only needs to be specified if the map was converted manually beforehand.
|
||||
#[inline]
|
||||
pub fn is_convert(mut self, is_convert: bool) -> Self {
|
||||
pub const fn is_convert(mut self, is_convert: bool) -> Self {
|
||||
self.is_convert = is_convert;
|
||||
|
||||
self
|
||||
@@ -242,8 +243,8 @@ fn calculate_skills(params: TaikoStars<'_>) -> (Peaks, usize) {
|
||||
|
||||
map.taiko_objects()
|
||||
.inspect(|(h, _)| {
|
||||
n_diff_objects += (max_combo < take) as usize;
|
||||
max_combo += (max_combo < take && h.is_hit) as usize;
|
||||
n_diff_objects += usize::from(max_combo < take);
|
||||
max_combo += usize::from(max_combo < take && h.is_hit);
|
||||
})
|
||||
.skip(2)
|
||||
.zip(map.hit_objects.iter().skip(1))
|
||||
@@ -276,7 +277,7 @@ fn calculate_skills(params: TaikoStars<'_>) -> (Peaks, usize) {
|
||||
map.hit_objects
|
||||
.iter()
|
||||
.take(2)
|
||||
.for_each(|h| n_diff_objects = n_diff_objects.saturating_sub(h.is_circle() as usize));
|
||||
.for_each(|h| n_diff_objects = n_diff_objects.saturating_sub(usize::from(h.is_circle())));
|
||||
|
||||
ColourDifficultyPreprocessor::process_and_assign(&mut diff_objects);
|
||||
|
||||
@@ -320,13 +321,13 @@ pub struct TaikoDifficultyAttributes {
|
||||
impl TaikoDifficultyAttributes {
|
||||
/// Return the maximum combo.
|
||||
#[inline]
|
||||
pub fn max_combo(&self) -> usize {
|
||||
pub const fn max_combo(&self) -> usize {
|
||||
self.max_combo
|
||||
}
|
||||
|
||||
/// Whether the [`Beatmap`] was a convert i.e. an osu!standard map.
|
||||
#[inline]
|
||||
pub fn is_convert(&self) -> bool {
|
||||
pub const fn is_convert(&self) -> bool {
|
||||
self.is_convert
|
||||
}
|
||||
|
||||
@@ -355,25 +356,25 @@ pub struct TaikoPerformanceAttributes {
|
||||
impl TaikoPerformanceAttributes {
|
||||
/// Return the star value.
|
||||
#[inline]
|
||||
pub fn stars(&self) -> f64 {
|
||||
pub const fn stars(&self) -> f64 {
|
||||
self.difficulty.stars
|
||||
}
|
||||
|
||||
/// Return the performance point value.
|
||||
#[inline]
|
||||
pub fn pp(&self) -> f64 {
|
||||
pub const fn pp(&self) -> f64 {
|
||||
self.pp
|
||||
}
|
||||
|
||||
/// Return the maximum combo of the map.
|
||||
#[inline]
|
||||
pub fn max_combo(&self) -> usize {
|
||||
pub const fn max_combo(&self) -> usize {
|
||||
self.difficulty.max_combo
|
||||
}
|
||||
|
||||
/// Whether the [`Beatmap`] was a convert i.e. an osu!standard map.
|
||||
#[inline]
|
||||
pub fn is_convert(&self) -> bool {
|
||||
pub const fn is_convert(&self) -> bool {
|
||||
self.difficulty.is_convert
|
||||
}
|
||||
}
|
||||
|
||||
+14
-12
@@ -37,6 +37,7 @@ use crate::{
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[must_use]
|
||||
pub struct TaikoPP<'map> {
|
||||
pub(crate) map_or_attrs: MapOrElse<Cow<'map, Beatmap>, TaikoDifficultyAttributes>,
|
||||
is_convert_overwrite: Option<bool>,
|
||||
@@ -89,7 +90,7 @@ impl<'map> TaikoPP<'map> {
|
||||
///
|
||||
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
|
||||
#[inline]
|
||||
pub fn mods(mut self, mods: u32) -> Self {
|
||||
pub const fn mods(mut self, mods: u32) -> Self {
|
||||
self.mods = mods;
|
||||
|
||||
self
|
||||
@@ -97,7 +98,7 @@ impl<'map> TaikoPP<'map> {
|
||||
|
||||
/// Specify the max combo of the play.
|
||||
#[inline]
|
||||
pub fn combo(mut self, combo: usize) -> Self {
|
||||
pub const fn combo(mut self, combo: usize) -> Self {
|
||||
self.combo = Some(combo);
|
||||
|
||||
self
|
||||
@@ -107,7 +108,7 @@ impl<'map> TaikoPP<'map> {
|
||||
///
|
||||
/// Defauls to [`HitResultPriority::BestCase`].
|
||||
#[inline]
|
||||
pub fn hitresult_priority(mut self, priority: HitResultPriority) -> Self {
|
||||
pub const fn hitresult_priority(mut self, priority: HitResultPriority) -> Self {
|
||||
self.hitresult_priority = Some(priority);
|
||||
|
||||
self
|
||||
@@ -115,7 +116,7 @@ impl<'map> TaikoPP<'map> {
|
||||
|
||||
/// Specify the amount of 300s of a play.
|
||||
#[inline]
|
||||
pub fn n300(mut self, n300: usize) -> Self {
|
||||
pub const fn n300(mut self, n300: usize) -> Self {
|
||||
self.n300 = Some(n300);
|
||||
|
||||
self
|
||||
@@ -123,7 +124,7 @@ impl<'map> TaikoPP<'map> {
|
||||
|
||||
/// Specify the amount of 100s of a play.
|
||||
#[inline]
|
||||
pub fn n100(mut self, n100: usize) -> Self {
|
||||
pub const fn n100(mut self, n100: usize) -> Self {
|
||||
self.n100 = Some(n100);
|
||||
|
||||
self
|
||||
@@ -131,7 +132,7 @@ impl<'map> TaikoPP<'map> {
|
||||
|
||||
/// Specify the amount of misses of the play.
|
||||
#[inline]
|
||||
pub fn n_misses(mut self, n_misses: usize) -> Self {
|
||||
pub const fn n_misses(mut self, n_misses: usize) -> Self {
|
||||
self.n_misses = Some(n_misses);
|
||||
|
||||
self
|
||||
@@ -155,7 +156,7 @@ impl<'map> TaikoPP<'map> {
|
||||
[`TaikoGradualPerformanceAttributes`](crate::taiko::TaikoGradualPerformance)."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
pub const fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
self.passed_objects = Some(passed_objects);
|
||||
|
||||
self
|
||||
@@ -165,7 +166,7 @@ impl<'map> TaikoPP<'map> {
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
pub const fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
@@ -175,7 +176,7 @@ impl<'map> TaikoPP<'map> {
|
||||
///
|
||||
/// This only needs to be specified if the map was converted manually beforehand.
|
||||
#[inline]
|
||||
pub fn is_convert(mut self, is_convert: bool) -> Self {
|
||||
pub const fn is_convert(mut self, is_convert: bool) -> Self {
|
||||
self.is_convert_overwrite = Some(is_convert);
|
||||
|
||||
self
|
||||
@@ -183,7 +184,8 @@ impl<'map> TaikoPP<'map> {
|
||||
|
||||
/// Provide parameters through a [`TaikoScoreState`].
|
||||
#[inline]
|
||||
pub fn state(mut self, state: TaikoScoreState) -> Self {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub const fn state(mut self, state: TaikoScoreState) -> Self {
|
||||
let TaikoScoreState {
|
||||
max_combo,
|
||||
n300,
|
||||
@@ -483,11 +485,11 @@ impl TaikoPpInner {
|
||||
acc_value
|
||||
}
|
||||
|
||||
fn total_hits(&self) -> f64 {
|
||||
const fn total_hits(&self) -> f64 {
|
||||
self.state.total_hits() as f64
|
||||
}
|
||||
|
||||
fn total_successful_hits(&self) -> usize {
|
||||
const fn total_successful_hits(&self) -> usize {
|
||||
self.state.n300 + self.state.n100
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ impl TaikoScoreState {
|
||||
|
||||
/// Return the total amount of hits by adding everything up.
|
||||
#[inline]
|
||||
pub fn total_hits(&self) -> usize {
|
||||
pub const fn total_hits(&self) -> usize {
|
||||
self.n300 + self.n100 + self.n_misses
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ impl Colour {
|
||||
impl Skill for Colour {
|
||||
#[inline]
|
||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||
<Self as StrainSkill>::process(self, curr, hit_objects)
|
||||
<Self as StrainSkill>::process(self, curr, hit_objects);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -100,20 +100,21 @@ impl ColourEvaluator {
|
||||
sigmoid * (height / 2.0) + middle
|
||||
}
|
||||
|
||||
fn evaluate_diff_of_mono_streak(mono_streak: Rc<RefCell<MonoStreak>>) -> f64 {
|
||||
fn evaluate_diff_of_mono_streak(mono_streak: &Rc<RefCell<MonoStreak>>) -> f64 {
|
||||
let mono_streak = mono_streak.borrow();
|
||||
|
||||
let parent_eval = mono_streak
|
||||
.parent
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.as_ref()
|
||||
.map_or(1.0, Self::evaluate_diff_of_alternating_mono_pattern);
|
||||
|
||||
Self::sigmoid(mono_streak.idx as f64, 2.0, 2.0, 0.5, 1.0) * parent_eval * 0.5
|
||||
}
|
||||
|
||||
fn evaluate_diff_of_alternating_mono_pattern(
|
||||
alternating_mono_pattern: Rc<RefCell<AlternatingMonoPattern>>,
|
||||
alternating_mono_pattern: &Rc<RefCell<AlternatingMonoPattern>>,
|
||||
) -> f64 {
|
||||
let alternating_mono_pattern = alternating_mono_pattern.borrow();
|
||||
|
||||
@@ -121,13 +122,14 @@ impl ColourEvaluator {
|
||||
.parent
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.as_ref()
|
||||
.map_or(1.0, Self::evaluate_diff_of_repeating_hit_patterns);
|
||||
|
||||
Self::sigmoid(alternating_mono_pattern.idx as f64, 2.0, 2.0, 0.5, 1.0) * parent_eval
|
||||
}
|
||||
|
||||
fn evaluate_diff_of_repeating_hit_patterns(
|
||||
repeating_hit_patterns: Rc<RefCell<RepeatingHitPatterns>>,
|
||||
repeating_hit_patterns: &Rc<RefCell<RepeatingHitPatterns>>,
|
||||
) -> f64 {
|
||||
let repetition_interval = repeating_hit_patterns.borrow().repetition_interval as f64;
|
||||
|
||||
@@ -140,7 +142,7 @@ impl ColourEvaluator {
|
||||
|
||||
// * Difficulty for MonoStreak
|
||||
if let Some(mono_streak) = colour.mono_streak.as_ref().and_then(Weak::upgrade) {
|
||||
difficulty += Self::evaluate_diff_of_mono_streak(mono_streak);
|
||||
difficulty += Self::evaluate_diff_of_mono_streak(&mono_streak);
|
||||
}
|
||||
|
||||
// * Difficulty for AlternatingMonoPattern
|
||||
@@ -149,12 +151,11 @@ impl ColourEvaluator {
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
{
|
||||
difficulty += Self::evaluate_diff_of_alternating_mono_pattern(alternating_mono_pattern);
|
||||
difficulty += Self::evaluate_diff_of_alternating_mono_pattern(&alternating_mono_pattern);
|
||||
}
|
||||
|
||||
// * Difficulty for RepeatingHitPattern
|
||||
if let Some(repeating_hit_patterns) = colour.repeating_hit_patterns.as_ref().map(Rc::clone)
|
||||
{
|
||||
if let Some(repeating_hit_patterns) = colour.repeating_hit_patterns.as_ref() {
|
||||
difficulty += Self::evaluate_diff_of_repeating_hit_patterns(repeating_hit_patterns);
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ impl Skill for Peaks {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub(crate) struct PeaksDifficultyValues {
|
||||
pub(crate) colour_rating: f64,
|
||||
pub(crate) rhythm_rating: f64,
|
||||
|
||||
@@ -102,7 +102,7 @@ impl Rhythm {
|
||||
impl Skill for Rhythm {
|
||||
#[inline]
|
||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||
<Self as StrainSkill>::process(self, curr, hit_objects)
|
||||
<Self as StrainSkill>::process(self, curr, hit_objects);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -194,7 +194,7 @@ pub(crate) struct HistoryElement {
|
||||
}
|
||||
|
||||
impl HistoryElement {
|
||||
fn new(difficulty_object: &TaikoDifficultyObject) -> Self {
|
||||
const fn new(difficulty_object: &TaikoDifficultyObject) -> Self {
|
||||
Self {
|
||||
idx: difficulty_object.idx,
|
||||
rhythm: difficulty_object.rhythm,
|
||||
|
||||
@@ -27,7 +27,7 @@ impl Stamina {
|
||||
impl Skill for Stamina {
|
||||
#[inline]
|
||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||
<Self as StrainSkill>::process(self, curr, hit_objects)
|
||||
<Self as StrainSkill>::process(self, curr, hit_objects);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -19,12 +19,12 @@ pub(crate) struct ByteHash {
|
||||
impl Hasher for ByteHash {
|
||||
#[inline]
|
||||
fn finish(&self) -> u64 {
|
||||
self.byte as u64
|
||||
u64::from(self.byte)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write(&mut self, _: &[u8]) {
|
||||
unreachable!()
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -17,7 +17,7 @@ impl CompactVec {
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, num: f64) {
|
||||
self.push_n(num, 1)
|
||||
self.push_n(num, 1);
|
||||
}
|
||||
|
||||
pub(crate) fn push_n(&mut self, num: f64, n: usize) {
|
||||
@@ -28,7 +28,7 @@ impl CompactVec {
|
||||
{
|
||||
last.count += n;
|
||||
} else if n > 0 {
|
||||
self.inner.push(Entry::new(num, n))
|
||||
self.inner.push(Entry::new(num, n));
|
||||
}
|
||||
|
||||
self.len += n;
|
||||
@@ -38,7 +38,7 @@ impl CompactVec {
|
||||
where
|
||||
F: FnMut(f64) -> bool,
|
||||
{
|
||||
self.inner.retain(|entry| f(entry.value))
|
||||
self.inner.retain(|entry| f(entry.value));
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> Iter<'_> {
|
||||
@@ -69,7 +69,7 @@ struct Entry {
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
fn new(value: f64, count: usize) -> Self {
|
||||
const fn new(value: f64, count: usize) -> Self {
|
||||
Self { value, count }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,18 +51,18 @@ impl<T, const N: usize> LimitedQueue<T, N> {
|
||||
pub(crate) fn push(&mut self, elem: T) {
|
||||
self.end = (self.end + 1) % N;
|
||||
self.queue[self.end] = elem;
|
||||
self.len += (self.len < N) as usize;
|
||||
self.len += usize::from(self.len < N);
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
pub(crate) const fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
pub(crate) const fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
pub(crate) fn last(&self) -> Option<&T> {
|
||||
pub(crate) const fn last(&self) -> Option<&T> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -74,7 +74,7 @@ impl<T, const N: usize> LimitedQueue<T, N> {
|
||||
self.queue
|
||||
.iter()
|
||||
.cycle()
|
||||
.skip((self.len == N) as usize * (self.end + 1))
|
||||
.skip(usize::from(self.len == N) * (self.end + 1))
|
||||
.take(self.len)
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ impl<T, const N: usize> Index<usize> for LimitedQueue<T, N> {
|
||||
self.len
|
||||
);
|
||||
|
||||
let idx = (idx + (self.len == N) as usize * (self.end + 1)) % N;
|
||||
let idx = (idx + usize::from(self.len == N) * (self.end + 1)) % N;
|
||||
|
||||
&self.queue[idx]
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ impl<'map, E> From<&'map Beatmap> for MapOrElse<MapRef<'map>, E> {
|
||||
pub(crate) struct MapRef<'map>(&'map Beatmap);
|
||||
|
||||
impl<'map> MapRef<'map> {
|
||||
pub(crate) fn into_inner(self) -> &'map Beatmap {
|
||||
pub(crate) const fn into_inner(self) -> &'map Beatmap {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +62,13 @@ impl TandemSorter {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn idx_is_marked(idx: usize) -> bool {
|
||||
const fn idx_is_marked(idx: usize) -> bool {
|
||||
// Check if first bit is set
|
||||
idx.leading_zeros() == 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn toggle_mark_idx(idx: usize) -> usize {
|
||||
const fn toggle_mark_idx(idx: usize) -> usize {
|
||||
// Flip the first bit
|
||||
idx ^ !(usize::MAX >> 1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user