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 {
|
||||
|
||||
Reference in New Issue
Block a user