feat: updated taiko calc
This commit is contained in:
@@ -5,6 +5,8 @@ use self::{
|
||||
repeating_hit_patterns::RepeatingHitPatterns,
|
||||
};
|
||||
|
||||
use super::object::{TaikoDifficultyObject, TaikoDifficultyObjects};
|
||||
|
||||
pub mod alternating_mono_pattern;
|
||||
pub mod mono_streak;
|
||||
pub mod preprocessor;
|
||||
@@ -16,3 +18,27 @@ pub struct TaikoDifficultyColor {
|
||||
pub alternating_mono_pattern: Option<Weak<AlternatingMonoPattern>>,
|
||||
pub repeating_hit_patterns: Option<RefCount<RepeatingHitPatterns>>,
|
||||
}
|
||||
|
||||
impl TaikoDifficultyColor {
|
||||
pub fn previous_color_change<'a>(
|
||||
&self,
|
||||
hit_objects: &'a TaikoDifficultyObjects,
|
||||
) -> Option<&'a RefCount<TaikoDifficultyObject>> {
|
||||
self.mono_streak
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.and_then(|mono| mono.get().first_hit_object())
|
||||
.and_then(|h| hit_objects.previous_note(&h.get(), 0))
|
||||
}
|
||||
|
||||
pub fn next_color_change<'a>(
|
||||
&self,
|
||||
hit_objects: &'a TaikoDifficultyObjects,
|
||||
) -> Option<&'a RefCount<TaikoDifficultyObject>> {
|
||||
self.mono_streak
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.and_then(|mono| mono.get().last_hit_object())
|
||||
.and_then(|h| hit_objects.next_note(&h.get(), 0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,4 +35,8 @@ impl MonoStreak {
|
||||
pub fn first_hit_object(&self) -> Option<RefCount<TaikoDifficultyObject>> {
|
||||
self.hit_objects.first().and_then(Weak::upgrade)
|
||||
}
|
||||
|
||||
pub fn last_hit_object(&self) -> Option<RefCount<TaikoDifficultyObject>> {
|
||||
self.hit_objects.last().and_then(Weak::upgrade)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
taiko::difficulty::object::TaikoDifficultyObjects,
|
||||
util::sync::{Ref, RefCount},
|
||||
util::sync::{Ref, RefCount, Weak},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -17,11 +17,6 @@ impl ColorDifficultyPreprocessor {
|
||||
let hit_patterns = Self::encode(hit_objects);
|
||||
|
||||
for repeating_hit_pattern in hit_patterns {
|
||||
if let Some(obj) = repeating_hit_pattern.get().first_hit_object() {
|
||||
obj.get_mut().color.repeating_hit_patterns =
|
||||
Some(RefCount::clone(&repeating_hit_pattern));
|
||||
}
|
||||
|
||||
let mono_patterns = Ref::map(repeating_hit_pattern.get(), |repeating| {
|
||||
repeating.alternating_mono_patterns.as_slice()
|
||||
});
|
||||
@@ -33,11 +28,6 @@ impl ColorDifficultyPreprocessor {
|
||||
mono_pattern.idx = i;
|
||||
}
|
||||
|
||||
if let Some(obj) = mono_pattern.get().first_hit_object() {
|
||||
obj.get_mut().color.alternating_mono_pattern =
|
||||
Some(RefCount::downgrade(mono_pattern));
|
||||
}
|
||||
|
||||
let mono_streaks = Ref::map(mono_pattern.get(), |alternating| {
|
||||
alternating.mono_streaks.as_slice()
|
||||
});
|
||||
@@ -49,9 +39,19 @@ impl ColorDifficultyPreprocessor {
|
||||
borrowed.idx = j;
|
||||
}
|
||||
|
||||
if let Some(obj) = mono_streak.get().first_hit_object() {
|
||||
obj.get_mut().color.mono_streak = Some(RefCount::downgrade(mono_streak));
|
||||
};
|
||||
for hit_object in mono_streak
|
||||
.get()
|
||||
.hit_objects
|
||||
.iter()
|
||||
.filter_map(Weak::upgrade)
|
||||
{
|
||||
let mut borrowed = hit_object.get_mut();
|
||||
borrowed.color.repeating_hit_patterns =
|
||||
Some(RefCount::clone(&repeating_hit_pattern));
|
||||
borrowed.color.alternating_mono_pattern =
|
||||
Some(RefCount::downgrade(mono_pattern));
|
||||
borrowed.color.mono_streak = Some(RefCount::downgrade(mono_streak));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{cmp, mem, slice::Iter};
|
||||
|
||||
use crate::{
|
||||
any::difficulty::skills::Skill,
|
||||
model::{beatmap::HitWindows, hit_object::HitObject},
|
||||
taiko::TaikoBeatmap,
|
||||
util::sync::RefCount,
|
||||
@@ -8,8 +9,9 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
combined_difficulty_value,
|
||||
object::{TaikoDifficultyObject, TaikoDifficultyObjects},
|
||||
skills::peaks::{Peaks, PeaksSkill},
|
||||
skills::TaikoSkills,
|
||||
DifficultyValues, TaikoDifficultyAttributes,
|
||||
};
|
||||
|
||||
@@ -53,7 +55,7 @@ pub struct TaikoGradualDifficulty {
|
||||
attrs: TaikoDifficultyAttributes,
|
||||
diff_objects: TaikoDifficultyObjects,
|
||||
diff_objects_iter: Iter<'static, RefCount<TaikoDifficultyObject>>,
|
||||
peaks: Peaks,
|
||||
skills: TaikoSkills,
|
||||
total_hits: usize,
|
||||
first_combos: FirstTwoCombos,
|
||||
}
|
||||
@@ -96,7 +98,7 @@ impl TaikoGradualDifficulty {
|
||||
&mut n_diff_objects,
|
||||
);
|
||||
|
||||
let peaks = Peaks::new();
|
||||
let skills = TaikoSkills::new();
|
||||
|
||||
let attrs = TaikoDifficultyAttributes {
|
||||
hit_window,
|
||||
@@ -117,7 +119,7 @@ impl TaikoGradualDifficulty {
|
||||
difficulty,
|
||||
diff_objects,
|
||||
diff_objects_iter,
|
||||
peaks,
|
||||
skills,
|
||||
attrs,
|
||||
total_hits,
|
||||
first_combos,
|
||||
@@ -144,7 +146,10 @@ impl Iterator for TaikoGradualDifficulty {
|
||||
loop {
|
||||
let curr = self.diff_objects_iter.next()?;
|
||||
let borrowed = curr.get();
|
||||
PeaksSkill::new(&mut self.peaks, &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.stamina, &self.diff_objects).process(&borrowed);
|
||||
|
||||
if borrowed.base_hit_type.is_hit() {
|
||||
self.attrs.max_combo += 1;
|
||||
@@ -166,10 +171,14 @@ impl Iterator for TaikoGradualDifficulty {
|
||||
|
||||
self.idx += 1;
|
||||
|
||||
let color = self.peaks.color_difficulty_value();
|
||||
let rhythm = self.peaks.rhythm_difficulty_value();
|
||||
let stamina = self.peaks.stamina_difficulty_value();
|
||||
let combined = self.peaks.clone().difficulty_value();
|
||||
let color = self.skills.color.as_difficulty_value();
|
||||
let rhythm = self.skills.rhythm.as_difficulty_value();
|
||||
let stamina = self.skills.stamina.as_difficulty_value();
|
||||
let combined = combined_difficulty_value(
|
||||
self.skills.color.clone(),
|
||||
self.skills.rhythm.clone(),
|
||||
self.skills.stamina.clone(),
|
||||
);
|
||||
|
||||
let mut attrs = self.attrs.clone();
|
||||
|
||||
@@ -225,13 +234,17 @@ impl Iterator for TaikoGradualDifficulty {
|
||||
}
|
||||
}
|
||||
|
||||
let mut peaks = PeaksSkill::new(&mut self.peaks, &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 stamina = Skill::new(&mut self.skills.stamina, &self.diff_objects);
|
||||
|
||||
for _ in 0..take {
|
||||
loop {
|
||||
let curr = self.diff_objects_iter.next()?;
|
||||
let borrowed = curr.get();
|
||||
peaks.process(&borrowed);
|
||||
rhythm.process(&borrowed);
|
||||
color.process(&borrowed);
|
||||
stamina.process(&borrowed);
|
||||
|
||||
if borrowed.base_hit_type.is_hit() {
|
||||
self.attrs.max_combo += 1;
|
||||
|
||||
+86
-31
@@ -1,16 +1,18 @@
|
||||
use std::cmp;
|
||||
|
||||
use crate::{
|
||||
any::difficulty::skills::Skill,
|
||||
taiko::{
|
||||
difficulty::{
|
||||
color::preprocessor::ColorDifficultyPreprocessor,
|
||||
object::{TaikoDifficultyObject, TaikoDifficultyObjects},
|
||||
skills::peaks::PeaksSkill,
|
||||
},
|
||||
object::TaikoObject,
|
||||
},
|
||||
Difficulty,
|
||||
};
|
||||
|
||||
use self::skills::peaks::Peaks;
|
||||
use self::skills::{color::Color, rhythm::Rhythm, stamina::Stamina, TaikoSkills};
|
||||
|
||||
use super::{attributes::TaikoDifficultyAttributes, convert::TaikoBeatmap};
|
||||
|
||||
@@ -20,7 +22,10 @@ mod object;
|
||||
mod rhythm;
|
||||
mod skills;
|
||||
|
||||
const DIFFICULTY_MULTIPLIER: f64 = 1.35;
|
||||
const DIFFICULTY_MULTIPLIER: f64 = 0.084_375;
|
||||
const RHYTHM_SKILL_MULTIPLIER: f64 = 0.2 * DIFFICULTY_MULTIPLIER;
|
||||
const COLOR_SKILL_MULTIPLIER: f64 = 0.375 * DIFFICULTY_MULTIPLIER;
|
||||
const STAMINA_SKILL_MULTIPLIER: f64 = 0.375 * DIFFICULTY_MULTIPLIER;
|
||||
|
||||
pub fn difficulty(
|
||||
difficulty: &Difficulty,
|
||||
@@ -32,7 +37,14 @@ pub fn difficulty(
|
||||
.hit_windows()
|
||||
.od;
|
||||
|
||||
let DifficultyValues { peaks, max_combo } = DifficultyValues::calculate(difficulty, converted);
|
||||
let DifficultyValues {
|
||||
skills: TaikoSkills {
|
||||
rhythm,
|
||||
color,
|
||||
stamina,
|
||||
},
|
||||
max_combo,
|
||||
} = DifficultyValues::calculate(difficulty, converted);
|
||||
|
||||
let mut attrs = TaikoDifficultyAttributes {
|
||||
hit_window,
|
||||
@@ -41,10 +53,10 @@ pub fn difficulty(
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let color_rating = peaks.color_difficulty_value();
|
||||
let rhythm_rating = peaks.rhythm_difficulty_value();
|
||||
let stamina_rating = peaks.stamina_difficulty_value();
|
||||
let combined_rating = peaks.difficulty_value();
|
||||
let color_rating = color.as_difficulty_value();
|
||||
let rhythm_rating = rhythm.as_difficulty_value();
|
||||
let stamina_rating = stamina.as_difficulty_value();
|
||||
let combined_rating = combined_difficulty_value(color, rhythm, stamina);
|
||||
|
||||
DifficultyValues::eval(
|
||||
&mut attrs,
|
||||
@@ -57,6 +69,57 @@ pub fn difficulty(
|
||||
attrs
|
||||
}
|
||||
|
||||
fn combined_difficulty_value(color: Color, rhythm: Rhythm, stamina: Stamina) -> f64 {
|
||||
fn norm(p: f64, values: [f64; 2]) -> f64 {
|
||||
values
|
||||
.into_iter()
|
||||
.fold(0.0, |sum, x| sum + x.powf(p))
|
||||
.powf(p.recip())
|
||||
}
|
||||
|
||||
let color_peaks = color.get_curr_strain_peaks();
|
||||
let rhythm_peaks = rhythm.get_curr_strain_peaks();
|
||||
let stamina_peaks = stamina.get_curr_strain_peaks();
|
||||
|
||||
let cap = cmp::min(
|
||||
cmp::min(color_peaks.len(), rhythm_peaks.len()),
|
||||
stamina_peaks.len(),
|
||||
);
|
||||
let mut peaks = Vec::with_capacity(cap);
|
||||
|
||||
let iter = color_peaks
|
||||
.iter()
|
||||
.zip(rhythm_peaks.iter())
|
||||
.zip(stamina_peaks.iter());
|
||||
|
||||
for ((mut color_peak, mut rhythm_peak), mut stamina_peak) in iter {
|
||||
color_peak *= COLOR_SKILL_MULTIPLIER;
|
||||
rhythm_peak *= RHYTHM_SKILL_MULTIPLIER;
|
||||
stamina_peak *= STAMINA_SKILL_MULTIPLIER;
|
||||
|
||||
let mut peak = norm(1.5, [color_peak, stamina_peak]);
|
||||
peak = norm(2.0, [peak, rhythm_peak]);
|
||||
|
||||
// * Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
||||
// * These sections will not contribute to the difficulty.
|
||||
if peak > 0.0 {
|
||||
peaks.push(peak);
|
||||
}
|
||||
}
|
||||
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
peaks.sort_by(|a, b| b.total_cmp(a));
|
||||
|
||||
for strain in peaks {
|
||||
difficulty += strain * weight;
|
||||
weight *= 0.9;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
|
||||
fn rescale(stars: f64) -> f64 {
|
||||
if stars < 0.0 {
|
||||
stars
|
||||
@@ -66,7 +129,7 @@ fn rescale(stars: f64) -> f64 {
|
||||
}
|
||||
|
||||
pub struct DifficultyValues {
|
||||
pub peaks: Peaks,
|
||||
pub skills: TaikoSkills,
|
||||
pub max_combo: u32,
|
||||
}
|
||||
|
||||
@@ -89,17 +152,21 @@ impl DifficultyValues {
|
||||
// The first two hit objects have no difficulty object
|
||||
n_diff_objects = n_diff_objects.saturating_sub(2);
|
||||
|
||||
let mut peaks = Peaks::new();
|
||||
let mut skills = TaikoSkills::new();
|
||||
|
||||
{
|
||||
let mut peaks = PeaksSkill::new(&mut peaks, &diff_objects);
|
||||
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);
|
||||
|
||||
for hit_object in diff_objects.iter().take(n_diff_objects) {
|
||||
peaks.process(&hit_object.get());
|
||||
rhythm.process(&hit_object.get());
|
||||
color.process(&hit_object.get());
|
||||
stamina.process(&hit_object.get());
|
||||
}
|
||||
}
|
||||
|
||||
Self { peaks, max_combo }
|
||||
Self { skills, max_combo }
|
||||
}
|
||||
|
||||
pub fn eval(
|
||||
@@ -107,26 +174,14 @@ impl DifficultyValues {
|
||||
color_difficulty_value: f64,
|
||||
rhythm_difficulty_value: f64,
|
||||
stamina_difficulty_value: f64,
|
||||
peaks_difficulty_value: f64,
|
||||
combined_difficulty_value: f64,
|
||||
) {
|
||||
let color_rating = color_difficulty_value * DIFFICULTY_MULTIPLIER;
|
||||
let rhythm_rating = rhythm_difficulty_value * DIFFICULTY_MULTIPLIER;
|
||||
let stamina_rating = stamina_difficulty_value * DIFFICULTY_MULTIPLIER;
|
||||
let combined_rating = peaks_difficulty_value * DIFFICULTY_MULTIPLIER;
|
||||
let color_rating = color_difficulty_value * COLOR_SKILL_MULTIPLIER;
|
||||
let rhythm_rating = rhythm_difficulty_value * RHYTHM_SKILL_MULTIPLIER;
|
||||
let stamina_rating = stamina_difficulty_value * STAMINA_SKILL_MULTIPLIER;
|
||||
let combined_rating = combined_difficulty_value;
|
||||
|
||||
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.8;
|
||||
}
|
||||
}
|
||||
let star_rating = rescale(combined_rating * 1.4);
|
||||
|
||||
attrs.stamina = stamina_rating;
|
||||
attrs.rhythm = rhythm_rating;
|
||||
|
||||
@@ -132,15 +132,23 @@ impl TaikoDifficultyObjects {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous_note(
|
||||
&self,
|
||||
pub fn previous_note<'a>(
|
||||
&'a self,
|
||||
curr: &TaikoDifficultyObject,
|
||||
backwards_idx: usize,
|
||||
) -> Option<&RefCount<TaikoDifficultyObject>> {
|
||||
) -> Option<&'a RefCount<TaikoDifficultyObject>> {
|
||||
curr.note_idx
|
||||
.checked_sub(backwards_idx + 1)
|
||||
.and_then(|idx| self.note_objects.get(idx))
|
||||
}
|
||||
|
||||
pub fn next_note<'a>(
|
||||
&'a self,
|
||||
curr: &TaikoDifficultyObject,
|
||||
forwards_idx: usize,
|
||||
) -> Option<&'a RefCount<TaikoDifficultyObject>> {
|
||||
self.note_objects.get(curr.note_idx + (forwards_idx + 1))
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
@@ -180,3 +188,9 @@ impl IDifficultyObject for TaikoDifficultyObject {
|
||||
self.idx
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TaikoDifficultyObject {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.idx == other.idx
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,11 @@ impl ColorEvaluator {
|
||||
let mut difficulty = 0.0;
|
||||
|
||||
if let Some(mono_streak) = color.mono_streak.as_ref().and_then(Weak::upgrade) {
|
||||
difficulty += Self::evaluate_diff_of_mono_streak(&mono_streak);
|
||||
if let Some(first_hit_object) = mono_streak.get().first_hit_object() {
|
||||
if &*first_hit_object.get() == hit_object {
|
||||
difficulty += Self::evaluate_diff_of_mono_streak(&mono_streak);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(alternating_mono_pattern) = color
|
||||
@@ -162,12 +166,21 @@ impl ColorEvaluator {
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
{
|
||||
difficulty +=
|
||||
Self::evaluate_diff_of_alternating_mono_pattern(&alternating_mono_pattern);
|
||||
if let Some(first_hit_object) = alternating_mono_pattern.get().first_hit_object() {
|
||||
if &*first_hit_object.get() == hit_object {
|
||||
difficulty +=
|
||||
Self::evaluate_diff_of_alternating_mono_pattern(&alternating_mono_pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(repeating_hit_patterns) = color.repeating_hit_patterns.as_ref() {
|
||||
difficulty += Self::evaluate_diff_of_repeating_hit_patterns(repeating_hit_patterns);
|
||||
if let Some(first_hit_object) = repeating_hit_patterns.get().first_hit_object() {
|
||||
if &*first_hit_object.get() == hit_object {
|
||||
difficulty +=
|
||||
Self::evaluate_diff_of_repeating_hit_patterns(repeating_hit_patterns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
difficulty
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
use self::{color::Color, rhythm::Rhythm, stamina::Stamina};
|
||||
|
||||
pub mod color;
|
||||
pub mod peaks;
|
||||
pub mod rhythm;
|
||||
pub mod stamina;
|
||||
|
||||
pub struct TaikoSkills {
|
||||
pub rhythm: Rhythm,
|
||||
pub color: Color,
|
||||
pub stamina: Stamina,
|
||||
}
|
||||
|
||||
impl TaikoSkills {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
rhythm: Rhythm::default(),
|
||||
color: Color::default(),
|
||||
stamina: Stamina::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
use std::cmp;
|
||||
|
||||
use crate::{
|
||||
any::difficulty::skills::Skill,
|
||||
taiko::difficulty::object::{TaikoDifficultyObject, TaikoDifficultyObjects},
|
||||
};
|
||||
|
||||
use super::{color::Color, rhythm::Rhythm, stamina::Stamina};
|
||||
|
||||
const RHYTHM_SKILL_MULTIPLIER: f64 = 0.2 * FINAL_MULTIPLIER;
|
||||
const COLOR_SKILL_MULTIPLIER: f64 = 0.375 * FINAL_MULTIPLIER;
|
||||
const STAMINA_SKILL_MULTIPLIER: f64 = 0.375 * FINAL_MULTIPLIER;
|
||||
|
||||
const FINAL_MULTIPLIER: f64 = 0.0625;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Peaks {
|
||||
pub color: Color,
|
||||
pub rhythm: Rhythm,
|
||||
pub stamina: Stamina,
|
||||
}
|
||||
|
||||
impl Peaks {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
color: Color::default(),
|
||||
rhythm: Rhythm::default(),
|
||||
stamina: Stamina::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color_difficulty_value(&self) -> f64 {
|
||||
self.color.as_difficulty_value() * COLOR_SKILL_MULTIPLIER
|
||||
}
|
||||
|
||||
pub fn rhythm_difficulty_value(&self) -> f64 {
|
||||
self.rhythm.as_difficulty_value() * RHYTHM_SKILL_MULTIPLIER
|
||||
}
|
||||
|
||||
pub fn stamina_difficulty_value(&self) -> f64 {
|
||||
self.stamina.as_difficulty_value() * STAMINA_SKILL_MULTIPLIER
|
||||
}
|
||||
|
||||
fn norm(p: f64, values: impl IntoIterator<Item = f64>) -> f64 {
|
||||
values
|
||||
.into_iter()
|
||||
.fold(0.0, |sum, x| sum + x.powf(p))
|
||||
.powf(p.recip())
|
||||
}
|
||||
|
||||
pub fn difficulty_value(self) -> f64 {
|
||||
let color_peaks = self.color.get_curr_strain_peaks();
|
||||
let rhythm_peaks = self.rhythm.get_curr_strain_peaks();
|
||||
let stamina_peaks = self.stamina.get_curr_strain_peaks();
|
||||
|
||||
let cap = cmp::min(
|
||||
cmp::min(color_peaks.len(), rhythm_peaks.len()),
|
||||
stamina_peaks.len(),
|
||||
);
|
||||
let mut peaks = Vec::with_capacity(cap);
|
||||
|
||||
let zip = color_peaks
|
||||
.iter()
|
||||
.zip(rhythm_peaks.iter())
|
||||
.zip(stamina_peaks.iter());
|
||||
|
||||
for ((mut color_peak, mut rhythm_peak), mut stamina_peak) in zip {
|
||||
color_peak *= COLOR_SKILL_MULTIPLIER;
|
||||
rhythm_peak *= RHYTHM_SKILL_MULTIPLIER;
|
||||
stamina_peak *= STAMINA_SKILL_MULTIPLIER;
|
||||
|
||||
let mut peak = Self::norm(1.5, [color_peak, stamina_peak]);
|
||||
peak = Self::norm(2.0, [peak, rhythm_peak]);
|
||||
|
||||
if peak > 0.0 {
|
||||
peaks.push(peak);
|
||||
}
|
||||
}
|
||||
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
peaks.sort_by(|a, b| b.total_cmp(a));
|
||||
|
||||
for strain in peaks {
|
||||
difficulty += strain * weight;
|
||||
weight *= 0.9;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PeaksSkill<'a> {
|
||||
pub color: Skill<'a, Color>,
|
||||
pub rhythm: Skill<'a, Rhythm>,
|
||||
pub stamina: Skill<'a, Stamina>,
|
||||
}
|
||||
|
||||
impl<'a> PeaksSkill<'a> {
|
||||
pub fn new(peaks: &'a mut Peaks, diff_objects: &'a TaikoDifficultyObjects) -> Self {
|
||||
Self {
|
||||
color: Skill::new(&mut peaks.color, diff_objects),
|
||||
rhythm: Skill::new(&mut peaks.rhythm, diff_objects),
|
||||
stamina: Skill::new(&mut peaks.stamina, diff_objects),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&mut self, curr: &TaikoDifficultyObject) {
|
||||
self.rhythm.process(curr);
|
||||
self.color.process(curr);
|
||||
self.stamina.process(curr);
|
||||
}
|
||||
}
|
||||
@@ -100,28 +100,53 @@ struct StaminaEvaluator;
|
||||
|
||||
impl StaminaEvaluator {
|
||||
fn speed_bonus(mut interval: f64) -> f64 {
|
||||
// * Cap to 600bpm 1/4, 25ms note interval, 50ms key interval
|
||||
// * Interval will be capped at a very small value to avoid infinite/negative speed bonuses.
|
||||
// * TODO - This is a temporary measure as we need to implement methods of detecting playstyle-abuse of SpeedBonus.
|
||||
interval = interval.max(50.0);
|
||||
// * Interval is capped at a very small value to prevent infinite values.
|
||||
interval = interval.max(1.0);
|
||||
|
||||
30.0 / interval
|
||||
}
|
||||
|
||||
fn available_fingers_for(
|
||||
hit_object: &TaikoDifficultyObject,
|
||||
hit_objects: &TaikoDifficultyObjects,
|
||||
) -> usize {
|
||||
let prev_color_change = hit_object.color.previous_color_change(hit_objects);
|
||||
|
||||
if prev_color_change
|
||||
.is_some_and(|change| hit_object.start_time - change.get().start_time < 300.0)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
let next_color_change = hit_object.color.next_color_change(hit_objects);
|
||||
|
||||
if next_color_change
|
||||
.is_some_and(|change| change.get().start_time - hit_object.start_time < 300.0)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
4
|
||||
}
|
||||
|
||||
fn evaluate_diff_of(curr: &TaikoDifficultyObject, hit_objects: &TaikoDifficultyObjects) -> f64 {
|
||||
if matches!(curr.base_hit_type, HitType::NonHit) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// * Find the previous hit object hit by the current key, which is two notes of the same colour prior.
|
||||
// * Find the previous hit object hit by the current finger, which is n notes prior, n being the number of
|
||||
// * available fingers.
|
||||
let taiko_curr = curr;
|
||||
let key_prev = hit_objects.previous_mono(taiko_curr, 1);
|
||||
let key_prev = hit_objects.previous_mono(
|
||||
taiko_curr,
|
||||
Self::available_fingers_for(taiko_curr, hit_objects) - 1,
|
||||
);
|
||||
|
||||
if let Some(key_prev) = key_prev {
|
||||
// * Add a base strain to all objects
|
||||
0.5 + Self::speed_bonus(taiko_curr.start_time - key_prev.get().start_time)
|
||||
} else {
|
||||
// * There is no previous hit object hit by the current key
|
||||
// * There is no previous hit object hit by the current finger
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,7 +446,7 @@ impl TaikoPerformanceInner<'_> {
|
||||
diff_value *= 0.985;
|
||||
}
|
||||
|
||||
if self.mods.hd() {
|
||||
if self.mods.hd() && !self.attrs.is_convert {
|
||||
diff_value *= 1.025;
|
||||
}
|
||||
|
||||
@@ -476,8 +476,8 @@ impl TaikoPerformanceInner<'_> {
|
||||
let len_bonus = (self.total_hits() / 1500.0).powf(0.3).min(1.15);
|
||||
acc_value *= len_bonus;
|
||||
|
||||
// * Slight HDFL Bonus for accuracy. A clamp is used to prevent against negative values
|
||||
if self.mods.hd() && self.mods.fl() {
|
||||
// * Slight HDFL Bonus for accuracy. A clamp is used to prevent against negative values.
|
||||
if self.mods.hd() && self.mods.fl() && !self.attrs.is_convert {
|
||||
acc_value *= (1.075 * len_bonus).max(1.05);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ pub fn strains(difficulty: &Difficulty, converted: &TaikoBeatmap<'_>) -> TaikoSt
|
||||
let values = DifficultyValues::calculate(difficulty, converted);
|
||||
|
||||
TaikoStrains {
|
||||
color: values.peaks.color.get_curr_strain_peaks().into_vec(),
|
||||
rhythm: values.peaks.rhythm.get_curr_strain_peaks().into_vec(),
|
||||
stamina: values.peaks.stamina.get_curr_strain_peaks().into_vec(),
|
||||
color: values.skills.color.get_curr_strain_peaks().into_vec(),
|
||||
rhythm: values.skills.rhythm.get_curr_strain_peaks().into_vec(),
|
||||
stamina: values.skills.stamina.get_curr_strain_peaks().into_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ pub use inner::*;
|
||||
mod inner {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct RefCount<T>(pub(super) Rc<RefCell<T>>);
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct Weak<T>(pub(super) std::rc::Weak<RefCell<T>>);
|
||||
|
||||
pub type Ref<'a, T> = std::cell::Ref<'a, T>;
|
||||
@@ -45,8 +47,10 @@ mod inner {
|
||||
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||
};
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct RefCount<T>(pub(super) Arc<RwLock<T>>);
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct Weak<T>(pub(super) std::sync::Weak<RwLock<T>>);
|
||||
|
||||
pub struct Ref<'a, T: ?Sized>(RwLockReadGuard<'a, T>);
|
||||
|
||||
Reference in New Issue
Block a user