Temporarily move to rosu

This commit is contained in:
Natsu Kagami 2022-02-09 15:20:36 -05:00
parent a1975d5b54
commit 51db72acb2
Signed by: nki
GPG key ID: 7306B3D3C3AD6E51
10 changed files with 186 additions and 172 deletions

19
Cargo.lock generated
View file

@ -1029,17 +1029,6 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "oppai-rs"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e060483dec5ed6590103a045f0a0fe09c06e185d582ec66efaa87e24cc1e877e"
dependencies = [
"bitflags",
"cc",
"libc",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.11.1" version = "0.11.1"
@ -1349,6 +1338,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "rosu-pp"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4efb6f419a910e96683aada6e12c14f4a4b1da286f1a205a1edc3eca3b8fa69e"
[[package]] [[package]]
name = "rustbreak" name = "rustbreak"
version = "2.0.0" version = "2.0.0"
@ -2308,9 +2303,9 @@ dependencies = [
"chrono", "chrono",
"dashmap", "dashmap",
"lazy_static", "lazy_static",
"oppai-rs",
"regex", "regex",
"reqwest", "reqwest",
"rosu-pp",
"serde", "serde",
"serde_json", "serde_json",
"serenity", "serenity",

View file

@ -14,7 +14,7 @@ serde = { version = "1.0", features = ["derive"] }
bitflags = "1" bitflags = "1"
lazy_static = "1" lazy_static = "1"
regex = "1" regex = "1"
oppai-rs = "0.2" rosu-pp = "0.4"
dashmap = "4" dashmap = "4"
bincode = "1" bincode = "1"

View file

@ -122,6 +122,7 @@ mod scores {
} }
pub mod table { pub mod table {
use crate::discord::oppai_cache::Accuracy;
use crate::discord::{Beatmap, BeatmapCache, BeatmapInfo, BeatmapMetaCache}; use crate::discord::{Beatmap, BeatmapCache, BeatmapInfo, BeatmapMetaCache};
use crate::models::{Mode, Score}; use crate::models::{Mode, Score};
use serenity::{framework::standard::CommandResult, model::channel::Message}; use serenity::{framework::standard::CommandResult, model::channel::Message};
@ -183,8 +184,8 @@ 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?;
mode.to_oppai_mode() (if mode == Mode::Std { Some(mode) } else { None })
.and_then(|mode| b.get_info_with(Some(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>)>
}) })
@ -198,25 +199,23 @@ 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<_> = Ok(mode let r: Result<_> =
.to_oppai_mode() Ok((if mode == Mode::Std { Some(mode) } else { None })
.and_then(|op| { .and_then(|_| {
b.get_pp_from( b.get_pp_from(
oppai_rs::Combo::NonFC { Some(p.max_combo as usize),
max_combo: p.max_combo as u32, Accuracy::ByCount(
misses: p.count_miss as u32, p.count_300,
}, p.count_100,
oppai_rs::Accuracy::from_hits( p.count_50,
p.count_100 as u32, p.count_miss,
p.count_50 as u32, ),
), p.mods,
Some(op), )
p.mods, .ok()
) .map(|pp| format!("{:.2}pp [?]", pp))
.ok() })
.map(|pp| format!("{:.2}pp [?]", pp)) .unwrap_or_else(|| "-".to_owned()));
})
.unwrap_or_else(|| "-".to_owned()));
r r
} }
} }
@ -325,7 +324,7 @@ mod scores {
page + 1, page + 1,
self.total_pages() self.total_pages()
)); ));
if self.mode.to_oppai_mode().is_none() { if self.mode != Mode::Std {
m.push_line("Note: star difficulty doesn't reflect mods applied."); m.push_line("Note: star difficulty doesn't reflect mods applied.");
} else { } else {
m.push_line("[?] means pp was predicted by oppai-rs."); m.push_line("[?] means pp was predicted by oppai-rs.");
@ -400,12 +399,12 @@ mod beatmapset {
async fn get_beatmap_info(&self, ctx: &Context, b: &Beatmap) -> Option<BeatmapInfoWithPP> { async fn get_beatmap_info(&self, ctx: &Context, b: &Beatmap) -> Option<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();
let mode = self.mode.unwrap_or(b.mode).to_oppai_mode();
cache cache
.get_beatmap(b.beatmap_id) .get_beatmap(b.beatmap_id)
.map(move |v| { .map(move |v| {
v.ok() v.ok()
.and_then(move |v| v.get_possible_pp_with(Some(mode?), self.mods).ok()) .filter(|_| b.mode == Mode::Std)
.and_then(move |v| v.get_possible_pp_with(self.mods).ok())
}) })
.await .await
} }

View file

@ -1,6 +1,6 @@
use super::BeatmapWithMode; use super::BeatmapWithMode;
use crate::{ use crate::{
discord::oppai_cache::{BeatmapContent, BeatmapInfo, BeatmapInfoWithPP, OppaiAccuracy}, discord::oppai_cache::{Accuracy, BeatmapContent, BeatmapInfo, BeatmapInfoWithPP},
models::{Beatmap, Mode, Mods, Rank, Score, User}, models::{Beatmap, Mode, Mods, Rank, Score, User},
}; };
use serenity::{builder::CreateEmbed, utils::MessageBuilder}; use serenity::{builder::CreateEmbed, utils::MessageBuilder};
@ -212,12 +212,14 @@ 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 = mode let info = if mode == Mode::Std {
.to_oppai_mode() content.get_info_with(s.mods).ok()
.and_then(|mode| content.get_info_with(Some(mode), s.mods).ok()); } else {
None
};
let stars = info let stars = info
.as_ref() .as_ref()
.map(|info| info.stars as f64) .map(|info| info.stars)
.unwrap_or(b.difficulty.stars); .unwrap_or(b.difficulty.stars);
let score_line = match s.rank { let score_line = match s.rank {
Rank::SS | Rank::SSH => "SS".to_string(), Rank::SS | Rank::SSH => "SS".to_string(),
@ -239,13 +241,12 @@ 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(|| {
mode.to_oppai_mode() (if mode == Mode::Std { Some(mode) } else { None })
.and_then(|op| { .and_then(|_| {
content content
.get_pp_from( .get_pp_from(
oppai_rs::Combo::non_fc(s.max_combo as u32, s.count_miss as u32), Some(s.max_combo as usize),
OppaiAccuracy::from_hits(s.count_100 as u32, s.count_50 as u32), Accuracy::ByCount(s.count_300, s.count_100, s.count_50, s.count_miss),
Some(op),
s.mods, s.mods,
) )
.ok() .ok()
@ -253,13 +254,17 @@ impl<'a> ScoreEmbedBuilder<'a> {
.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 {
mode.to_oppai_mode() (if mode == Mode::Std { Some(mode) } else { None })
.and_then(|op| { .and_then(|_| {
content content
.get_pp_from( .get_pp_from(
oppai_rs::Combo::FC(0), None,
OppaiAccuracy::from_hits(s.count_100 as u32, s.count_50 as u32), Accuracy::ByCount(
Some(op), s.count_300 + s.count_miss,
s.count_100,
s.count_50,
0,
),
s.mods, s.mods,
) )
.ok() .ok()
@ -354,7 +359,7 @@ impl<'a> ScoreEmbedBuilder<'a> {
) )
.field("Map stats", diff.format_info(mode, s.mods, b), false); .field("Map stats", diff.format_info(mode, s.mods, b), false);
let mut footer = self.footer.take().unwrap_or_else(String::new); let mut footer = self.footer.take().unwrap_or_else(String::new);
if mode.to_oppai_mode().is_none() && s.mods != Mods::NOMOD { if mode != Mode::Std && s.mods != Mods::NOMOD {
footer += " Star difficulty does not reflect game mods."; footer += " Star difficulty does not reflect game mods.";
} }
if !footer.is_empty() { if !footer.is_empty() {

View file

@ -122,13 +122,13 @@ 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).to_oppai_mode() { let info = match mode.unwrap_or(b.mode) {
Some(mode) => cache Mode::Std => cache
.get_beatmap(b.beatmap_id) .get_beatmap(b.beatmap_id)
.await .await
.and_then(|b| b.get_possible_pp_with(Some(mode), mods)) .and_then(|b| b.get_possible_pp_with(mods))
.pls_ok(), .pls_ok(),
None => None, _ => None,
}; };
Some(ToPrint { Some(ToPrint {
embed: EmbedType::Beatmap(b, info, mods), embed: EmbedType::Beatmap(b, info, mods),
@ -192,13 +192,13 @@ 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).to_oppai_mode() { let info = match mode.unwrap_or(beatmap.mode) {
Some(mode) => cache Mode::Std => cache
.get_beatmap(beatmap.beatmap_id) .get_beatmap(beatmap.beatmap_id)
.await .await
.and_then(|b| b.get_possible_pp_with(Some(mode), mods)) .and_then(|b| b.get_possible_pp_with(mods))
.pls_ok(), .pls_ok(),
None => None, _ => None,
}; };
Some(ToPrint { Some(ToPrint {
embed: EmbedType::Beatmap(beatmap, info, mods), embed: EmbedType::Beatmap(beatmap, info, mods),
@ -258,13 +258,13 @@ 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).to_oppai_mode() { let info = match mode.unwrap_or(beatmap.mode) {
Some(mode) => cache Mode::Std => cache
.get_beatmap(beatmap.beatmap_id) .get_beatmap(beatmap.beatmap_id)
.await .await
.and_then(|b| b.get_possible_pp_with(Some(mode), mods)) .and_then(|b| b.get_possible_pp_with(mods))
.pls_ok(), .pls_ok(),
None => None, _ => None,
}; };
let r: Result<_> = Ok(ToPrint { let r: Result<_> = Ok(ToPrint {
embed: EmbedType::Beatmap(beatmap, info, mods), embed: EmbedType::Beatmap(beatmap, info, mods),

View file

@ -421,7 +421,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(m.to_oppai_mode(), mods) .get_possible_pp_with(mods)
.ok(); .ok();
msg.channel_id msg.channel_id
.send_message(&ctx, |f| { .send_message(&ctx, |f| {
@ -589,14 +589,14 @@ 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.to_oppai_mode() { let info = match mode {
Some(mode) => Some( Mode::Std => Some(
oppai oppai
.get_beatmap(m.beatmap_id) .get_beatmap(m.beatmap_id)
.await? .await?
.get_info_with(Some(mode), m.mods)?, .get_info_with(m.mods)?,
), ),
None => None, _ => None,
}; };
Some((m, BeatmapWithMode(beatmap, mode), info)) Some((m, BeatmapWithMode(beatmap, mode), info))
} }

View file

@ -1,77 +1,106 @@
use std::{ffi::CString, sync::Arc}; use crate::mods::Mods;
use rosu_pp::{Beatmap, BeatmapExt};
use std::sync::Arc;
use youmubot_db_sql::{models::osu as models, Pool}; use youmubot_db_sql::{models::osu as models, Pool};
use youmubot_prelude::*; use youmubot_prelude::*;
pub use oppai_rs::Accuracy as OppaiAccuracy;
/// the information collected from a download/Oppai request. /// the information collected from a download/Oppai request.
#[derive(Debug)] #[derive(Debug)]
pub struct BeatmapContent { pub struct BeatmapContent {
id: u64, id: u64,
content: Arc<CString>, content: Arc<Beatmap>,
} }
/// the output of "one" oppai run. /// the output of "one" oppai run.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct BeatmapInfo { pub struct BeatmapInfo {
pub objects: u32, pub objects: usize,
pub stars: f32, pub stars: f64,
}
#[derive(Clone, Copy, Debug)]
pub enum Accuracy {
ByCount(u64, u64, u64, u64), // 300 / 100 / 50 / misses
#[allow(dead_code)]
ByValue(f64, u64),
}
impl Into<f64> for Accuracy {
fn into(self) -> f64 {
match self {
Accuracy::ByValue(v, _) => v,
Accuracy::ByCount(n300, n100, n50, nmiss) => {
((6 * n300 + 2 * n100 + n50) as f64) / ((6 * (n300 + n100 + n50 + nmiss)) as f64)
}
}
}
}
impl Accuracy {
pub fn misses(&self) -> usize {
(match self {
Accuracy::ByCount(_, _, _, nmiss) => *nmiss,
Accuracy::ByValue(_, nmiss) => *nmiss,
}) as usize
}
} }
/// Beatmap Info with attached 95/98/99/100% FC pp. /// Beatmap Info with attached 95/98/99/100% FC pp.
pub type BeatmapInfoWithPP = (BeatmapInfo, [f32; 4]); pub type BeatmapInfoWithPP = (BeatmapInfo, [f64; 4]);
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(&self, combo: Option<usize>, accuracy: Accuracy, mods: Mods) -> Result<f64> {
&self, let bm = self.content.as_ref();
combo: oppai_rs::Combo, let mut rosu = rosu_pp::OsuPP::new(bm).mods(mods.bits() as u32);
accuracy: impl Into<OppaiAccuracy>, if let Some(combo) = combo {
mode: Option<oppai_rs::Mode>, rosu = rosu.combo(combo);
mods: impl Into<oppai_rs::Mods>,
) -> Result<f32> {
let mut oppai = oppai_rs::Oppai::new_from_content(&self.content[..])?;
oppai.combo(combo)?.accuracy(accuracy)?.mods(mods.into());
if let Some(mode) = mode {
oppai.mode(mode)?;
} }
Ok(oppai.pp()) if let Accuracy::ByCount(n300, n100, n50, _) = accuracy {
rosu = rosu
.n300(n300 as usize)
.n100(n100 as usize)
.n50(n50 as usize);
}
Ok(rosu
.misses(accuracy.misses())
.accuracy(accuracy.into())
.calculate()
.pp)
} }
/// Get info given mods. /// Get info given mods.
pub fn get_info_with( pub fn get_info_with(&self, mods: Mods) -> Result<BeatmapInfo> {
&self, let stars = self.content.stars(mods.bits() as u32, None);
mode: Option<oppai_rs::Mode>, Ok(BeatmapInfo {
mods: impl Into<oppai_rs::Mods>, objects: stars.max_combo().unwrap_or(0),
) -> Result<BeatmapInfo> { stars: stars.stars(),
let mut oppai = oppai_rs::Oppai::new_from_content(&self.content[..])?; })
if let Some(mode) = mode {
oppai.mode(mode)?;
}
oppai.mods(mods.into());
let objects = oppai.num_objects();
let stars = oppai.stars();
Ok(BeatmapInfo { objects, stars })
} }
pub fn get_possible_pp_with( pub fn get_possible_pp_with(&self, mods: Mods) -> Result<BeatmapInfoWithPP> {
&self, let rosu = || self.content.pp().mods(mods.bits() as u32);
mode: Option<oppai_rs::Mode>, let pp95 = rosu().accuracy(95.0).calculate();
mods: impl Into<oppai_rs::Mods>,
) -> Result<BeatmapInfoWithPP> {
let mut oppai = oppai_rs::Oppai::new_from_content(&self.content[..])?;
if let Some(mode) = mode {
oppai.mode(mode)?;
}
oppai.mods(mods.into()).combo(oppai_rs::Combo::PERFECT)?;
let pp = [ let pp = [
oppai.accuracy(95.0)?.pp(), pp95.pp(),
oppai.accuracy(98.0)?.pp(), rosu()
oppai.accuracy(99.0)?.pp(), .attributes(pp95.clone())
oppai.accuracy(100.0)?.pp(), .accuracy(98.0)
.calculate()
.pp(),
rosu()
.attributes(pp95.clone())
.accuracy(99.0)
.calculate()
.pp(),
rosu()
.attributes(pp95.clone())
.accuracy(100.0)
.calculate()
.pp(),
]; ];
let objects = oppai.num_objects(); let objects = pp95.difficulty_attributes().max_combo().unwrap_or(0);
let stars = oppai.stars(); let stars = pp95.difficulty_attributes().stars();
Ok((BeatmapInfo { objects, stars }, pp)) Ok((BeatmapInfo { objects, stars }, pp))
} }
} }
@ -99,40 +128,37 @@ impl BeatmapCache {
.await? .await?
.bytes() .bytes()
.await?; .await?;
Ok(BeatmapContent { let bm = BeatmapContent {
id, id,
content: Arc::new(CString::new(content.into_iter().collect::<Vec<_>>())?), content: Arc::new(Beatmap::parse(content.as_ref())?),
}) };
let mut bc = models::CachedBeatmapContent {
beatmap_id: id as i64,
cached_at: chrono::Utc::now(),
content: content.as_ref().to_owned(),
};
bc.store(&self.pool).await?;
Ok(bm)
} }
async fn get_beatmap_db(&self, id: u64) -> Result<Option<BeatmapContent>> { async fn get_beatmap_db(&self, id: u64) -> Result<Option<BeatmapContent>> {
Ok(models::CachedBeatmapContent::by_id(id as i64, &self.pool) Ok(models::CachedBeatmapContent::by_id(id as i64, &self.pool)
.await? .await?
.map(|v| BeatmapContent { .map(|v| {
id, Ok(BeatmapContent {
content: Arc::new(CString::new(v.content).unwrap()), id,
})) content: Arc::new(Beatmap::parse(&v.content[..])?),
} }) as Result<_>
})
async fn save_beatmap(&self, b: &BeatmapContent) -> Result<()> { .transpose()?)
let mut bc = models::CachedBeatmapContent {
beatmap_id: b.id as i64,
cached_at: chrono::Utc::now(),
content: b.content.as_ref().clone().into_bytes(),
};
bc.store(&self.pool).await?;
Ok(())
} }
/// Get a beatmap from the cache. /// Get a beatmap from the cache.
pub async fn get_beatmap(&self, id: u64) -> Result<BeatmapContent> { pub async fn get_beatmap(&self, id: u64) -> Result<BeatmapContent> {
match self.get_beatmap_db(id).await? { match self.get_beatmap_db(id).await? {
Some(v) => Ok(v), Some(v) => Ok(v),
None => { None => self.download_beatmap(id).await,
let m = self.download_beatmap(id).await?;
self.save_beatmap(&m).await?;
Ok(m)
}
} }
} }
} }

View file

@ -6,7 +6,7 @@ use super::{
use crate::{ use crate::{
discord::{ discord::{
display::ScoreListStyle, display::ScoreListStyle,
oppai_cache::{BeatmapCache, OppaiAccuracy}, oppai_cache::{Accuracy, BeatmapCache},
BeatmapWithMode, BeatmapWithMode,
}, },
models::{Mode, Mods, Score}, models::{Mode, Mods, Score},
@ -275,18 +275,9 @@ async fn show_leaderboard(
let mode = bm.1; let mode = bm.1;
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, misses: u64, acc: OppaiAccuracy, mods: Mods| { let get_oppai_pp = move |combo: u64, acc: Accuracy, mods: Mods| {
mode.to_oppai_mode().and_then(|mode| { (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(
oppai_rs::Combo::non_fc(combo as u32, misses as u32),
acc,
Some(mode),
mods,
)
.ok()
.map(|v| v as f64)
})
}; };
let guild = m.guild_id.expect("Guild-only command"); let guild = m.guild_id.expect("Guild-only command");
@ -321,10 +312,11 @@ async fn show_leaderboard(
.or_else(|| { .or_else(|| {
get_oppai_pp( get_oppai_pp(
score.max_combo, score.max_combo,
score.count_miss, Accuracy::ByCount(
OppaiAccuracy::from_hits( score.count_300,
score.count_100 as u32, score.count_100,
score.count_50 as u32, score.count_50,
score.count_miss,
), ),
score.mods, score.mods,
) )

View file

@ -1,5 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use regex::Regex; use regex::Regex;
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;
@ -258,6 +259,17 @@ impl From<u8> for Mode {
} }
} }
impl From<Mode> for GameMode {
fn from(n: Mode) -> Self {
match n {
Mode::Std => GameMode::STD,
Mode::Taiko => GameMode::TKO,
Mode::Catch => GameMode::CTB,
Mode::Mania => GameMode::MNA,
}
}
}
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::*;
@ -275,15 +287,6 @@ impl fmt::Display for Mode {
} }
impl Mode { impl Mode {
/// Convert to oppai mode.
pub fn to_oppai_mode(self) -> Option<oppai_rs::Mode> {
Some(match self {
Mode::Std => oppai_rs::Mode::Std,
Mode::Taiko => oppai_rs::Mode::Taiko,
_ => return None,
})
}
/// Parse from the display output of the enum itself. /// Parse from the display output of the enum itself.
pub fn parse_from_display(s: &str) -> Option<Self> { pub fn parse_from_display(s: &str) -> Option<Self> {
Some(match s { Some(match s {

View file

@ -136,9 +136,3 @@ impl fmt::Display for Mods {
Ok(()) Ok(())
} }
} }
impl From<Mods> for oppai_rs::Mods {
fn from(m: Mods) -> Self {
oppai_rs::Mods::from_bits_truncate(m.bits() as i32)
}
}