initial rewrite

This commit is contained in:
MaxOhn
2024-02-09 17:54:14 +01:00
parent ecf1b2c7f4
commit a8bf9601b6
190 changed files with 10892 additions and 18142 deletions
+89
View File
@@ -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(),
}
}
}
+27
View File
@@ -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
}
}
+1
View File
@@ -0,0 +1 @@
pub mod strain;
+186
View File
@@ -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)
}