provided a way for partial plays

This commit is contained in:
MaxOhn
2021-01-17 19:43:18 +01:00
parent 12bf2f01b6
commit bbc6698fc7
13 changed files with 143 additions and 47 deletions
+24 -5
View File
@@ -17,7 +17,6 @@ impl PpProvider for Beatmap {
}
}
// TODO: Allow partial plays
pub struct PpCalculator<'m> {
map: &'m Beatmap,
attributes: Option<Attributes>,
@@ -29,6 +28,7 @@ pub struct PpCalculator<'m> {
n100: Option<usize>,
n50: Option<usize>,
n_misses: usize,
passed_objects: Option<usize>,
}
impl<'m> PpCalculator<'m> {
@@ -45,6 +45,7 @@ impl<'m> PpCalculator<'m> {
n100: None,
n50: None,
n_misses: 0,
passed_objects: None,
}
}
@@ -97,11 +98,23 @@ impl<'m> PpCalculator<'m> {
self
}
/// Amount of passed objects for partial plays, e.g. a fail.
#[inline]
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
self.passed_objects.replace(passed_objects);
self
}
/// Generate the hit results with respect to the given accuracy between `0` and `100`.
///
/// 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 {
let n_objects = self.map.hit_objects.len();
let n_objects = self
.passed_objects
.unwrap_or_else(|| self.map.hit_objects.len());
let acc = acc / 100.0;
if self.n100.or(self.n50).is_some() {
@@ -132,14 +145,20 @@ impl<'m> PpCalculator<'m> {
/// Consume the calculator and calculate the pp.
///
/// `stars_func` will be used to calculate stars and other necessary attributes if not already given.
pub fn calculate(mut self, stars_func: impl FnOnce(&Beatmap, u32) -> Attributes) -> PpResult {
/// Only called if `attributes` was not set.
pub fn calculate(
mut self,
stars_func: impl FnOnce(&Beatmap, u32, Option<usize>) -> Attributes,
) -> PpResult {
if self.attributes.is_none() {
let attributes = stars_func(self.map, self.mods);
let attributes = stars_func(self.map, self.mods, self.passed_objects);
self.attributes.replace(attributes);
}
if self.acc.is_none() {
let n_objects = self.map.hit_objects.len();
let n_objects = self
.passed_objects
.unwrap_or_else(|| self.map.hit_objects.len());
let remaining = n_objects
.saturating_sub(self.n300.unwrap_or(0))
+11 -2
View File
@@ -15,12 +15,20 @@ const SECTION_LEN: f32 = 400.0;
const DIFFICULTY_MULTIPLIER: f32 = 0.0675;
/// Star calculation for osu!standard maps
pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
///
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
pub fn stars(
map: &Beatmap,
mods: impl Mods,
passed_objects: Option<usize>,
) -> DifficultyAttributes {
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
let attributes = map.attributes().mods(mods);
let hitwindow = super::difficulty_range(attributes.od).floor() / attributes.clock_rate;
let od = (80.0 - hitwindow) / 6.0;
if map.hit_objects.len() < 2 {
if take < 2 {
return DifficultyAttributes {
ar: attributes.ar,
od,
@@ -33,6 +41,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
let mut hit_objects = map
.hit_objects
.iter()
.take(take)
.map(|h| OsuObject::new(h, map, &attributes));
let mut skills = vec![Skill::new(SkillKind::Aim), Skill::new(SkillKind::Speed)];
+11 -3
View File
@@ -27,7 +27,15 @@ const NORMALIZED_RADIUS: f32 = 52.0;
/// it has generally little effect on stars, the results are close to perfect.
/// This version is considerably more efficient than `all_included` since
/// processing stack leniency is relatively expensive.
pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
///
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
pub fn stars(
map: &Beatmap,
mods: impl Mods,
passed_objects: Option<usize>,
) -> DifficultyAttributes {
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
let map_attributes = map.attributes().mods(mods);
let hitwindow = super::difficulty_range(map_attributes.od).floor() / map_attributes.clock_rate;
let od = (80.0 - hitwindow) / 6.0;
@@ -38,7 +46,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
..Default::default()
};
if map.hit_objects.len() < 2 {
if take < 2 {
return diff_attributes;
}
@@ -54,7 +62,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
let mut slider_state = SliderState::new(map);
let mut ticks_buf = Vec::new();
let mut hit_objects = map.hit_objects.iter().map(|h| {
let mut hit_objects = map.hit_objects.iter().take(take).map(|h| {
OsuObject::new(
h,
map,
+11 -3
View File
@@ -25,12 +25,20 @@ const NORMALIZED_RADIUS: f32 = 52.0;
/// Sliders are considered as regular hitcircles and stack leniency is ignored.
/// Still very good results but the least precise version in general.
/// However, this is the most efficient one.
pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
///
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
pub fn stars(
map: &Beatmap,
mods: impl Mods,
passed_objects: Option<usize>,
) -> DifficultyAttributes {
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
let attributes = map.attributes().mods(mods);
let hitwindow = super::difficulty_range(attributes.od).floor() / attributes.clock_rate;
let od = (80.0 - hitwindow) / 6.0;
if map.hit_objects.len() < 2 {
if take < 2 {
return DifficultyAttributes {
ar: attributes.ar,
od,
@@ -52,7 +60,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
let mut n_spinners = 0;
let mut state = SliderState::new(&map);
let mut hit_objects = map.hit_objects.iter().map(|h| match &h.kind {
let mut hit_objects = map.hit_objects.iter().take(take).map(|h| match &h.kind {
HitObjectKind::Circle => {
max_combo += 1;
n_circles += 1;