feat: include latest taiko changes

This commit is contained in:
MaxOhn
2024-11-08 02:21:46 +01:00
parent 45cd1ed357
commit 9e4db4096e
8 changed files with 124 additions and 47 deletions
+3
View File
@@ -15,6 +15,9 @@ pub struct TaikoDifficultyAttributes {
pub great_hit_window: f64, pub great_hit_window: f64,
/// The perceived hit window for an n100 inclusive of rate-adjusting mods (DT/HT/etc) /// The perceived hit window for an n100 inclusive of rate-adjusting mods (DT/HT/etc)
pub ok_hit_window: f64, pub ok_hit_window: f64,
/// The ratio of stamina difficulty from mono-color (single color) streams to total
/// stamina difficulty.
pub mono_stamina_factor: f64,
/// The final star rating. /// The final star rating.
pub stars: f64, pub stars: f64,
/// The maximum combo. /// The maximum combo.
+5
View File
@@ -153,6 +153,8 @@ impl Iterator for TaikoGradualDifficulty {
Skill::new(&mut self.skills.rhythm, &self.diff_objects).process(&borrowed); Skill::new(&mut self.skills.rhythm, &self.diff_objects).process(&borrowed);
Skill::new(&mut self.skills.color, &self.diff_objects).process(&borrowed); Skill::new(&mut self.skills.color, &self.diff_objects).process(&borrowed);
Skill::new(&mut self.skills.stamina, &self.diff_objects).process(&borrowed); Skill::new(&mut self.skills.stamina, &self.diff_objects).process(&borrowed);
Skill::new(&mut self.skills.single_color_stamina, &self.diff_objects)
.process(&borrowed);
if borrowed.base_hit_type.is_hit() { if borrowed.base_hit_type.is_hit() {
self.attrs.max_combo += 1; self.attrs.max_combo += 1;
@@ -231,6 +233,8 @@ impl Iterator for TaikoGradualDifficulty {
let mut rhythm = Skill::new(&mut self.skills.rhythm, &self.diff_objects); let mut rhythm = Skill::new(&mut self.skills.rhythm, &self.diff_objects);
let mut color = Skill::new(&mut self.skills.color, &self.diff_objects); let mut color = Skill::new(&mut self.skills.color, &self.diff_objects);
let mut stamina = Skill::new(&mut self.skills.stamina, &self.diff_objects); let mut stamina = Skill::new(&mut self.skills.stamina, &self.diff_objects);
let mut single_color_stamina =
Skill::new(&mut self.skills.single_color_stamina, &self.diff_objects);
for _ in 0..take { for _ in 0..take {
loop { loop {
@@ -239,6 +243,7 @@ impl Iterator for TaikoGradualDifficulty {
rhythm.process(&borrowed); rhythm.process(&borrowed);
color.process(&borrowed); color.process(&borrowed);
stamina.process(&borrowed); stamina.process(&borrowed);
single_color_stamina.process(&borrowed);
if borrowed.base_hit_type.is_hit() { if borrowed.base_hit_type.is_hit() {
self.attrs.max_combo += 1; self.attrs.max_combo += 1;
+21 -1
View File
@@ -142,11 +142,14 @@ impl DifficultyValues {
let mut rhythm = Skill::new(&mut skills.rhythm, &diff_objects); let mut rhythm = Skill::new(&mut skills.rhythm, &diff_objects);
let mut color = Skill::new(&mut skills.color, &diff_objects); let mut color = Skill::new(&mut skills.color, &diff_objects);
let mut stamina = Skill::new(&mut skills.stamina, &diff_objects); let mut stamina = Skill::new(&mut skills.stamina, &diff_objects);
let mut single_color_stamina =
Skill::new(&mut skills.single_color_stamina, &diff_objects);
for hit_object in diff_objects.iter().take(n_diff_objects) { for hit_object in diff_objects.iter().take(n_diff_objects) {
rhythm.process(&hit_object.get()); rhythm.process(&hit_object.get());
color.process(&hit_object.get()); color.process(&hit_object.get());
stamina.process(&hit_object.get()); stamina.process(&hit_object.get());
single_color_stamina.process(&hit_object.get());
} }
} }
@@ -157,15 +160,32 @@ impl DifficultyValues {
let color_rating = skills.color.as_difficulty_value() * COLOR_SKILL_MULTIPLIER; let color_rating = skills.color.as_difficulty_value() * COLOR_SKILL_MULTIPLIER;
let rhythm_rating = skills.rhythm.as_difficulty_value() * RHYTHM_SKILL_MULTIPLIER; let rhythm_rating = skills.rhythm.as_difficulty_value() * RHYTHM_SKILL_MULTIPLIER;
let stamina_rating = skills.stamina.as_difficulty_value() * STAMINA_SKILL_MULTIPLIER; let stamina_rating = skills.stamina.as_difficulty_value() * STAMINA_SKILL_MULTIPLIER;
let mono_stamina_rating =
skills.single_color_stamina.as_difficulty_value() * STAMINA_SKILL_MULTIPLIER;
let mono_stamina_factor = if stamina_rating.abs() >= f64::EPSILON {
(mono_stamina_rating / stamina_rating).powf(5.0)
} else {
1.0
};
let combined_rating = let combined_rating =
combined_difficulty_value(skills.color, skills.rhythm, skills.stamina); combined_difficulty_value(skills.color, skills.rhythm, skills.stamina);
let star_rating = rescale(combined_rating * 1.4); let mut star_rating = rescale(combined_rating * 1.4);
// * TODO: This is temporary measure as we don't detect abuse of multiple-input playstyles of converts within the current system.
if attrs.is_convert {
star_rating *= 0.925;
// * For maps with low colour variance and high stamina requirement, multiple inputs are more likely to be abused.
if color_rating < 2.0 && stamina_rating > 8.0 {
star_rating *= 0.80;
}
}
attrs.stamina = stamina_rating; attrs.stamina = stamina_rating;
attrs.rhythm = rhythm_rating; attrs.rhythm = rhythm_rating;
attrs.color = color_rating; attrs.color = color_rating;
attrs.peak = combined_rating; attrs.peak = combined_rating;
attrs.mono_stamina_factor = mono_stamina_factor;
attrs.stars = star_rating; attrs.stars = star_rating;
} }
+3 -1
View File
@@ -9,6 +9,7 @@ pub struct TaikoSkills {
pub rhythm: Rhythm, pub rhythm: Rhythm,
pub color: Color, pub color: Color,
pub stamina: Stamina, pub stamina: Stamina,
pub single_color_stamina: Stamina,
} }
impl TaikoSkills { impl TaikoSkills {
@@ -16,7 +17,8 @@ impl TaikoSkills {
Self { Self {
rhythm: Rhythm::default(), rhythm: Rhythm::default(),
color: Color::default(), color: Color::default(),
stamina: Stamina::default(), stamina: Stamina::new(false),
single_color_stamina: Stamina::new(true),
} }
} }
} }
+46 -15
View File
@@ -1,24 +1,34 @@
use crate::{ use crate::{
any::difficulty::{ any::difficulty::{
object::IDifficultyObject, object::IDifficultyObject,
skills::{strain_decay, ISkill, Skill, StrainDecaySkill}, skills::{strain_decay, ISkill, Skill, StrainDecaySkill, StrainSkill},
}, },
taiko::{ taiko::{
difficulty::object::{TaikoDifficultyObject, TaikoDifficultyObjects}, difficulty::object::{TaikoDifficultyObject, TaikoDifficultyObjects},
object::HitType, object::HitType,
}, },
util::strains_vec::StrainsVec, util::{strains_vec::StrainsVec, sync::Weak},
}; };
const SKILL_MULTIPLIER: f64 = 1.1; const SKILL_MULTIPLIER: f64 = 1.1;
const STRAIN_DECAY_BASE: f64 = 0.4; const STRAIN_DECAY_BASE: f64 = 0.4;
#[derive(Clone, Default)] #[derive(Clone)]
pub struct Stamina { pub struct Stamina {
inner: StrainDecaySkill, inner: StrainSkill,
single_color: bool,
curr_strain: f64,
} }
impl Stamina { impl Stamina {
pub fn new(single_color: bool) -> Self {
Self {
inner: StrainSkill::default(),
single_color,
curr_strain: 0.0,
}
}
pub fn get_curr_strain_peaks(self) -> StrainsVec { pub fn get_curr_strain_peaks(self) -> StrainsVec {
self.inner.get_curr_strain_peaks() self.inner.get_curr_strain_peaks()
} }
@@ -36,6 +46,10 @@ impl ISkill for Stamina {
impl Skill<'_, Stamina> { impl Skill<'_, Stamina> {
fn calculate_initial_strain(&mut self, time: f64, curr: &TaikoDifficultyObject) -> f64 { fn calculate_initial_strain(&mut self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
if self.inner.single_color {
return 0.0;
}
let prev_start_time = curr let prev_start_time = curr
.previous(0, &self.diff_objects.objects) .previous(0, &self.diff_objects.objects)
.map_or(0.0, |prev| prev.get().start_time); .map_or(0.0, |prev| prev.get().start_time);
@@ -44,27 +58,27 @@ impl Skill<'_, Stamina> {
} }
const fn curr_strain(&self) -> f64 { const fn curr_strain(&self) -> f64 {
self.inner.inner.curr_strain self.inner.curr_strain
} }
fn curr_strain_mut(&mut self) -> &mut f64 { fn curr_strain_mut(&mut self) -> &mut f64 {
&mut self.inner.inner.curr_strain &mut self.inner.curr_strain
} }
const fn curr_section_peak(&self) -> f64 { const fn curr_section_peak(&self) -> f64 {
self.inner.inner.inner.curr_section_peak self.inner.inner.curr_section_peak
} }
fn curr_section_peak_mut(&mut self) -> &mut f64 { fn curr_section_peak_mut(&mut self) -> &mut f64 {
&mut self.inner.inner.inner.curr_section_peak &mut self.inner.inner.curr_section_peak
} }
const fn curr_section_end(&self) -> f64 { const fn curr_section_end(&self) -> f64 {
self.inner.inner.inner.curr_section_end self.inner.inner.curr_section_end
} }
fn curr_section_end_mut(&mut self) -> &mut f64 { fn curr_section_end_mut(&mut self) -> &mut f64 {
&mut self.inner.inner.inner.curr_section_end &mut self.inner.inner.curr_section_end
} }
pub fn process(&mut self, curr: &TaikoDifficultyObject) { pub fn process(&mut self, curr: &TaikoDifficultyObject) {
@@ -86,13 +100,30 @@ impl Skill<'_, Stamina> {
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject) -> f64 { fn strain_value_at(&mut self, curr: &TaikoDifficultyObject) -> f64 {
*self.curr_strain_mut() *= strain_decay(curr.delta_time, STRAIN_DECAY_BASE); *self.curr_strain_mut() *= strain_decay(curr.delta_time, STRAIN_DECAY_BASE);
*self.curr_strain_mut() += self.strain_value_of(curr) * SKILL_MULTIPLIER; *self.curr_strain_mut() +=
StaminaEvaluator::evaluate_diff_of(curr, self.diff_objects) * SKILL_MULTIPLIER;
self.curr_strain() // Safely prevents previous strains from shifting as new notes are added.
} let index = curr
.color
.mono_streak
.as_ref()
.and_then(Weak::upgrade)
.and_then(|mono| {
mono.get().hit_objects.iter().position(|h| {
let Some(h) = h.upgrade() else { return false };
let h = h.get();
fn strain_value_of(&self, curr: &TaikoDifficultyObject) -> f64 { h.idx == curr.idx
StaminaEvaluator::evaluate_diff_of(curr, self.diff_objects) })
})
.unwrap_or(0);
if self.inner.single_color {
self.curr_strain() / (1.0 + ((-(index as isize - 10)) as f64 / 2.0).exp())
} else {
self.curr_strain()
}
} }
} }
+14 -6
View File
@@ -416,12 +416,12 @@ impl TaikoPerformanceInner<'_> {
let mut multiplier = 1.13; let mut multiplier = 1.13;
if self.mods.hd() { if self.mods.hd() && !self.attrs.is_convert {
multiplier *= 1.075; multiplier *= 1.075;
} }
if self.mods.ez() { if self.mods.ez() {
multiplier *= 0.975; multiplier *= 0.95;
} }
let diff_value = let diff_value =
@@ -459,10 +459,10 @@ impl TaikoPerformanceInner<'_> {
diff_value *= 0.986_f64.powf(effective_miss_count); diff_value *= 0.986_f64.powf(effective_miss_count);
if self.mods.ez() { if self.mods.ez() {
diff_value *= 0.985; diff_value *= 0.9;
} }
if self.mods.hd() && !self.attrs.is_convert { if self.mods.hd() {
diff_value *= 1.025; diff_value *= 1.025;
} }
@@ -471,11 +471,19 @@ impl TaikoPerformanceInner<'_> {
} }
if self.mods.fl() { if self.mods.fl() {
diff_value *= 1.05 * len_bonus; diff_value *=
(1.05 - (self.attrs.mono_stamina_factor / 50.0).min(1.0) * len_bonus).max(1.0);
} }
// * Scale accuracy more harshly on nearly-completely mono (single coloured) speed maps.
let acc_scaling_exp = f64::from(2) + self.attrs.mono_stamina_factor;
let acc_scaling_shift = f64::from(300) - f64::from(100) * self.attrs.mono_stamina_factor;
diff_value diff_value
* (special_functions::erf(400.0 / (2.0_f64.sqrt() * estimated_unstable_rate))).powf(2.0) * (special_functions::erf(
acc_scaling_shift / (2.0_f64.sqrt() * estimated_unstable_rate),
))
.powf(acc_scaling_exp)
} }
fn compute_accuracy_value(&self, estimated_unstable_rate: Option<f64>) -> f64 { fn compute_accuracy_value(&self, estimated_unstable_rate: Option<f64>) -> f64 {
+12 -4
View File
@@ -71,6 +71,7 @@ macro_rules! test_cases {
rhythm: $rhythm:literal, rhythm: $rhythm:literal,
color: $color:literal, color: $color:literal,
peak: $peak:literal, peak: $peak:literal,
mono_stamina_factor: $mono_stamina_factor:literal,
great_hit_window: $great_hit_window:literal, great_hit_window: $great_hit_window:literal,
ok_hit_window: $ok_hit_window:literal, ok_hit_window: $ok_hit_window:literal,
stars: $stars:literal, stars: $stars:literal,
@@ -82,6 +83,7 @@ macro_rules! test_cases {
rhythm: $rhythm, rhythm: $rhythm,
color: $color, color: $color,
peak: $peak, peak: $peak,
mono_stamina_factor: $mono_stamina_factor,
great_hit_window: $great_hit_window, great_hit_window: $great_hit_window,
ok_hit_window: $ok_hit_window, ok_hit_window: $ok_hit_window,
stars: $stars, stars: $stars,
@@ -362,6 +364,7 @@ fn basic_taiko() {
rhythm: 0.20130047251681948, rhythm: 0.20130047251681948,
color: 1.0487315549761433, color: 1.0487315549761433,
peak: 1.8422453377400778, peak: 1.8422453377400778,
mono_stamina_factor: 2.66403971858592e-07,
great_hit_window: 35.0, great_hit_window: 35.0,
ok_hit_window: 80.0, ok_hit_window: 80.0,
stars: 2.914589700180437, stars: 2.914589700180437,
@@ -373,6 +376,7 @@ fn basic_taiko() {
rhythm: 0.20130047251681948, rhythm: 0.20130047251681948,
color: 1.0487315549761433, color: 1.0487315549761433,
peak: 1.8422453377400778, peak: 1.8422453377400778,
mono_stamina_factor: 2.66403971858592e-07,
great_hit_window: 29.0, great_hit_window: 29.0,
ok_hit_window: 68.0, ok_hit_window: 68.0,
stars: 2.914589700180437, stars: 2.914589700180437,
@@ -384,6 +388,7 @@ fn basic_taiko() {
rhythm: 0.4448175371191029, rhythm: 0.4448175371191029,
color: 1.363762496098889, color: 1.363762496098889,
peak: 2.625066421324458, peak: 2.625066421324458,
mono_stamina_factor: 2.515617502055679e-07,
great_hit_window: 23.333333333333332, great_hit_window: 23.333333333333332,
ok_hit_window: 53.333333333333336, ok_hit_window: 53.333333333333336,
stars: 3.942709244618132, stars: 3.942709244618132,
@@ -403,9 +408,10 @@ fn convert_taiko() {
rhythm: 1.4696991260446617, rhythm: 1.4696991260446617,
color: 2.303228172964907, color: 2.303228172964907,
peak: 4.117779264387738, peak: 4.117779264387738,
mono_stamina_factor: 0.0016957378202796742,
great_hit_window: 23.59999942779541, great_hit_window: 23.59999942779541,
ok_hit_window: 57.19999885559082, ok_hit_window: 57.19999885559082,
stars: 5.660149021515273, stars: 5.235637844901627,
max_combo: 908, max_combo: 908,
is_convert: true, is_convert: true,
}; };
@@ -414,9 +420,10 @@ fn convert_taiko() {
rhythm: 1.4696991260446617, rhythm: 1.4696991260446617,
color: 2.303228172964907, color: 2.303228172964907,
peak: 4.117779264387738, peak: 4.117779264387738,
mono_stamina_factor: 0.0016957378202796742,
great_hit_window: 20.0, great_hit_window: 20.0,
ok_hit_window: 50.0, ok_hit_window: 50.0
stars: 5.660149021515273, stars: 5.235637844901627,
max_combo: 908, max_combo: 908,
is_convert: true, is_convert: true,
}; };
@@ -425,9 +432,10 @@ fn convert_taiko() {
rhythm: 2.002843919169095, rhythm: 2.002843919169095,
color: 3.1864894777399986, color: 3.1864894777399986,
peak: 6.103209631166694, peak: 6.103209631166694,
mono_stamina_factor: 0.0017075184344987763,
great_hit_window: 15.733332951863607, great_hit_window: 15.733332951863607,
ok_hit_window: 38.13333257039388, ok_hit_window: 38.13333257039388,
stars: 7.578560915020682, stars: 7.010168846394131,
max_combo: 908, max_combo: 908,
is_convert: true, is_convert: true,
}; };
+20 -20
View File
@@ -230,30 +230,30 @@ fn basic_taiko() {
test_cases! { test_cases! {
Taiko: TAIKO { Taiko: TAIKO {
NM => { NM => {
pp: 117.93083232512124, pp: 114.68651694107942,
pp_acc: 67.10083752258917, pp_acc: 67.10083752258917,
pp_difficulty: 43.804435430934774, pp_difficulty: 40.6658183165898,
effective_miss_count: 0.0, effective_miss_count: 0.0,
estimated_unstable_rate: Some(148.44150180469418), estimated_unstable_rate: Some(148.44150180469418),
}; };
HD => { HD => {
pp: 127.99624094636974, pp: 124.41592086295445,
pp_acc: 67.10083752258917, pp_acc: 67.10083752258917,
pp_difficulty: 44.89954631670814, pp_difficulty: 41.68246377450454,
effective_miss_count: 0.0, effective_miss_count: 0.0,
estimated_unstable_rate: Some(148.44150180469418), estimated_unstable_rate: Some(148.44150180469418),
}; };
HR => { HR => {
pp: 139.75239372681187, pp: 138.3981102935321,
pp_acc: 82.52109686788792, pp_acc: 82.52109686788792,
pp_difficulty: 48.75926757049594, pp_difficulty: 47.44272798866182,
effective_miss_count: 0.0, effective_miss_count: 0.0,
estimated_unstable_rate: Some(122.99438720960376), estimated_unstable_rate: Some(122.99438720960376),
}; };
DT => { DT => {
pp: 220.51543873147975, pp: 220.07140899937482,
pp_acc: 118.28107309573312, pp_acc: 118.28107309573312,
pp_difficulty: 89.35584221033577, pp_difficulty: 88.93091255724303,
effective_miss_count: 0.0, effective_miss_count: 0.0,
estimated_unstable_rate: Some(98.96100120312946), estimated_unstable_rate: Some(98.96100120312946),
}; };
@@ -266,30 +266,30 @@ fn convert_taiko() {
test_cases! { test_cases! {
Taiko: OSU { Taiko: OSU {
NM => { NM => {
pp: 396.36982258196866, pp: 353.6961706002712,
pp_acc: 160.00481201044695, pp_acc: 155.09212159726567,
pp_difficulty: 213.19920144243838, pp_difficulty: 178.19145253120928,
effective_miss_count: 0.0, effective_miss_count: 0.0,
estimated_unstable_rate: Some(85.75868894575865), estimated_unstable_rate: Some(85.75868894575865),
}; };
HD => { HD => {
pp: 426.0975592756163, pp: 358.45704044422996,
pp_acc: 160.00481201044695, pp_acc: 155.09212159726567,
pp_difficulty: 213.19920144243838, pp_difficulty: 182.6462388444895,
effective_miss_count: 0.0, effective_miss_count: 0.0,
estimated_unstable_rate: Some(85.75868894575865), estimated_unstable_rate: Some(85.75868894575865),
}; };
HR => { HR => {
pp: 452.71458235192836, pp: 405.57235351353773,
pp_acc: 191.95668459371925, pp_acc: 186.06296332183615,
pp_difficulty: 234.5205569790155, pp_difficulty: 196.1813610529617,
effective_miss_count: 0.0, effective_miss_count: 0.0,
estimated_unstable_rate: Some(72.67685680089848), estimated_unstable_rate: Some(72.67685680089848),
}; };
DT => { DT => {
pp: 739.7393581199891, pp: 658.0214875413873,
pp_acc: 280.8904545747157, pp_acc: 272.26616492989393,
pp_difficulty: 415.0249135067657, pp_difficulty: 347.4712042359611,
effective_miss_count: 0.0, effective_miss_count: 0.0,
estimated_unstable_rate: Some(57.17245929717244), estimated_unstable_rate: Some(57.17245929717244),
}; };