Update to rosu-pp 1.0

This commit is contained in:
Natsu Kagami 2024-06-19 22:11:49 +02:00 committed by Natsu Kagami
parent 22fc980a51
commit 0b111d5b26
5 changed files with 75 additions and 247 deletions

15
Cargo.lock generated
View file

@ -1795,10 +1795,19 @@ dependencies = [
] ]
[[package]] [[package]]
name = "rosu-pp" name = "rosu-map"
version = "0.9.5" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "rosu-v2" name = "rosu-v2"

View file

@ -15,7 +15,7 @@ lazy_static = "1.4.0"
osuparse = { git = "https://github.com/eltrufas/osuparse", rev = "ad8f6e5e7771e7cbaa2ec96c376558f9731139af" } osuparse = { git = "https://github.com/eltrufas/osuparse", rev = "ad8f6e5e7771e7cbaa2ec96c376558f9731139af" }
regex = "1.5.6" regex = "1.5.6"
reqwest = "0.11.10" reqwest = "0.11.10"
rosu-pp = "0.9.1" rosu-pp = "1.0"
rosu-v2 = { git = "https://github.com/MaxOhn/rosu-v2", rev = "d2cd3ff8417e66890f0cd8ca38bc34717a9629dd" } rosu-v2 = { git = "https://github.com/MaxOhn/rosu-v2", rev = "d2cd3ff8417e66890f0cd8ca38bc34717a9629dd" }
time = "0.3" time = "0.3"
serde = { version = "1.0.137", features = ["derive"] } serde = { version = "1.0.137", features = ["derive"] }

View file

@ -70,13 +70,26 @@ pub fn beatmap_offline_embed(
let total_length = if !bm.hit_objects.is_empty() { let total_length = if !bm.hit_objects.is_empty() {
Duration::from_millis( 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, as u64,
) )
} else { } else {
Duration::from_secs(0) 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 { let diff = Difficulty {
stars: info.stars, stars: info.stars,
aim: None, // TODO: this is currently unused aim: None, // TODO: this is currently unused
@ -85,9 +98,9 @@ pub fn beatmap_offline_embed(
od: bm.od as f64, od: bm.od as f64,
ar: bm.ar as f64, ar: bm.ar as f64,
hp: bm.hp as f64, hp: bm.hp as f64,
count_normal: bm.n_circles as u64, count_normal: circles,
count_slider: bm.n_sliders as u64, count_slider: sliders,
count_spinner: bm.n_spinners as u64, count_spinner: spinners,
max_combo: Some(info.max_combo as u64), max_combo: Some(info.max_combo as u64),
bpm: bm.bpm(), bpm: bm.bpm(),
drain_length: total_length, // It's hard to calculate so maybe just skip... drain_length: total_length, // It's hard to calculate so maybe just skip...

View file

@ -2,11 +2,8 @@ use std::io::Read;
use std::sync::Arc; use std::sync::Arc;
use osuparse::MetadataSection; use osuparse::MetadataSection;
use rosu_pp::catch::CatchDifficultyAttributes; use rosu_pp::any::DifficultyAttributes;
use rosu_pp::mania::ManiaDifficultyAttributes; use rosu_pp::Beatmap;
use rosu_pp::osu::OsuDifficultyAttributes;
use rosu_pp::taiko::TaikoDifficultyAttributes;
use rosu_pp::{AttributeProvider, Beatmap, CatchPP, DifficultyAttributes, ManiaPP, OsuPP, TaikoPP};
use youmubot_db_sql::{models::osu as models, Pool}; use youmubot_db_sql::{models::osu as models, Pool};
use youmubot_prelude::*; use youmubot_prelude::*;
@ -32,7 +29,7 @@ impl BeatmapInfo {
fn extract(beatmap: &Beatmap, attrs: DifficultyAttributes) -> Self { fn extract(beatmap: &Beatmap, attrs: DifficultyAttributes) -> Self {
BeatmapInfo { BeatmapInfo {
objects: beatmap.hit_objects.len(), objects: beatmap.hit_objects.len(),
max_combo: attrs.max_combo(), max_combo: attrs.max_combo() as usize,
stars: attrs.stars(), stars: attrs.stars(),
} }
} }
@ -70,206 +67,6 @@ impl Accuracy {
/// Beatmap Info with attached 95/98/99/100% FC pp. /// Beatmap Info with attached 95/98/99/100% FC pp.
pub type BeatmapInfoWithPP = (BeatmapInfo, [f64; 4]); 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<usize>) -> 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<usize>, 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 { impl BeatmapContent {
/// Get pp given the combo and accuracy. /// Get pp given the combo and accuracy.
pub fn get_pp_from( pub fn get_pp_from(
@ -279,34 +76,55 @@ impl BeatmapContent {
accuracy: Accuracy, accuracy: Accuracy,
mods: Mods, mods: Mods,
) -> Result<f64> { ) -> Result<f64> {
let bm = self.content.as_ref(); let mut perf = self
Ok(match mode { .content
Mode::Std => OsuPP::map_pp(bm, mods, combo, accuracy), .performance()
Mode::Taiko => TaikoPP::map_pp(bm, mods, combo, accuracy), .mode_or_ignore(mode.into())
Mode::Catch => CatchPP::map_pp(bm, mods, combo, accuracy), .accuracy(accuracy.into())
Mode::Mania => ManiaPP::map_pp(bm, mods, combo, accuracy), .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. /// Get info given mods.
pub fn get_info_with(&self, mode: Mode, mods: Mods) -> Result<BeatmapInfo> { pub fn get_info_with(&self, mode: Mode, mods: Mods) -> Result<BeatmapInfo> {
let bm = self.content.as_ref(); let attrs = self
Ok(match mode { .content
Mode::Std => OsuPP::map_info(bm, mods), .performance()
Mode::Taiko => TaikoPP::map_info(bm, mods), .mode_or_ignore(mode.into())
Mode::Catch => CatchPP::map_info(bm, mods), .mods(mods.bits() as u32)
Mode::Mania => ManiaPP::map_info(bm, mods), .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<BeatmapInfoWithPP> { pub fn get_possible_pp_with(&self, mode: Mode, mods: Mods) -> Result<BeatmapInfoWithPP> {
let bm = self.content.as_ref(); let pp: [f64; 4] = [
Ok(match mode { self.get_pp_from(mode, None, Accuracy::ByValue(95.0, 0), mods)?,
Mode::Std => OsuPP::map_info_with_pp(bm, mods), self.get_pp_from(mode, None, Accuracy::ByValue(98.0, 0), mods)?,
Mode::Taiko => TaikoPP::map_info_with_pp(bm, mods), self.get_pp_from(mode, None, Accuracy::ByValue(99.0, 0), mods)?,
Mode::Catch => CatchPP::map_info_with_pp(bm, mods), self.get_pp_from(mode, None, Accuracy::ByValue(100.0, 0), mods)?,
Mode::Mania => ManiaPP::map_info_with_pp(bm, mods), ];
}) Ok((self.get_info_with(mode, mods)?, pp))
}
}
impl From<Mode> 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; .metadata;
Ok(BeatmapContent { Ok(BeatmapContent {
metadata, metadata,
content: Arc::new(Beatmap::parse(content.as_bytes())?), content: Arc::new(Beatmap::from_bytes(content.as_bytes())?),
}) })
} }

View file

@ -1,5 +1,4 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use rosu_pp::GameMode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::time::Duration; use std::time::Duration;
@ -275,17 +274,6 @@ impl From<u8> for Mode {
} }
} }
impl From<Mode> 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 { impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Mode::*; use Mode::*;