Merge remote-tracking branch 'upstream/main'
This commit is contained in:
+7
-27
@@ -1,35 +1,15 @@
|
||||
use rosu_map::section::{general::GameMode, hit_objects::CurveBuffers};
|
||||
use rosu_map::section::hit_objects::CurveBuffers;
|
||||
|
||||
use crate::model::{
|
||||
beatmap::{Beatmap, Converted},
|
||||
mode::ConvertStatus,
|
||||
mods::Reflection,
|
||||
};
|
||||
use crate::model::{beatmap::Beatmap, mods::Reflection};
|
||||
|
||||
use super::{
|
||||
attributes::OsuDifficultyAttributes,
|
||||
difficulty::scaling_factor::ScalingFactor,
|
||||
object::{NestedSliderObjectKind, OsuObject, OsuObjectKind},
|
||||
Osu,
|
||||
};
|
||||
|
||||
/// A [`Beatmap`] for [`Osu`] calculations.
|
||||
pub type OsuBeatmap<'a> = Converted<'a, Osu>;
|
||||
|
||||
pub fn check_convert(map: &Beatmap) -> ConvertStatus {
|
||||
if map.mode == GameMode::Osu {
|
||||
ConvertStatus::Noop
|
||||
} else {
|
||||
ConvertStatus::Incompatible
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_convert(map: &mut Beatmap) -> ConvertStatus {
|
||||
check_convert(map)
|
||||
}
|
||||
|
||||
pub fn convert_objects(
|
||||
converted: &OsuBeatmap<'_>,
|
||||
map: &Beatmap,
|
||||
scaling_factor: &ScalingFactor,
|
||||
reflection: Reflection,
|
||||
time_preempt: f64,
|
||||
@@ -40,10 +20,10 @@ pub fn convert_objects(
|
||||
// mean=5.16 | median=4
|
||||
let mut ticks_buf = Vec::new();
|
||||
|
||||
let mut osu_objects: Box<[_]> = converted
|
||||
let mut osu_objects: Box<[_]> = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.map(|h| OsuObject::new(h, converted, &mut curve_bufs, &mut ticks_buf))
|
||||
.map(|h| OsuObject::new(h, map, &mut curve_bufs, &mut ticks_buf))
|
||||
.inspect(|h| {
|
||||
if take == 0 {
|
||||
return;
|
||||
@@ -77,9 +57,9 @@ pub fn convert_objects(
|
||||
.for_each(OsuObject::reflect_both_axes),
|
||||
}
|
||||
|
||||
let stack_threshold = time_preempt * f64::from(converted.stack_leniency);
|
||||
let stack_threshold = time_preempt * f64::from(map.stack_leniency);
|
||||
|
||||
if converted.version >= 6 {
|
||||
if map.version >= 6 {
|
||||
stacking(&mut osu_objects, stack_threshold);
|
||||
} else {
|
||||
old_stacking(&mut osu_objects, stack_threshold);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use std::{cmp, mem};
|
||||
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::{
|
||||
any::difficulty::skills::Skill,
|
||||
model::mode::ConvertError,
|
||||
osu::{
|
||||
convert::convert_objects,
|
||||
object::{OsuObject, OsuObjectKind},
|
||||
OsuBeatmap,
|
||||
},
|
||||
Difficulty,
|
||||
Beatmap, Difficulty,
|
||||
};
|
||||
|
||||
use self::osu_objects::OsuObjects;
|
||||
@@ -33,12 +35,10 @@ use super::{
|
||||
/// use akatsuki_pp::{Beatmap, Difficulty};
|
||||
/// use akatsuki_pp::osu::{Osu, OsuGradualDifficulty};
|
||||
///
|
||||
/// let converted = Beatmap::from_path("./resources/2785319.osu")
|
||||
/// .unwrap()
|
||||
/// .unchecked_into_converted::<Osu>();
|
||||
/// let map = Beatmap::from_path("./resources/2785319.osu").unwrap();
|
||||
///
|
||||
/// let difficulty = Difficulty::new().mods(64); // DT
|
||||
/// let mut iter = OsuGradualDifficulty::new(difficulty, &converted);
|
||||
/// let mut iter = OsuGradualDifficulty::new(difficulty, &map).unwrap();
|
||||
///
|
||||
/// // the difficulty of the map after the first hit object
|
||||
/// let attrs1 = iter.next();
|
||||
@@ -71,22 +71,23 @@ struct NotClonable;
|
||||
|
||||
impl OsuGradualDifficulty {
|
||||
/// Create a new difficulty attributes iterator for osu!standard maps.
|
||||
pub fn new(difficulty: Difficulty, converted: &OsuBeatmap<'_>) -> Self {
|
||||
pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, ConvertError> {
|
||||
let mods = difficulty.get_mods();
|
||||
let map = map.convert_ref(GameMode::Osu, mods)?;
|
||||
|
||||
let OsuDifficultySetup {
|
||||
scaling_factor,
|
||||
map_attrs,
|
||||
mut attrs,
|
||||
time_preempt,
|
||||
} = OsuDifficultySetup::new(&difficulty, converted);
|
||||
} = OsuDifficultySetup::new(&difficulty, &map);
|
||||
|
||||
let osu_objects = convert_objects(
|
||||
converted,
|
||||
&map,
|
||||
&scaling_factor,
|
||||
mods.reflection(),
|
||||
time_preempt,
|
||||
converted.hit_objects.len(),
|
||||
map.hit_objects.len(),
|
||||
&mut attrs,
|
||||
);
|
||||
|
||||
@@ -111,7 +112,7 @@ impl OsuGradualDifficulty {
|
||||
let skills = OsuSkills::new(mods, &scaling_factor, &map_attrs, time_preempt);
|
||||
let diff_objects = extend_lifetime(diff_objects.into_boxed_slice());
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
idx: 0,
|
||||
difficulty,
|
||||
attrs,
|
||||
@@ -119,7 +120,7 @@ impl OsuGradualDifficulty {
|
||||
diff_objects,
|
||||
osu_objects,
|
||||
_not_clonable: NotClonable,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn increment_combo(h: &OsuObject, attrs: &mut OsuDifficultyAttributes) {
|
||||
@@ -265,28 +266,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let converted = Beatmap::from_bytes(&[])
|
||||
.unwrap()
|
||||
.unchecked_into_converted::<Osu>();
|
||||
|
||||
let mut gradual = OsuGradualDifficulty::new(Difficulty::new(), &converted);
|
||||
|
||||
let map = Beatmap::from_bytes(&[]).unwrap();
|
||||
let mut gradual = OsuGradualDifficulty::new(Difficulty::new(), &map).unwrap();
|
||||
assert!(gradual.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_and_nth() {
|
||||
let converted = Beatmap::from_path("./resources/2785319.osu")
|
||||
.unwrap()
|
||||
.unchecked_into_converted::<Osu>();
|
||||
let map = Beatmap::from_path("./resources/2785319.osu").unwrap();
|
||||
|
||||
let difficulty = Difficulty::new();
|
||||
|
||||
let mut gradual = OsuGradualDifficulty::new(difficulty.clone(), &converted);
|
||||
let mut gradual_2nd = OsuGradualDifficulty::new(difficulty.clone(), &converted);
|
||||
let mut gradual_3rd = OsuGradualDifficulty::new(difficulty.clone(), &converted);
|
||||
let mut gradual = OsuGradualDifficulty::new(difficulty.clone(), &map).unwrap();
|
||||
let mut gradual_2nd = OsuGradualDifficulty::new(difficulty.clone(), &map).unwrap();
|
||||
let mut gradual_3rd = OsuGradualDifficulty::new(difficulty.clone(), &map).unwrap();
|
||||
|
||||
let hit_objects_len = converted.hit_objects.len();
|
||||
let hit_objects_len = map.hit_objects.len();
|
||||
|
||||
for i in 1.. {
|
||||
let Some(next_gradual) = gradual.next() else {
|
||||
@@ -309,8 +304,8 @@ mod tests {
|
||||
let expected = difficulty
|
||||
.clone()
|
||||
.passed_objects(i as u32)
|
||||
.with_mode()
|
||||
.calculate(&converted);
|
||||
.calculate_for_mode::<Osu>(&map)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(next_gradual, expected);
|
||||
}
|
||||
|
||||
+18
-11
@@ -1,5 +1,6 @@
|
||||
use std::{cmp, pin::Pin};
|
||||
|
||||
use rosu_map::section::general::GameMode;
|
||||
use skills::{
|
||||
flashlight::Flashlight,
|
||||
strain::{DifficultyValue, OsuStrainSkill, UsedOsuStrainSkills},
|
||||
@@ -7,18 +8,19 @@ use skills::{
|
||||
|
||||
use crate::{
|
||||
any::difficulty::{skills::Skill, Difficulty},
|
||||
model::{beatmap::BeatmapAttributes, mods::GameMods},
|
||||
model::{beatmap::BeatmapAttributes, mode::ConvertError, mods::GameMods},
|
||||
osu::{
|
||||
convert::convert_objects,
|
||||
difficulty::{object::OsuDifficultyObject, scaling_factor::ScalingFactor},
|
||||
object::OsuObject,
|
||||
performance::PERFORMANCE_BASE_MULTIPLIER,
|
||||
},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use self::skills::OsuSkills;
|
||||
|
||||
use super::{attributes::OsuDifficultyAttributes, convert::OsuBeatmap};
|
||||
use super::attributes::OsuDifficultyAttributes;
|
||||
|
||||
pub mod gradual;
|
||||
mod object;
|
||||
@@ -30,7 +32,12 @@ const DIFFICULTY_MULTIPLIER: f64 = 0.0675;
|
||||
const HD_FADE_IN_DURATION_MULTIPLIER: f64 = 0.4;
|
||||
const HD_FADE_OUT_DURATION_MULTIPLIER: f64 = 0.3;
|
||||
|
||||
pub fn difficulty(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> OsuDifficultyAttributes {
|
||||
pub fn difficulty(
|
||||
difficulty: &Difficulty,
|
||||
map: &Beatmap,
|
||||
) -> Result<OsuDifficultyAttributes, ConvertError> {
|
||||
let map = map.convert_ref(GameMode::Osu, difficulty.get_mods())?;
|
||||
|
||||
let DifficultyValues {
|
||||
skills:
|
||||
OsuSkills {
|
||||
@@ -40,7 +47,7 @@ pub fn difficulty(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> OsuDif
|
||||
flashlight,
|
||||
},
|
||||
mut attrs,
|
||||
} = DifficultyValues::calculate(difficulty, converted);
|
||||
} = DifficultyValues::calculate(difficulty, &map);
|
||||
|
||||
let aim_difficulty_value = aim.difficulty_value();
|
||||
let aim_no_sliders_difficulty_value = aim_no_sliders.difficulty_value();
|
||||
@@ -60,7 +67,7 @@ pub fn difficulty(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> OsuDif
|
||||
flashlight_difficulty_value,
|
||||
);
|
||||
|
||||
attrs
|
||||
Ok(attrs)
|
||||
}
|
||||
|
||||
pub struct OsuDifficultySetup {
|
||||
@@ -71,9 +78,9 @@ pub struct OsuDifficultySetup {
|
||||
}
|
||||
|
||||
impl OsuDifficultySetup {
|
||||
pub fn new(difficulty: &Difficulty, converted: &OsuBeatmap) -> Self {
|
||||
pub fn new(difficulty: &Difficulty, map: &Beatmap) -> Self {
|
||||
let clock_rate = difficulty.get_clock_rate();
|
||||
let map_attrs = converted.attributes().difficulty(difficulty).build();
|
||||
let map_attrs = map.attributes().difficulty(difficulty).build();
|
||||
let scaling_factor = ScalingFactor::new(map_attrs.cs);
|
||||
|
||||
let attrs = OsuDifficultyAttributes {
|
||||
@@ -100,7 +107,7 @@ pub struct DifficultyValues {
|
||||
}
|
||||
|
||||
impl DifficultyValues {
|
||||
pub fn calculate(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> Self {
|
||||
pub fn calculate(difficulty: &Difficulty, map: &Beatmap) -> Self {
|
||||
let mods = difficulty.get_mods();
|
||||
let take = difficulty.get_passed_objects();
|
||||
|
||||
@@ -109,10 +116,10 @@ impl DifficultyValues {
|
||||
map_attrs,
|
||||
mut attrs,
|
||||
time_preempt,
|
||||
} = OsuDifficultySetup::new(difficulty, converted);
|
||||
} = OsuDifficultySetup::new(difficulty, map);
|
||||
|
||||
let mut osu_objects = convert_objects(
|
||||
converted,
|
||||
map,
|
||||
&scaling_factor,
|
||||
mods.reflection(),
|
||||
time_preempt,
|
||||
@@ -134,7 +141,7 @@ impl DifficultyValues {
|
||||
let mut flashlight = Skill::new(&mut skills.flashlight, &diff_objects);
|
||||
|
||||
// The first hit object has no difficulty object
|
||||
let take_diff_objects = cmp::min(converted.hit_objects.len(), take).saturating_sub(1);
|
||||
let take_diff_objects = cmp::min(map.hit_objects.len(), take).saturating_sub(1);
|
||||
|
||||
for hit_object in diff_objects.iter().take(take_diff_objects) {
|
||||
aim.process(hit_object);
|
||||
|
||||
+13
-19
@@ -3,14 +3,13 @@ use rosu_map::util::Pos;
|
||||
use crate::{
|
||||
model::{
|
||||
beatmap::Beatmap,
|
||||
mode::{ConvertStatus, IGameMode},
|
||||
mode::{ConvertError, IGameMode},
|
||||
},
|
||||
Difficulty,
|
||||
};
|
||||
|
||||
pub use self::{
|
||||
attributes::{OsuDifficultyAttributes, OsuPerformanceAttributes},
|
||||
convert::OsuBeatmap,
|
||||
difficulty::gradual::OsuGradualDifficulty,
|
||||
performance::{gradual::OsuGradualPerformance, OsuPerformance},
|
||||
score_state::{OsuScoreOrigin, OsuScoreState},
|
||||
@@ -39,37 +38,32 @@ impl IGameMode for Osu {
|
||||
type GradualDifficulty = OsuGradualDifficulty;
|
||||
type GradualPerformance = OsuGradualPerformance;
|
||||
|
||||
fn check_convert(map: &Beatmap) -> ConvertStatus {
|
||||
convert::check_convert(map)
|
||||
}
|
||||
|
||||
fn try_convert(map: &mut Beatmap) -> ConvertStatus {
|
||||
convert::try_convert(map)
|
||||
}
|
||||
|
||||
fn difficulty(
|
||||
difficulty: &Difficulty,
|
||||
converted: &OsuBeatmap<'_>,
|
||||
) -> Self::DifficultyAttributes {
|
||||
difficulty::difficulty(difficulty, converted)
|
||||
map: &Beatmap,
|
||||
) -> Result<Self::DifficultyAttributes, ConvertError> {
|
||||
difficulty::difficulty(difficulty, map)
|
||||
}
|
||||
|
||||
fn strains(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> Self::Strains {
|
||||
strains::strains(difficulty, converted)
|
||||
fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result<Self::Strains, ConvertError> {
|
||||
strains::strains(difficulty, map)
|
||||
}
|
||||
|
||||
fn performance(map: OsuBeatmap<'_>) -> Self::Performance<'_> {
|
||||
fn performance(map: &Beatmap) -> Self::Performance<'_> {
|
||||
OsuPerformance::new(map)
|
||||
}
|
||||
|
||||
fn gradual_difficulty(difficulty: Difficulty, map: &OsuBeatmap<'_>) -> Self::GradualDifficulty {
|
||||
fn gradual_difficulty(
|
||||
difficulty: Difficulty,
|
||||
map: &Beatmap,
|
||||
) -> Result<Self::GradualDifficulty, ConvertError> {
|
||||
OsuGradualDifficulty::new(difficulty, map)
|
||||
}
|
||||
|
||||
fn gradual_performance(
|
||||
difficulty: Difficulty,
|
||||
map: &OsuBeatmap<'_>,
|
||||
) -> Self::GradualPerformance {
|
||||
map: &Beatmap,
|
||||
) -> Result<Self::GradualPerformance, ConvertError> {
|
||||
OsuGradualPerformance::new(difficulty, map)
|
||||
}
|
||||
}
|
||||
|
||||
+10
-9
@@ -14,9 +14,10 @@ use crate::{
|
||||
hit_object::{HitObject, HitObjectKind, HoldNote, Slider, Spinner},
|
||||
},
|
||||
util::{get_precision_adjusted_beat_len, sort},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use super::{convert::OsuBeatmap, PLAYFIELD_BASE_SIZE};
|
||||
use super::PLAYFIELD_BASE_SIZE;
|
||||
|
||||
pub struct OsuObject {
|
||||
pub pos: Pos,
|
||||
@@ -34,14 +35,14 @@ impl OsuObject {
|
||||
|
||||
pub fn new(
|
||||
h: &HitObject,
|
||||
converted: &OsuBeatmap<'_>,
|
||||
map: &Beatmap,
|
||||
curve_bufs: &mut CurveBuffers,
|
||||
ticks_buf: &mut Vec<SliderEvent>,
|
||||
) -> Self {
|
||||
let kind = match h.kind {
|
||||
HitObjectKind::Circle => OsuObjectKind::Circle,
|
||||
HitObjectKind::Slider(ref slider) => {
|
||||
OsuObjectKind::Slider(OsuSlider::new(h, slider, converted, curve_bufs, ticks_buf))
|
||||
OsuObjectKind::Slider(OsuSlider::new(h, slider, map, curve_bufs, ticks_buf))
|
||||
}
|
||||
HitObjectKind::Spinner(spinner) => OsuObjectKind::Spinner(spinner),
|
||||
HitObjectKind::Hold(HoldNote { duration }) => {
|
||||
@@ -188,19 +189,19 @@ impl OsuSlider {
|
||||
fn new(
|
||||
h: &HitObject,
|
||||
slider: &Slider,
|
||||
converted: &OsuBeatmap<'_>,
|
||||
map: &Beatmap,
|
||||
curve_bufs: &mut CurveBuffers,
|
||||
ticks_buf: &mut Vec<SliderEvent>,
|
||||
) -> Self {
|
||||
let start_time = h.start_time;
|
||||
let slider_multiplier = converted.slider_multiplier;
|
||||
let slider_tick_rate = converted.slider_tick_rate;
|
||||
let slider_multiplier = map.slider_multiplier;
|
||||
let slider_tick_rate = map.slider_tick_rate;
|
||||
|
||||
let beat_len = converted
|
||||
let beat_len = map
|
||||
.timing_point_at(start_time)
|
||||
.map_or(TimingPoint::DEFAULT_BEAT_LEN, |point| point.beat_len);
|
||||
|
||||
let (slider_velocity, generate_ticks) = converted.difficulty_point_at(start_time).map_or(
|
||||
let (slider_velocity, generate_ticks) = map.difficulty_point_at(start_time).map_or(
|
||||
(
|
||||
DifficultyPoint::DEFAULT_SLIDER_VELOCITY,
|
||||
DifficultyPoint::DEFAULT_GENERATE_TICKS,
|
||||
@@ -221,7 +222,7 @@ impl OsuSlider {
|
||||
let duration = end_time - start_time;
|
||||
let span_duration = duration / span_count;
|
||||
|
||||
let tick_dist_multiplier = if converted.version < 8 {
|
||||
let tick_dist_multiplier = if map.version < 8 {
|
||||
slider_velocity.recip()
|
||||
} else {
|
||||
1.0
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use crate::{
|
||||
osu::{OsuBeatmap, OsuGradualDifficulty},
|
||||
Difficulty,
|
||||
};
|
||||
use crate::{model::mode::ConvertError, osu::OsuGradualDifficulty, Beatmap, Difficulty};
|
||||
|
||||
use super::{OsuPerformanceAttributes, OsuScoreState};
|
||||
|
||||
@@ -23,12 +20,10 @@ use super::{OsuPerformanceAttributes, OsuScoreState};
|
||||
/// use akatsuki_pp::{Beatmap, Difficulty};
|
||||
/// use akatsuki_pp::osu::{Osu, OsuGradualPerformance, OsuScoreState};
|
||||
///
|
||||
/// let converted = Beatmap::from_path("./resources/2785319.osu")
|
||||
/// .unwrap()
|
||||
/// .unchecked_into_converted::<Osu>();
|
||||
/// let map = Beatmap::from_path("./resources/2785319.osu").unwrap();
|
||||
///
|
||||
/// let difficulty = Difficulty::new().mods(64); // DT
|
||||
/// let mut gradual = OsuGradualPerformance::new(difficulty, &converted);
|
||||
/// let mut gradual = OsuGradualPerformance::new(difficulty, &map).unwrap();
|
||||
/// let mut state = OsuScoreState::new(); // empty state, everything is on 0.
|
||||
///
|
||||
/// // The first 10 hits are 300s and there are no sliders for additional combo
|
||||
@@ -86,11 +81,11 @@ pub struct OsuGradualPerformance {
|
||||
|
||||
impl OsuGradualPerformance {
|
||||
/// Create a new gradual performance calculator for osu!standard maps.
|
||||
pub fn new(difficulty: Difficulty, converted: &OsuBeatmap<'_>) -> Self {
|
||||
pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, ConvertError> {
|
||||
let lazer = difficulty.get_lazer();
|
||||
let difficulty = OsuGradualDifficulty::new(difficulty, converted);
|
||||
let difficulty = OsuGradualDifficulty::new(difficulty, map)?;
|
||||
|
||||
Self { lazer, difficulty }
|
||||
Ok(Self { lazer, difficulty })
|
||||
}
|
||||
|
||||
/// Process the next hit object and calculate the performance attributes
|
||||
@@ -110,6 +105,7 @@ impl OsuGradualPerformance {
|
||||
///
|
||||
/// Note that the count is zero-indexed, so `n=0` will process 1 object,
|
||||
/// `n=1` will process 2, and so on.
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn nth(&mut self, state: OsuScoreState, n: usize) -> Option<OsuPerformanceAttributes> {
|
||||
let performance = self
|
||||
.difficulty
|
||||
@@ -119,7 +115,8 @@ impl OsuGradualPerformance {
|
||||
.state(state)
|
||||
.difficulty(self.difficulty.difficulty.clone())
|
||||
.passed_objects(self.difficulty.idx as u32)
|
||||
.calculate();
|
||||
.calculate()
|
||||
.expect("no conversion required");
|
||||
|
||||
Some(performance)
|
||||
}
|
||||
@@ -133,28 +130,23 @@ impl OsuGradualPerformance {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
osu::{Osu, OsuPerformance},
|
||||
Beatmap,
|
||||
};
|
||||
use crate::{osu::OsuPerformance, Beatmap};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn next_and_nth() {
|
||||
let converted = Beatmap::from_path("./resources/2785319.osu")
|
||||
.unwrap()
|
||||
.unchecked_into_converted::<Osu>();
|
||||
let map = Beatmap::from_path("./resources/2785319.osu").unwrap();
|
||||
|
||||
let difficulty = Difficulty::new().mods(88); // HDHRDT
|
||||
|
||||
let mut gradual = OsuGradualPerformance::new(difficulty.clone(), &converted);
|
||||
let mut gradual_2nd = OsuGradualPerformance::new(difficulty.clone(), &converted);
|
||||
let mut gradual_3rd = OsuGradualPerformance::new(difficulty.clone(), &converted);
|
||||
let mut gradual = OsuGradualPerformance::new(difficulty.clone(), &map).unwrap();
|
||||
let mut gradual_2nd = OsuGradualPerformance::new(difficulty.clone(), &map).unwrap();
|
||||
let mut gradual_3rd = OsuGradualPerformance::new(difficulty.clone(), &map).unwrap();
|
||||
|
||||
let mut state = OsuScoreState::default();
|
||||
|
||||
let hit_objects_len = converted.hit_objects.len();
|
||||
let hit_objects_len = map.hit_objects.len();
|
||||
|
||||
for i in 1.. {
|
||||
state.misses += 1;
|
||||
@@ -176,15 +168,15 @@ mod tests {
|
||||
assert_eq!(next_gradual, next_gradual_3rd);
|
||||
}
|
||||
|
||||
let mut regular_calc = OsuPerformance::new(converted.as_owned())
|
||||
let mut regular_calc = OsuPerformance::new(&map)
|
||||
.difficulty(difficulty.clone())
|
||||
.passed_objects(i as u32)
|
||||
.state(state);
|
||||
|
||||
let regular_state = regular_calc.generate_state();
|
||||
let regular_state = regular_calc.generate_state().unwrap();
|
||||
assert_eq!(state, regular_state);
|
||||
|
||||
let expected = regular_calc.calculate();
|
||||
let expected = regular_calc.calculate().unwrap();
|
||||
|
||||
assert_eq!(next_gradual, expected);
|
||||
}
|
||||
|
||||
+65
-37
@@ -1,4 +1,4 @@
|
||||
use std::cmp;
|
||||
use std::{borrow::Cow, cmp};
|
||||
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
@@ -6,9 +6,10 @@ use crate::{
|
||||
any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance, Performance},
|
||||
catch::CatchPerformance,
|
||||
mania::ManiaPerformance,
|
||||
model::mods::GameMods,
|
||||
model::{mode::ConvertError, mods::GameMods},
|
||||
taiko::TaikoPerformance,
|
||||
util::{float_ext::FloatExt, map_or_attrs::MapOrAttrs},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -43,7 +44,7 @@ impl<'map> OsuPerformance<'map> {
|
||||
/// The argument `map_or_attrs` must be either
|
||||
/// - previously calculated attributes ([`OsuDifficultyAttributes`]
|
||||
/// or [`OsuPerformanceAttributes`])
|
||||
/// - a beatmap ([`OsuBeatmap<'map>`])
|
||||
/// - a [`Beatmap`] (by reference or value)
|
||||
///
|
||||
/// If a map is given, difficulty attributes will need to be calculated
|
||||
/// internally which is a costly operation. Hence, passing attributes
|
||||
@@ -52,21 +53,18 @@ impl<'map> OsuPerformance<'map> {
|
||||
/// However, when passing previously calculated attributes, make sure they
|
||||
/// have been calculated for the same map and [`Difficulty`] settings.
|
||||
/// Otherwise, the final attributes will be incorrect.
|
||||
///
|
||||
/// [`OsuBeatmap<'map>`]: crate::osu::OsuBeatmap
|
||||
pub fn new(map_or_attrs: impl IntoModePerformance<'map, Osu>) -> Self {
|
||||
map_or_attrs.into_performance()
|
||||
}
|
||||
|
||||
/// Try to create a new performance calculator for osu! maps.
|
||||
///
|
||||
/// Returns `None` if `map_or_attrs` does not belong to osu! e.g.
|
||||
/// a [`Converted`], [`DifficultyAttributes`], or [`PerformanceAttributes`]
|
||||
/// of a different mode.
|
||||
/// Returns `None` if `map_or_attrs` does not belong to osu! i.e.
|
||||
/// a [`DifficultyAttributes`] or [`PerformanceAttributes`] of a different
|
||||
/// mode.
|
||||
///
|
||||
/// See [`OsuPerformance::new`] for more information.
|
||||
///
|
||||
/// [`Converted`]: crate::model::beatmap::Converted
|
||||
/// [`DifficultyAttributes`]: crate::any::DifficultyAttributes
|
||||
/// [`PerformanceAttributes`]: crate::any::PerformanceAttributes
|
||||
pub fn try_new(map_or_attrs: impl IntoPerformance<'map>) -> Option<Self> {
|
||||
@@ -349,10 +347,10 @@ impl<'map> OsuPerformance<'map> {
|
||||
|
||||
/// 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) -> Result<OsuScoreState, ConvertError> {
|
||||
let attrs = match self.map_or_attrs {
|
||||
MapOrAttrs::Map(ref map) => {
|
||||
let attrs = self.difficulty.with_mode().calculate(map);
|
||||
let attrs = self.difficulty.calculate_for_mode::<Osu>(map)?;
|
||||
|
||||
self.map_or_attrs.insert_attrs(attrs)
|
||||
}
|
||||
@@ -636,7 +634,7 @@ impl<'map> OsuPerformance<'map> {
|
||||
self.n50 = Some(n50);
|
||||
self.misses = Some(misses);
|
||||
|
||||
OsuScoreState {
|
||||
Ok(OsuScoreState {
|
||||
max_combo,
|
||||
large_tick_hits,
|
||||
slider_end_hits,
|
||||
@@ -644,16 +642,16 @@ impl<'map> OsuPerformance<'map> {
|
||||
n100,
|
||||
n50,
|
||||
misses,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate all performance related values, including pp and stars.
|
||||
pub fn calculate(mut self) -> OsuPerformanceAttributes {
|
||||
let state = self.generate_state();
|
||||
pub fn calculate(mut self) -> Result<OsuPerformanceAttributes, ConvertError> {
|
||||
let state = self.generate_state()?;
|
||||
|
||||
let attrs = match self.map_or_attrs {
|
||||
MapOrAttrs::Map(ref map) => self.difficulty.with_mode().calculate(map),
|
||||
MapOrAttrs::Attrs(attrs) => attrs,
|
||||
MapOrAttrs::Map(ref map) => self.difficulty.calculate_for_mode::<Osu>(map)?,
|
||||
};
|
||||
|
||||
let mods = self.difficulty.get_mods();
|
||||
@@ -718,7 +716,7 @@ impl<'map> OsuPerformance<'map> {
|
||||
using_classic_slider_acc,
|
||||
};
|
||||
|
||||
inner.calculate()
|
||||
Ok(inner.calculate())
|
||||
}
|
||||
|
||||
pub(crate) const fn from_map_or_attrs(map_or_attrs: MapOrAttrs<'map, Osu>) -> Self {
|
||||
@@ -736,6 +734,33 @@ impl<'map> OsuPerformance<'map> {
|
||||
hitresult_priority: HitResultPriority::DEFAULT,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub(crate) fn try_convert_map(
|
||||
map_or_attrs: MapOrAttrs<'map, Osu>,
|
||||
mode: GameMode,
|
||||
mods: &GameMods,
|
||||
) -> Result<Cow<'map, Beatmap>, MapOrAttrs<'map, Osu>> {
|
||||
let MapOrAttrs::Map(map) = map_or_attrs else {
|
||||
return Err(map_or_attrs);
|
||||
};
|
||||
|
||||
match map {
|
||||
Cow::Borrowed(map) => match map.convert_ref(mode, mods) {
|
||||
Ok(map) => Ok(map),
|
||||
Err(_) => {
|
||||
return Err(MapOrAttrs::Map(Cow::Borrowed(map)));
|
||||
}
|
||||
},
|
||||
Cow::Owned(mut map) => {
|
||||
if map.convert_mut(mode, mods).is_err() {
|
||||
return Err(MapOrAttrs::Map(Cow::Owned(map)));
|
||||
}
|
||||
|
||||
Ok(Cow::Owned(map))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map, T: IntoModePerformance<'map, Osu>> From<T> for OsuPerformance<'map> {
|
||||
@@ -1153,7 +1178,7 @@ mod test {
|
||||
|
||||
use crate::{
|
||||
any::{DifficultyAttributes, PerformanceAttributes},
|
||||
taiko::{Taiko, TaikoDifficultyAttributes, TaikoPerformanceAttributes},
|
||||
taiko::{TaikoDifficultyAttributes, TaikoPerformanceAttributes},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
@@ -1172,8 +1197,8 @@ mod test {
|
||||
fn attrs() -> OsuDifficultyAttributes {
|
||||
ATTRS
|
||||
.get_or_init(|| {
|
||||
let converted = beatmap().unchecked_into_converted::<Osu>();
|
||||
let attrs = Difficulty::new().with_mode().calculate(&converted);
|
||||
let map = beatmap();
|
||||
let attrs = Difficulty::new().calculate_for_mode::<Osu>(&map).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
(attrs.n_circles, attrs.n_sliders, attrs.n_spinners),
|
||||
@@ -1388,8 +1413,8 @@ mod test {
|
||||
state = state.misses(misses);
|
||||
}
|
||||
|
||||
let first = state.generate_state();
|
||||
let state = state.generate_state();
|
||||
let first = state.generate_state().unwrap();
|
||||
let state = state.generate_state().unwrap();
|
||||
assert_eq!(first, state);
|
||||
|
||||
let mut expected = brute_force_best(
|
||||
@@ -1419,7 +1444,8 @@ mod test {
|
||||
.n100(20)
|
||||
.misses(2)
|
||||
.hitresult_priority(HitResultPriority::BestCase)
|
||||
.generate_state();
|
||||
.generate_state()
|
||||
.unwrap();
|
||||
|
||||
let expected = OsuScoreState {
|
||||
max_combo: 500,
|
||||
@@ -1443,7 +1469,8 @@ mod test {
|
||||
.n50(10)
|
||||
.misses(2)
|
||||
.hitresult_priority(HitResultPriority::BestCase)
|
||||
.generate_state();
|
||||
.generate_state()
|
||||
.unwrap();
|
||||
|
||||
let expected = OsuScoreState {
|
||||
max_combo: 500,
|
||||
@@ -1466,7 +1493,8 @@ mod test {
|
||||
.n50(10)
|
||||
.misses(2)
|
||||
.hitresult_priority(HitResultPriority::WorstCase)
|
||||
.generate_state();
|
||||
.generate_state()
|
||||
.unwrap();
|
||||
|
||||
let expected = OsuScoreState {
|
||||
max_combo: 500,
|
||||
@@ -1491,7 +1519,8 @@ mod test {
|
||||
.n50(10)
|
||||
.misses(2)
|
||||
.hitresult_priority(HitResultPriority::WorstCase)
|
||||
.generate_state();
|
||||
.generate_state()
|
||||
.unwrap();
|
||||
|
||||
let expected = OsuScoreState {
|
||||
max_combo: 500,
|
||||
@@ -1509,12 +1538,11 @@ mod test {
|
||||
#[test]
|
||||
fn create() {
|
||||
let mut map = beatmap();
|
||||
let converted = map.unchecked_as_converted();
|
||||
|
||||
let _ = OsuPerformance::new(OsuDifficultyAttributes::default());
|
||||
let _ = OsuPerformance::new(OsuPerformanceAttributes::default());
|
||||
let _ = OsuPerformance::new(&converted);
|
||||
let _ = OsuPerformance::new(converted.as_owned());
|
||||
let _ = OsuPerformance::new(&map);
|
||||
let _ = OsuPerformance::new(map.clone());
|
||||
|
||||
let _ = OsuPerformance::try_new(OsuDifficultyAttributes::default()).unwrap();
|
||||
let _ = OsuPerformance::try_new(OsuPerformanceAttributes::default()).unwrap();
|
||||
@@ -1525,19 +1553,19 @@ mod test {
|
||||
OsuPerformanceAttributes::default(),
|
||||
))
|
||||
.unwrap();
|
||||
let _ = OsuPerformance::try_new(&converted).unwrap();
|
||||
let _ = OsuPerformance::try_new(converted.as_owned()).unwrap();
|
||||
let _ = OsuPerformance::try_new(&map).unwrap();
|
||||
let _ = OsuPerformance::try_new(map.clone()).unwrap();
|
||||
|
||||
let _ = OsuPerformance::from(OsuDifficultyAttributes::default());
|
||||
let _ = OsuPerformance::from(OsuPerformanceAttributes::default());
|
||||
let _ = OsuPerformance::from(&converted);
|
||||
let _ = OsuPerformance::from(converted);
|
||||
let _ = OsuPerformance::from(&map);
|
||||
let _ = OsuPerformance::from(map.clone());
|
||||
|
||||
let _ = OsuDifficultyAttributes::default().performance();
|
||||
let _ = OsuPerformanceAttributes::default().performance();
|
||||
|
||||
map.mode = GameMode::Taiko;
|
||||
let converted = map.unchecked_as_converted::<Taiko>();
|
||||
map.convert_mut(GameMode::Taiko, &GameMods::default())
|
||||
.unwrap();
|
||||
|
||||
assert!(OsuPerformance::try_new(TaikoDifficultyAttributes::default()).is_none());
|
||||
assert!(OsuPerformance::try_new(TaikoPerformanceAttributes::default()).is_none());
|
||||
@@ -1549,7 +1577,7 @@ mod test {
|
||||
TaikoPerformanceAttributes::default()
|
||||
))
|
||||
.is_none());
|
||||
assert!(OsuPerformance::try_new(&converted).is_none());
|
||||
assert!(OsuPerformance::try_new(converted).is_none());
|
||||
assert!(OsuPerformance::try_new(&map).is_none());
|
||||
assert!(OsuPerformance::try_new(map).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ impl Default for OsuScoreState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Type to pass [`OsuScoreState::accuracy`] and specify the origin of a score.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum OsuScoreOrigin {
|
||||
/// For scores set on osu!stable
|
||||
|
||||
+10
-9
@@ -1,9 +1,8 @@
|
||||
use crate::Difficulty;
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use super::{
|
||||
convert::OsuBeatmap,
|
||||
difficulty::{skills::OsuSkills, DifficultyValues},
|
||||
};
|
||||
use crate::{model::mode::ConvertError, Beatmap, Difficulty};
|
||||
|
||||
use super::difficulty::{skills::OsuSkills, DifficultyValues};
|
||||
|
||||
/// The result of calculating the strains on a osu! map.
|
||||
///
|
||||
@@ -25,7 +24,9 @@ impl OsuStrains {
|
||||
pub const SECTION_LEN: f64 = 400.0;
|
||||
}
|
||||
|
||||
pub fn strains(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> OsuStrains {
|
||||
pub fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result<OsuStrains, ConvertError> {
|
||||
let map = map.convert_ref(GameMode::Osu, difficulty.get_mods())?;
|
||||
|
||||
let DifficultyValues {
|
||||
skills:
|
||||
OsuSkills {
|
||||
@@ -35,12 +36,12 @@ pub fn strains(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> OsuStrain
|
||||
flashlight,
|
||||
},
|
||||
attrs: _,
|
||||
} = DifficultyValues::calculate(difficulty, converted);
|
||||
} = DifficultyValues::calculate(difficulty, &map);
|
||||
|
||||
OsuStrains {
|
||||
Ok(OsuStrains {
|
||||
aim: aim.get_curr_strain_peaks().into_vec(),
|
||||
aim_no_sliders: aim_no_sliders.get_curr_strain_peaks().into_vec(),
|
||||
speed: speed.get_curr_strain_peaks().into_vec(),
|
||||
flashlight: flashlight.get_curr_strain_peaks().into_vec(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user