refactor: music-db crate
This commit is contained in:
@@ -18,6 +18,7 @@ strum = { workspace = true }
|
||||
tokio = { workspace = true, optional = true }
|
||||
compio = { workspace = true, optional = true }
|
||||
spdlog-rs = { workspace = true }
|
||||
music-db = { workspace = true }
|
||||
|
||||
# (de)serialization
|
||||
serde = { workspace = true }
|
||||
@@ -43,8 +44,3 @@ bincode = { version = "2.0.1", optional = true }
|
||||
|
||||
# magic macro
|
||||
crabtime = { git = "https://github.com/wdanilo/crabtime.git", rev = "2ed856f5" }
|
||||
rustc-hash = "2.1.1"
|
||||
rust_decimal = { version = "1.37.2", default-features = false, features = [
|
||||
"serde-with-arbitrary-precision",
|
||||
"macros",
|
||||
] }
|
||||
|
||||
@@ -9,6 +9,3 @@ pub fn level_name(level: u32) -> &'static str {
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
mod music_db;
|
||||
pub use music_db::{Level, MusicInfo, query_music, query_music_level, preload_db};
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
use std::{fs::OpenOptions, io::BufReader, sync::LazyLock, time::SystemTime};
|
||||
|
||||
use rust_decimal::{Decimal, dec, serde::DecimalFromString};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
use spdlog::{info, warn};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MusicInfo {
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub version: i64,
|
||||
pub levels: Vec<Level>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Level {
|
||||
/// 0, 1, 2, 3, 4, 5
|
||||
pub level: u32,
|
||||
/// for example: "13.7"
|
||||
pub difficulty: DecimalFromString,
|
||||
}
|
||||
|
||||
type MusicDB = FxHashMap<u32, MusicInfo>;
|
||||
|
||||
pub fn preload_db() {
|
||||
_ = &*MUSIC_DB;
|
||||
}
|
||||
|
||||
pub fn query_music(music_id: u32) -> Option<&'static MusicInfo> {
|
||||
MUSIC_DB.as_ref()?.get(&music_id)
|
||||
}
|
||||
pub fn query_music_level(music_id: u32, level: u32) -> Option<&'static Level> {
|
||||
MUSIC_DB
|
||||
.as_ref()?
|
||||
.get(&music_id)?
|
||||
.levels
|
||||
.iter()
|
||||
.find(|d| d.level == level)
|
||||
}
|
||||
|
||||
pub static MUSIC_DB: LazyLock<Option<MusicDB>> = LazyLock::new(|| {
|
||||
let time = SystemTime::now();
|
||||
info!("loading musicDB...");
|
||||
|
||||
let json = OpenOptions::new()
|
||||
.read(true)
|
||||
.create(false)
|
||||
.open("musicDB.json")
|
||||
.inspect_err(|e| warn!("failed to load musicDB: {e}"))
|
||||
.ok()?;
|
||||
let buf_reader = BufReader::new(json);
|
||||
|
||||
let db: Vec<MusicInfo> = serde_json::from_reader(buf_reader)
|
||||
.inspect_err(|e| warn!("failed to load musicDB: {e}"))
|
||||
.ok()?;
|
||||
|
||||
info!(
|
||||
"loaded musicDB, cost {}ms",
|
||||
time.elapsed().unwrap_or_default().as_millis()
|
||||
);
|
||||
|
||||
Some(db.into_iter().map(|entry| (entry.id, entry)).collect())
|
||||
});
|
||||
|
||||
impl Level {
|
||||
/// achievement: xxx.xxxx% * 10000
|
||||
///
|
||||
/// This will **NOT** ignore utage level, you can calculate a in-theory DX Rating.
|
||||
///
|
||||
/// On invalid input, it returns 0.
|
||||
pub fn dx_rating(&self, achievement: i32) -> u32 {
|
||||
let achievement = Decimal::new(achievement as _, 4);
|
||||
let difficulty_rank: Decimal = self.difficulty.value;
|
||||
|
||||
let factor = match () {
|
||||
// larger than best achievement
|
||||
_ if achievement > dec!(101.0) => return 0,
|
||||
|
||||
_ if achievement >= SSS_PLUS_THRESHOLD => SSS_PLUS_FACTOR,
|
||||
_ if achievement >= SSS_PRO_THRESHOLD => SSS_PRO_FACTOR,
|
||||
_ if achievement >= SSS_THRESHOLD => SSS_FACTOR,
|
||||
_ if achievement >= SS_PLUS_PRO_THRESHOLD => SS_PLUS_PRO_FACTOR,
|
||||
_ if achievement >= SS_PLUS_THRESHOLD => SS_PLUS_FACTOR,
|
||||
_ if achievement >= SS_THRESHOLD => SS_FACTOR,
|
||||
_ if achievement >= S_PLUS_PRO_THRESHOLD => S_PLUS_PRO_FACTOR,
|
||||
_ if achievement >= S_PLUS_THRESHOLD => S_PLUS_FACTOR,
|
||||
_ if achievement >= S_THRESHOLD => S_FACTOR,
|
||||
_ if achievement >= AAA_PRO_THRESHOLD => AAA_PRO_FACTOR,
|
||||
_ if achievement >= AAA_THRESHOLD => AAA_FACTOR,
|
||||
_ if achievement >= AA_THRESHOLD => AA_FACTOR,
|
||||
_ if achievement >= A_THRESHOLD => A_FACTOR,
|
||||
|
||||
// lower than A rank, does not get rating.
|
||||
_ => return 0,
|
||||
};
|
||||
|
||||
// when ach > 100.5%, calculate as 100.5%
|
||||
(factor * difficulty_rank * achievement.min(Decimal::new(1005, 1)))
|
||||
.floor()
|
||||
.try_into()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
const SSS_PLUS_THRESHOLD: Decimal = dec!(100.5);
|
||||
const SSS_PLUS_FACTOR: Decimal = dec!(0.224);
|
||||
|
||||
const SSS_PRO_THRESHOLD: Decimal = dec!(100.4999);
|
||||
const SSS_PRO_FACTOR: Decimal = dec!(0.222);
|
||||
|
||||
const SSS_THRESHOLD: Decimal = dec!(100);
|
||||
const SSS_FACTOR: Decimal = dec!(0.216);
|
||||
|
||||
const SS_PLUS_PRO_THRESHOLD: Decimal = dec!(99.9999);
|
||||
const SS_PLUS_PRO_FACTOR: Decimal = dec!(0.214);
|
||||
|
||||
const SS_PLUS_THRESHOLD: Decimal = dec!(99.5);
|
||||
const SS_PLUS_FACTOR: Decimal = dec!(0.211);
|
||||
|
||||
const SS_THRESHOLD: Decimal = dec!(99);
|
||||
const SS_FACTOR: Decimal = dec!(0.208);
|
||||
|
||||
const S_PLUS_PRO_THRESHOLD: Decimal = dec!(98.9999);
|
||||
const S_PLUS_PRO_FACTOR: Decimal = dec!(0.206);
|
||||
|
||||
const S_PLUS_THRESHOLD: Decimal = dec!(98);
|
||||
const S_PLUS_FACTOR: Decimal = dec!(0.203);
|
||||
|
||||
const S_THRESHOLD: Decimal = dec!(97);
|
||||
const S_FACTOR: Decimal = dec!(0.2);
|
||||
|
||||
const AAA_PRO_THRESHOLD: Decimal = dec!(96.9999);
|
||||
const AAA_PRO_FACTOR: Decimal = dec!(0.176);
|
||||
|
||||
const AAA_THRESHOLD: Decimal = dec!(94);
|
||||
const AAA_FACTOR: Decimal = dec!(0.168);
|
||||
|
||||
const AA_THRESHOLD: Decimal = dec!(90);
|
||||
const AA_FACTOR: Decimal = dec!(0.152);
|
||||
|
||||
const A_THRESHOLD: Decimal = dec!(80);
|
||||
const A_FACTOR: Decimal = dec!(0.136);
|
||||
|
||||
/*
|
||||
TODO: calculate (below) BBB dx rating
|
||||
[0, 0, 'd'],
|
||||
[10, 1.6, 'd'],
|
||||
[20, 3.2, 'd'],
|
||||
[30, 4.8, 'd'],
|
||||
[40, 6.4, 'd'],
|
||||
[50, 8, 'c'],
|
||||
[60, 9.6, 'b'],
|
||||
[70, 11.2, 'bb'],
|
||||
[75, 12.0, 'bbb'],
|
||||
[79.9999, 12.8, 'bbb'],
|
||||
*/
|
||||
@@ -1,10 +1,9 @@
|
||||
use crate::{
|
||||
helper::query_music,
|
||||
title::model::{
|
||||
dxrating::{DxCalculatedEntries, DxLevelName, DxMusicRecord, DxSheetId},
|
||||
get_user_music_api::UserMusicDetail,
|
||||
get_user_rating_api::{MusicRating, UserRating},
|
||||
},
|
||||
use music_db::query_music;
|
||||
|
||||
use crate::title::model::{
|
||||
dxrating::{DxCalculatedEntries, DxLevelName, DxMusicRecord, DxSheetId},
|
||||
get_user_music_api::UserMusicDetail,
|
||||
get_user_rating_api::{MusicRating, UserRating},
|
||||
};
|
||||
|
||||
impl DxCalculatedEntries {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use music_db::query_music;
|
||||
use music_db::query_music_level;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::helper::level_name;
|
||||
use crate::helper::query_music;
|
||||
use crate::helper::query_music_level;
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@@ -2,12 +2,12 @@ use std::fmt::Display;
|
||||
|
||||
use bincode::Decode;
|
||||
use bincode::Encode;
|
||||
use music_db::query_music;
|
||||
use music_db::query_music_level;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::helper::level_name;
|
||||
use crate::helper::query_music;
|
||||
use crate::helper::query_music_level;
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
Reference in New Issue
Block a user