feat!: adjust osu!standard hitresult generation

This commit is contained in:
MaxOhn
2024-10-14 00:47:38 +02:00
parent f646cba538
commit f72ff79b4a
11 changed files with 297 additions and 32 deletions
@@ -12,3 +12,5 @@ cc e5a861f6c665dd09e46423e71d7596edf98897d4130d3144aa6f5be580f31a8b # shrinks to
cc 2cd5c105bcca0b4255afccc15bee3894b06bd20ac3f5c5d3b785f7e0ef99df46 # shrinks to acc = 0.0, combo = None, n300 = Some(0), n100 = None, n50 = Some(479), n_misses = Some(123), best_case = false
cc 2cba8a76243aac7233e9207a3162aaa1f08f933c0cb3a2ac79580ece3a7329fc # shrinks to acc = 0.0, n300 = Some(0), n100 = Some(0), n50 = Some(0), n_misses = None, best_case = false
cc e93787ad8a849ec6d05750c8d09494b8f5a9fa785f843d9a8e2db986c0b32645 # shrinks to acc = 0.0, n300 = None, n100 = None, n50 = None, n_misses = Some(602), best_case = false
cc a53cb48861126aa63be54606f9a770db5eae95242c9a9d75cf1fd101cfb21729 # shrinks to lazer = true, acc = 0.5679586776392227, n_slider_ticks = None, n_slider_ends = None, n300 = None, n100 = None, n50 = Some(0), n_misses = None, best_case = false
cc cacb94cb2a61cf05e7083e332b378290a6267a499bf30821228bc0ae4dfe46f6 # shrinks to lazer = true, acc = 0.5270982297689498, n_slider_ticks = None, n_slider_ends = None, n300 = Some(70), n100 = None, n50 = None, n_misses = None, best_case = false
+20
View File
@@ -15,6 +15,14 @@ pub struct ScoreState {
///
/// Irrelevant for osu!mania.
pub max_combo: u32,
/// Amount of successfully hit slider ticks and repeats.
///
/// Only relevant for osu!standard in lazer.
pub slider_tick_hits: u32,
/// Amount of successfully hit slider ends.
///
/// Only relevant for osu!standard in lazer.
pub slider_end_hits: u32,
/// Amount of current gekis (n320 for osu!mania).
pub n_geki: u32,
/// Amount of current katus (tiny droplet misses for osu!catch / n200 for
@@ -35,6 +43,8 @@ impl ScoreState {
pub const fn new() -> Self {
Self {
max_combo: 0,
slider_tick_hits: 0,
slider_end_hits: 0,
n_geki: 0,
n_katu: 0,
n300: 0,
@@ -66,6 +76,8 @@ impl From<ScoreState> for OsuScoreState {
fn from(state: ScoreState) -> Self {
Self {
max_combo: state.max_combo,
slider_tick_hits: state.slider_tick_hits,
slider_end_hits: state.slider_end_hits,
n300: state.n300,
n100: state.n100,
n50: state.n50,
@@ -115,6 +127,8 @@ impl From<OsuScoreState> for ScoreState {
fn from(state: OsuScoreState) -> Self {
Self {
max_combo: state.max_combo,
slider_tick_hits: state.slider_tick_hits,
slider_end_hits: state.slider_end_hits,
n_geki: 0,
n_katu: 0,
n300: state.n300,
@@ -129,6 +143,8 @@ impl From<TaikoScoreState> for ScoreState {
fn from(state: TaikoScoreState) -> Self {
Self {
max_combo: state.max_combo,
slider_tick_hits: 0,
slider_end_hits: 0,
n_geki: 0,
n_katu: 0,
n300: state.n300,
@@ -143,6 +159,8 @@ impl From<CatchScoreState> for ScoreState {
fn from(state: CatchScoreState) -> Self {
Self {
max_combo: state.max_combo,
slider_tick_hits: 0,
slider_end_hits: 0,
n_geki: 0,
n_katu: state.tiny_droplet_misses,
n300: state.fruits,
@@ -157,6 +175,8 @@ impl From<ManiaScoreState> for ScoreState {
fn from(state: ManiaScoreState) -> Self {
Self {
max_combo: 0,
slider_tick_hits: 0,
slider_end_hits: 0,
n_geki: state.n320,
n_katu: state.n200,
n300: state.n300,
+2
View File
@@ -474,6 +474,8 @@ impl<'map> TryFrom<OsuPerformance<'map>> for CatchPerformance<'map> {
difficulty,
acc,
combo,
slider_tick_hits: _,
slider_end_hits: _,
n300,
n100,
n50,
+2
View File
@@ -827,6 +827,8 @@ impl<'map> TryFrom<OsuPerformance<'map>> for ManiaPerformance<'map> {
difficulty,
acc,
combo: _,
slider_tick_hits: _,
slider_end_hits: _,
n300,
n100,
n50,
+2
View File
@@ -27,6 +27,8 @@ pub struct OsuDifficultyAttributes {
pub n_circles: u32,
/// The amount of sliders.
pub n_sliders: u32,
/// The amount of slider ticks and repeat points.
pub n_slider_ticks: u32,
/// The amount of spinners.
pub n_spinners: u32,
/// The final star rating
+11
View File
@@ -56,6 +56,17 @@ pub fn convert_objects(
OsuObjectKind::Slider(ref slider) => {
attrs.n_sliders += 1;
attrs.max_combo += slider.nested_objects.len() as u32;
attrs.n_slider_ticks += slider
.nested_objects
.iter()
.filter(|nested| {
matches!(
nested.kind,
NestedSliderObjectKind::Tick | NestedSliderObjectKind::Repeat
)
})
.count() as u32;
}
OsuObjectKind::Spinner(_) => attrs.n_spinners += 1,
}
+209 -23
View File
@@ -28,6 +28,8 @@ pub struct OsuPerformance<'map> {
pub(crate) difficulty: Difficulty,
pub(crate) acc: Option<f64>,
pub(crate) combo: Option<u32>,
pub(crate) slider_tick_hits: Option<u32>,
pub(crate) slider_end_hits: Option<u32>,
pub(crate) n300: Option<u32>,
pub(crate) n100: Option<u32>,
pub(crate) n50: Option<u32>,
@@ -166,6 +168,24 @@ impl<'map> OsuPerformance<'map> {
self
}
/// Specify the amount of hit slider ticks.
///
/// Only relevant for osu!lazer.
pub const fn n_slider_ticks(mut self, n_slider_ticks: u32) -> Self {
self.slider_tick_hits = Some(n_slider_ticks);
self
}
/// Specify the amount of hit slider ends.
///
/// Only relevant for osu!lazer.
pub const fn n_slider_ends(mut self, n_slider_ends: u32) -> Self {
self.slider_end_hits = Some(n_slider_ends);
self
}
/// Specify the amount of 300s of a play.
pub const fn n300(mut self, n300: u32) -> Self {
self.n300 = Some(n300);
@@ -293,6 +313,8 @@ impl<'map> OsuPerformance<'map> {
pub const fn state(mut self, state: OsuScoreState) -> Self {
let OsuScoreState {
max_combo,
slider_tick_hits,
slider_end_hits,
n300,
n100,
n50,
@@ -300,6 +322,8 @@ impl<'map> OsuPerformance<'map> {
} = state;
self.combo = Some(max_combo);
self.slider_tick_hits = Some(slider_tick_hits);
self.slider_end_hits = Some(slider_end_hits);
self.n300 = Some(n300);
self.n100 = Some(n100);
self.n50 = Some(n50);
@@ -342,8 +366,29 @@ impl<'map> OsuPerformance<'map> {
let mut n100 = self.n100.map_or(0, |n| cmp::min(n, n_remaining));
let mut n50 = self.n50.map_or(0, |n| cmp::min(n, n_remaining));
let lazer = self.lazer.unwrap_or(true);
let (n_slider_ends, n_slider_ticks, max_slider_ends, max_slider_ticks) = if lazer {
let n_slider_ends = self
.slider_end_hits
.map_or(attrs.n_sliders, |n| cmp::min(n, attrs.n_sliders));
let n_slider_ticks = self
.slider_tick_hits
.map_or(attrs.n_slider_ticks, |n| cmp::min(n, attrs.n_slider_ticks));
(
n_slider_ends,
n_slider_ticks,
attrs.n_sliders,
attrs.n_slider_ticks,
)
} else {
(0, 0, 0, 0)
};
if let Some(acc) = self.acc {
let target_total = acc * f64::from(6 * n_objects);
let target_total =
acc * f64::from(30 * n_objects + 15 * max_slider_ends + 3 * max_slider_ticks);
match (self.n300, self.n100, self.n50) {
(Some(_), Some(_), Some(_)) => {
@@ -363,13 +408,28 @@ impl<'map> OsuPerformance<'map> {
n300 = cmp::min(n300, n_remaining);
let n_remaining = n_remaining - n300;
let raw_n100 = target_total - f64::from(n_remaining + 6 * n300);
let raw_n100 = (target_total
- f64::from(
5 * n_remaining + 30 * n300 + 15 * n_slider_ends + 3 * n_slider_ticks,
))
/ 5.0;
let min_n100 = cmp::min(n_remaining, raw_n100.floor() as u32);
let max_n100 = cmp::min(n_remaining, raw_n100.ceil() as u32);
for new100 in min_n100..=max_n100 {
let new50 = n_remaining - new100;
let dist = (acc - accuracy(n300, new100, new50, misses)).abs();
let dist = (acc
- accuracy(
n_slider_ticks,
n_slider_ends,
n300,
new100,
new50,
misses,
max_slider_ticks,
max_slider_ends,
))
.abs();
if dist < best_dist {
best_dist = dist;
@@ -384,13 +444,28 @@ impl<'map> OsuPerformance<'map> {
n100 = cmp::min(n100, n_remaining);
let n_remaining = n_remaining - n100;
let raw_n300 = (target_total - f64::from(n_remaining + 2 * n100)) / 5.0;
let raw_n300 = (target_total
- f64::from(
5 * n_remaining + 10 * n100 + 15 * n_slider_ends + 3 * n_slider_ticks,
))
/ 25.0;
let min_n300 = cmp::min(n_remaining, raw_n300.floor() as u32);
let max_n300 = cmp::min(n_remaining, raw_n300.ceil() as u32);
for new300 in min_n300..=max_n300 {
let new50 = n_remaining - new300;
let curr_dist = (acc - accuracy(new300, n100, new50, misses)).abs();
let curr_dist = (acc
- accuracy(
n_slider_ticks,
n_slider_ends,
new300,
n100,
new50,
misses,
max_slider_ticks,
max_slider_ends,
))
.abs();
if curr_dist < best_dist {
best_dist = curr_dist;
@@ -405,16 +480,27 @@ impl<'map> OsuPerformance<'map> {
n50 = cmp::min(n50, n_remaining);
let n_remaining = n_remaining - n50;
let raw_n300 = (target_total + f64::from(2 * misses + n50)
- f64::from(2 * n_objects))
/ 4.0;
let raw_n300 = (target_total + f64::from(10 * misses + 5 * n50)
- f64::from(10 * n_objects + 15 * n_slider_ends + 3 * n_slider_ticks))
/ 20.0;
let min_n300 = cmp::min(n_remaining, raw_n300.floor() as u32);
let max_n300 = cmp::min(n_remaining, raw_n300.ceil() as u32);
for new300 in min_n300..=max_n300 {
let new100 = n_remaining - new300;
let curr_dist = (acc - accuracy(new300, new100, n50, misses)).abs();
let curr_dist = (acc
- accuracy(
n_slider_ticks,
n_slider_ends,
new300,
new100,
n50,
misses,
max_slider_ticks,
max_slider_ends,
))
.abs();
if curr_dist < best_dist {
best_dist = curr_dist;
@@ -426,18 +512,38 @@ impl<'map> OsuPerformance<'map> {
(None, None, None) => {
let mut best_dist = f64::MAX;
let raw_n300 = (target_total - f64::from(n_remaining)) / 5.0;
let raw_n300 = (target_total
- f64::from(5 * n_remaining + 15 * n_slider_ends + 3 * n_slider_ticks))
/ 25.0;
let min_n300 = cmp::min(n_remaining, raw_n300.floor() as u32);
let max_n300 = cmp::min(n_remaining, raw_n300.ceil() as u32);
for new300 in min_n300..=max_n300 {
let raw_n100 = target_total - f64::from(n_remaining + 5 * new300);
let raw_n100 = (target_total
- f64::from(
5 * n_remaining
+ 25 * new300
+ 15 * n_slider_ends
+ 3 * n_slider_ticks,
))
/ 5.0;
let min_n100 = cmp::min(raw_n100.floor() as u32, n_remaining - new300);
let max_n100 = cmp::min(raw_n100.ceil() as u32, n_remaining - new300);
for new100 in min_n100..=max_n100 {
let new50 = n_remaining - new300 - new100;
let curr_dist = (acc - accuracy(new300, new100, new50, misses)).abs();
let curr_dist = (acc
- accuracy(
n_slider_ticks,
n_slider_ends,
new300,
new100,
new50,
misses,
max_slider_ticks,
max_slider_ends,
))
.abs();
if curr_dist < best_dist {
best_dist = curr_dist;
@@ -492,6 +598,8 @@ impl<'map> OsuPerformance<'map> {
});
self.combo = Some(max_combo);
self.slider_end_hits = Some(n_slider_ends);
self.slider_tick_hits = Some(n_slider_ticks);
self.n300 = Some(n300);
self.n100 = Some(n100);
self.n50 = Some(n50);
@@ -499,6 +607,8 @@ impl<'map> OsuPerformance<'map> {
OsuScoreState {
max_combo,
slider_tick_hits: n_slider_ticks,
slider_end_hits: n_slider_ends,
n300,
n100,
n50,
@@ -517,13 +627,21 @@ impl<'map> OsuPerformance<'map> {
let effective_miss_count = calculate_effective_misses(&attrs, &state);
let lazer = self.lazer.unwrap_or(true);
let (n_slider_ends, n_slider_ticks) = if lazer {
(attrs.n_sliders, attrs.n_slider_ticks)
} else {
(0, 0)
};
let inner = OsuPerformanceInner {
attrs,
mods: self.difficulty.get_mods(),
acc: state.accuracy(),
acc: state.accuracy(n_slider_ticks, n_slider_ends),
state,
effective_miss_count,
lazer: self.lazer.unwrap_or(true),
lazer,
};
inner.calculate()
@@ -535,6 +653,8 @@ impl<'map> OsuPerformance<'map> {
difficulty: Difficulty::new(),
acc: None,
combo: None,
slider_tick_hits: None,
slider_end_hits: None,
n300: None,
n100: None,
n50: None,
@@ -894,13 +1014,24 @@ fn calculate_effective_misses(attrs: &OsuDifficultyAttributes, state: &OsuScoreS
combo_based_miss_count.max(f64::from(state.misses))
}
fn accuracy(n300: u32, n100: u32, n50: u32, misses: u32) -> f64 {
if n300 + n100 + n50 + misses == 0 {
fn accuracy(
n_slider_ticks: u32,
n_slider_ends: u32,
n300: u32,
n100: u32,
n50: u32,
misses: u32,
max_slider_ticks: u32,
max_slider_ends: u32,
) -> f64 {
if n_slider_ticks + n_slider_ends + n300 + n100 + n50 + misses == 0 {
return 0.0;
}
let numerator = 6 * n300 + 2 * n100 + n50;
let denominator = 6 * (n300 + n100 + n50 + misses);
let numerator = 300 * n300 + 100 * n100 + 50 * n50 + 150 * n_slider_ends + 30 * n_slider_ticks;
let denominator =
300 * (n300 + n100 + n50 + misses) + 150 * max_slider_ends + 30 * max_slider_ticks;
f64::from(numerator) / f64::from(denominator)
}
@@ -922,6 +1053,8 @@ mod test {
static ATTRS: OnceLock<OsuDifficultyAttributes> = OnceLock::new();
const N_OBJECTS: u32 = 601;
const N_SLIDERS: u32 = 293;
const N_SLIDER_TICKS: u32 = 15;
fn beatmap() -> Beatmap {
Beatmap::from_path("./resources/2785319.osu").unwrap()
@@ -941,6 +1074,8 @@ mod test {
attrs.n_circles + attrs.n_sliders + attrs.n_spinners,
N_OBJECTS,
);
assert_eq!(attrs.n_sliders, N_SLIDERS);
assert_eq!(attrs.n_slider_ticks, N_SLIDER_TICKS);
attrs
})
@@ -952,7 +1087,10 @@ mod test {
///
/// Very slow but accurate.
fn brute_force_best(
lazer: bool,
acc: f64,
n_slider_ticks: Option<u32>,
n_slider_ends: Option<u32>,
n300: Option<u32>,
n100: Option<u32>,
n50: Option<u32>,
@@ -961,8 +1099,20 @@ mod test {
) -> OsuScoreState {
let misses = cmp::min(misses, N_OBJECTS);
let (n_slider_ends, n_slider_ticks, max_slider_ends, max_slider_ticks) = if lazer {
let n_slider_ends = n_slider_ends.map_or(N_SLIDERS, |n| cmp::min(n, N_SLIDERS));
let n_slider_ticks =
n_slider_ticks.map_or(N_SLIDER_TICKS, |n| cmp::min(n, N_SLIDER_TICKS));
(n_slider_ends, n_slider_ticks, N_SLIDERS, N_SLIDER_TICKS)
} else {
(0, 0, 0, 0)
};
let mut best_state = OsuScoreState {
misses,
slider_end_hits: n_slider_ends,
slider_tick_hits: n_slider_ticks,
..Default::default()
};
@@ -998,7 +1148,16 @@ mod test {
None => n_remaining.saturating_sub(new300 + new100),
};
let curr_acc = accuracy(new300, new100, new50, misses);
let curr_acc = accuracy(
n_slider_ticks,
n_slider_ends,
new300,
new100,
new50,
misses,
max_slider_ticks,
max_slider_ends,
);
let curr_dist = (acc - curr_acc).abs();
if curr_dist < best_dist {
@@ -1042,10 +1201,13 @@ mod test {
#[test]
fn hitresults(
acc in 0.0..=1.0,
n300 in prop::option::weighted(0.10, 0_u32..=N_OBJECTS + 10),
n100 in prop::option::weighted(0.10, 0_u32..=N_OBJECTS + 10),
n50 in prop::option::weighted(0.10, 0_u32..=N_OBJECTS + 10),
lazer in prop::bool::ANY,
acc in 0.0_f64..=1.0,
n_slider_ticks in prop::option::weighted(0.1, 0_u32..=N_SLIDER_TICKS + 10),
n_slider_ends in prop::option::weighted(0.1, 0_u32..=N_SLIDERS + 10),
n300 in prop::option::weighted(0.1, 0_u32..=N_OBJECTS + 10),
n100 in prop::option::weighted(0.1, 0_u32..=N_OBJECTS + 10),
n50 in prop::option::weighted(0.1, 0_u32..=N_OBJECTS + 10),
n_misses in prop::option::weighted(0.15, 0_u32..=N_OBJECTS + 10),
best_case in prop::bool::ANY,
) {
@@ -1060,8 +1222,17 @@ mod test {
let mut state = OsuPerformance::from(attrs)
.accuracy(acc * 100.0)
.lazer(lazer)
.hitresult_priority(priority);
if let Some(n_slider_ticks) = n_slider_ticks {
state = state.n_slider_ticks(n_slider_ticks);
}
if let Some(n_slider_ends) = n_slider_ends {
state = state.n_slider_ends(n_slider_ends);
}
if let Some(n300) = n300 {
state = state.n300(n300);
}
@@ -1083,7 +1254,10 @@ mod test {
assert_eq!(first, state);
let mut expected = brute_force_best(
lazer,
acc,
n_slider_ticks,
n_slider_ends,
n300,
n100,
n50,
@@ -1100,6 +1274,7 @@ mod test {
fn hitresults_n300_n100_misses_best() {
let state = OsuPerformance::from(attrs())
.combo(500)
.lazer(true)
.n300(300)
.n100(20)
.misses(2)
@@ -1108,6 +1283,8 @@ mod test {
let expected = OsuScoreState {
max_combo: 500,
slider_tick_hits: N_SLIDER_TICKS,
slider_end_hits: N_SLIDERS,
n300: 300,
n100: 20,
n50: 279,
@@ -1120,6 +1297,7 @@ mod test {
#[test]
fn hitresults_n300_n50_misses_best() {
let state = OsuPerformance::from(attrs())
.lazer(false)
.combo(500)
.n300(300)
.n50(10)
@@ -1129,6 +1307,8 @@ mod test {
let expected = OsuScoreState {
max_combo: 500,
slider_tick_hits: 0,
slider_end_hits: 0,
n300: 300,
n100: 289,
n50: 10,
@@ -1141,6 +1321,7 @@ mod test {
#[test]
fn hitresults_n50_misses_worst() {
let state = OsuPerformance::from(attrs())
.lazer(true)
.combo(500)
.n50(10)
.misses(2)
@@ -1149,6 +1330,8 @@ mod test {
let expected = OsuScoreState {
max_combo: 500,
slider_tick_hits: N_SLIDER_TICKS,
slider_end_hits: N_SLIDERS,
n300: 0,
n100: 589,
n50: 10,
@@ -1161,6 +1344,7 @@ mod test {
#[test]
fn hitresults_n300_n100_n50_misses_worst() {
let state = OsuPerformance::from(attrs())
.lazer(false)
.combo(500)
.n300(300)
.n100(50)
@@ -1171,6 +1355,8 @@ mod test {
let expected = OsuScoreState {
max_combo: 500,
slider_tick_hits: 0,
slider_end_hits: 0,
n300: 300,
n100: 50,
n50: 249,
+38 -8
View File
@@ -1,9 +1,17 @@
/// Aggregation for a score's current state.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct OsuScoreState {
/// Maximum combo that the score has had so far.
/// **Not** the maximum possible combo of the map so far.
/// Maximum combo that the score has had so far. **Not** the maximum
/// possible combo of the map so far.
pub max_combo: u32,
/// Amount of successfully hit slider ticks and repeat.
///
/// Only relevant for osu!lazer.
pub slider_tick_hits: u32,
/// Amount of successfully hit slider ends.
///
/// Only relevant for osu!lazer.
pub slider_end_hits: u32,
/// Amount of current 300s.
pub n300: u32,
/// Amount of current 100s.
@@ -19,6 +27,8 @@ impl OsuScoreState {
pub const fn new() -> Self {
Self {
max_combo: 0,
slider_tick_hits: 0,
slider_end_hits: 0,
n300: 0,
n100: 0,
n50: 0,
@@ -32,15 +42,35 @@ impl OsuScoreState {
}
/// Calculate the accuracy between `0.0` and `1.0` for this state.
pub fn accuracy(&self) -> f64 {
let total_hits = self.total_hits();
if total_hits == 0 {
///
/// `max_slider_ticks` and `max_slider_ends` are only relevant for
/// `osu!lazer` scores. Otherwise, they may be `0`.
pub fn accuracy(&self, max_slider_ticks: u32, max_slider_ends: u32) -> f64 {
if self.total_hits() + self.slider_tick_hits + self.slider_end_hits == 0 {
return 0.0;
}
let numerator = 6 * self.n300 + 2 * self.n100 + self.n50;
let denominator = 6 * total_hits;
debug_assert!(
self.slider_end_hits <= max_slider_ends,
"`self.slider_end_hits` must not be greater than `max_slider_ends`"
);
debug_assert!(
self.slider_tick_hits <= max_slider_ticks,
"`self.slider_tick_hits` must not be greater than `max_slider_ticks`"
);
let numerator = 300 * self.n300
+ 100 * self.n100
+ 50 * self.n50
+ 150 * self.slider_end_hits
+ 30 * self.slider_tick_hits;
let denominator = 300 * self.n300
+ 300 * self.n100
+ 300 * self.n50
+ 300 * self.misses
+ 150 * max_slider_ends
+ 30 * max_slider_ticks;
f64::from(numerator) / f64::from(denominator)
}
+2
View File
@@ -364,6 +364,8 @@ impl<'map> TryFrom<OsuPerformance<'map>> for TaikoPerformance<'map> {
difficulty,
acc,
combo,
slider_tick_hits: _,
slider_end_hits: _,
n300,
n100,
n50: _,
+8
View File
@@ -42,6 +42,7 @@ macro_rules! test_cases {
hp: $hp:literal,
n_circles: $n_circles:literal,
n_sliders: $n_sliders:literal,
n_slider_ticks: $n_slider_ticks:literal,
n_spinners: $n_spinners:literal,
stars: $stars:literal,
max_combo: $max_combo:literal,
@@ -59,6 +60,7 @@ macro_rules! test_cases {
hp: $hp,
n_circles: $n_circles,
n_sliders: $n_sliders,
n_slider_ticks: $n_slider_ticks,
n_spinners: $n_spinners,
stars: $stars,
max_combo: $max_combo,
@@ -139,6 +141,7 @@ fn basic_osu() {
hp: 5.0,
n_circles: 307,
n_sliders: 293,
n_slider_ticks: 15,
n_spinners: 1,
stars: 5.643619989739299,
max_combo: 909,
@@ -156,6 +159,7 @@ fn basic_osu() {
hp: 5.0,
n_circles: 307,
n_sliders: 293,
n_slider_ticks: 15,
n_spinners: 1,
stars: 5.643619989739299,
max_combo: 909,
@@ -173,6 +177,7 @@ fn basic_osu() {
hp: 7.0,
n_circles: 307,
n_sliders: 293,
n_slider_ticks: 15,
n_spinners: 1,
stars: 6.243301253337941,
max_combo: 909,
@@ -190,6 +195,7 @@ fn basic_osu() {
hp: 5.0,
n_circles: 307,
n_sliders: 293,
n_slider_ticks: 15,
n_spinners: 1,
stars: 8.030649319285482,
max_combo: 909,
@@ -207,6 +213,7 @@ fn basic_osu() {
hp: 5.0,
n_circles: 307,
n_sliders: 293,
n_slider_ticks: 15,
n_spinners: 1,
stars: 6.858771801534423,
max_combo: 909,
@@ -224,6 +231,7 @@ fn basic_osu() {
hp: 5.0,
n_circles: 307,
n_sliders: 293,
n_slider_ticks: 15,
n_spinners: 1,
stars: 7.167932950561898,
max_combo: 909,
+1 -1
View File
@@ -39,7 +39,7 @@ macro_rules! test_cases {
effective_miss_count: $effective_miss_count:expr,
}) => {
(
OsuPerformance::from($map.as_owned()).lazer(false),
OsuPerformance::from($map.as_owned()).lazer(true),
OsuPerformanceAttributes {
pp: $pp,
pp_acc: $pp_acc,