Enable pp calculation for all modes

This commit is contained in:
Natsu Kagami 2023-10-22 17:19:32 +02:00
parent a04fcca1d6
commit 17e59e7135
Signed by: nki
GPG key ID: 55A032EB38B49ADB
7 changed files with 325 additions and 167 deletions

View file

@ -184,8 +184,7 @@ mod scores {
let beatmap = osu.get_beatmap(play.beatmap_id, mode).await?; let beatmap = osu.get_beatmap(play.beatmap_id, mode).await?;
let info = { let info = {
let b = beatmap_cache.get_beatmap(beatmap.beatmap_id).await?; let b = beatmap_cache.get_beatmap(beatmap.beatmap_id).await?;
(if mode == Mode::Std { Some(mode) } else { None }) b.get_info_with(mode, play.mods).ok()
.and_then(|_| b.get_info_with(play.mods).ok())
}; };
Ok((beatmap, info)) as Result<(Beatmap, Option<BeatmapInfo>)> Ok((beatmap, info)) as Result<(Beatmap, Option<BeatmapInfo>)>
}) })
@ -199,23 +198,22 @@ mod scores {
Some(v) => Ok(v), Some(v) => Ok(v),
None => { None => {
let b = beatmap_cache.get_beatmap(p.beatmap_id).await?; let b = beatmap_cache.get_beatmap(p.beatmap_id).await?;
let r: Result<_> = let r: Result<_> = Ok({
Ok((if mode == Mode::Std { Some(mode) } else { None }) b.get_pp_from(
.and_then(|_| { mode,
b.get_pp_from( Some(p.max_combo as usize),
Some(p.max_combo as usize), Accuracy::ByCount(
Accuracy::ByCount( p.count_300,
p.count_300, p.count_100,
p.count_100, p.count_50,
p.count_50, p.count_miss,
p.count_miss, ),
), p.mods,
p.mods, )
) .ok()
.ok() .map(|pp| format!("{:.2}pp [?]", pp))
.map(|pp| format!("{:.2}pp [?]", pp)) }
}) .unwrap_or_else(|| "-".to_owned()));
.unwrap_or_else(|| "-".to_owned()));
r r
} }
} }
@ -389,24 +387,20 @@ mod beatmapset {
struct Paginate { struct Paginate {
maps: Vec<Beatmap>, maps: Vec<Beatmap>,
infos: Vec<Option<Option<BeatmapInfoWithPP>>>, infos: Vec<Option<BeatmapInfoWithPP>>,
mode: Option<Mode>, mode: Option<Mode>,
mods: Mods, mods: Mods,
message: String, message: String,
} }
impl Paginate { impl Paginate {
async fn get_beatmap_info(&self, ctx: &Context, b: &Beatmap) -> Option<BeatmapInfoWithPP> { async fn get_beatmap_info(&self, ctx: &Context, b: &Beatmap) -> Result<BeatmapInfoWithPP> {
let data = ctx.data.read().await; let data = ctx.data.read().await;
let cache = data.get::<BeatmapCache>().unwrap(); let cache = data.get::<BeatmapCache>().unwrap();
cache cache
.get_beatmap(b.beatmap_id) .get_beatmap(b.beatmap_id)
.map(move |v| {
v.ok()
.filter(|_| b.mode == Mode::Std)
.and_then(move |v| v.get_possible_pp_with(self.mods).ok())
})
.await .await
.and_then(move |v| v.get_possible_pp_with(self.mode.unwrap_or(b.mode), self.mods))
} }
} }
@ -440,7 +434,7 @@ mod beatmapset {
let info = match &self.infos[page] { let info = match &self.infos[page] {
Some(info) => *info, Some(info) => *info,
None => { None => {
let info = self.get_beatmap_info(ctx, map).await; let info = self.get_beatmap_info(ctx, map).await?;
self.infos[page] = Some(info); self.infos[page] = Some(info);
info info
} }

View file

@ -63,7 +63,7 @@ pub fn beatmap_offline_embed(
) -> Result<Box<dyn FnOnce(&mut CreateEmbed) -> &mut CreateEmbed + Send + Sync>> { ) -> Result<Box<dyn FnOnce(&mut CreateEmbed) -> &mut CreateEmbed + Send + Sync>> {
let bm = b.content.clone(); let bm = b.content.clone();
let metadata = b.metadata.clone(); let metadata = b.metadata.clone();
let (info, pp) = b.get_possible_pp_with(mods)?; let (info, pp) = b.get_possible_pp_with(m, mods)?;
let total_length = if bm.hit_objects.len() >= 1 { let total_length = if bm.hit_objects.len() >= 1 {
Duration::from_millis( Duration::from_millis(
@ -90,7 +90,7 @@ pub fn beatmap_offline_embed(
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...
total_length, total_length,
} }
.apply_mods(mods, Some(info.stars)); .apply_mods(mods, info.stars);
Ok(Box::new(move |c: &mut CreateEmbed| { Ok(Box::new(move |c: &mut CreateEmbed| {
c.title(beatmap_title( c.title(beatmap_title(
&metadata.artist, &metadata.artist,
@ -145,12 +145,10 @@ pub fn beatmap_embed<'a>(
b: &'_ Beatmap, b: &'_ Beatmap,
m: Mode, m: Mode,
mods: Mods, mods: Mods,
info: Option<BeatmapInfoWithPP>, info: BeatmapInfoWithPP,
c: &'a mut CreateEmbed, c: &'a mut CreateEmbed,
) -> &'a mut CreateEmbed { ) -> &'a mut CreateEmbed {
let diff = b let diff = b.difficulty.apply_mods(mods, info.0.stars);
.difficulty
.apply_mods(mods, info.map(|(v, _)| v.stars as f64));
c.title(beatmap_title(&b.artist, &b.title, &b.difficulty_name, mods)) c.title(beatmap_title(&b.artist, &b.title, &b.difficulty_name, mods))
.author(|a| { .author(|a| {
a.name(&b.creator) a.name(&b.creator)
@ -160,24 +158,19 @@ pub fn beatmap_embed<'a>(
.url(b.link()) .url(b.link())
.image(b.cover_url()) .image(b.cover_url())
.color(0xffb6c1) .color(0xffb6c1)
.fields(info.map(|(_, pp)| { .fields({
( let pp = info.1;
std::iter::once((
"Calculated pp", "Calculated pp",
format!( format!(
"95%: **{:.2}**pp, 98%: **{:.2}**pp, 99%: **{:.2}**pp, 100%: **{:.2}**pp", "95%: **{:.2}**pp, 98%: **{:.2}**pp, 99%: **{:.2}**pp, 100%: **{:.2}**pp",
pp[0], pp[1], pp[2], pp[3] pp[0], pp[1], pp[2], pp[3]
), ),
false, false,
) ))
})) })
.field("Information", diff.format_info(m, mods, b), false) .field("Information", diff.format_info(m, mods, b), false)
.description(beatmap_description(b)) .description(beatmap_description(b))
.footer(|f| {
if info.is_none() && mods != Mods::NOMOD {
f.text("Star difficulty not reflecting mods applied.");
}
f
})
} }
const MAX_DIFFS: usize = 25 - 4; const MAX_DIFFS: usize = 25 - 4;
@ -283,11 +276,7 @@ impl<'a> ScoreEmbedBuilder<'a> {
let content = self.content; let content = self.content;
let u = self.u; let u = self.u;
let accuracy = s.accuracy(mode); let accuracy = s.accuracy(mode);
let info = if mode == Mode::Std { let info = content.get_info_with(mode, s.mods).ok();
content.get_info_with(s.mods).ok()
} else {
None
};
let stars = info let stars = info
.as_ref() .as_ref()
.map(|info| info.stars) .map(|info| info.stars)
@ -312,34 +301,25 @@ impl<'a> ScoreEmbedBuilder<'a> {
), ),
}; };
let pp = s.pp.map(|pp| (pp, format!("{:.2}pp", pp))).or_else(|| { let pp = s.pp.map(|pp| (pp, format!("{:.2}pp", pp))).or_else(|| {
(if mode == Mode::Std { Some(mode) } else { None }) content
.and_then(|_| { .get_pp_from(
content mode,
.get_pp_from( Some(s.max_combo as usize),
Some(s.max_combo as usize), Accuracy::ByCount(s.count_300, s.count_100, s.count_50, s.count_miss),
Accuracy::ByCount(s.count_300, s.count_100, s.count_50, s.count_miss), s.mods,
s.mods, )
) .ok()
.ok()
})
.map(|pp| (pp as f64, format!("{:.2}pp [?]", pp))) .map(|pp| (pp as f64, format!("{:.2}pp [?]", pp)))
}); });
let pp = if !s.perfect { let pp = if !s.perfect {
(if mode == Mode::Std { Some(mode) } else { None }) content
.and_then(|_| { .get_pp_from(
content mode,
.get_pp_from( None,
None, Accuracy::ByCount(s.count_300 + s.count_miss, s.count_100, s.count_50, 0),
Accuracy::ByCount( s.mods,
s.count_300 + s.count_miss, )
s.count_100, .ok()
s.count_50,
0,
),
s.mods,
)
.ok()
})
.filter(|&v| { .filter(|&v| {
pp.as_ref() pp.as_ref()
.map(|&(origin, _)| origin < v as f64) .map(|&(origin, _)| origin < v as f64)
@ -382,7 +362,7 @@ impl<'a> ScoreEmbedBuilder<'a> {
.world_record .world_record
.map(|v| format!("| #{} on Global Rankings!", v)) .map(|v| format!("| #{} on Global Rankings!", v))
.unwrap_or_else(|| "".to_owned()); .unwrap_or_else(|| "".to_owned());
let diff = b.difficulty.apply_mods(s.mods, Some(stars)); let diff = b.difficulty.apply_mods(s.mods, stars);
let creator = if b.difficulty_name.contains("'s") { let creator = if b.difficulty_name.contains("'s") {
"".to_owned() "".to_owned()
} else { } else {
@ -442,7 +422,7 @@ impl<'a> ScoreEmbedBuilder<'a> {
pub(crate) fn user_embed( pub(crate) fn user_embed(
u: User, u: User,
best: Option<(Score, BeatmapWithMode, Option<BeatmapInfo>)>, best: Option<(Score, BeatmapWithMode, BeatmapInfo)>,
m: &mut CreateEmbed, m: &mut CreateEmbed,
) -> &mut CreateEmbed { ) -> &mut CreateEmbed {
m.title(u.username) m.title(u.username)
@ -521,7 +501,7 @@ pub(crate) fn user_embed(
.push(format!( .push(format!(
"> {}", "> {}",
map.difficulty map.difficulty
.apply_mods(v.mods, info.map(|i| i.stars as f64)) .apply_mods(v.mods, info.stars as f64)
.format_info(mode, v.mods, &map) .format_info(mode, v.mods, &map)
.replace("\n", "\n> ") .replace("\n", "\n> ")
)) ))

View file

@ -163,7 +163,7 @@ pub fn hook<'a>(
} }
enum EmbedType { enum EmbedType {
Beatmap(Beatmap, Option<BeatmapInfoWithPP>, Mods), Beatmap(Beatmap, BeatmapInfoWithPP, Mods),
Beatmapset(Vec<Beatmap>), Beatmapset(Vec<Beatmap>),
} }
@ -217,13 +217,12 @@ fn handle_old_links<'a>(
.map(|v| Mods::from_str(v.as_str()).pls_ok()) .map(|v| Mods::from_str(v.as_str()).pls_ok())
.flatten() .flatten()
.unwrap_or(Mods::NOMOD); .unwrap_or(Mods::NOMOD);
let info = match mode.unwrap_or(b.mode) { let info = {
Mode::Std => cache let mode = mode.unwrap_or(b.mode);
cache
.get_beatmap(b.beatmap_id) .get_beatmap(b.beatmap_id)
.await .await
.and_then(|b| b.get_possible_pp_with(mods)) .and_then(|b| b.get_possible_pp_with(mode, mods))?
.pls_ok(),
_ => None,
}; };
Some(ToPrint { Some(ToPrint {
embed: EmbedType::Beatmap(b, info, mods), embed: EmbedType::Beatmap(b, info, mods),
@ -287,13 +286,12 @@ fn handle_new_links<'a>(
.name("mods") .name("mods")
.and_then(|v| Mods::from_str(v.as_str()).pls_ok()) .and_then(|v| Mods::from_str(v.as_str()).pls_ok())
.unwrap_or(Mods::NOMOD); .unwrap_or(Mods::NOMOD);
let info = match mode.unwrap_or(beatmap.mode) { let info = {
Mode::Std => cache let mode = mode.unwrap_or(beatmap.mode);
cache
.get_beatmap(beatmap.beatmap_id) .get_beatmap(beatmap.beatmap_id)
.await .await
.and_then(|b| b.get_possible_pp_with(mods)) .and_then(|b| b.get_possible_pp_with(mode, mods))?
.pls_ok(),
_ => None,
}; };
Some(ToPrint { Some(ToPrint {
embed: EmbedType::Beatmap(beatmap, info, mods), embed: EmbedType::Beatmap(beatmap, info, mods),
@ -353,13 +351,12 @@ fn handle_short_links<'a>(
.name("mods") .name("mods")
.and_then(|v| Mods::from_str(v.as_str()).pls_ok()) .and_then(|v| Mods::from_str(v.as_str()).pls_ok())
.unwrap_or(Mods::NOMOD); .unwrap_or(Mods::NOMOD);
let info = match mode.unwrap_or(beatmap.mode) { let info = {
Mode::Std => cache let mode = mode.unwrap_or(beatmap.mode);
cache
.get_beatmap(beatmap.beatmap_id) .get_beatmap(beatmap.beatmap_id)
.await .await
.and_then(|b| b.get_possible_pp_with(mods)) .and_then(|b| b.get_possible_pp_with(mode, mods))?
.pls_ok(),
_ => None,
}; };
let r: Result<_> = Ok(ToPrint { let r: Result<_> = Ok(ToPrint {
embed: EmbedType::Beatmap(beatmap, info, mods), embed: EmbedType::Beatmap(beatmap, info, mods),
@ -383,7 +380,7 @@ fn handle_short_links<'a>(
async fn handle_beatmap<'a, 'b>( async fn handle_beatmap<'a, 'b>(
ctx: &Context, ctx: &Context,
beatmap: &Beatmap, beatmap: &Beatmap,
info: Option<BeatmapInfoWithPP>, info: BeatmapInfoWithPP,
link: &'_ str, link: &'_ str,
mode: Option<Mode>, mode: Option<Mode>,
mods: Mods, mods: Mods,

View file

@ -194,9 +194,15 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
.into_iter() .into_iter()
.next() .next()
.unwrap(); .unwrap();
let info = data
.get::<BeatmapCache>()
.unwrap()
.get_beatmap(beatmap.beatmap_id)
.await?
.get_possible_pp_with(Mode::Std, Mods::NOMOD)?;
msg.await? msg.await?
.edit(&ctx, |f| { .edit(&ctx, |f| {
f.embed(|e| beatmap_embed(&beatmap, Mode::Std, Mods::NOMOD, None, e)) f.embed(|e| beatmap_embed(&beatmap, Mode::Std, Mods::NOMOD, info, e))
}) })
.await?; .await?;
return Ok(()); return Ok(());
@ -478,8 +484,7 @@ pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
.unwrap() .unwrap()
.get_beatmap(b.beatmap_id) .get_beatmap(b.beatmap_id)
.await? .await?
.get_possible_pp_with(mods) .get_possible_pp_with(m, mods)?;
.ok();
msg.channel_id msg.channel_id
.send_message(&ctx, |f| { .send_message(&ctx, |f| {
f.content("Here is the beatmap you requested!") f.content("Here is the beatmap you requested!")
@ -646,15 +651,10 @@ async fn get_user(ctx: &Context, msg: &Message, mut args: Args, mode: Mode) -> C
{ {
Some(m) => { Some(m) => {
let beatmap = cache.get_beatmap(m.beatmap_id, mode).await?; let beatmap = cache.get_beatmap(m.beatmap_id, mode).await?;
let info = match mode { let info = oppai
Mode::Std => Some( .get_beatmap(m.beatmap_id)
oppai .await?
.get_beatmap(m.beatmap_id) .get_info_with(mode, m.mods)?;
.await?
.get_info_with(m.mods)?,
),
_ => None,
};
Some((m, BeatmapWithMode(beatmap, mode), info)) Some((m, BeatmapWithMode(beatmap, mode), info))
} }
None => None, None => None,

View file

@ -1,6 +1,10 @@
use crate::mods::Mods; use crate::{models::Mode, mods::Mods};
use osuparse::MetadataSection; use osuparse::MetadataSection;
use rosu_pp::{Beatmap, BeatmapExt}; 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 std::io::Read; use std::io::Read;
use std::sync::Arc; use std::sync::Arc;
use youmubot_db_sql::{models::osu as models, Pool}; use youmubot_db_sql::{models::osu as models, Pool};
@ -21,6 +25,16 @@ pub struct BeatmapInfo {
pub stars: f64, pub stars: f64,
} }
impl BeatmapInfo {
fn extract(beatmap: &Beatmap, attrs: DifficultyAttributes) -> Self {
BeatmapInfo {
objects: beatmap.hit_objects.len(),
max_combo: attrs.max_combo(),
stars: attrs.stars(),
}
}
}
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Accuracy { pub enum Accuracy {
ByCount(u64, u64, u64, u64), // 300 / 100 / 50 / misses ByCount(u64, u64, u64, u64), // 300 / 100 / 50 / misses
@ -52,68 +66,240 @@ 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]);
impl BeatmapContent { trait PPCalc<'a>: Sized {
/// Get pp given the combo and accuracy. type Attrs: rosu_pp::AttributeProvider + Clone;
pub fn get_pp_from(&self, combo: Option<usize>, accuracy: Accuracy, mods: Mods) -> Result<f64> {
let bm = self.content.as_ref(); fn new(beatmap: &'a Beatmap) -> Self;
let mut rosu = rosu_pp::OsuPP::new(bm).mods(mods.bits() as u32); fn mods(self, mods: u32) -> Self;
if let Some(combo) = combo { fn attributes(self, attrs: Self::Attrs) -> Self;
rosu = rosu.combo(combo);
/* 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,
} }
if let Accuracy::ByCount(n300, n100, n50, _) = accuracy { }
rosu = rosu fn accuracy_from(self, accuracy: Accuracy) -> Self {
.n300(n300 as usize) self.misses(accuracy.misses()).accuracy(accuracy.into())
.n100(n100 as usize)
.n50(n50 as usize);
}
Ok(rosu
.n_misses(accuracy.misses())
.accuracy(accuracy.into())
.calculate()
.pp)
} }
/// Get info given mods. fn map_attributes(beatmap: &'a Beatmap, mods: Mods) -> Self::Attrs {
pub fn get_info_with(&self, mods: Mods) -> Result<BeatmapInfo> { Self::new(beatmap).mods(mods.bits() as u32).get_attrs()
let stars = self.content.stars().mods(mods.bits() as u32).calculate(); }
Ok(BeatmapInfo { fn map_pp(beatmap: &'a Beatmap, mods: Mods, combo: Option<usize>, accuracy: Accuracy) -> f64 {
max_combo: stars.max_combo(), Self::new(beatmap)
objects: self.content.hit_objects.len(), .mods(mods.bits() as u32)
stars: stars.stars(), .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(
&self,
mode: Mode,
combo: Option<usize>,
accuracy: Accuracy,
mods: Mods,
) -> Result<f64> {
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),
}) })
} }
pub fn get_possible_pp_with(&self, mods: Mods) -> Result<BeatmapInfoWithPP> { /// Get info given mods.
let rosu = || self.content.pp().mods(mods.bits() as u32); pub fn get_info_with(&self, mode: Mode, mods: Mods) -> Result<BeatmapInfo> {
let pp95 = rosu().accuracy(95.0).calculate(); let bm = self.content.as_ref();
let pp = [ Ok(match mode {
pp95.pp(), Mode::Std => OsuPP::map_info(bm, mods),
rosu() Mode::Taiko => TaikoPP::map_info(bm, mods),
.attributes(pp95.clone()) Mode::Catch => CatchPP::map_info(bm, mods),
.accuracy(98.0) Mode::Mania => ManiaPP::map_info(bm, mods),
.calculate() })
.pp(), }
rosu()
.attributes(pp95.clone()) pub fn get_possible_pp_with(&self, mode: Mode, mods: Mods) -> Result<BeatmapInfoWithPP> {
.accuracy(99.0) let bm = self.content.as_ref();
.calculate() Ok(match mode {
.pp(), Mode::Std => OsuPP::map_info_with_pp(bm, mods),
rosu() Mode::Taiko => TaikoPP::map_info_with_pp(bm, mods),
.attributes(pp95.clone()) Mode::Catch => CatchPP::map_info_with_pp(bm, mods),
.accuracy(100.0) Mode::Mania => ManiaPP::map_info_with_pp(bm, mods),
.calculate() })
.pp(),
];
let max_combo = pp95.difficulty_attributes().max_combo();
let stars = pp95.difficulty_attributes().stars();
Ok((
BeatmapInfo {
objects: self.content.hit_objects.len(),
max_combo,
stars,
},
pp,
))
} }
} }

View file

@ -277,8 +277,9 @@ async fn show_leaderboard(
let oppai = data.get::<BeatmapCache>().unwrap(); let oppai = data.get::<BeatmapCache>().unwrap();
let oppai_map = oppai.get_beatmap(bm.0.beatmap_id).await?; let oppai_map = oppai.get_beatmap(bm.0.beatmap_id).await?;
let get_oppai_pp = move |combo: u64, acc: Accuracy, mods: Mods| { let get_oppai_pp = move |combo: u64, acc: Accuracy, mods: Mods| {
(if mode == Mode::Std { Some(mode) } else { None }) oppai_map
.and_then(|_| oppai_map.get_pp_from(Some(combo as usize), acc, mods).ok()) .get_pp_from(mode, Some(combo as usize), acc, mods)
.ok()
}; };
let guild = m.guild_id.expect("Guild-only command"); let guild = m.guild_id.expect("Guild-only command");

View file

@ -99,9 +99,9 @@ impl Difficulty {
} }
/// Apply mods to the given difficulty. /// Apply mods to the given difficulty.
/// Note that `stars`, `aim` and `speed` cannot be calculated from this alone. /// Note that `stars`, `aim` and `speed` cannot be calculated from this alone.
pub fn apply_mods(&self, mods: Mods, updated_stars: Option<f64>) -> Difficulty { pub fn apply_mods(&self, mods: Mods, updated_stars: f64) -> Difficulty {
let mut diff = Difficulty { let mut diff = Difficulty {
stars: updated_stars.unwrap_or(self.stars), stars: updated_stars,
..self.clone() ..self.clone()
}; };