feat: include latest taiko changes
This commit is contained in:
@@ -15,6 +15,9 @@ pub struct TaikoDifficultyAttributes {
|
||||
pub great_hit_window: f64,
|
||||
/// The perceived hit window for an n100 inclusive of rate-adjusting mods (DT/HT/etc)
|
||||
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.
|
||||
pub stars: f64,
|
||||
/// The maximum combo.
|
||||
|
||||
@@ -153,6 +153,8 @@ impl Iterator for TaikoGradualDifficulty {
|
||||
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.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() {
|
||||
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 color = Skill::new(&mut self.skills.color, &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 {
|
||||
loop {
|
||||
@@ -239,6 +243,7 @@ impl Iterator for TaikoGradualDifficulty {
|
||||
rhythm.process(&borrowed);
|
||||
color.process(&borrowed);
|
||||
stamina.process(&borrowed);
|
||||
single_color_stamina.process(&borrowed);
|
||||
|
||||
if borrowed.base_hit_type.is_hit() {
|
||||
self.attrs.max_combo += 1;
|
||||
|
||||
@@ -142,11 +142,14 @@ impl DifficultyValues {
|
||||
let mut rhythm = Skill::new(&mut skills.rhythm, &diff_objects);
|
||||
let mut color = Skill::new(&mut skills.color, &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) {
|
||||
rhythm.process(&hit_object.get());
|
||||
color.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 rhythm_rating = skills.rhythm.as_difficulty_value() * RHYTHM_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 =
|
||||
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.rhythm = rhythm_rating;
|
||||
attrs.color = color_rating;
|
||||
attrs.peak = combined_rating;
|
||||
attrs.mono_stamina_factor = mono_stamina_factor;
|
||||
attrs.stars = star_rating;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ pub struct TaikoSkills {
|
||||
pub rhythm: Rhythm,
|
||||
pub color: Color,
|
||||
pub stamina: Stamina,
|
||||
pub single_color_stamina: Stamina,
|
||||
}
|
||||
|
||||
impl TaikoSkills {
|
||||
@@ -16,7 +17,8 @@ impl TaikoSkills {
|
||||
Self {
|
||||
rhythm: Rhythm::default(),
|
||||
color: Color::default(),
|
||||
stamina: Stamina::default(),
|
||||
stamina: Stamina::new(false),
|
||||
single_color_stamina: Stamina::new(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
use crate::{
|
||||
any::difficulty::{
|
||||
object::IDifficultyObject,
|
||||
skills::{strain_decay, ISkill, Skill, StrainDecaySkill},
|
||||
skills::{strain_decay, ISkill, Skill, StrainDecaySkill, StrainSkill},
|
||||
},
|
||||
taiko::{
|
||||
difficulty::object::{TaikoDifficultyObject, TaikoDifficultyObjects},
|
||||
object::HitType,
|
||||
},
|
||||
util::strains_vec::StrainsVec,
|
||||
util::{strains_vec::StrainsVec, sync::Weak},
|
||||
};
|
||||
|
||||
const SKILL_MULTIPLIER: f64 = 1.1;
|
||||
const STRAIN_DECAY_BASE: f64 = 0.4;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct Stamina {
|
||||
inner: StrainDecaySkill,
|
||||
inner: StrainSkill,
|
||||
single_color: bool,
|
||||
curr_strain: f64,
|
||||
}
|
||||
|
||||
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 {
|
||||
self.inner.get_curr_strain_peaks()
|
||||
}
|
||||
@@ -36,6 +46,10 @@ impl ISkill for Stamina {
|
||||
|
||||
impl Skill<'_, Stamina> {
|
||||
fn calculate_initial_strain(&mut self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
||||
if self.inner.single_color {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let prev_start_time = curr
|
||||
.previous(0, &self.diff_objects.objects)
|
||||
.map_or(0.0, |prev| prev.get().start_time);
|
||||
@@ -44,27 +58,27 @@ impl Skill<'_, Stamina> {
|
||||
}
|
||||
|
||||
const fn curr_strain(&self) -> f64 {
|
||||
self.inner.inner.curr_strain
|
||||
self.inner.curr_strain
|
||||
}
|
||||
|
||||
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 {
|
||||
self.inner.inner.inner.curr_section_peak
|
||||
self.inner.inner.curr_section_peak
|
||||
}
|
||||
|
||||
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 {
|
||||
self.inner.inner.inner.curr_section_end
|
||||
self.inner.inner.curr_section_end
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -86,13 +100,30 @@ impl Skill<'_, Stamina> {
|
||||
|
||||
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() += 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 {
|
||||
StaminaEvaluator::evaluate_diff_of(curr, self.diff_objects)
|
||||
h.idx == curr.idx
|
||||
})
|
||||
})
|
||||
.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -416,12 +416,12 @@ impl TaikoPerformanceInner<'_> {
|
||||
|
||||
let mut multiplier = 1.13;
|
||||
|
||||
if self.mods.hd() {
|
||||
if self.mods.hd() && !self.attrs.is_convert {
|
||||
multiplier *= 1.075;
|
||||
}
|
||||
|
||||
if self.mods.ez() {
|
||||
multiplier *= 0.975;
|
||||
multiplier *= 0.95;
|
||||
}
|
||||
|
||||
let diff_value =
|
||||
@@ -459,10 +459,10 @@ impl TaikoPerformanceInner<'_> {
|
||||
diff_value *= 0.986_f64.powf(effective_miss_count);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -471,11 +471,19 @@ impl TaikoPerformanceInner<'_> {
|
||||
}
|
||||
|
||||
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
|
||||
* (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 {
|
||||
|
||||
+12
-4
@@ -71,6 +71,7 @@ macro_rules! test_cases {
|
||||
rhythm: $rhythm:literal,
|
||||
color: $color:literal,
|
||||
peak: $peak:literal,
|
||||
mono_stamina_factor: $mono_stamina_factor:literal,
|
||||
great_hit_window: $great_hit_window:literal,
|
||||
ok_hit_window: $ok_hit_window:literal,
|
||||
stars: $stars:literal,
|
||||
@@ -82,6 +83,7 @@ macro_rules! test_cases {
|
||||
rhythm: $rhythm,
|
||||
color: $color,
|
||||
peak: $peak,
|
||||
mono_stamina_factor: $mono_stamina_factor,
|
||||
great_hit_window: $great_hit_window,
|
||||
ok_hit_window: $ok_hit_window,
|
||||
stars: $stars,
|
||||
@@ -362,6 +364,7 @@ fn basic_taiko() {
|
||||
rhythm: 0.20130047251681948,
|
||||
color: 1.0487315549761433,
|
||||
peak: 1.8422453377400778,
|
||||
mono_stamina_factor: 2.66403971858592e-07,
|
||||
great_hit_window: 35.0,
|
||||
ok_hit_window: 80.0,
|
||||
stars: 2.914589700180437,
|
||||
@@ -373,6 +376,7 @@ fn basic_taiko() {
|
||||
rhythm: 0.20130047251681948,
|
||||
color: 1.0487315549761433,
|
||||
peak: 1.8422453377400778,
|
||||
mono_stamina_factor: 2.66403971858592e-07,
|
||||
great_hit_window: 29.0,
|
||||
ok_hit_window: 68.0,
|
||||
stars: 2.914589700180437,
|
||||
@@ -384,6 +388,7 @@ fn basic_taiko() {
|
||||
rhythm: 0.4448175371191029,
|
||||
color: 1.363762496098889,
|
||||
peak: 2.625066421324458,
|
||||
mono_stamina_factor: 2.515617502055679e-07,
|
||||
great_hit_window: 23.333333333333332,
|
||||
ok_hit_window: 53.333333333333336,
|
||||
stars: 3.942709244618132,
|
||||
@@ -403,9 +408,10 @@ fn convert_taiko() {
|
||||
rhythm: 1.4696991260446617,
|
||||
color: 2.303228172964907,
|
||||
peak: 4.117779264387738,
|
||||
mono_stamina_factor: 0.0016957378202796742,
|
||||
great_hit_window: 23.59999942779541,
|
||||
ok_hit_window: 57.19999885559082,
|
||||
stars: 5.660149021515273,
|
||||
stars: 5.235637844901627,
|
||||
max_combo: 908,
|
||||
is_convert: true,
|
||||
};
|
||||
@@ -414,9 +420,10 @@ fn convert_taiko() {
|
||||
rhythm: 1.4696991260446617,
|
||||
color: 2.303228172964907,
|
||||
peak: 4.117779264387738,
|
||||
mono_stamina_factor: 0.0016957378202796742,
|
||||
great_hit_window: 20.0,
|
||||
ok_hit_window: 50.0,
|
||||
stars: 5.660149021515273,
|
||||
ok_hit_window: 50.0
|
||||
stars: 5.235637844901627,
|
||||
max_combo: 908,
|
||||
is_convert: true,
|
||||
};
|
||||
@@ -425,9 +432,10 @@ fn convert_taiko() {
|
||||
rhythm: 2.002843919169095,
|
||||
color: 3.1864894777399986,
|
||||
peak: 6.103209631166694,
|
||||
mono_stamina_factor: 0.0017075184344987763,
|
||||
great_hit_window: 15.733332951863607,
|
||||
ok_hit_window: 38.13333257039388,
|
||||
stars: 7.578560915020682,
|
||||
stars: 7.010168846394131,
|
||||
max_combo: 908,
|
||||
is_convert: true,
|
||||
};
|
||||
|
||||
+20
-20
@@ -230,30 +230,30 @@ fn basic_taiko() {
|
||||
test_cases! {
|
||||
Taiko: TAIKO {
|
||||
NM => {
|
||||
pp: 117.93083232512124,
|
||||
pp: 114.68651694107942,
|
||||
pp_acc: 67.10083752258917,
|
||||
pp_difficulty: 43.804435430934774,
|
||||
pp_difficulty: 40.6658183165898,
|
||||
effective_miss_count: 0.0,
|
||||
estimated_unstable_rate: Some(148.44150180469418),
|
||||
};
|
||||
HD => {
|
||||
pp: 127.99624094636974,
|
||||
pp: 124.41592086295445,
|
||||
pp_acc: 67.10083752258917,
|
||||
pp_difficulty: 44.89954631670814,
|
||||
pp_difficulty: 41.68246377450454,
|
||||
effective_miss_count: 0.0,
|
||||
estimated_unstable_rate: Some(148.44150180469418),
|
||||
};
|
||||
HR => {
|
||||
pp: 139.75239372681187,
|
||||
pp: 138.3981102935321,
|
||||
pp_acc: 82.52109686788792,
|
||||
pp_difficulty: 48.75926757049594,
|
||||
pp_difficulty: 47.44272798866182,
|
||||
effective_miss_count: 0.0,
|
||||
estimated_unstable_rate: Some(122.99438720960376),
|
||||
};
|
||||
DT => {
|
||||
pp: 220.51543873147975,
|
||||
pp: 220.07140899937482,
|
||||
pp_acc: 118.28107309573312,
|
||||
pp_difficulty: 89.35584221033577,
|
||||
pp_difficulty: 88.93091255724303,
|
||||
effective_miss_count: 0.0,
|
||||
estimated_unstable_rate: Some(98.96100120312946),
|
||||
};
|
||||
@@ -266,30 +266,30 @@ fn convert_taiko() {
|
||||
test_cases! {
|
||||
Taiko: OSU {
|
||||
NM => {
|
||||
pp: 396.36982258196866,
|
||||
pp_acc: 160.00481201044695,
|
||||
pp_difficulty: 213.19920144243838,
|
||||
pp: 353.6961706002712,
|
||||
pp_acc: 155.09212159726567,
|
||||
pp_difficulty: 178.19145253120928,
|
||||
effective_miss_count: 0.0,
|
||||
estimated_unstable_rate: Some(85.75868894575865),
|
||||
};
|
||||
HD => {
|
||||
pp: 426.0975592756163,
|
||||
pp_acc: 160.00481201044695,
|
||||
pp_difficulty: 213.19920144243838,
|
||||
pp: 358.45704044422996,
|
||||
pp_acc: 155.09212159726567,
|
||||
pp_difficulty: 182.6462388444895,
|
||||
effective_miss_count: 0.0,
|
||||
estimated_unstable_rate: Some(85.75868894575865),
|
||||
};
|
||||
HR => {
|
||||
pp: 452.71458235192836,
|
||||
pp_acc: 191.95668459371925,
|
||||
pp_difficulty: 234.5205569790155,
|
||||
pp: 405.57235351353773,
|
||||
pp_acc: 186.06296332183615,
|
||||
pp_difficulty: 196.1813610529617,
|
||||
effective_miss_count: 0.0,
|
||||
estimated_unstable_rate: Some(72.67685680089848),
|
||||
};
|
||||
DT => {
|
||||
pp: 739.7393581199891,
|
||||
pp_acc: 280.8904545747157,
|
||||
pp_difficulty: 415.0249135067657,
|
||||
pp: 658.0214875413873,
|
||||
pp_acc: 272.26616492989393,
|
||||
pp_difficulty: 347.4712042359611,
|
||||
effective_miss_count: 0.0,
|
||||
estimated_unstable_rate: Some(57.17245929717244),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user