initial rewrite
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
use crate::{
|
||||
any::difficulty::{skills::Skill, ModeDifficulty},
|
||||
mania::{
|
||||
difficulty::{object::ManiaDifficultyObject, skills::strain::Strain},
|
||||
object::{ManiaObject, ObjectParams},
|
||||
},
|
||||
util::float_ext::FloatExt,
|
||||
};
|
||||
|
||||
use super::{attributes::ManiaDifficultyAttributes, convert::ManiaBeatmap};
|
||||
|
||||
mod object;
|
||||
mod skills;
|
||||
|
||||
const STAR_SCALING_FACTOR: f64 = 0.018;
|
||||
|
||||
pub fn difficulty(
|
||||
difficulty: &ModeDifficulty,
|
||||
converted: &ManiaBeatmap<'_>,
|
||||
) -> ManiaDifficultyAttributes {
|
||||
let hit_window = converted
|
||||
.attributes()
|
||||
.mods(difficulty.get_mods())
|
||||
.clock_rate(difficulty.get_clock_rate())
|
||||
.hit_windows()
|
||||
.od;
|
||||
|
||||
let n_objects = converted.map.hit_objects.len() as u32;
|
||||
let values = DifficultyValues::calculate(difficulty, converted);
|
||||
|
||||
ManiaDifficultyAttributes {
|
||||
stars: values.strain.difficulty_value() * STAR_SCALING_FACTOR,
|
||||
hit_window,
|
||||
max_combo: values.max_combo,
|
||||
n_objects,
|
||||
is_convert: converted.is_convert,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DifficultyValues {
|
||||
pub strain: Strain,
|
||||
pub max_combo: u32,
|
||||
}
|
||||
|
||||
impl DifficultyValues {
|
||||
pub fn calculate(difficulty: &ModeDifficulty, converted: &ManiaBeatmap<'_>) -> Self {
|
||||
let take = difficulty.get_passed_objects();
|
||||
let total_columns = converted.map.cs.round_even().max(1.0);
|
||||
let clock_rate = difficulty.get_clock_rate();
|
||||
let mut params = ObjectParams::new(converted.map.as_ref());
|
||||
|
||||
let mut mania_objects = converted
|
||||
.map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.map(|h| ManiaObject::new(h, total_columns, &mut params))
|
||||
.take(take);
|
||||
|
||||
let Some(first) = mania_objects.next() else {
|
||||
return DifficultyValues {
|
||||
strain: Strain::new(total_columns as usize),
|
||||
max_combo: 0,
|
||||
};
|
||||
};
|
||||
|
||||
let n_diff_objects = mania_objects.len();
|
||||
|
||||
let diff_objects_iter = mania_objects.enumerate().scan(first, |last, (i, base)| {
|
||||
let diff_object = ManiaDifficultyObject::new(&base, last, clock_rate, i);
|
||||
*last = base;
|
||||
|
||||
Some(diff_object)
|
||||
});
|
||||
|
||||
let mut diff_objects = Vec::with_capacity(n_diff_objects);
|
||||
diff_objects.extend(diff_objects_iter);
|
||||
|
||||
let mut strain = Skill::new(Strain::new(total_columns as usize), &diff_objects);
|
||||
|
||||
for curr in diff_objects.iter() {
|
||||
strain.process(curr);
|
||||
}
|
||||
|
||||
Self {
|
||||
strain: strain.inner,
|
||||
max_combo: params.into_max_combo(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use crate::{any::difficulty::object::IDifficultyObject, mania::object::ManiaObject};
|
||||
|
||||
pub struct ManiaDifficultyObject {
|
||||
pub idx: usize,
|
||||
pub base_column: usize,
|
||||
pub delta_time: f64,
|
||||
pub start_time: f64,
|
||||
pub end_time: f64,
|
||||
}
|
||||
|
||||
impl ManiaDifficultyObject {
|
||||
pub fn new(base: &ManiaObject, last: &ManiaObject, clock_rate: f64, idx: usize) -> Self {
|
||||
Self {
|
||||
idx,
|
||||
base_column: base.column,
|
||||
delta_time: (base.start_time - last.start_time) / clock_rate,
|
||||
start_time: base.start_time / clock_rate,
|
||||
end_time: base.end_time / clock_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IDifficultyObject for ManiaDifficultyObject {
|
||||
fn idx(&self) -> usize {
|
||||
self.idx
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod strain;
|
||||
@@ -0,0 +1,186 @@
|
||||
use crate::{
|
||||
any::difficulty::{
|
||||
object::IDifficultyObject,
|
||||
skills::{strain_decay, ISkill, Skill, StrainDecaySkill},
|
||||
},
|
||||
mania::difficulty::object::ManiaDifficultyObject,
|
||||
};
|
||||
|
||||
const INDIVIDUAL_DECAY_BASE: f64 = 0.125;
|
||||
const OVERALL_DECAY_BASE: f64 = 0.3;
|
||||
const RELEASE_THRESHOLD: f64 = 24.0;
|
||||
|
||||
const SKILL_MULTIPLIER: f64 = 1.0;
|
||||
const STRAIN_DECAY_BASE: f64 = 1.0;
|
||||
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub struct Strain {
|
||||
start_times: Box<[f64]>,
|
||||
end_times: Box<[f64]>,
|
||||
individual_strains: Box<[f64]>,
|
||||
|
||||
individual_strain: f64,
|
||||
overall_strain: f64,
|
||||
|
||||
inner: StrainDecaySkill,
|
||||
}
|
||||
|
||||
impl Strain {
|
||||
pub fn new(total_columns: usize) -> Self {
|
||||
Self {
|
||||
start_times: vec![0.0; total_columns].into_boxed_slice(),
|
||||
end_times: vec![0.0; total_columns].into_boxed_slice(),
|
||||
individual_strains: vec![0.0; total_columns].into_boxed_slice(),
|
||||
individual_strain: 0.0,
|
||||
overall_strain: 1.0,
|
||||
inner: StrainDecaySkill::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_curr_strain_peaks(self) -> Vec<f64> {
|
||||
self.inner.get_curr_strain_peaks()
|
||||
}
|
||||
|
||||
pub fn difficulty_value(self) -> f64 {
|
||||
self.inner.difficulty_value(StrainDecaySkill::DECAY_WEIGHT)
|
||||
}
|
||||
|
||||
const fn curr_strain(&self) -> f64 {
|
||||
self.inner.curr_strain
|
||||
}
|
||||
|
||||
fn curr_strain_mut(&mut self) -> &mut f64 {
|
||||
&mut self.inner.curr_strain
|
||||
}
|
||||
|
||||
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject) -> 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()
|
||||
}
|
||||
|
||||
fn strain_value_of(&mut self, curr: &ManiaDifficultyObject) -> f64 {
|
||||
let mania_curr = curr;
|
||||
let start_time = mania_curr.start_time;
|
||||
let end_time = mania_curr.end_time;
|
||||
let column = mania_curr.base_column;
|
||||
let mut is_overlapping = false;
|
||||
|
||||
// * Lowest value we can assume with the current information
|
||||
let mut closest_end_time = (end_time - start_time).abs();
|
||||
// * Factor to all additional strains in case something else is held
|
||||
let mut hold_factor = 1.0;
|
||||
// * Addition to the current note in case it's a hold and has to be released awkwardly
|
||||
let mut hold_addition = 0.0;
|
||||
|
||||
for i in 0..self.end_times.len() {
|
||||
// * The current note is overlapped if a previous note or end is overlapping the current note body
|
||||
is_overlapping |=
|
||||
self.end_times[i] > start_time + 1.0 && end_time > self.end_times[i] + 1.0;
|
||||
|
||||
// * We give a slight bonus to everything if something is held meanwhile
|
||||
if self.end_times[i] > end_time + 1.0 {
|
||||
hold_factor = 1.25;
|
||||
}
|
||||
|
||||
closest_end_time = (end_time - self.end_times[i]).abs().min(closest_end_time);
|
||||
}
|
||||
|
||||
// * The hold addition is given if there was an overlap, however it is only valid if there are no other note with a similar ending.
|
||||
// * Releasing multiple notes is just as easy as releasing 1. Nerfs the hold addition by half if the closest release is release_threshold away.
|
||||
// * holdAddition
|
||||
// * ^
|
||||
// * 1.0 + - - - - - -+-----------
|
||||
// * | /
|
||||
// * 0.5 + - - - - -/ Sigmoid Curve
|
||||
// * | /|
|
||||
// * 0.0 +--------+-+---------------> Release Difference / ms
|
||||
// * release_threshold
|
||||
if is_overlapping {
|
||||
hold_addition = (1.0 + (0.5 * (RELEASE_THRESHOLD - closest_end_time)).exp()).recip();
|
||||
}
|
||||
|
||||
// * Decay and increase individualStrains in own column
|
||||
self.individual_strains[column] = apply_decay(
|
||||
self.individual_strains[column],
|
||||
start_time - self.start_times[column],
|
||||
INDIVIDUAL_DECAY_BASE,
|
||||
);
|
||||
self.individual_strains[column] += 2.0 * hold_factor;
|
||||
|
||||
// * For notes at the same time (in a chord), the individualStrain should be the hardest individualStrain out of those columns
|
||||
self.individual_strain = if mania_curr.delta_time <= 1.0 {
|
||||
self.individual_strain.max(self.individual_strains[column])
|
||||
} else {
|
||||
self.individual_strains[column]
|
||||
};
|
||||
|
||||
// * Decay and increase overallStrain
|
||||
self.overall_strain = apply_decay(self.overall_strain, curr.delta_time, OVERALL_DECAY_BASE);
|
||||
self.overall_strain += (1.0 + hold_addition) * hold_factor;
|
||||
|
||||
// * Update startTimes and endTimes arrays
|
||||
self.start_times[column] = start_time;
|
||||
self.end_times[column] = end_time;
|
||||
|
||||
// * By subtracting CurrentStrain, this skill effectively only considers the maximum strain of any one hitobject within each strain section.
|
||||
self.individual_strain + self.overall_strain - self.curr_strain()
|
||||
}
|
||||
}
|
||||
|
||||
impl ISkill for Strain {
|
||||
type DifficultyObjects<'a> = [ManiaDifficultyObject];
|
||||
}
|
||||
|
||||
impl Skill<'_, Strain> {
|
||||
fn calculate_initial_strain(&mut self, offset: f64, curr: &ManiaDifficultyObject) -> f64 {
|
||||
let prev_start_time = curr
|
||||
.previous(0, self.diff_objects)
|
||||
.map_or(0.0, |prev| prev.start_time);
|
||||
|
||||
let time = offset - prev_start_time;
|
||||
|
||||
let individual = apply_decay(self.inner.individual_strain, time, INDIVIDUAL_DECAY_BASE);
|
||||
let overall = apply_decay(self.inner.overall_strain, time, OVERALL_DECAY_BASE);
|
||||
|
||||
individual + overall
|
||||
}
|
||||
|
||||
const fn curr_section_peak(&self) -> f64 {
|
||||
self.inner.inner.inner.curr_section_peak
|
||||
}
|
||||
|
||||
fn curr_section_peak_mut(&mut self) -> &mut f64 {
|
||||
&mut self.inner.inner.inner.curr_section_peak
|
||||
}
|
||||
|
||||
const fn curr_section_end(&self) -> f64 {
|
||||
self.inner.inner.inner.curr_section_end
|
||||
}
|
||||
|
||||
fn curr_section_end_mut(&mut self) -> &mut f64 {
|
||||
&mut self.inner.inner.inner.curr_section_end
|
||||
}
|
||||
|
||||
pub fn process(&mut self, curr: &ManiaDifficultyObject) {
|
||||
if curr.idx == 0 {
|
||||
*self.curr_section_end_mut() = (curr.start_time / StrainDecaySkill::SECTION_LEN).ceil()
|
||||
* StrainDecaySkill::SECTION_LEN;
|
||||
}
|
||||
|
||||
while curr.start_time > self.curr_section_end() {
|
||||
self.inner.inner.save_curr_peak();
|
||||
let initial_strain = self.calculate_initial_strain(self.curr_section_end(), curr);
|
||||
self.inner.inner.start_new_section_from(initial_strain);
|
||||
*self.curr_section_end_mut() += StrainDecaySkill::SECTION_LEN;
|
||||
}
|
||||
|
||||
let strain_value_at = self.inner.strain_value_at(curr);
|
||||
*self.curr_section_peak_mut() = strain_value_at.max(self.curr_section_peak());
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_decay(value: f64, delta_time: f64, decay_base: f64) -> f64 {
|
||||
value * decay_base.powf(delta_time / 1000.0)
|
||||
}
|
||||
Reference in New Issue
Block a user