From 33f19dbaba0f0e96c9569e5e9da36aab758d7f6b Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sun, 14 Jun 2020 18:44:06 -0400 Subject: [PATCH] Handle cs/od/ar/hp with mods --- youmubot-osu/src/discord/embeds.rs | 50 +++++++------------- youmubot-osu/src/models/mod.rs | 73 +++++++++++++++++++++++++++++- youmubot-osu/src/models/parse.rs | 4 +- 3 files changed, 90 insertions(+), 37 deletions(-) diff --git a/youmubot-osu/src/discord/embeds.rs b/youmubot-osu/src/discord/embeds.rs index aec5282..9498f48 100644 --- a/youmubot-osu/src/discord/embeds.rs +++ b/youmubot-osu/src/discord/embeds.rs @@ -27,20 +27,7 @@ pub fn beatmap_embed<'a>( } else { format!(" {}", mods) }; - let total_length = if mods.intersects(Mods::DT | Mods::NC) { - b.total_length * 2 / 3 - } else if mods.intersects(Mods::HT) { - b.total_length * 4 / 3 - } else { - b.total_length - }; - let drain_length = if mods.intersects(Mods::DT | Mods::NC) { - b.drain_length * 2 / 3 - } else if mods.intersects(Mods::HT) { - b.drain_length * 4 / 3 - } else { - b.drain_length - }; + let diff = b.difficulty.apply_mods(mods); c.title( MessageBuilder::new() .push_bold_safe(&b.artist) @@ -67,8 +54,9 @@ pub fn beatmap_embed<'a>( "{:.2}⭐", info.map(|v| v.stars as f64).unwrap_or(b.difficulty.stars) ), - false, + true, ) + .fields(Some(("Mods", mods, true)).filter(|_| mods != Mods::NOMOD)) .fields(info.map(|info| { ( "Calculated pp", @@ -79,25 +67,20 @@ pub fn beatmap_embed<'a>( false, ) })) - .fields(Some(("Mods", mods, false)).filter(|_| mods != Mods::NOMOD)) .field( "Length", MessageBuilder::new() - .push_bold_safe(Duration(total_length)) + .push_bold_safe(Duration(diff.total_length)) .push(" (") - .push_bold_safe(Duration(drain_length)) + .push_bold_safe(Duration(diff.drain_length)) .push(" drain)") .build(), false, ) - .field("Circle Size", format!("{:.1}", b.difficulty.cs), true) - .field("Approach Rate", format!("{:.1}", b.difficulty.ar), true) - .field( - "Overall Difficulty", - format!("{:.1}", b.difficulty.od), - true, - ) - .field("HP Drain", format!("{:.1}", b.difficulty.hp), true) + .field("Circle Size", format!("{:.1}", diff.cs), true) + .field("Approach Rate", format!("{:.1}", diff.ar), true) + .field("Overall Difficulty", format!("{:.1}", diff.od), true) + .field("HP Drain", format!("{:.1}", diff.hp), true) .field("BPM", b.bpm.round(), true) .fields(b.difficulty.max_combo.map(|v| ("Max combo", v, true))) .field("Mode", format_mode(m, b.mode), true) @@ -194,7 +177,7 @@ pub fn beatmapset_embed<'a>( .field( "Length", MessageBuilder::new() - .push_bold_safe(Duration(b.total_length)) + .push_bold_safe(Duration(b.difficulty.total_length)) .build(), true, ) @@ -240,7 +223,7 @@ pub fn beatmapset_embed<'a>( .push(", HP") .push_bold(format!("{:.1}", b.difficulty.hp)) .push(", ⌛ ") - .push_bold(format!("{}", Duration(b.drain_length))) + .push_bold(format!("{}", Duration(b.difficulty.drain_length))) .build(), false, ) @@ -272,6 +255,7 @@ pub(crate) fn score_embed<'a>( let top_record = top_record .map(|v| format!("| #{} top record!", v)) .unwrap_or("".to_owned()); + let diff = b.difficulty.apply_mods(s.mods); m.author(|f| f.name(&u.username).url(u.link()).icon_url(u.avatar_url())) .color(0xffb6c1) .title(format!( @@ -313,15 +297,15 @@ pub(crate) fn score_embed<'a>( }, ) .push("CS") - .push_bold(format!("{:.1}", b.difficulty.cs)) + .push_bold(format!("{:.1}", diff.cs)) .push(", AR") - .push_bold(format!("{:.1}", b.difficulty.ar)) + .push_bold(format!("{:.1}", diff.ar)) .push(", OD") - .push_bold(format!("{:.1}", b.difficulty.od)) + .push_bold(format!("{:.1}", diff.od)) .push(", HP") - .push_bold(format!("{:.1}", b.difficulty.hp)) + .push_bold(format!("{:.1}", diff.hp)) .push(", ⌛ ") - .push_bold(format!("{}", Duration(b.drain_length))) + .push_bold(format!("{}", Duration(diff.drain_length))) .build(), false, ) diff --git a/youmubot-osu/src/models/mod.rs b/youmubot-osu/src/models/mod.rs index 6476bf6..06c883e 100644 --- a/youmubot-osu/src/models/mod.rs +++ b/youmubot-osu/src/models/mod.rs @@ -45,6 +45,77 @@ pub struct Difficulty { pub count_slider: u64, pub count_spinner: u64, pub max_combo: Option, + + pub drain_length: Duration, + pub total_length: Duration, +} + +impl Difficulty { + // Difficulty calculation is based on + // https://www.reddit.com/r/osugame/comments/6phntt/difficulty_settings_table_with_all_values/ + + fn apply_everything_by_ratio(&mut self, rat: f64) { + self.cs = (self.cs * rat).min(10.0); + self.od = (self.od * rat).min(10.0); + self.ar = (self.ar * rat).min(10.0); + self.hp = (self.hp * rat).min(10.0); + } + fn apply_ar_by_time_ratio(&mut self, rat: f64) { + // Convert AR to approach time... + let approach_time = if self.ar < 5.0 { + 1800.0 - self.ar * 120.0 + } else { + 1200.0 - (self.ar - 5.0) * 150.0 + }; + // Update it... + let approach_time = approach_time * rat; + // Convert it back to AR... + self.ar = if approach_time > 1200.0 { + (1800.0 - approach_time) / 120.0 + } else { + (1200.0 - approach_time) / 150.0 + 5.0 + }; + } + fn apply_od_by_time_ratio(&mut self, rat: f64) { + // Convert OD to hit timing + let hit_timing = 79.0 - self.od * 6.0 + 0.5; + // Update it... + let hit_timing = hit_timing * rat + 0.5 / rat; + // then convert back + self.od = (79.0 - (hit_timing - 0.5)) / 6.0; + } + fn apply_length_by_ratio(&mut self, mul: u32, div: u32) { + self.drain_length = self.drain_length * mul / div; + self.total_length = self.total_length * mul / div; + } + /// Apply mods to the given difficulty. + /// Note that `stars`, `aim` and `speed` cannot be calculated from this alone. + pub fn apply_mods(&self, mods: Mods) -> Difficulty { + let mut diff = self.clone(); + + // Apply mods one by one + if mods.contains(Mods::EZ) { + diff.apply_everything_by_ratio(0.5); + } + if mods.contains(Mods::HT) { + diff.apply_ar_by_time_ratio(4.0 / 3.0); + diff.apply_od_by_time_ratio(4.0 / 3.0); + diff.apply_length_by_ratio(4, 3); + } + if mods.contains(Mods::HR) { + let old_cs = diff.cs; + diff.apply_everything_by_ratio(1.4); + // CS is changed by 1.3 tho + diff.cs = old_cs * 1.3; + } + if mods.contains(Mods::DT) { + diff.apply_ar_by_time_ratio(2.0 / 3.0); + diff.apply_od_by_time_ratio(2.0 / 3.0); + diff.apply_length_by_ratio(2, 3); + } + + diff + } } #[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)] @@ -152,8 +223,6 @@ pub struct Beatmap { pub beatmap_id: u64, pub difficulty_name: String, pub difficulty: Difficulty, - pub drain_length: Duration, - pub total_length: Duration, pub file_hash: String, pub mode: Mode, pub favourite_count: u64, diff --git a/youmubot-osu/src/models/parse.rs b/youmubot-osu/src/models/parse.rs index 0d17431..fcb0532 100644 --- a/youmubot-osu/src/models/parse.rs +++ b/youmubot-osu/src/models/parse.rs @@ -139,9 +139,9 @@ impl TryFrom for Beatmap { count_slider: parse_from_str(&raw.count_slider)?, count_spinner: parse_from_str(&raw.count_spinner)?, max_combo: raw.max_combo.map(parse_from_str).transpose()?, + drain_length: parse_duration(&raw.hit_length)?, + total_length: parse_duration(&raw.total_length)?, }, - drain_length: parse_duration(&raw.hit_length)?, - total_length: parse_duration(&raw.total_length)?, file_hash: raw.file_md5, mode: parse_mode(&raw.mode)?, favourite_count: parse_from_str(&raw.favourite_count)?,