feat!: overhaul convert api

This commit is contained in:
MaxOhn
2024-11-11 02:51:43 +01:00
parent 9f45d9594d
commit b3d0475d47
49 changed files with 837 additions and 1298 deletions
+10 -31
View File
@@ -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)
}
+21 -20
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}
+8 -7
View File
@@ -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
+15 -14
View File
@@ -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);
}
+35 -37
View File
@@ -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());
}
}
+9 -6
View File
@@ -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(),
}
})
}