feat!: overhaul convert api
This commit is contained in:
+10
-31
@@ -2,9 +2,8 @@ use rosu_map::section::{general::GameMode, hit_objects::CurveBuffers};
|
||||
|
||||
use crate::{
|
||||
model::{
|
||||
beatmap::{Beatmap, Converted},
|
||||
beatmap::Beatmap,
|
||||
hit_object::{HitObject, HitObjectKind, HoldNote, Spinner},
|
||||
mode::ConvertStatus,
|
||||
mods::Reflection,
|
||||
},
|
||||
util::{float_ext::FloatExt, random::Random},
|
||||
@@ -19,37 +18,18 @@ use super::{
|
||||
juice_stream::{JuiceStream, JuiceStreamBufs, NestedJuiceStreamObjectKind},
|
||||
palpable::PalpableObject,
|
||||
},
|
||||
Catch, PLAYFIELD_WIDTH,
|
||||
PLAYFIELD_WIDTH,
|
||||
};
|
||||
|
||||
const RNG_SEED: i32 = 1337;
|
||||
|
||||
/// A [`Beatmap`] for [`Catch`] calculations.
|
||||
pub type CatchBeatmap<'a> = Converted<'a, Catch>;
|
||||
|
||||
pub const fn check_convert(map: &Beatmap) -> ConvertStatus {
|
||||
match map.mode {
|
||||
GameMode::Osu => ConvertStatus::Conversion,
|
||||
GameMode::Catch => ConvertStatus::Noop,
|
||||
GameMode::Taiko | GameMode::Mania => ConvertStatus::Incompatible,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_convert(map: &mut Beatmap) -> ConvertStatus {
|
||||
match map.mode {
|
||||
GameMode::Osu => {
|
||||
map.mode = GameMode::Catch;
|
||||
map.is_convert = true;
|
||||
|
||||
ConvertStatus::Conversion
|
||||
}
|
||||
GameMode::Catch => ConvertStatus::Noop,
|
||||
GameMode::Taiko | GameMode::Mania => ConvertStatus::Incompatible,
|
||||
}
|
||||
pub fn convert(map: &mut Beatmap) {
|
||||
map.mode = GameMode::Catch;
|
||||
map.is_convert = true;
|
||||
}
|
||||
|
||||
pub fn convert_objects(
|
||||
converted: &CatchBeatmap<'_>,
|
||||
map: &Beatmap,
|
||||
count: &mut ObjectCountBuilder,
|
||||
reflection: Reflection,
|
||||
hr_offsets: bool,
|
||||
@@ -70,8 +50,8 @@ pub fn convert_objects(
|
||||
let mut last_pos = None;
|
||||
let mut last_start_time = 0.0;
|
||||
|
||||
for h in converted.hit_objects.iter() {
|
||||
let mut new_objects = convert_object(h, converted, count, &mut bufs);
|
||||
for h in map.hit_objects.iter() {
|
||||
let mut new_objects = convert_object(h, map, count, &mut bufs);
|
||||
|
||||
apply_pos_offset(
|
||||
&mut new_objects,
|
||||
@@ -99,7 +79,7 @@ pub fn convert_objects(
|
||||
|
||||
fn convert_object<'a>(
|
||||
h: &'a HitObject,
|
||||
converted: &CatchBeatmap<'_>,
|
||||
map: &Beatmap,
|
||||
count: &mut ObjectCountBuilder,
|
||||
bufs: &'a mut JuiceStreamBufs,
|
||||
) -> ObjectIter<'a> {
|
||||
@@ -107,8 +87,7 @@ fn convert_object<'a>(
|
||||
HitObjectKind::Circle => ObjectIterState::Fruit(Some(Fruit::new(count))),
|
||||
HitObjectKind::Slider(ref slider) => {
|
||||
let effective_x = h.pos.x.clamp(0.0, PLAYFIELD_WIDTH);
|
||||
let stream =
|
||||
JuiceStream::new(effective_x, h.start_time, slider, converted, count, bufs);
|
||||
let stream = JuiceStream::new(effective_x, h.start_time, slider, map, count, bufs);
|
||||
|
||||
ObjectIterState::JuiceStream(stream)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use std::cmp;
|
||||
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::{
|
||||
any::difficulty::skills::Skill,
|
||||
catch::{
|
||||
attributes::{GradualObjectCount, ObjectCountBuilder},
|
||||
convert::convert_objects,
|
||||
CatchBeatmap, CatchDifficultyAttributes,
|
||||
CatchDifficultyAttributes,
|
||||
},
|
||||
Difficulty,
|
||||
model::mode::ConvertError,
|
||||
Beatmap, Difficulty,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -65,18 +68,20 @@ pub struct CatchGradualDifficulty {
|
||||
|
||||
impl CatchGradualDifficulty {
|
||||
/// Create a new difficulty attributes iterator for osu!catch maps.
|
||||
pub fn new(difficulty: Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
||||
pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, ConvertError> {
|
||||
let map = map.convert_ref(GameMode::Catch, difficulty.get_mods())?;
|
||||
|
||||
let clock_rate = difficulty.get_clock_rate();
|
||||
|
||||
let CatchDifficultySetup { map_attrs, attrs } =
|
||||
CatchDifficultySetup::new(&difficulty, converted);
|
||||
CatchDifficultySetup::new(&difficulty, &map);
|
||||
|
||||
let hr_offsets = difficulty.get_hardrock_offsets();
|
||||
let reflection = difficulty.get_mods().reflection();
|
||||
let mut count = ObjectCountBuilder::new_gradual();
|
||||
|
||||
let palpable_objects = convert_objects(
|
||||
converted,
|
||||
&map,
|
||||
&mut count,
|
||||
reflection,
|
||||
hr_offsets,
|
||||
@@ -92,14 +97,14 @@ impl CatchGradualDifficulty {
|
||||
let count = count.into_gradual();
|
||||
let movement = Movement::new(clock_rate);
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
idx: 0,
|
||||
difficulty,
|
||||
attrs,
|
||||
count,
|
||||
diff_objects,
|
||||
movement,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,30 +173,26 @@ impl ExactSizeIterator for CatchGradualDifficulty {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Beatmap;
|
||||
use crate::{catch::Catch, Beatmap};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let converted = Beatmap::from_bytes(&[]).unwrap().unchecked_into_converted();
|
||||
|
||||
let mut gradual = CatchGradualDifficulty::new(Difficulty::new(), &converted);
|
||||
|
||||
let map = Beatmap::from_bytes(&[]).unwrap();
|
||||
let mut gradual = CatchGradualDifficulty::new(Difficulty::new(), &map).unwrap();
|
||||
assert!(gradual.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_and_nth() {
|
||||
let converted = Beatmap::from_path("./resources/2118524.osu")
|
||||
.unwrap()
|
||||
.unchecked_into_converted();
|
||||
let map = Beatmap::from_path("./resources/2118524.osu").unwrap();
|
||||
|
||||
let difficulty = Difficulty::new();
|
||||
|
||||
let mut gradual = CatchGradualDifficulty::new(difficulty.clone(), &converted);
|
||||
let mut gradual_2nd = CatchGradualDifficulty::new(difficulty.clone(), &converted);
|
||||
let mut gradual_3rd = CatchGradualDifficulty::new(difficulty.clone(), &converted);
|
||||
let mut gradual = CatchGradualDifficulty::new(difficulty.clone(), &map).unwrap();
|
||||
let mut gradual_2nd = CatchGradualDifficulty::new(difficulty.clone(), &map).unwrap();
|
||||
let mut gradual_3rd = CatchGradualDifficulty::new(difficulty.clone(), &map).unwrap();
|
||||
|
||||
for i in 1.. {
|
||||
let Some(next_gradual) = gradual.next() else {
|
||||
@@ -214,8 +215,8 @@ mod tests {
|
||||
let expected = difficulty
|
||||
.clone()
|
||||
.passed_objects(i as u32)
|
||||
.with_mode()
|
||||
.calculate(&converted);
|
||||
.calculate_for_mode::<Catch>(&map)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(next_gradual, expected);
|
||||
}
|
||||
|
||||
+17
-18
@@ -1,16 +1,18 @@
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::{
|
||||
any::difficulty::{skills::Skill, Difficulty},
|
||||
catch::{
|
||||
catcher::Catcher, convert::convert_objects, difficulty::object::CatchDifficultyObject,
|
||||
},
|
||||
model::beatmap::BeatmapAttributes,
|
||||
model::{beatmap::BeatmapAttributes, mode::ConvertError},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use self::skills::movement::Movement;
|
||||
|
||||
use super::{
|
||||
attributes::{CatchDifficultyAttributes, ObjectCountBuilder},
|
||||
convert::CatchBeatmap,
|
||||
object::palpable::PalpableObject,
|
||||
};
|
||||
|
||||
@@ -22,16 +24,18 @@ const DIFFICULTY_MULTIPLIER: f64 = 4.59;
|
||||
|
||||
pub fn difficulty(
|
||||
difficulty: &Difficulty,
|
||||
converted: &CatchBeatmap<'_>,
|
||||
) -> CatchDifficultyAttributes {
|
||||
map: &Beatmap,
|
||||
) -> Result<CatchDifficultyAttributes, ConvertError> {
|
||||
let map = map.convert_ref(GameMode::Catch, difficulty.get_mods())?;
|
||||
|
||||
let DifficultyValues {
|
||||
movement,
|
||||
mut attrs,
|
||||
} = DifficultyValues::calculate(difficulty, converted);
|
||||
} = DifficultyValues::calculate(difficulty, &map);
|
||||
|
||||
DifficultyValues::eval(&mut attrs, movement.difficulty_value());
|
||||
|
||||
attrs
|
||||
Ok(attrs)
|
||||
}
|
||||
|
||||
pub struct CatchDifficultySetup {
|
||||
@@ -40,12 +44,12 @@ pub struct CatchDifficultySetup {
|
||||
}
|
||||
|
||||
impl CatchDifficultySetup {
|
||||
pub fn new(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
||||
let map_attrs = converted.attributes().difficulty(difficulty).build();
|
||||
pub fn new(difficulty: &Difficulty, map: &Beatmap) -> Self {
|
||||
let map_attrs = map.attributes().difficulty(difficulty).build();
|
||||
|
||||
let attrs = CatchDifficultyAttributes {
|
||||
ar: map_attrs.ar,
|
||||
is_convert: converted.is_convert,
|
||||
is_convert: map.is_convert,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -59,26 +63,21 @@ pub struct DifficultyValues {
|
||||
}
|
||||
|
||||
impl DifficultyValues {
|
||||
pub fn calculate(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
||||
pub fn calculate(difficulty: &Difficulty, map: &Beatmap) -> Self {
|
||||
let take = difficulty.get_passed_objects();
|
||||
let clock_rate = difficulty.get_clock_rate();
|
||||
|
||||
let CatchDifficultySetup {
|
||||
map_attrs,
|
||||
mut attrs,
|
||||
} = CatchDifficultySetup::new(difficulty, converted);
|
||||
} = CatchDifficultySetup::new(difficulty, map);
|
||||
|
||||
let hr_offsets = difficulty.get_hardrock_offsets();
|
||||
let reflection = difficulty.get_mods().reflection();
|
||||
let mut count = ObjectCountBuilder::new_regular(take);
|
||||
|
||||
let palpable_objects = convert_objects(
|
||||
converted,
|
||||
&mut count,
|
||||
reflection,
|
||||
hr_offsets,
|
||||
map_attrs.cs as f32,
|
||||
);
|
||||
let palpable_objects =
|
||||
convert_objects(map, &mut count, reflection, hr_offsets, map_attrs.cs as f32);
|
||||
|
||||
let diff_objects = Self::create_difficulty_objects(
|
||||
&map_attrs,
|
||||
|
||||
+20
-20
@@ -1,14 +1,15 @@
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::{
|
||||
model::{
|
||||
beatmap::Beatmap,
|
||||
mode::{ConvertStatus, IGameMode},
|
||||
mode::{ConvertError, IGameMode},
|
||||
},
|
||||
Difficulty,
|
||||
};
|
||||
|
||||
pub use self::{
|
||||
attributes::{CatchDifficultyAttributes, CatchPerformanceAttributes},
|
||||
convert::CatchBeatmap,
|
||||
difficulty::gradual::CatchGradualDifficulty,
|
||||
performance::{gradual::CatchGradualPerformance, CatchPerformance},
|
||||
score_state::CatchScoreState,
|
||||
@@ -31,6 +32,13 @@ const PLAYFIELD_WIDTH: f32 = 512.0;
|
||||
/// [`GameMode::Catch`]: rosu_map::section::general::GameMode::Catch
|
||||
pub struct Catch;
|
||||
|
||||
impl Catch {
|
||||
pub fn convert(map: &mut Beatmap) {
|
||||
debug_assert!(!map.is_convert && map.mode == GameMode::Osu);
|
||||
convert::convert(map);
|
||||
}
|
||||
}
|
||||
|
||||
impl IGameMode for Catch {
|
||||
type DifficultyAttributes = CatchDifficultyAttributes;
|
||||
type Strains = CatchStrains;
|
||||
@@ -38,40 +46,32 @@ impl IGameMode for Catch {
|
||||
type GradualDifficulty = CatchGradualDifficulty;
|
||||
type GradualPerformance = CatchGradualPerformance;
|
||||
|
||||
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: &CatchBeatmap<'_>,
|
||||
) -> Self::DifficultyAttributes {
|
||||
difficulty::difficulty(difficulty, converted)
|
||||
map: &Beatmap,
|
||||
) -> Result<Self::DifficultyAttributes, ConvertError> {
|
||||
difficulty::difficulty(difficulty, map)
|
||||
}
|
||||
|
||||
fn strains(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self::Strains {
|
||||
strains::strains(difficulty, converted)
|
||||
fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result<Self::Strains, ConvertError> {
|
||||
strains::strains(difficulty, map)
|
||||
}
|
||||
|
||||
fn performance(map: CatchBeatmap<'_>) -> Self::Performance<'_> {
|
||||
fn performance(map: &Beatmap) -> Self::Performance<'_> {
|
||||
CatchPerformance::new(map)
|
||||
}
|
||||
|
||||
fn gradual_difficulty(
|
||||
difficulty: Difficulty,
|
||||
map: &CatchBeatmap<'_>,
|
||||
) -> Self::GradualDifficulty {
|
||||
map: &Beatmap,
|
||||
) -> Result<Self::GradualDifficulty, ConvertError> {
|
||||
CatchGradualDifficulty::new(difficulty, map)
|
||||
}
|
||||
|
||||
fn gradual_performance(
|
||||
difficulty: Difficulty,
|
||||
map: &CatchBeatmap<'_>,
|
||||
) -> Self::GradualPerformance {
|
||||
map: &Beatmap,
|
||||
) -> Result<Self::GradualPerformance, ConvertError> {
|
||||
CatchGradualPerformance::new(difficulty, map)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@ use rosu_map::section::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
catch::{attributes::ObjectCountBuilder, convert::CatchBeatmap},
|
||||
catch::attributes::ObjectCountBuilder,
|
||||
model::{
|
||||
control_point::{DifficultyPoint, TimingPoint},
|
||||
hit_object::Slider,
|
||||
},
|
||||
util::get_precision_adjusted_beat_len,
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
pub struct JuiceStream<'a> {
|
||||
@@ -26,18 +27,18 @@ impl<'a> JuiceStream<'a> {
|
||||
effective_x: f32,
|
||||
start_time: f64,
|
||||
slider: &'a Slider,
|
||||
converted: &CatchBeatmap<'_>,
|
||||
map: &Beatmap,
|
||||
count: &mut ObjectCountBuilder,
|
||||
bufs: &'a mut JuiceStreamBufs,
|
||||
) -> Self {
|
||||
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 = converted
|
||||
let slider_velocity = map
|
||||
.difficulty_point_at(start_time)
|
||||
.map_or(DifficultyPoint::DEFAULT_SLIDER_VELOCITY, |point| {
|
||||
point.slider_velocity
|
||||
@@ -49,7 +50,7 @@ impl<'a> JuiceStream<'a> {
|
||||
/ get_precision_adjusted_beat_len(slider_velocity, beat_len);
|
||||
let scoring_dist = velocity * beat_len;
|
||||
|
||||
let tick_dist_multiplier = if converted.version < 8 {
|
||||
let tick_dist_multiplier = if map.version < 8 {
|
||||
slider_velocity.recip()
|
||||
} else {
|
||||
1.0
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
catch::{CatchBeatmap, CatchGradualDifficulty, CatchPerformanceAttributes, CatchScoreState},
|
||||
Difficulty,
|
||||
catch::{CatchGradualDifficulty, CatchPerformanceAttributes, CatchScoreState},
|
||||
model::mode::ConvertError,
|
||||
Beatmap, Difficulty,
|
||||
};
|
||||
|
||||
/// Gradually calculate the performance attributes of an osu!catch map.
|
||||
@@ -90,10 +91,10 @@ pub struct CatchGradualPerformance {
|
||||
|
||||
impl CatchGradualPerformance {
|
||||
/// Create a new gradual performance calculator for osu!catch maps.
|
||||
pub fn new(difficulty: Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
||||
let difficulty = CatchGradualDifficulty::new(difficulty, converted);
|
||||
pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, ConvertError> {
|
||||
let difficulty = CatchGradualDifficulty::new(difficulty, map)?;
|
||||
|
||||
Self { difficulty }
|
||||
Ok(Self { difficulty })
|
||||
}
|
||||
|
||||
/// Process the next hit object and calculate the performance attributes
|
||||
@@ -116,6 +117,7 @@ impl CatchGradualPerformance {
|
||||
///
|
||||
/// 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: CatchScoreState, n: usize) -> Option<CatchPerformanceAttributes> {
|
||||
let performance = self
|
||||
.difficulty
|
||||
@@ -124,7 +126,8 @@ impl CatchGradualPerformance {
|
||||
.state(state)
|
||||
.difficulty(self.difficulty.difficulty.clone())
|
||||
.passed_objects(self.difficulty.idx as u32)
|
||||
.calculate();
|
||||
.calculate()
|
||||
.expect("no conversion required");
|
||||
|
||||
Some(performance)
|
||||
}
|
||||
@@ -144,15 +147,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn next_and_nth() {
|
||||
let converted = Beatmap::from_path("./resources/2118524.osu")
|
||||
.unwrap()
|
||||
.unchecked_into_converted();
|
||||
let map = Beatmap::from_path("./resources/2118524.osu").unwrap();
|
||||
|
||||
let difficulty = Difficulty::new().mods(88); // HDHRDT
|
||||
|
||||
let mut gradual = CatchGradualPerformance::new(difficulty.clone(), &converted);
|
||||
let mut gradual_2nd = CatchGradualPerformance::new(difficulty.clone(), &converted);
|
||||
let mut gradual_3rd = CatchGradualPerformance::new(difficulty.clone(), &converted);
|
||||
let mut gradual = CatchGradualPerformance::new(difficulty.clone(), &map).unwrap();
|
||||
let mut gradual_2nd = CatchGradualPerformance::new(difficulty.clone(), &map).unwrap();
|
||||
let mut gradual_3rd = CatchGradualPerformance::new(difficulty.clone(), &map).unwrap();
|
||||
|
||||
let mut state = CatchScoreState::default();
|
||||
|
||||
@@ -176,12 +177,12 @@ mod tests {
|
||||
assert_eq!(next_gradual, next_gradual_3rd);
|
||||
}
|
||||
|
||||
let regular_calc = CatchPerformance::new(converted.as_owned())
|
||||
let regular_calc = CatchPerformance::new(&map)
|
||||
.difficulty(difficulty.clone())
|
||||
.passed_objects(i as u32)
|
||||
.state(state.clone());
|
||||
|
||||
let expected = regular_calc.calculate();
|
||||
let expected = regular_calc.calculate().unwrap();
|
||||
|
||||
assert_eq!(next_gradual, expected);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::cmp::{self, Ordering};
|
||||
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::{
|
||||
any::{Difficulty, IntoModePerformance, IntoPerformance},
|
||||
model::mods::GameMods,
|
||||
model::{mode::ConvertError, mods::GameMods},
|
||||
osu::OsuPerformance,
|
||||
util::map_or_attrs::MapOrAttrs,
|
||||
Performance,
|
||||
@@ -37,7 +39,7 @@ impl<'map> CatchPerformance<'map> {
|
||||
/// The argument `map_or_attrs` must be either
|
||||
/// - previously calculated attributes ([`CatchDifficultyAttributes`]
|
||||
/// or [`CatchPerformanceAttributes`])
|
||||
/// - a beatmap ([`CatchBeatmap<'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
|
||||
@@ -47,20 +49,19 @@ impl<'map> CatchPerformance<'map> {
|
||||
/// have been calculated for the same map and [`Difficulty`] settings.
|
||||
/// Otherwise, the final attributes will be incorrect.
|
||||
///
|
||||
/// [`CatchBeatmap<'map>`]: crate::catch::CatchBeatmap
|
||||
/// [`Beatmap`]: crate::model::beatmap::Beatmap
|
||||
pub fn new(map_or_attrs: impl IntoModePerformance<'map, Catch>) -> Self {
|
||||
map_or_attrs.into_performance()
|
||||
}
|
||||
|
||||
/// Try to create a new performance calculator for osu!catch maps.
|
||||
///
|
||||
/// Returns `None` if `map_or_attrs` does not belong to osu!catch e.g.
|
||||
/// a [`Converted`], [`DifficultyAttributes`], or [`PerformanceAttributes`]
|
||||
/// of a different mode.
|
||||
/// Returns `None` if `map_or_attrs` does not belong to osu!catch i.e.
|
||||
/// a [`DifficultyAttributes`] or [`PerformanceAttributes`] of a different
|
||||
/// mode.
|
||||
///
|
||||
/// See [`CatchPerformance::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> {
|
||||
@@ -262,10 +263,10 @@ impl<'map> CatchPerformance<'map> {
|
||||
|
||||
/// 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) -> Result<CatchScoreState, 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::<Catch>(map)?;
|
||||
|
||||
self.map_or_attrs.insert_attrs(attrs)
|
||||
}
|
||||
@@ -411,16 +412,16 @@ impl<'map> CatchPerformance<'map> {
|
||||
self.tiny_droplet_misses = Some(best_state.tiny_droplet_misses);
|
||||
self.misses = Some(best_state.misses);
|
||||
|
||||
best_state
|
||||
Ok(best_state)
|
||||
}
|
||||
|
||||
/// Calculate all performance related values, including pp and stars.
|
||||
pub fn calculate(mut self) -> CatchPerformanceAttributes {
|
||||
let state = self.generate_state();
|
||||
pub fn calculate(mut self) -> Result<CatchPerformanceAttributes, 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::<Catch>(map)?,
|
||||
};
|
||||
|
||||
let inner = CatchPerformanceInner {
|
||||
@@ -429,7 +430,7 @@ impl<'map> CatchPerformance<'map> {
|
||||
state,
|
||||
};
|
||||
|
||||
inner.calculate()
|
||||
Ok(inner.calculate())
|
||||
}
|
||||
|
||||
pub(crate) const fn from_map_or_attrs(map_or_attrs: MapOrAttrs<'map, Catch>) -> Self {
|
||||
@@ -456,14 +457,12 @@ impl<'map> TryFrom<OsuPerformance<'map>> for CatchPerformance<'map> {
|
||||
/// if it was constructed through attributes or
|
||||
/// [`OsuPerformance::generate_state`] was called.
|
||||
fn try_from(mut osu: OsuPerformance<'map>) -> Result<Self, Self::Error> {
|
||||
let MapOrAttrs::Map(converted) = osu.map_or_attrs else {
|
||||
return Err(osu);
|
||||
};
|
||||
let mods = osu.difficulty.get_mods();
|
||||
|
||||
let map = match converted.try_convert() {
|
||||
let map = match OsuPerformance::try_convert_map(osu.map_or_attrs, GameMode::Catch, mods) {
|
||||
Ok(map) => map,
|
||||
Err(map) => {
|
||||
osu.map_or_attrs = MapOrAttrs::Map(map);
|
||||
Err(map_or_attrs) => {
|
||||
osu.map_or_attrs = map_or_attrs;
|
||||
|
||||
return Err(osu);
|
||||
}
|
||||
@@ -607,7 +606,7 @@ mod test {
|
||||
|
||||
use crate::{
|
||||
any::{DifficultyAttributes, PerformanceAttributes},
|
||||
osu::{Osu, OsuDifficultyAttributes, OsuPerformanceAttributes},
|
||||
osu::{OsuDifficultyAttributes, OsuPerformanceAttributes},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
@@ -626,8 +625,8 @@ mod test {
|
||||
fn attrs() -> CatchDifficultyAttributes {
|
||||
ATTRS
|
||||
.get_or_init(|| {
|
||||
let converted = beatmap().unchecked_into_converted::<Catch>();
|
||||
let attrs = Difficulty::new().with_mode().calculate(&converted);
|
||||
let map = beatmap();
|
||||
let attrs = Difficulty::new().calculate_for_mode::<Catch>(&map).unwrap();
|
||||
|
||||
assert_eq!(N_FRUITS, attrs.n_fruits);
|
||||
assert_eq!(N_DROPLETS, attrs.n_droplets);
|
||||
@@ -782,8 +781,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 expected = brute_force_best(
|
||||
@@ -807,7 +806,8 @@ mod test {
|
||||
.tiny_droplets(N_TINY_DROPLETS - 50)
|
||||
.tiny_droplet_misses(20)
|
||||
.misses(2)
|
||||
.generate_state();
|
||||
.generate_state()
|
||||
.unwrap();
|
||||
|
||||
let expected = CatchScoreState {
|
||||
max_combo: N_FRUITS + N_DROPLETS - 2,
|
||||
@@ -824,12 +824,11 @@ mod test {
|
||||
#[test]
|
||||
fn create() {
|
||||
let mut map = beatmap();
|
||||
let converted = map.unchecked_as_converted();
|
||||
|
||||
let _ = CatchPerformance::new(CatchDifficultyAttributes::default());
|
||||
let _ = CatchPerformance::new(CatchPerformanceAttributes::default());
|
||||
let _ = CatchPerformance::new(&converted);
|
||||
let _ = CatchPerformance::new(converted.as_owned());
|
||||
let _ = CatchPerformance::new(&map);
|
||||
let _ = CatchPerformance::new(map.clone());
|
||||
|
||||
let _ = CatchPerformance::try_new(CatchDifficultyAttributes::default()).unwrap();
|
||||
let _ = CatchPerformance::try_new(CatchPerformanceAttributes::default()).unwrap();
|
||||
@@ -841,19 +840,20 @@ mod test {
|
||||
CatchPerformanceAttributes::default(),
|
||||
))
|
||||
.unwrap();
|
||||
let _ = CatchPerformance::try_new(&converted).unwrap();
|
||||
let _ = CatchPerformance::try_new(converted.as_owned()).unwrap();
|
||||
let _ = CatchPerformance::try_new(&map).unwrap();
|
||||
let _ = CatchPerformance::try_new(map.clone()).unwrap();
|
||||
|
||||
let _ = CatchPerformance::from(CatchDifficultyAttributes::default());
|
||||
let _ = CatchPerformance::from(CatchPerformanceAttributes::default());
|
||||
let _ = CatchPerformance::from(&converted);
|
||||
let _ = CatchPerformance::from(converted);
|
||||
let _ = CatchPerformance::from(&map);
|
||||
let _ = CatchPerformance::from(map.clone());
|
||||
|
||||
let _ = CatchDifficultyAttributes::default().performance();
|
||||
let _ = CatchPerformanceAttributes::default().performance();
|
||||
|
||||
map.mode = GameMode::Osu;
|
||||
let converted = map.unchecked_as_converted::<Osu>();
|
||||
assert!(map
|
||||
.convert_mut(GameMode::Osu, &GameMods::default())
|
||||
.is_err());
|
||||
|
||||
assert!(CatchPerformance::try_new(OsuDifficultyAttributes::default()).is_none());
|
||||
assert!(CatchPerformance::try_new(OsuPerformanceAttributes::default()).is_none());
|
||||
@@ -865,7 +865,5 @@ mod test {
|
||||
OsuPerformanceAttributes::default()
|
||||
))
|
||||
.is_none());
|
||||
assert!(CatchPerformance::try_new(&converted).is_none());
|
||||
assert!(CatchPerformance::try_new(converted).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::{any::Difficulty, catch::difficulty::DifficultyValues};
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use super::convert::CatchBeatmap;
|
||||
use crate::{
|
||||
any::Difficulty, catch::difficulty::DifficultyValues, model::mode::ConvertError, Beatmap,
|
||||
};
|
||||
|
||||
/// The result of calculating the strains on a osu!catch map.
|
||||
///
|
||||
@@ -16,10 +18,11 @@ impl CatchStrains {
|
||||
pub const SECTION_LEN: f64 = 750.0;
|
||||
}
|
||||
|
||||
pub fn strains(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> CatchStrains {
|
||||
let DifficultyValues { movement, .. } = DifficultyValues::calculate(difficulty, converted);
|
||||
pub fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result<CatchStrains, ConvertError> {
|
||||
let map = map.convert_ref(GameMode::Catch, difficulty.get_mods())?;
|
||||
let DifficultyValues { movement, .. } = DifficultyValues::calculate(difficulty, &map);
|
||||
|
||||
CatchStrains {
|
||||
Ok(CatchStrains {
|
||||
movement: movement.get_curr_strain_peaks().into_vec(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user