allow relax pp calculations to be done purely via attributes
This commit is contained in:
+32
-22
@@ -30,7 +30,7 @@ use crate::{Beatmap, GameMods};
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OsuPP<'m> {
|
pub struct OsuPP<'m> {
|
||||||
map: &'m Beatmap,
|
map: Option<&'m Beatmap>,
|
||||||
attributes: Option<OsuDifficultyAttributes>,
|
attributes: Option<OsuDifficultyAttributes>,
|
||||||
mods: GameMods,
|
mods: GameMods,
|
||||||
combo: Option<u32>,
|
combo: Option<u32>,
|
||||||
@@ -45,9 +45,9 @@ pub struct OsuPP<'m> {
|
|||||||
impl<'m> OsuPP<'m> {
|
impl<'m> OsuPP<'m> {
|
||||||
/// Creates a new calculator for the given map.
|
/// Creates a new calculator for the given map.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(map: &'m Beatmap) -> Self {
|
pub fn from_map(map: &'m Beatmap) -> Self {
|
||||||
Self {
|
Self {
|
||||||
map,
|
map: Some(map),
|
||||||
attributes: None,
|
attributes: None,
|
||||||
mods: GameMods::default(),
|
mods: GameMods::default(),
|
||||||
combo: None,
|
combo: None,
|
||||||
@@ -59,18 +59,20 @@ impl<'m> OsuPP<'m> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`OsuAttributeProvider`] is implemented by [`DifficultyAttributes`](crate::osu::DifficultyAttributes)
|
/// Creates a new calculator for the given attributes.
|
||||||
/// and by [`PpResult`](crate::PpResult) meaning you can give the
|
|
||||||
/// result of a star calculation or a pp calculation.
|
|
||||||
/// If you already calculated the attributes for the current map-mod combination,
|
|
||||||
/// be sure to put them in here so that they don't have to be recalculated.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn attributes(mut self, attributes: impl OsuAttributeProvider) -> Self {
|
pub fn from_attributes(attributes: OsuDifficultyAttributes) -> Self {
|
||||||
if let Some(attributes) = attributes.attributes() {
|
Self {
|
||||||
self.attributes = Some(attributes);
|
map: None,
|
||||||
|
attributes: Some(attributes),
|
||||||
|
mods: GameMods::default(),
|
||||||
|
combo: None,
|
||||||
|
acc: None,
|
||||||
|
n300: None,
|
||||||
|
n100: None,
|
||||||
|
n50: None,
|
||||||
|
n_misses: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify mods through their bit values.
|
/// Specify mods through their bit values.
|
||||||
@@ -126,9 +128,8 @@ impl<'m> OsuPP<'m> {
|
|||||||
/// Generate the hit results with respect to the given accuracy between `0` and `100`.
|
/// Generate the hit results with respect to the given accuracy between `0` and `100`.
|
||||||
///
|
///
|
||||||
/// Be sure to set `misses` beforehand!
|
/// Be sure to set `misses` beforehand!
|
||||||
/// In case of a partial play, be also sure to set `passed_objects` beforehand!
|
|
||||||
pub fn accuracy(mut self, acc: f32) -> Self {
|
pub fn accuracy(mut self, acc: f32) -> Self {
|
||||||
let n_objects = self.map.hit_objects.len() as u32;
|
let n_objects = self.n_objects();
|
||||||
|
|
||||||
let acc = acc / 100.0;
|
let acc = acc / 100.0;
|
||||||
|
|
||||||
@@ -187,7 +188,7 @@ impl<'m> OsuPP<'m> {
|
|||||||
|
|
||||||
fn assert_hitresults(&mut self) {
|
fn assert_hitresults(&mut self) {
|
||||||
if self.acc.is_none() {
|
if self.acc.is_none() {
|
||||||
let n_objects = self.map.hit_objects.len() as u32;
|
let n_objects = self.n_objects();
|
||||||
|
|
||||||
let remaining = n_objects
|
let remaining = n_objects
|
||||||
.saturating_sub(self.n300.unwrap_or(0))
|
.saturating_sub(self.n300.unwrap_or(0))
|
||||||
@@ -223,7 +224,7 @@ impl<'m> OsuPP<'m> {
|
|||||||
/// containing stars and other attributes.
|
/// containing stars and other attributes.
|
||||||
pub fn calculate(mut self) -> OsuPerformanceAttributes {
|
pub fn calculate(mut self) -> OsuPerformanceAttributes {
|
||||||
if self.attributes.is_none() {
|
if self.attributes.is_none() {
|
||||||
let attributes = stars(self.map, self.mods.clone());
|
let attributes = stars(self.map.unwrap(), self.mods.clone());
|
||||||
self.attributes.replace(attributes);
|
self.attributes.replace(attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,11 +276,11 @@ impl<'m> OsuPP<'m> {
|
|||||||
pp *= 1.025;
|
pp *= 1.025;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.map.creator == "gwb" || self.map.creator == "Plasma" {
|
if difficulty.beatmap_creator == "gwb" || difficulty.beatmap_creator == "Plasma" {
|
||||||
pp *= 0.9;
|
pp *= 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
pp *= match self.map.beatmap_id {
|
pp *= match difficulty.beatmap_id {
|
||||||
// Louder than steel [ok this is epic]
|
// Louder than steel [ok this is epic]
|
||||||
1808605 => 0.85,
|
1808605 => 0.85,
|
||||||
|
|
||||||
@@ -317,10 +318,9 @@ impl<'m> OsuPP<'m> {
|
|||||||
|
|
||||||
OsuPerformanceAttributes {
|
OsuPerformanceAttributes {
|
||||||
difficulty: self.attributes.unwrap(),
|
difficulty: self.attributes.unwrap(),
|
||||||
pp_acc: 0.0,
|
|
||||||
pp_aim: aim_value as f64,
|
pp_aim: aim_value as f64,
|
||||||
pp_flashlight: 0.0,
|
|
||||||
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,
|
||||||
}
|
}
|
||||||
@@ -481,7 +481,7 @@ impl<'m> OsuPP<'m> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn total_hits(&self) -> u32 {
|
fn total_hits(&self) -> u32 {
|
||||||
let n_objects = self.map.hit_objects.len() as u32;
|
let n_objects = self.n_objects();
|
||||||
|
|
||||||
(self.n300.unwrap_or(0) + self.n100.unwrap_or(0) + self.n50.unwrap_or(0) + self.n_misses)
|
(self.n300.unwrap_or(0) + self.n100.unwrap_or(0) + self.n50.unwrap_or(0) + self.n_misses)
|
||||||
.min(n_objects)
|
.min(n_objects)
|
||||||
@@ -514,6 +514,16 @@ impl<'m> OsuPP<'m> {
|
|||||||
combo_based_miss_count = combo_based_miss_count.min(n100 + n50 + self.n_misses as f32);
|
combo_based_miss_count = combo_based_miss_count.min(n100 + n50 + self.n_misses as f32);
|
||||||
combo_based_miss_count.max(self.n_misses as f32)
|
combo_based_miss_count.max(self.n_misses as f32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn n_objects(&self) -> u32 {
|
||||||
|
match self.attributes.as_ref() {
|
||||||
|
Some(attributes) => {
|
||||||
|
(attributes.n_circles + attributes.n_sliders + attributes.n_spinners) as u32
|
||||||
|
}
|
||||||
|
None => self.map.unwrap().hit_objects.len() as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides attributes for an osu! beatmap.
|
/// Provides attributes for an osu! beatmap.
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ pub fn stars(map: &Beatmap, mods: GameMods) -> OsuDifficultyAttributes {
|
|||||||
ar: map_attributes.ar,
|
ar: map_attributes.ar,
|
||||||
od: map_attributes.od,
|
od: map_attributes.od,
|
||||||
cs: map_attributes.cs,
|
cs: map_attributes.cs,
|
||||||
|
beatmap_id: map.beatmap_id,
|
||||||
|
beatmap_creator: map.creator.clone(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,6 +158,8 @@ pub struct OsuDifficultyAttributes {
|
|||||||
pub max_combo: usize,
|
pub max_combo: usize,
|
||||||
pub aim_difficult_strain_count: f64,
|
pub aim_difficult_strain_count: f64,
|
||||||
pub speed_difficult_strain_count: f64,
|
pub speed_difficult_strain_count: f64,
|
||||||
|
pub beatmap_id: i32,
|
||||||
|
pub beatmap_creator: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -164,7 +168,6 @@ pub struct OsuPerformanceAttributes {
|
|||||||
pub pp: f64,
|
pub pp: f64,
|
||||||
pub pp_acc: f64,
|
pub pp_acc: f64,
|
||||||
pub pp_aim: f64,
|
pub pp_aim: f64,
|
||||||
pub pp_flashlight: 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