diff --git a/Cargo.lock b/Cargo.lock index c4c3ea8..d0fe733 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1795,10 +1795,19 @@ dependencies = [ ] [[package]] -name = "rosu-pp" -version = "0.9.5" +name = "rosu-map" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9e281b71d3797817a1e6615dd8fb081dd61359b4c41d08792cc7c3c1c13b4e" +checksum = "3c55926c8f0fed1db12fbe96f7a6083a2c4186443dd32532ab34e6902467a4f3" + +[[package]] +name = "rosu-pp" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26f146c66bed5900ee1fa2b55ef5cc5dd2dbd45e6cac0f7bee5cae535980afbc" +dependencies = [ + "rosu-map", +] [[package]] name = "rosu-v2" diff --git a/youmubot-osu/Cargo.toml b/youmubot-osu/Cargo.toml index 56daef1..d3b7519 100644 --- a/youmubot-osu/Cargo.toml +++ b/youmubot-osu/Cargo.toml @@ -15,7 +15,7 @@ lazy_static = "1.4.0" osuparse = { git = "https://github.com/eltrufas/osuparse", rev = "ad8f6e5e7771e7cbaa2ec96c376558f9731139af" } regex = "1.5.6" reqwest = "0.11.10" -rosu-pp = "0.9.1" +rosu-pp = "1.0" rosu-v2 = { git = "https://github.com/MaxOhn/rosu-v2", rev = "d2cd3ff8417e66890f0cd8ca38bc34717a9629dd" } time = "0.3" serde = { version = "1.0.137", features = ["derive"] } diff --git a/youmubot-osu/src/discord/embeds.rs b/youmubot-osu/src/discord/embeds.rs index f5ef3d8..a890750 100644 --- a/youmubot-osu/src/discord/embeds.rs +++ b/youmubot-osu/src/discord/embeds.rs @@ -70,13 +70,26 @@ pub fn beatmap_offline_embed( let total_length = if !bm.hit_objects.is_empty() { Duration::from_millis( - (bm.hit_objects.last().unwrap().end_time() - bm.hit_objects.first().unwrap().start_time) + (bm.hit_objects.last().unwrap().start_time - bm.hit_objects.first().unwrap().start_time) as u64, ) } else { Duration::from_secs(0) }; + let (circles, sliders, spinners) = { + let (mut circles, mut sliders, mut spinners) = (0u64, 0u64, 0u64); + for obj in bm.hit_objects.iter() { + match obj.kind { + rosu_pp::model::hit_object::HitObjectKind::Circle => circles += 1, + rosu_pp::model::hit_object::HitObjectKind::Slider(_) => sliders += 1, + rosu_pp::model::hit_object::HitObjectKind::Spinner(_) => spinners += 1, + rosu_pp::model::hit_object::HitObjectKind::Hold(_) => sliders += 1, + } + } + (circles, sliders, spinners) + }; + let diff = Difficulty { stars: info.stars, aim: None, // TODO: this is currently unused @@ -85,9 +98,9 @@ pub fn beatmap_offline_embed( od: bm.od as f64, ar: bm.ar as f64, hp: bm.hp as f64, - count_normal: bm.n_circles as u64, - count_slider: bm.n_sliders as u64, - count_spinner: bm.n_spinners as u64, + count_normal: circles, + count_slider: sliders, + count_spinner: spinners, max_combo: Some(info.max_combo as u64), bpm: bm.bpm(), drain_length: total_length, // It's hard to calculate so maybe just skip... diff --git a/youmubot-osu/src/discord/oppai_cache.rs b/youmubot-osu/src/discord/oppai_cache.rs index 1a788e9..7e99a5e 100644 --- a/youmubot-osu/src/discord/oppai_cache.rs +++ b/youmubot-osu/src/discord/oppai_cache.rs @@ -2,11 +2,8 @@ use std::io::Read; use std::sync::Arc; use osuparse::MetadataSection; -use rosu_pp::catch::CatchDifficultyAttributes; -use rosu_pp::mania::ManiaDifficultyAttributes; -use rosu_pp::osu::OsuDifficultyAttributes; -use rosu_pp::taiko::TaikoDifficultyAttributes; -use rosu_pp::{AttributeProvider, Beatmap, CatchPP, DifficultyAttributes, ManiaPP, OsuPP, TaikoPP}; +use rosu_pp::any::DifficultyAttributes; +use rosu_pp::Beatmap; use youmubot_db_sql::{models::osu as models, Pool}; use youmubot_prelude::*; @@ -32,7 +29,7 @@ impl BeatmapInfo { fn extract(beatmap: &Beatmap, attrs: DifficultyAttributes) -> Self { BeatmapInfo { objects: beatmap.hit_objects.len(), - max_combo: attrs.max_combo(), + max_combo: attrs.max_combo() as usize, stars: attrs.stars(), } } @@ -70,206 +67,6 @@ impl Accuracy { /// Beatmap Info with attached 95/98/99/100% FC pp. pub type BeatmapInfoWithPP = (BeatmapInfo, [f64; 4]); -trait PPCalc<'a>: Sized { - type Attrs: rosu_pp::AttributeProvider + Clone; - - fn new(beatmap: &'a Beatmap) -> Self; - fn mods(self, mods: u32) -> Self; - fn attributes(self, attrs: Self::Attrs) -> Self; - - /* For pp calculation */ - fn combo(self, combo: usize) -> Self; - fn accuracy(self, accuracy: f64) -> Self; - fn misses(self, misses: usize) -> Self; - fn get_pp(self) -> f64; - - /* For difficulty calculation */ - fn get_attrs(self) -> Self::Attrs; - - fn combo_opt(self, combo: Option) -> Self { - match combo { - Some(c) => self.combo(c), - None => self, - } - } - fn accuracy_from(self, accuracy: Accuracy) -> Self { - self.misses(accuracy.misses()).accuracy(accuracy.into()) - } - - fn map_attributes(beatmap: &'a Beatmap, mods: Mods) -> Self::Attrs { - Self::new(beatmap).mods(mods.bits() as u32).get_attrs() - } - fn map_pp(beatmap: &'a Beatmap, mods: Mods, combo: Option, accuracy: Accuracy) -> f64 { - Self::new(beatmap) - .mods(mods.bits() as u32) - .combo_opt(combo) - .accuracy_from(accuracy) - .get_pp() - } - fn map_info(beatmap: &'a Beatmap, mods: Mods) -> BeatmapInfo { - let attrs = Self::map_attributes(beatmap, mods).attributes(); - BeatmapInfo::extract(beatmap, attrs) - } - - fn map_info_with_pp(beatmap: &'a Beatmap, mods: Mods) -> BeatmapInfoWithPP { - let attrs = Self::map_attributes(beatmap, mods); - let nw = || { - Self::new(beatmap) - .mods(mods.bits() as u32) - .attributes(attrs.clone()) - }; - let pps = [ - nw().accuracy_from(Accuracy::ByValue(95.0, 0)).get_pp(), - nw().accuracy_from(Accuracy::ByValue(98.0, 0)).get_pp(), - nw().accuracy_from(Accuracy::ByValue(99.0, 0)).get_pp(), - nw().accuracy_from(Accuracy::ByValue(100.0, 0)).get_pp(), - ]; - let info = BeatmapInfo::extract(beatmap, attrs.attributes()); - (info, pps) - } -} - -impl<'a> PPCalc<'a> for OsuPP<'a> { - type Attrs = OsuDifficultyAttributes; - - fn new(beatmap: &'a Beatmap) -> Self { - Self::new(beatmap) - } - fn mods(self, mods: u32) -> Self { - self.mods(mods) - } - - fn attributes(self, attrs: Self::Attrs) -> Self { - self.attributes(attrs) - } - - fn combo(self, combo: usize) -> Self { - self.combo(combo) - } - - fn accuracy(self, accuracy: f64) -> Self { - self.accuracy(accuracy) - } - - fn misses(self, misses: usize) -> Self { - self.n_misses(misses) - } - - fn get_pp(self) -> f64 { - self.calculate().pp() - } - - fn get_attrs(self) -> Self::Attrs { - self.calculate().difficulty - } -} - -impl<'a> PPCalc<'a> for TaikoPP<'a> { - type Attrs = TaikoDifficultyAttributes; - - fn new(beatmap: &'a Beatmap) -> Self { - Self::new(beatmap) - } - fn mods(self, mods: u32) -> Self { - self.mods(mods) - } - - fn attributes(self, attrs: Self::Attrs) -> Self { - self.attributes(attrs) - } - - fn combo(self, combo: usize) -> Self { - self.combo(combo) - } - - fn accuracy(self, accuracy: f64) -> Self { - self.accuracy(accuracy) - } - - fn misses(self, misses: usize) -> Self { - self.n_misses(misses) - } - - fn get_pp(self) -> f64 { - self.calculate().pp() - } - - fn get_attrs(self) -> Self::Attrs { - self.calculate().difficulty - } -} - -impl<'a> PPCalc<'a> for CatchPP<'a> { - type Attrs = CatchDifficultyAttributes; - - fn new(beatmap: &'a Beatmap) -> Self { - Self::new(beatmap) - } - fn mods(self, mods: u32) -> Self { - self.mods(mods) - } - - fn attributes(self, attrs: Self::Attrs) -> Self { - self.attributes(attrs) - } - - fn combo(self, combo: usize) -> Self { - self.combo(combo) - } - - fn accuracy(self, accuracy: f64) -> Self { - self.accuracy(accuracy) - } - - fn misses(self, misses: usize) -> Self { - self.misses(misses) - } - - fn get_pp(self) -> f64 { - self.calculate().pp() - } - - fn get_attrs(self) -> Self::Attrs { - self.calculate().difficulty - } -} - -impl<'a> PPCalc<'a> for ManiaPP<'a> { - type Attrs = ManiaDifficultyAttributes; - - fn new(beatmap: &'a Beatmap) -> Self { - Self::new(beatmap) - } - fn mods(self, mods: u32) -> Self { - self.mods(mods) - } - - fn attributes(self, attrs: Self::Attrs) -> Self { - self.attributes(attrs) - } - - fn combo(self, _combo: usize) -> Self { - // Mania doesn't seem to care about combo? - self - } - - fn accuracy(self, accuracy: f64) -> Self { - self.accuracy(accuracy) - } - - fn misses(self, misses: usize) -> Self { - self.n_misses(misses) - } - - fn get_pp(self) -> f64 { - self.calculate().pp() - } - - fn get_attrs(self) -> Self::Attrs { - self.calculate().difficulty - } -} - impl BeatmapContent { /// Get pp given the combo and accuracy. pub fn get_pp_from( @@ -279,34 +76,55 @@ impl BeatmapContent { accuracy: Accuracy, mods: Mods, ) -> Result { - let bm = self.content.as_ref(); - Ok(match mode { - Mode::Std => OsuPP::map_pp(bm, mods, combo, accuracy), - Mode::Taiko => TaikoPP::map_pp(bm, mods, combo, accuracy), - Mode::Catch => CatchPP::map_pp(bm, mods, combo, accuracy), - Mode::Mania => ManiaPP::map_pp(bm, mods, combo, accuracy), - }) + let mut perf = self + .content + .performance() + .mode_or_ignore(mode.into()) + .accuracy(accuracy.into()) + .misses(accuracy.misses() as u32) + .mods(mods.bits() as u32); + if let Some(combo) = combo { + perf = perf.combo(combo as u32); + } + let attrs = perf.calculate(); + Ok(attrs.pp()) } /// Get info given mods. pub fn get_info_with(&self, mode: Mode, mods: Mods) -> Result { - let bm = self.content.as_ref(); - Ok(match mode { - Mode::Std => OsuPP::map_info(bm, mods), - Mode::Taiko => TaikoPP::map_info(bm, mods), - Mode::Catch => CatchPP::map_info(bm, mods), - Mode::Mania => ManiaPP::map_info(bm, mods), + let attrs = self + .content + .performance() + .mode_or_ignore(mode.into()) + .mods(mods.bits() as u32) + .calculate(); + Ok(BeatmapInfo { + objects: self.content.hit_objects.len(), + max_combo: attrs.max_combo() as usize, + stars: attrs.stars(), }) } pub fn get_possible_pp_with(&self, mode: Mode, mods: Mods) -> Result { - let bm = self.content.as_ref(); - Ok(match mode { - Mode::Std => OsuPP::map_info_with_pp(bm, mods), - Mode::Taiko => TaikoPP::map_info_with_pp(bm, mods), - Mode::Catch => CatchPP::map_info_with_pp(bm, mods), - Mode::Mania => ManiaPP::map_info_with_pp(bm, mods), - }) + let pp: [f64; 4] = [ + self.get_pp_from(mode, None, Accuracy::ByValue(95.0, 0), mods)?, + self.get_pp_from(mode, None, Accuracy::ByValue(98.0, 0), mods)?, + self.get_pp_from(mode, None, Accuracy::ByValue(99.0, 0), mods)?, + self.get_pp_from(mode, None, Accuracy::ByValue(100.0, 0), mods)?, + ]; + Ok((self.get_info_with(mode, mods)?, pp)) + } +} + +impl From for rosu_pp::model::mode::GameMode { + fn from(value: Mode) -> Self { + use rosu_pp::model::mode::GameMode; + match value { + Mode::Std => GameMode::Osu, + Mode::Taiko => GameMode::Taiko, + Mode::Catch => GameMode::Catch, + Mode::Mania => GameMode::Mania, + } } } @@ -337,7 +155,7 @@ impl BeatmapCache { .metadata; Ok(BeatmapContent { metadata, - content: Arc::new(Beatmap::parse(content.as_bytes())?), + content: Arc::new(Beatmap::from_bytes(content.as_bytes())?), }) } diff --git a/youmubot-osu/src/models/mod.rs b/youmubot-osu/src/models/mod.rs index 0f76f5a..54aac59 100644 --- a/youmubot-osu/src/models/mod.rs +++ b/youmubot-osu/src/models/mod.rs @@ -1,5 +1,4 @@ use chrono::{DateTime, Utc}; -use rosu_pp::GameMode; use serde::{Deserialize, Serialize}; use std::fmt; use std::time::Duration; @@ -275,17 +274,6 @@ impl From for Mode { } } -impl From for GameMode { - fn from(n: Mode) -> Self { - match n { - Mode::Std => GameMode::Osu, - Mode::Taiko => GameMode::Taiko, - Mode::Catch => GameMode::Catch, - Mode::Mania => GameMode::Mania, - } - } -} - impl fmt::Display for Mode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use Mode::*;