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