all of it at once
This commit is contained in:
+44
-93
@@ -243,7 +243,7 @@ impl<'m> OsuPP<'m> {
|
|||||||
self.assert_hitresults();
|
self.assert_hitresults();
|
||||||
|
|
||||||
let total_hits = self.total_hits() as f32;
|
let total_hits = self.total_hits() as f32;
|
||||||
let mut multiplier = 1.09;
|
let mut multiplier = 1.2;
|
||||||
|
|
||||||
let effective_miss_count = self.calculate_effective_miss_count();
|
let effective_miss_count = self.calculate_effective_miss_count();
|
||||||
|
|
||||||
@@ -253,11 +253,8 @@ impl<'m> OsuPP<'m> {
|
|||||||
1.0 - (self.attributes.as_ref().unwrap().n_spinners as f32 / total_hits).powf(0.85);
|
1.0 - (self.attributes.as_ref().unwrap().n_spinners as f32 / total_hits).powf(0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut aim_value = self.compute_aim_value(total_hits, effective_miss_count);
|
let aim_value = self.compute_aim_value(total_hits, effective_miss_count);
|
||||||
let speed_value = self.compute_speed_value(total_hits, effective_miss_count);
|
let mut speed_value = self.compute_speed_value(total_hits, effective_miss_count);
|
||||||
let acc_value = self.compute_accuracy_value(total_hits);
|
|
||||||
|
|
||||||
let mut acc_depression = 1.0;
|
|
||||||
|
|
||||||
let difficulty = self.attributes.as_ref().unwrap();
|
let difficulty = self.attributes.as_ref().unwrap();
|
||||||
let streams_nerf =
|
let streams_nerf =
|
||||||
@@ -265,24 +262,15 @@ impl<'m> OsuPP<'m> {
|
|||||||
|
|
||||||
if streams_nerf < 1.09 {
|
if streams_nerf < 1.09 {
|
||||||
let acc_factor = (1.0 - self.acc.unwrap()).abs();
|
let acc_factor = (1.0 - self.acc.unwrap()).abs();
|
||||||
acc_depression = (0.86 - acc_factor).max(0.5);
|
speed_value *= (0.86 - acc_factor).max(0.5);
|
||||||
|
|
||||||
if acc_depression > 0.0 {
|
|
||||||
aim_value *= acc_depression;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let pp = (aim_value.powf(1.185)
|
let pp = (aim_value.powf(1.1) + speed_value.powf(1.1)).powf(1.0 / 1.1) * multiplier;
|
||||||
+ speed_value.powf(0.83 * acc_depression)
|
|
||||||
+ acc_value.powf(1.14))
|
|
||||||
.powf(1.0 / 1.1)
|
|
||||||
* multiplier;
|
|
||||||
|
|
||||||
OsuPerformanceAttributes {
|
OsuPerformanceAttributes {
|
||||||
difficulty: self.attributes.unwrap(),
|
difficulty: self.attributes.unwrap(),
|
||||||
pp_aim: aim_value as f64,
|
pp_aim: aim_value as f64,
|
||||||
pp_speed: speed_value as f64,
|
pp_speed: speed_value as f64,
|
||||||
pp_acc: acc_value as f64,
|
|
||||||
pp: pp as f64,
|
pp: pp as f64,
|
||||||
effective_miss_count: effective_miss_count as f64,
|
effective_miss_count: effective_miss_count as f64,
|
||||||
}
|
}
|
||||||
@@ -301,33 +289,23 @@ impl<'m> OsuPP<'m> {
|
|||||||
let mut aim_value = (5.0 * (raw_aim / 0.0675).max(1.0) - 4.0).powi(3) / 100_000.0;
|
let mut aim_value = (5.0 * (raw_aim / 0.0675).max(1.0) - 4.0).powi(3) / 100_000.0;
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
let len_bonus = 0.88
|
let len_bonus = 0.95
|
||||||
+ 0.4 * (total_hits / 2000.0).min(1.0)
|
+ 0.4 * (total_hits / 2000.0).min(1.0)
|
||||||
+ (total_hits > 2000.0) as u8 as f32 * 0.5 * (total_hits / 2000.0).log10();
|
+ (total_hits > 2000.0) as u8 as f32 * 0.5 * (total_hits / 2000.0).log10();
|
||||||
aim_value *= len_bonus;
|
aim_value *= len_bonus;
|
||||||
|
|
||||||
// Penalize misses
|
// Penalize misses
|
||||||
if effective_miss_count > 0.0 {
|
if effective_miss_count > 0.0 {
|
||||||
let miss_penalty = self.calculate_miss_penalty(effective_miss_count);
|
let miss_penalty = self.calculate_miss_penalty(
|
||||||
|
effective_miss_count,
|
||||||
|
attributes.aim_difficult_strain_count,
|
||||||
|
);
|
||||||
aim_value *= miss_penalty;
|
aim_value *= miss_penalty;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AR bonus
|
|
||||||
let mut ar_factor = if attributes.ar > 10.33 {
|
|
||||||
0.3 * (attributes.ar - 10.33)
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
};
|
|
||||||
|
|
||||||
if attributes.ar < 8.0 {
|
|
||||||
ar_factor = 0.025 * (8.0 - attributes.ar);
|
|
||||||
}
|
|
||||||
|
|
||||||
aim_value *= 1.0 + ar_factor as f32 * len_bonus;
|
|
||||||
|
|
||||||
// HD bonus
|
// HD bonus
|
||||||
if self.mods.hd() {
|
if self.mods.hd() {
|
||||||
aim_value *= 1.0 + 0.05 * (11.0 - attributes.ar) as f32;
|
aim_value *= 1.0 + 0.04 * (12.0 - attributes.ar) as f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FL bonus
|
// FL bonus
|
||||||
@@ -341,7 +319,7 @@ impl<'m> OsuPP<'m> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scale with accuracy
|
// Scale with accuracy
|
||||||
aim_value *= 0.3 + self.acc.unwrap() / 2.0;
|
aim_value *= self.acc.unwrap();
|
||||||
aim_value *= 0.98 + attributes.od as f32 * attributes.od as f32 / 2500.0;
|
aim_value *= 0.98 + attributes.od as f32 * attributes.od as f32 / 2500.0;
|
||||||
|
|
||||||
aim_value
|
aim_value
|
||||||
@@ -354,45 +332,47 @@ impl<'m> OsuPP<'m> {
|
|||||||
(5.0 * (attributes.speed_strain as f32 / 0.0675).max(1.0) - 4.0).powi(3) / 100_000.0;
|
(5.0 * (attributes.speed_strain as f32 / 0.0675).max(1.0) - 4.0).powi(3) / 100_000.0;
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
let len_bonus = 0.88
|
let len_bonus = 0.95
|
||||||
+ 0.4 * (total_hits / 2000.0).min(1.0)
|
+ 0.4 * (total_hits / 2000.0).min(1.0)
|
||||||
+ (total_hits > 2000.0) as u8 as f32 * 0.5 * (total_hits / 2000.0).log10();
|
+ (total_hits > 2000.0) as u8 as f32 * 0.5 * (total_hits / 2000.0).log10();
|
||||||
speed_value *= len_bonus;
|
speed_value *= len_bonus;
|
||||||
|
|
||||||
// Penalize misses
|
// Penalize misses
|
||||||
if effective_miss_count > 0.0 {
|
if effective_miss_count > 0.0 {
|
||||||
let miss_penalty = self.calculate_miss_penalty(effective_miss_count);
|
let miss_penalty = self.calculate_miss_penalty(
|
||||||
|
effective_miss_count,
|
||||||
|
attributes.speed_difficult_strain_count,
|
||||||
|
);
|
||||||
speed_value *= miss_penalty;
|
speed_value *= miss_penalty;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AR bonus
|
|
||||||
if attributes.ar > 10.33 {
|
|
||||||
let mut ar_factor = if attributes.ar > 10.33 {
|
|
||||||
0.3 * (attributes.ar - 10.33)
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
};
|
|
||||||
|
|
||||||
if attributes.ar < 8.0 {
|
|
||||||
ar_factor = 0.025 * (8.0 - attributes.ar);
|
|
||||||
}
|
|
||||||
|
|
||||||
speed_value *= 1.0 + ar_factor as f32 * len_bonus;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HD bonus
|
// HD bonus
|
||||||
if self.mods.hd() {
|
if self.mods.hd() {
|
||||||
speed_value *= 1.0 + 0.05 * (11.0 - attributes.ar) as f32;
|
speed_value *= 1.0 + 0.04 * (12.0 - attributes.ar) as f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scaling the speed value with accuracy and OD
|
// Scaling the speed value with accuracy and OD
|
||||||
speed_value *= (0.93 + attributes.od as f32 * attributes.od as f32 / 750.0)
|
let n300 = self.n300.unwrap();
|
||||||
* self
|
let n100 = self.n100.unwrap();
|
||||||
.acc
|
let n50 = self.n50.unwrap();
|
||||||
.unwrap()
|
|
||||||
.powf((14.5 - attributes.od.max(8.0) as f32) / 2.0);
|
|
||||||
|
|
||||||
speed_value *= 0.98_f32.powf(match (self.n50.unwrap() as f32) < total_hits / 500.0 {
|
let relevant_total_diff = total_hits - attributes.speed_note_count;
|
||||||
|
let relevant_n300 = (n300 as f32 - relevant_total_diff).max(0.0);
|
||||||
|
let relevant_n100 = (n100 as f32 - (relevant_total_diff - n300 as f32).max(0.0)).max(0.0);
|
||||||
|
let relevant_n50 =
|
||||||
|
(n50 as f32 - (relevant_total_diff - (n300 + n100) as f32).max(0.0)).max(0.0);
|
||||||
|
|
||||||
|
let relevant_acc = if attributes.speed_note_count == 0.0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
(relevant_n300 * 6.0 + relevant_n100 * 2.0 + relevant_n50)
|
||||||
|
/ (attributes.speed_note_count * 6.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
speed_value *= (0.95 + attributes.od as f32 * attributes.od as f32 / 750.0)
|
||||||
|
* ((self.acc.unwrap() + relevant_acc) / 2.0).powf((14.5 - attributes.od as f32) / 2.0);
|
||||||
|
|
||||||
|
speed_value *= 0.99_f32.powf(match (n50 as f32) < total_hits / 500.0 {
|
||||||
true => 0.0,
|
true => 0.0,
|
||||||
false => self.n50.unwrap() as f32 - total_hits / 500.0,
|
false => self.n50.unwrap() as f32 - total_hits / 500.0,
|
||||||
});
|
});
|
||||||
@@ -400,36 +380,6 @@ impl<'m> OsuPP<'m> {
|
|||||||
speed_value
|
speed_value
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_accuracy_value(&self, total_hits: f32) -> f32 {
|
|
||||||
let attributes = self.attributes.as_ref().unwrap();
|
|
||||||
let n_circles = attributes.n_circles as f32;
|
|
||||||
let n300 = self.n300.unwrap_or(0) as f32;
|
|
||||||
let n100 = self.n100.unwrap_or(0) as f32;
|
|
||||||
let n50 = self.n50.unwrap_or(0) as f32;
|
|
||||||
|
|
||||||
let better_acc_percentage = (n_circles > 0.0) as u8 as f32
|
|
||||||
* (((n300 - (total_hits - n_circles)) * 6.0 + n100 * 2.0 + n50) / (n_circles * 6.0))
|
|
||||||
.max(0.0);
|
|
||||||
|
|
||||||
let mut acc_value =
|
|
||||||
1.52163_f32.powf(attributes.od as f32) * better_acc_percentage.powi(24) * 2.83;
|
|
||||||
|
|
||||||
// Bonus for many hitcircles
|
|
||||||
acc_value *= ((n_circles as f32 / 1000.0).powf(0.3)).min(1.15);
|
|
||||||
|
|
||||||
// HD bonus
|
|
||||||
if self.mods.hd() {
|
|
||||||
acc_value *= 1.08;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FL bonus
|
|
||||||
if self.mods.fl() {
|
|
||||||
acc_value *= 1.02;
|
|
||||||
}
|
|
||||||
|
|
||||||
acc_value
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn total_hits(&self) -> u32 {
|
fn total_hits(&self) -> u32 {
|
||||||
let n_objects = self.n_objects();
|
let n_objects = self.n_objects();
|
||||||
@@ -439,11 +389,12 @@ impl<'m> OsuPP<'m> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn calculate_miss_penalty(&self, effective_miss_count: f32) -> f32 {
|
fn calculate_miss_penalty(
|
||||||
let total_hits = self.total_hits() as f32;
|
&self,
|
||||||
|
effective_miss_count: f32,
|
||||||
0.97 * (1.0 - (effective_miss_count / total_hits).powf(0.5))
|
difficult_strain_count: f32,
|
||||||
.powf(1.0 + (effective_miss_count / 1.5))
|
) -> f32 {
|
||||||
|
0.96 / ((effective_miss_count / (4.0 * difficult_strain_count.ln().powf(0.94))) + 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
+24
-7
@@ -19,6 +19,8 @@ pub(crate) struct Skill {
|
|||||||
|
|
||||||
prev_time: Option<f32>,
|
prev_time: Option<f32>,
|
||||||
pub(crate) object_strains: Vec<f32>,
|
pub(crate) object_strains: Vec<f32>,
|
||||||
|
|
||||||
|
difficulty_value: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Skill {
|
impl Skill {
|
||||||
@@ -33,6 +35,8 @@ impl Skill {
|
|||||||
|
|
||||||
prev_time: None,
|
prev_time: None,
|
||||||
object_strains: Vec::new(),
|
object_strains: Vec::new(),
|
||||||
|
|
||||||
|
difficulty_value: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,19 +73,32 @@ impl Skill {
|
|||||||
weight *= DECAY_WEIGHT;
|
weight *= DECAY_WEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.difficulty_value = Some(difficulty);
|
||||||
|
|
||||||
difficulty
|
difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn count_difficult_strains(&mut self) -> f64 {
|
pub(crate) fn count_difficult_strains(&mut self) -> f32 {
|
||||||
let top_strain = self
|
let difficulty_value = self.difficulty_value.unwrap_or(self.difficulty_value());
|
||||||
.object_strains
|
let single_strain = difficulty_value / 10.0;
|
||||||
.iter()
|
|
||||||
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(*curr as f64));
|
|
||||||
|
|
||||||
self.object_strains
|
self.object_strains
|
||||||
.iter()
|
.iter()
|
||||||
.map(|strain| (strain / top_strain as f32).powi(4))
|
.map(|strain| 1.1 / (1.0 + (-10.0 * (strain / single_strain - 0.88)).exp()))
|
||||||
.sum::<f32>() as f64
|
.sum::<f32>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn relevant_note_count(&self) -> f32 {
|
||||||
|
self.object_strains
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.max_by(f32::total_cmp)
|
||||||
|
.filter(|&n| n > 0.0)
|
||||||
|
.map_or(0.0, |max_strain| {
|
||||||
|
self.object_strains.iter().fold(0.0, |sum, strain| {
|
||||||
|
sum + (1.0 + (-(strain / max_strain * 12.0 - 6.0)).exp()).recip()
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ pub fn stars(
|
|||||||
let aim_difficult_strain_count = aim.count_difficult_strains();
|
let aim_difficult_strain_count = aim.count_difficult_strains();
|
||||||
let speed_difficult_strain_count = speed.count_difficult_strains();
|
let speed_difficult_strain_count = speed.count_difficult_strains();
|
||||||
|
|
||||||
|
let speed_note_count = speed.relevant_note_count();
|
||||||
|
|
||||||
let stars = aim_strain + speed_strain + (aim_strain - speed_strain).abs() / 2.0;
|
let stars = aim_strain + speed_strain + (aim_strain - speed_strain).abs() / 2.0;
|
||||||
|
|
||||||
diff_attributes.stars = stars as f64;
|
diff_attributes.stars = stars as f64;
|
||||||
@@ -145,6 +147,7 @@ pub fn stars(
|
|||||||
diff_attributes.aim_strain = aim_strain as f64;
|
diff_attributes.aim_strain = aim_strain as f64;
|
||||||
diff_attributes.aim_difficult_strain_count = aim_difficult_strain_count;
|
diff_attributes.aim_difficult_strain_count = aim_difficult_strain_count;
|
||||||
diff_attributes.speed_difficult_strain_count = speed_difficult_strain_count;
|
diff_attributes.speed_difficult_strain_count = speed_difficult_strain_count;
|
||||||
|
diff_attributes.speed_note_count = speed_note_count;
|
||||||
|
|
||||||
diff_attributes
|
diff_attributes
|
||||||
}
|
}
|
||||||
@@ -162,17 +165,17 @@ pub struct OsuDifficultyAttributes {
|
|||||||
pub n_spinners: usize,
|
pub n_spinners: usize,
|
||||||
pub stars: f64,
|
pub stars: f64,
|
||||||
pub max_combo: usize,
|
pub max_combo: usize,
|
||||||
pub aim_difficult_strain_count: f64,
|
pub aim_difficult_strain_count: f32,
|
||||||
pub speed_difficult_strain_count: f64,
|
pub speed_difficult_strain_count: f32,
|
||||||
pub beatmap_id: i32,
|
pub beatmap_id: i32,
|
||||||
pub beatmap_creator: String,
|
pub beatmap_creator: String,
|
||||||
|
pub speed_note_count: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OsuPerformanceAttributes {
|
pub struct OsuPerformanceAttributes {
|
||||||
pub difficulty: OsuDifficultyAttributes,
|
pub difficulty: OsuDifficultyAttributes,
|
||||||
pub pp: f64,
|
pub pp: f64,
|
||||||
pub pp_acc: f64,
|
|
||||||
pub pp_aim: f64,
|
pub pp_aim: f64,
|
||||||
pub pp_speed: f64,
|
pub pp_speed: f64,
|
||||||
pub effective_miss_count: f64,
|
pub effective_miss_count: f64,
|
||||||
|
|||||||
Reference in New Issue
Block a user