various minor adjustments
This commit is contained in:
@@ -170,10 +170,12 @@ A large portion of this repository is a port of [osu!lazer](https://github.com/p
|
||||
- osu!:
|
||||
- osu!lazer: Commit `85adfc2df7d931164181e145377a6ced8db2bfb3` (Wed Sep 28 18:26:36 2022 +0300)
|
||||
- osu!tools: Commit `146d5916937161ef65906aa97f85d367035f3712` (Sat Oct 8 14:28:49 2022 +0900)
|
||||
- [Article](https://osu.ppy.sh/home/news/2022-09-30-changes-to-osu-sr-and-pp)
|
||||
|
||||
- taiko:
|
||||
- osu!lazer: Commit `234c6ac7998fbc6742503e1a589536255554e56a` (Wed Oct 5 20:21:15 2022 +0900)
|
||||
- osu!tools: Commit `146d5916937161ef65906aa97f85d367035f3712` (Sat Oct 8 14:28:49 2022 +0900)
|
||||
- [Article](https://osu.ppy.sh/home/news/2022-09-28-changes-to-osu-taiko-sr-and-pp)
|
||||
|
||||
- catch: (will be updated on the next rework)
|
||||
- osu!lazer: -
|
||||
@@ -182,6 +184,7 @@ A large portion of this repository is a port of [osu!lazer](https://github.com/p
|
||||
- mania:
|
||||
- osu!lazer: Commit `7342fb7f51b34533a42bffda89c3d6c569cc69ce` (Tue Oct 11 14:34:50 2022 +0900)
|
||||
- osu!tools: Commit `146d5916937161ef65906aa97f85d367035f3712` (Sat Oct 8 14:28:49 2022 +0900)
|
||||
- [Article](https://osu.ppy.sh/home/news/2022-10-09-changes-to-osu-mania-sr-and-pp)
|
||||
|
||||
### Accuracy
|
||||
|
||||
|
||||
@@ -89,8 +89,6 @@ impl Beatmap {
|
||||
}
|
||||
|
||||
/// Return the [`TimingPoint`] for the given timestamp.
|
||||
///
|
||||
/// If `time` is before the first timing point, `None` is returned.
|
||||
#[inline]
|
||||
pub fn timing_point_at(&self, time: f64) -> TimingPoint {
|
||||
let idx_result = self
|
||||
|
||||
+4
-2
@@ -16,8 +16,8 @@ use crate::{Beatmap, DifficultyAttributes, Mods, OsuPP, PerformanceAttributes};
|
||||
/// let pp_result = CatchPP::new(&map)
|
||||
/// .mods(8 + 64) // HDDT
|
||||
/// .combo(1234)
|
||||
/// .misses(1)
|
||||
/// .accuracy(98.5)
|
||||
/// .misses(1)
|
||||
/// .calculate();
|
||||
///
|
||||
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
||||
@@ -181,7 +181,8 @@ impl<'map> CatchPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Generate the hit results with respect to the given accuracy between `0` and `100`.
|
||||
// TODO: adjust this on the next rework
|
||||
/// Generate the hit results with respect to the given accuracy between `0.0` and `100.0`.
|
||||
///
|
||||
/// Be sure to set `misses` beforehand! Also, if available, set `attributes` beforehand.
|
||||
pub fn accuracy(mut self, mut acc: f64) -> Self {
|
||||
@@ -438,6 +439,7 @@ impl CatchPPInner {
|
||||
}
|
||||
|
||||
impl<'map> From<OsuPP<'map>> for CatchPP<'map> {
|
||||
#[inline]
|
||||
fn from(osu: OsuPP<'map>) -> Self {
|
||||
let OsuPP {
|
||||
map,
|
||||
|
||||
+1
-46
@@ -217,7 +217,7 @@ pub use mods::Mods;
|
||||
pub use parse::{ParseError, ParseResult};
|
||||
pub use util::SortedVec;
|
||||
|
||||
/// Provides some additional methods on [`Beatmap`](crate::Beatmap).
|
||||
/// Provides some additional methods on [`Beatmap`].
|
||||
pub trait BeatmapExt {
|
||||
/// Calculate the stars and other attributes of a beatmap which are required for pp calculation.
|
||||
fn stars(&self) -> AnyStars<'_>;
|
||||
@@ -509,48 +509,3 @@ impl From<taiko::TaikoPerformanceAttributes> for PerformanceAttributes {
|
||||
|
||||
#[cfg(all(feature = "async_tokio", feature = "async_std"))]
|
||||
compile_error!("Only one of the features `async_tokio` and `async_std` should be enabled");
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Beatmap, GameMode, OsuPP, PerformanceAttributes};
|
||||
|
||||
#[test]
|
||||
fn custom() {
|
||||
let path = "F:/osu!/beatmaps/1028484.osu";
|
||||
let map = Beatmap::from_path(path).unwrap();
|
||||
|
||||
let attrs = match OsuPP::new(&map).mode(GameMode::Taiko).mods(0).calculate() {
|
||||
PerformanceAttributes::Taiko(attrs) => attrs,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
println!("{attrs:#?}");
|
||||
|
||||
// println!(
|
||||
// "difficulty:\n\
|
||||
// stamina={}\n\
|
||||
// rhythm={}\n\
|
||||
// colour={}\n\
|
||||
// peak={}\n\
|
||||
// hit_window={}\n\
|
||||
// max_combo={}\n\
|
||||
// stars={}\n\
|
||||
// performance:\n\
|
||||
// difficulty={}\n\
|
||||
// acc={}\n\
|
||||
// effective={}\n\
|
||||
// pp={}\n",
|
||||
// attrs.difficulty.stamina,
|
||||
// attrs.difficulty.rhythm,
|
||||
// attrs.difficulty.colour,
|
||||
// attrs.difficulty.peak,
|
||||
// attrs.difficulty.hit_window,
|
||||
// attrs.difficulty.max_combo,
|
||||
// attrs.difficulty.stars,
|
||||
// attrs.pp_difficulty,
|
||||
// attrs.pp_acc,
|
||||
// attrs.effective_miss_count,
|
||||
// attrs.pp,
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -135,7 +135,7 @@ impl<'map> ManiaStars<'map> {
|
||||
let ManiaResult { strain, .. } = calculate_result(self);
|
||||
|
||||
ManiaStrains {
|
||||
section_len: SECTION_LEN * clock_rate, // TODO: clock_rate correct here?
|
||||
section_len: SECTION_LEN * clock_rate,
|
||||
strains: strain.strain_peaks,
|
||||
}
|
||||
}
|
||||
@@ -224,6 +224,14 @@ pub struct ManiaDifficultyAttributes {
|
||||
pub max_combo: usize,
|
||||
}
|
||||
|
||||
impl ManiaDifficultyAttributes {
|
||||
/// Return the maximum combo.
|
||||
#[inline]
|
||||
pub fn max_combo(&self) -> usize {
|
||||
self.max_combo
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a performance calculation on an osu!mania map.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub struct ManiaPerformanceAttributes {
|
||||
|
||||
+4
-6
@@ -5,7 +5,6 @@ use crate::{
|
||||
Beatmap, DifficultyAttributes, GameMode, HitResultPriority, Mods, OsuPP, PerformanceAttributes,
|
||||
};
|
||||
|
||||
// TODO: update
|
||||
/// Performance calculator on osu!mania maps.
|
||||
///
|
||||
/// # Example
|
||||
@@ -124,7 +123,7 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the accuracy of a play between `0` and `100`.
|
||||
/// Specify the accuracy of a play between `0.0` and `100.0`.
|
||||
/// This will be used to generate matching hitresults.
|
||||
#[inline]
|
||||
pub fn accuracy(mut self, acc: f64) -> Self {
|
||||
@@ -252,7 +251,6 @@ impl<'map> ManiaPP<'map> {
|
||||
let n_misses = self.n_misses.unwrap_or(0);
|
||||
|
||||
if let Some(acc) = self.acc {
|
||||
// TODO: test
|
||||
let target_total = (acc * (n_objects * 6) as f64).round() as usize;
|
||||
|
||||
match (self.n320, self.n300, self.n200, self.n100, self.n50) {
|
||||
@@ -530,11 +528,11 @@ impl ManiaPpInner {
|
||||
}
|
||||
|
||||
fn compute_difficulty_value(&self) -> f64 {
|
||||
// Star rating to pp curve
|
||||
// * Star rating to pp curve
|
||||
(self.attrs.stars - 0.15).max(0.05).powf(2.2)
|
||||
// From 80% accuracy, 1/20th of total pp is awarded per additional 1% accuracy
|
||||
// * From 80% accuracy, 1/20th of total pp is awarded per additional 1% accuracy
|
||||
* (5.0 * self.calculate_custom_accuracy() - 4.0).max(0.0)
|
||||
// Length bonus, capped at 1500 notes
|
||||
// * Length bonus, capped at 1500 notes
|
||||
* (1.0 + 0.1 * (self.total_hits() / 1500.0).min(1.0))
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,8 @@ pub(crate) trait StrainSkill: Sized + Skill {
|
||||
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject) -> f64;
|
||||
|
||||
fn process(&mut self, curr: &ManiaDifficultyObject, diff_objects: &[ManiaDifficultyObject]) {
|
||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
// * The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
if curr.idx == 0 {
|
||||
// currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
|
||||
*self.curr_section_end_mut() = (curr.start_time / SECTION_LEN).ceil() * SECTION_LEN;
|
||||
}
|
||||
|
||||
@@ -72,14 +71,14 @@ pub(crate) trait StrainSkill: Sized + Skill {
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
// Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
||||
// These sections will not contribute to the difficulty.
|
||||
// * Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
||||
// * These sections will not contribute to the difficulty.
|
||||
let mut peaks = self.get_curr_strain_peaks();
|
||||
peaks.retain(|&peak| peak > 0.0);
|
||||
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||
|
||||
// Difficulty is the weighted sum of the highest strains from every section.
|
||||
// We're sorting from highest to lowest strain.
|
||||
// * Difficulty is the weighted sum of the highest strains from every section.
|
||||
// * We're sorting from highest to lowest strain.
|
||||
for strain in peaks {
|
||||
difficulty += strain * weight;
|
||||
weight *= Self::DECAY_WEIGHT;
|
||||
|
||||
+31
-20
@@ -43,10 +43,12 @@ impl Strain {
|
||||
}
|
||||
|
||||
impl Skill for Strain {
|
||||
#[inline]
|
||||
fn process(&mut self, curr: &ManiaDifficultyObject, diff_objects: &[ManiaDifficultyObject]) {
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difficulty_value(self) -> f64 {
|
||||
<Self as StrainSkill>::difficulty_value(self)
|
||||
}
|
||||
@@ -55,30 +57,37 @@ impl Skill for Strain {
|
||||
impl StrainSkill for Strain {
|
||||
const DECAY_WEIGHT: f64 = 0.9;
|
||||
|
||||
#[inline]
|
||||
fn curr_section_end(&self) -> f64 {
|
||||
self.curr_section_end
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_end_mut(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_end
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_peak(&self) -> f64 {
|
||||
self.curr_section_peak
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_peak_mut(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_peak
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||
&mut self.strain_peaks
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject) -> f64 {
|
||||
<Self as StrainDecaySkill>::strain_value_at(self, curr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calculate_initial_strain(
|
||||
&self,
|
||||
time: f64,
|
||||
@@ -93,10 +102,12 @@ impl StrainDecaySkill for Strain {
|
||||
const SKILL_MULTIPLIER: f64 = 1.0;
|
||||
const STRAIN_DECAY_BASE: f64 = 1.0;
|
||||
|
||||
#[inline]
|
||||
fn curr_strain(&self) -> f64 {
|
||||
self.curr_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_strain_mut(&mut self) -> &mut f64 {
|
||||
&mut self.curr_strain
|
||||
}
|
||||
@@ -108,19 +119,19 @@ impl StrainDecaySkill for Strain {
|
||||
let col = mania_curr.base_column;
|
||||
let mut is_overlapping = false;
|
||||
|
||||
// Lowest value we can assume with the current information
|
||||
// * 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
|
||||
// * 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
|
||||
// * 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
|
||||
// * 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
|
||||
// * 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;
|
||||
}
|
||||
@@ -128,22 +139,22 @@ impl StrainDecaySkill for Strain {
|
||||
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
|
||||
// * 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 * (Self::RELEASE_THRESHOLD - closest_end_time)).exp()).recip();
|
||||
}
|
||||
|
||||
// Decay and increase individualStrains in own column
|
||||
// * Decay and increase individualStrains in own column
|
||||
self.individual_strains[col] = Self::apply_decay(
|
||||
self.individual_strains[col],
|
||||
start_time - self.start_times[col],
|
||||
@@ -151,14 +162,14 @@ impl StrainDecaySkill for Strain {
|
||||
);
|
||||
self.individual_strains[col] += 2.0 * hold_factor;
|
||||
|
||||
// For notes at the same time (in a chord), the individualStrain should be the hardest individualStrain out of those columns
|
||||
// * 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[col])
|
||||
} else {
|
||||
self.individual_strains[col]
|
||||
};
|
||||
|
||||
// Decay and increase overallStrain
|
||||
// * Decay and increase overallStrain
|
||||
self.overall_strain = Self::apply_decay(
|
||||
self.overall_strain,
|
||||
curr.delta_time,
|
||||
@@ -166,11 +177,11 @@ impl StrainDecaySkill for Strain {
|
||||
);
|
||||
self.overall_strain += (1.0 + hold_addition) * hold_factor;
|
||||
|
||||
// Update startTimes and endTimes arrays
|
||||
// * Update startTimes and endTimes arrays
|
||||
self.start_times[col] = start_time;
|
||||
self.end_times[col] = end_time;
|
||||
|
||||
// By subtracting CurrentStrain, this skill effectively only considers the maximum strain of any one hitobject within each strain section.
|
||||
// * 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
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ impl Debug for OsuGradualDifficultyAttributes {
|
||||
.field("attrs", &self.attrs)
|
||||
.field("diff_objects", &self.diff_objects)
|
||||
.field("skills", &"<cannot be displayed>")
|
||||
.field("hit_window", &self.hit_window)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -107,13 +108,11 @@ impl OsuGradualDifficultyAttributes {
|
||||
curve_bufs: CurveBuffers::default(),
|
||||
};
|
||||
|
||||
let hit_objects_iter = map
|
||||
let mut hit_objects: Vec<_> = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.filter_map(|h| OsuObject::new(h, &mut params));
|
||||
|
||||
let mut hit_objects = Vec::with_capacity(map.hit_objects.len());
|
||||
hit_objects.extend(hit_objects_iter);
|
||||
.map(|h| OsuObject::new(h, &mut params))
|
||||
.collect();
|
||||
|
||||
attrs.n_circles = 0;
|
||||
attrs.n_sliders = 0;
|
||||
|
||||
+3
-5
@@ -274,14 +274,12 @@ fn calculate_skills(params: OsuStars<'_>) -> ([Box<dyn Skill>; 4], OsuDifficulty
|
||||
curve_bufs: CurveBuffers::default(),
|
||||
};
|
||||
|
||||
let hit_objects_iter = map
|
||||
let mut hit_objects: Vec<_> = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.take(take)
|
||||
.filter_map(|h| OsuObject::new(h, &mut params));
|
||||
|
||||
let mut hit_objects = Vec::with_capacity(take.min(map.hit_objects.len()));
|
||||
hit_objects.extend(hit_objects_iter);
|
||||
.map(|h| OsuObject::new(h, &mut params))
|
||||
.collect();
|
||||
|
||||
let stack_threshold = time_preempt * map.stack_leniency as f64;
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ pub(crate) struct ObjectParameters<'a> {
|
||||
}
|
||||
|
||||
impl OsuObject {
|
||||
pub(crate) fn new(h: &HitObject, params: &mut ObjectParameters<'_>) -> Option<Self> {
|
||||
pub(crate) fn new(h: &HitObject, params: &mut ObjectParameters<'_>) -> Self {
|
||||
let ObjectParameters {
|
||||
map,
|
||||
attrs,
|
||||
@@ -103,7 +103,7 @@ impl OsuObject {
|
||||
attrs.max_combo += 1; // hitcircle, slider head, or spinner
|
||||
let pos = h.pos;
|
||||
|
||||
let obj = match &h.kind {
|
||||
match &h.kind {
|
||||
HitObjectKind::Circle => {
|
||||
attrs.n_circles += 1;
|
||||
|
||||
@@ -317,7 +317,7 @@ impl OsuObject {
|
||||
kind: OsuObjectKind::Slider(slider),
|
||||
}
|
||||
}
|
||||
HitObjectKind::Spinner { end_time } => {
|
||||
HitObjectKind::Spinner { end_time } | HitObjectKind::Hold { end_time } => {
|
||||
attrs.n_spinners += 1;
|
||||
|
||||
Self {
|
||||
@@ -330,10 +330,7 @@ impl OsuObject {
|
||||
},
|
||||
}
|
||||
}
|
||||
HitObjectKind::Hold { .. } => return None,
|
||||
};
|
||||
|
||||
Some(obj)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn end_time(&self) -> f64 {
|
||||
|
||||
+2
-2
@@ -21,8 +21,8 @@ use crate::{
|
||||
/// let pp_result = OsuPP::new(&map)
|
||||
/// .mods(8 + 64) // HDDT
|
||||
/// .combo(1234)
|
||||
/// .accuracy(98.5)
|
||||
/// .n_misses(1)
|
||||
/// .accuracy(98.5) // should be set last
|
||||
/// .calculate();
|
||||
///
|
||||
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
||||
@@ -199,7 +199,7 @@ impl<'map> OsuPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the accuracy of a play between `0` and `100`.
|
||||
/// Specify the accuracy of a play between `0.0` and `100.0`.
|
||||
/// This will be used to generate matching hitresults.
|
||||
#[inline]
|
||||
pub fn accuracy(mut self, acc: f64) -> Self {
|
||||
|
||||
@@ -37,6 +37,7 @@ impl Aim {
|
||||
}
|
||||
|
||||
impl Skill for Aim {
|
||||
#[inline]
|
||||
fn process(
|
||||
&mut self,
|
||||
curr: &OsuDifficultyObject<'_>,
|
||||
@@ -46,36 +47,44 @@ impl Skill for Aim {
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects, hit_window)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difficulty_value(&mut self) -> f64 {
|
||||
<Self as OsuStrainSkill>::difficulty_value(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
||||
mem::take(&mut self.strain_peaks)
|
||||
}
|
||||
}
|
||||
|
||||
impl StrainSkill for Aim {
|
||||
#[inline]
|
||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||
&mut self.strain_peaks
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_peak
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_end(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_end
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_value_at(
|
||||
&mut self,
|
||||
curr: &OsuDifficultyObject<'_>,
|
||||
@@ -89,6 +98,7 @@ impl StrainSkill for Aim {
|
||||
self.curr_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calculate_initial_strain(
|
||||
&self,
|
||||
time: f64,
|
||||
@@ -98,6 +108,7 @@ impl StrainSkill for Aim {
|
||||
self.curr_strain * Self::strain_decay(time - previous_start_time(diff_objects, curr.idx, 0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difficulty_value(&mut self) -> f64 {
|
||||
<Self as OsuStrainSkill>::difficulty_value(self)
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ impl Flashlight {
|
||||
}
|
||||
|
||||
impl Skill for Flashlight {
|
||||
#[inline]
|
||||
fn process(
|
||||
&mut self,
|
||||
curr: &OsuDifficultyObject<'_>,
|
||||
@@ -51,18 +52,22 @@ impl Skill for Flashlight {
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects, hit_window)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difficulty_value(&mut self) -> f64 {
|
||||
<Self as StrainSkill>::difficulty_value(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
||||
mem::take(&mut self.strain_peaks)
|
||||
}
|
||||
@@ -71,18 +76,22 @@ impl Skill for Flashlight {
|
||||
impl StrainSkill for Flashlight {
|
||||
const DECAY_WEIGHT: f64 = 0.9;
|
||||
|
||||
#[inline]
|
||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||
&mut self.strain_peaks
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_peak
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_end(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_end
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_value_at(
|
||||
&mut self,
|
||||
curr: &OsuDifficultyObject<'_>,
|
||||
@@ -102,6 +111,7 @@ impl StrainSkill for Flashlight {
|
||||
self.curr_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calculate_initial_strain(
|
||||
&self,
|
||||
time: f64,
|
||||
@@ -111,6 +121,7 @@ impl StrainSkill for Flashlight {
|
||||
self.curr_strain * Self::strain_decay(time - previous_start_time(diff_objects, curr.idx, 0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difficulty_value(&mut self) -> f64 {
|
||||
self.get_curr_strain_peaks().into_iter().sum::<f64>() * Self::DIFFICULTY_MULTIPLER
|
||||
}
|
||||
|
||||
@@ -70,11 +70,13 @@ pub(crate) trait StrainSkill: Skill + Sized {
|
||||
.max(*self.curr_section_peak());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn save_curr_peak(&mut self) {
|
||||
let peak = *self.curr_section_peak();
|
||||
self.strain_peaks_mut().push(peak);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn start_new_section_from(
|
||||
&mut self,
|
||||
time: f64,
|
||||
@@ -89,6 +91,7 @@ pub(crate) trait StrainSkill: Skill + Sized {
|
||||
|
||||
fn difficulty_value(&mut self) -> f64;
|
||||
|
||||
#[inline]
|
||||
fn get_curr_strain_peaks(&mut self) -> Vec<f64> {
|
||||
let curr_peak = *self.curr_section_peak();
|
||||
let mut strain_peaks = mem::take(self.strain_peaks_mut());
|
||||
|
||||
@@ -48,6 +48,7 @@ impl Speed {
|
||||
}
|
||||
|
||||
impl Skill for Speed {
|
||||
#[inline]
|
||||
fn process(
|
||||
&mut self,
|
||||
curr: &OsuDifficultyObject<'_>,
|
||||
@@ -57,36 +58,44 @@ impl Skill for Speed {
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects, hit_window)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difficulty_value(&mut self) -> f64 {
|
||||
<Self as OsuStrainSkill>::difficulty_value(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
||||
mem::take(&mut self.strain_peaks)
|
||||
}
|
||||
}
|
||||
|
||||
impl StrainSkill for Speed {
|
||||
#[inline]
|
||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||
&mut self.strain_peaks
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_peak
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_end(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_end
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_value_at(
|
||||
&mut self,
|
||||
curr: &OsuDifficultyObject<'_>,
|
||||
@@ -104,6 +113,7 @@ impl StrainSkill for Speed {
|
||||
total_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calculate_initial_strain(
|
||||
&self,
|
||||
time: f64,
|
||||
@@ -114,6 +124,7 @@ impl StrainSkill for Speed {
|
||||
* Self::strain_decay(time - previous_start_time(diff_objects, curr.idx, 0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difficulty_value(&mut self) -> f64 {
|
||||
<Self as OsuStrainSkill>::difficulty_value(self)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ impl HitObject {
|
||||
#[inline]
|
||||
pub fn end_time(&self) -> f64 {
|
||||
match &self.kind {
|
||||
HitObjectKind::Circle { .. } => self.start_time,
|
||||
HitObjectKind::Circle => self.start_time,
|
||||
// incorrect, only called in mania which has no sliders though
|
||||
HitObjectKind::Slider { .. } => self.start_time,
|
||||
HitObjectKind::Spinner { end_time } => *end_time,
|
||||
@@ -30,7 +30,7 @@ impl HitObject {
|
||||
/// If the object is a circle.
|
||||
#[inline]
|
||||
pub fn is_circle(&self) -> bool {
|
||||
matches!(self.kind, HitObjectKind::Circle { .. })
|
||||
matches!(self.kind, HitObjectKind::Circle)
|
||||
}
|
||||
|
||||
/// If the object is a slider.
|
||||
|
||||
@@ -12,18 +12,22 @@ pub trait HitSound {
|
||||
}
|
||||
|
||||
impl HitSound for u8 {
|
||||
#[inline]
|
||||
fn normal(self) -> bool {
|
||||
self == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn whistle(self) -> bool {
|
||||
self & Self::HITSOUND_WHISTLE > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn finish(self) -> bool {
|
||||
self & Self::HITSOUND_FINISH > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clap(self) -> bool {
|
||||
self & Self::HITSOUND_CLAP > 0
|
||||
}
|
||||
|
||||
+11
-89
@@ -689,7 +689,6 @@ macro_rules! parse_hitobjects_body {
|
||||
sorter.toggle_marks();
|
||||
sorter.sort(&mut $self.sounds);
|
||||
}
|
||||
// No need to sort hitsounds for the rest
|
||||
GameMode::Mania => {
|
||||
// First a _stable_ sort by time
|
||||
$self
|
||||
@@ -975,6 +974,7 @@ impl Beatmap {
|
||||
) -> ParseResult<bool> {
|
||||
parse_timingpoints_body!(self, reader, section)
|
||||
}
|
||||
|
||||
/// Pass the path to a `.osu` file.
|
||||
///
|
||||
/// Useful when you don't want to create the [`File`](std::fs::File) manually.
|
||||
@@ -983,6 +983,11 @@ impl Beatmap {
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
|
||||
Self::parse(File::open(path)?)
|
||||
}
|
||||
|
||||
/// Parse the content of a `.osu` file in form of a slice of bytes into a beatmap.
|
||||
pub fn from_bytes(bytes: &[u8]) -> ParseResult<Self> {
|
||||
Self::parse(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "async_tokio", feature = "async_std"))]
|
||||
@@ -1043,6 +1048,11 @@ impl Beatmap {
|
||||
pub async fn from_path<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
|
||||
Self::parse(File::open(path).await?).await
|
||||
}
|
||||
|
||||
/// Parse the content of a `.osu` file in form of a slice of bytes into a beatmap.
|
||||
pub async fn from_bytes(bytes: &[u8]) -> ParseResult<Self> {
|
||||
Self::parse(bytes).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -1067,91 +1077,3 @@ impl Section {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
|
||||
#[test]
|
||||
fn parsing_sync() {
|
||||
for map_id in map_ids() {
|
||||
println!("map_id: {}", map_id);
|
||||
|
||||
let map = match Beatmap::from_path(format!("./maps/{}.osu", map_id)) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
print_info(map);
|
||||
println!("---");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_tokio")]
|
||||
#[test]
|
||||
fn parsing_async_tokio() {
|
||||
use tokio::runtime::Builder;
|
||||
|
||||
Builder::new_current_thread()
|
||||
.build()
|
||||
.expect("could not start runtime")
|
||||
.block_on(async {
|
||||
for map_id in map_ids() {
|
||||
println!("map_id: {}", map_id);
|
||||
|
||||
let map = match Beatmap::from_path(format!("./maps/{}.osu", map_id)).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
print_info(map);
|
||||
println!("---");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_std")]
|
||||
#[test]
|
||||
fn parsing_async_std() {
|
||||
async_std::task::block_on(async {
|
||||
for map_id in map_ids() {
|
||||
println!("map_id: {}", map_id);
|
||||
|
||||
let map = match Beatmap::from_path(format!("./maps/{}.osu", map_id)).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
print_info(map);
|
||||
println!("---");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn map_ids() -> Vec<i32> {
|
||||
vec![
|
||||
2785319, // osu
|
||||
1974394, // mania
|
||||
2118524, // catch
|
||||
1028484, // taiko
|
||||
]
|
||||
}
|
||||
|
||||
fn print_info(map: Beatmap) {
|
||||
println!("Mode: {}", map.mode as u8);
|
||||
println!("n_circles: {}", map.n_circles);
|
||||
println!("n_sliders: {}", map.n_sliders);
|
||||
println!("n_spinners: {}", map.n_spinners);
|
||||
println!("ar: {}", map.ar);
|
||||
println!("od: {}", map.od);
|
||||
println!("cs: {}", map.cs);
|
||||
println!("hp: {}", map.hp);
|
||||
println!("slider_mult: {}", map.slider_mult);
|
||||
println!("tick_rate: {}", map.tick_rate);
|
||||
println!("hit_objects: {}", map.hit_objects.len());
|
||||
println!("stack_leniency: {}", map.stack_leniency);
|
||||
println!("timing_points: {}", map.timing_points.len());
|
||||
println!("difficulty_points: {}", map.difficulty_points.len());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,15 +21,15 @@ use crate::{
|
||||
/// let pp_result = AnyPP::new(&map)
|
||||
/// .mods(8 + 64) // HDDT
|
||||
/// .combo(1234)
|
||||
/// .accuracy(98.5)
|
||||
/// .n_misses(1)
|
||||
/// .accuracy(98.5) // should be set last
|
||||
/// .calculate();
|
||||
///
|
||||
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
||||
///
|
||||
/// let next_result = AnyPP::new(&map)
|
||||
/// .attributes(pp_result) // reusing previous results for performance
|
||||
/// .mods(8 + 64) // has to be the same to reuse attributes
|
||||
/// .attributes(pp_result) // reusing previous results for performance
|
||||
/// .mods(8 + 64) // has to be the same to reuse attributes
|
||||
/// .accuracy(99.5)
|
||||
/// .calculate();
|
||||
///
|
||||
@@ -151,10 +151,7 @@ impl<'map> AnyPP<'map> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the accuracy between 0.0 and 100.0.
|
||||
///
|
||||
/// For some modes this method depends on previously set values.
|
||||
/// Be sure to call this last before calling `calculate`.
|
||||
/// Set the accuracy between `0.0` and `100.0`.
|
||||
#[inline]
|
||||
pub fn accuracy(self, acc: f64) -> Self {
|
||||
match self {
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ use crate::{
|
||||
/// let pp_result = TaikoPP::new(&map)
|
||||
/// .mods(8 + 64) // HDDT
|
||||
/// .combo(1234)
|
||||
/// .n_misses(1)
|
||||
/// .accuracy(98.5)
|
||||
/// .n_misses(1)
|
||||
/// .calculate();
|
||||
///
|
||||
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
||||
@@ -134,7 +134,7 @@ impl<'map> TaikoPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the accuracy of a play between `0` and `100`.
|
||||
/// Specify the accuracy of a play between `0.0` and `100.0`.
|
||||
/// This will be used to generate matching hitresults.
|
||||
#[inline]
|
||||
pub fn accuracy(mut self, acc: f64) -> Self {
|
||||
|
||||
@@ -30,32 +30,39 @@ impl Colour {
|
||||
}
|
||||
|
||||
impl Skill for Colour {
|
||||
#[inline]
|
||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||
<Self as StrainSkill>::process(self, curr, hit_objects)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difficulty_value(self) -> f64 {
|
||||
<Self as StrainSkill>::difficulty_value(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl StrainSkill for Colour {
|
||||
#[inline]
|
||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||
&mut self.strain_peaks
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_peak
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_end(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_end
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
||||
<Self as StrainDecaySkill>::strain_value_at(self, curr, hit_objects)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
||||
<Self as StrainDecaySkill>::calculate_initial_strain(self, time, curr)
|
||||
}
|
||||
@@ -65,14 +72,17 @@ impl StrainDecaySkill for Colour {
|
||||
const SKILL_MULTIPLIER: f64 = 0.12;
|
||||
const STRAIN_DECAY_BASE: f64 = 0.8;
|
||||
|
||||
#[inline]
|
||||
fn curr_strain(&self) -> f64 {
|
||||
self.curr_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_strain_mut(&mut self) -> &mut f64 {
|
||||
&mut self.curr_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_value_of(&mut self, curr: &TaikoDifficultyObject, _: &ObjectLists) -> f64 {
|
||||
ColourEvaluator::evaluate_diff_of(curr)
|
||||
}
|
||||
|
||||
@@ -51,11 +51,13 @@ pub(crate) trait StrainSkill: Skill {
|
||||
.max(*self.curr_section_peak());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn save_curr_peak(&mut self) {
|
||||
let peak = *self.curr_section_peak();
|
||||
self.strain_peaks_mut().push(peak);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn start_new_section_from(&mut self, time: f64, curr: &TaikoDifficultyObject) {
|
||||
// * The maximum strain of the new section is not zero by default
|
||||
// * This means we need to capture the strain level at the beginning of the new section,
|
||||
@@ -84,6 +86,7 @@ pub(crate) trait StrainSkill: Skill {
|
||||
difficulty
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_curr_strain_peaks(mut self) -> Vec<f64> {
|
||||
let curr_peak = *self.curr_section_peak();
|
||||
let mut strain_peaks = mem::take(self.strain_peaks_mut());
|
||||
@@ -102,10 +105,12 @@ pub(crate) trait StrainDecaySkill: StrainSkill {
|
||||
|
||||
fn strain_value_of(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64;
|
||||
|
||||
#[inline]
|
||||
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
||||
self.curr_strain() * self.strain_decay(time - curr.prev_time)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
||||
*self.curr_strain_mut() *= self.strain_decay(curr.delta);
|
||||
*self.curr_strain_mut() += self.strain_value_of(curr, hit_objects) * Self::SKILL_MULTIPLIER;
|
||||
@@ -113,6 +118,7 @@ pub(crate) trait StrainDecaySkill: StrainSkill {
|
||||
self.curr_strain()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay(&self, ms: f64) -> f64 {
|
||||
Self::STRAIN_DECAY_BASE.powf(ms / 1000.0)
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ impl Peaks {
|
||||
}
|
||||
|
||||
impl Skill for Peaks {
|
||||
#[inline]
|
||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||
<Colour as Skill>::process(&mut self.colour, curr, hit_objects);
|
||||
<Rhythm as Skill>::process(&mut self.rhythm, curr, hit_objects);
|
||||
|
||||
@@ -100,32 +100,39 @@ impl Rhythm {
|
||||
}
|
||||
|
||||
impl Skill for Rhythm {
|
||||
#[inline]
|
||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||
<Self as StrainSkill>::process(self, curr, hit_objects)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difficulty_value(self) -> f64 {
|
||||
<Self as StrainSkill>::difficulty_value(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl StrainSkill for Rhythm {
|
||||
#[inline]
|
||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||
&mut self.strain_peaks
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_peak
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_end(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_end
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
||||
<Self as StrainDecaySkill>::strain_value_at(self, curr, hit_objects)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
||||
<Self as StrainDecaySkill>::calculate_initial_strain(self, time, curr)
|
||||
}
|
||||
@@ -135,10 +142,12 @@ impl StrainDecaySkill for Rhythm {
|
||||
const SKILL_MULTIPLIER: f64 = 10.0;
|
||||
const STRAIN_DECAY_BASE: f64 = 0.0;
|
||||
|
||||
#[inline]
|
||||
fn curr_strain(&self) -> f64 {
|
||||
self.curr_decay_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_strain_mut(&mut self) -> &mut f64 {
|
||||
&mut self.curr_decay_strain
|
||||
}
|
||||
|
||||
@@ -22,32 +22,39 @@ impl Stamina {
|
||||
}
|
||||
|
||||
impl Skill for Stamina {
|
||||
#[inline]
|
||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||
<Self as StrainSkill>::process(self, curr, hit_objects)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difficulty_value(self) -> f64 {
|
||||
<Self as StrainSkill>::difficulty_value(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl StrainSkill for Stamina {
|
||||
#[inline]
|
||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||
&mut self.strain_peaks
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_peak
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_section_end(&mut self) -> &mut f64 {
|
||||
&mut self.curr_section_end
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
||||
<Self as StrainDecaySkill>::strain_value_at(self, curr, hit_objects)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
||||
<Self as StrainDecaySkill>::calculate_initial_strain(self, time, curr)
|
||||
}
|
||||
@@ -57,14 +64,17 @@ impl StrainDecaySkill for Stamina {
|
||||
const SKILL_MULTIPLIER: f64 = 1.1;
|
||||
const STRAIN_DECAY_BASE: f64 = 0.4;
|
||||
|
||||
#[inline]
|
||||
fn curr_strain(&self) -> f64 {
|
||||
self.curr_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn curr_strain_mut(&mut self) -> &mut f64 {
|
||||
&mut self.curr_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_value_of(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
||||
StaminaEvaluator::evaluate_diff_of(curr, hit_objects)
|
||||
}
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
#![cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
|
||||
use rosu_pp::GameMode;
|
||||
|
||||
use crate::common::{Catch, Mania, Osu, Taiko};
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn parse_osu() {
|
||||
let map = test_map!(Osu);
|
||||
|
||||
assert_eq!(map.mode, GameMode::Osu);
|
||||
assert_eq!(map.version, 14);
|
||||
assert_eq!(map.n_circles, 307);
|
||||
assert_eq!(map.n_sliders, 293);
|
||||
assert_eq!(map.n_spinners, 1);
|
||||
assert!((map.ar - 9.3).abs() <= f32::EPSILON);
|
||||
assert!((map.od - 8.8).abs() <= f32::EPSILON);
|
||||
assert!((map.cs - 4.5).abs() <= f32::EPSILON);
|
||||
assert!((map.hp - 5.0).abs() <= f32::EPSILON);
|
||||
assert!((map.slider_mult - 1.7).abs() <= f64::EPSILON);
|
||||
assert!((map.tick_rate - 1.0).abs() <= f64::EPSILON);
|
||||
assert_eq!(map.hit_objects.len(), 601);
|
||||
assert_eq!(map.sounds.len(), 601);
|
||||
assert_eq!(map.timing_points.len(), 1);
|
||||
assert_eq!(map.difficulty_points.len(), 50);
|
||||
assert_eq!(map.effect_points.len(), 131);
|
||||
assert!((map.stack_leniency - 0.5).abs() <= f32::EPSILON);
|
||||
assert_eq!(map.breaks.len(), 1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_taiko() {
|
||||
let map = test_map!(Taiko);
|
||||
|
||||
assert_eq!(map.mode, GameMode::Taiko);
|
||||
assert_eq!(map.version, 14);
|
||||
assert_eq!(map.n_circles, 289);
|
||||
assert_eq!(map.n_sliders, 4);
|
||||
assert_eq!(map.n_spinners, 2);
|
||||
assert!((map.ar - 8.0).abs() <= f32::EPSILON);
|
||||
assert!((map.od - 5.0).abs() <= f32::EPSILON);
|
||||
assert!((map.cs - 2.0).abs() <= f32::EPSILON);
|
||||
assert!((map.hp - 6.0).abs() <= f32::EPSILON);
|
||||
assert!((map.slider_mult - 1.4).abs() <= f64::EPSILON);
|
||||
assert!((map.tick_rate - 1.0).abs() <= f64::EPSILON);
|
||||
assert_eq!(map.hit_objects.len(), 295);
|
||||
assert_eq!(map.sounds.len(), 295);
|
||||
assert_eq!(map.timing_points.len(), 1);
|
||||
assert_eq!(map.difficulty_points.len(), 3);
|
||||
assert_eq!(map.effect_points.len(), 8);
|
||||
assert!((map.stack_leniency - 0.7).abs() <= f32::EPSILON);
|
||||
assert_eq!(map.breaks.len(), 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_catch() {
|
||||
let map = test_map!(Catch);
|
||||
|
||||
assert_eq!(map.mode, GameMode::Catch);
|
||||
assert_eq!(map.version, 14);
|
||||
assert_eq!(map.n_circles, 249);
|
||||
assert_eq!(map.n_sliders, 227);
|
||||
assert_eq!(map.n_spinners, 1);
|
||||
assert!((map.ar - 8.0).abs() <= f32::EPSILON);
|
||||
assert!((map.od - 8.0).abs() <= f32::EPSILON);
|
||||
assert!((map.cs - 3.5).abs() <= f32::EPSILON);
|
||||
assert!((map.hp - 5.0).abs() <= f32::EPSILON);
|
||||
assert!((map.slider_mult - 1.45).abs() <= f64::EPSILON);
|
||||
assert!((map.tick_rate - 1.0).abs() <= f64::EPSILON);
|
||||
assert_eq!(map.hit_objects.len(), 477);
|
||||
assert_eq!(map.sounds.len(), 477);
|
||||
assert_eq!(map.timing_points.len(), 1);
|
||||
assert_eq!(map.difficulty_points.len(), 0);
|
||||
assert_eq!(map.effect_points.len(), 57);
|
||||
assert!((map.stack_leniency - 0.7).abs() <= f32::EPSILON);
|
||||
assert_eq!(map.breaks.len(), 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mania() {
|
||||
let map = test_map!(Mania);
|
||||
|
||||
assert_eq!(map.mode, GameMode::Mania);
|
||||
assert_eq!(map.version, 14);
|
||||
assert_eq!(map.n_circles, 2815);
|
||||
assert_eq!(map.n_sliders, 423);
|
||||
assert_eq!(map.n_spinners, 0);
|
||||
assert!((map.ar - 5.0).abs() <= f32::EPSILON);
|
||||
assert!((map.od - 8.0).abs() <= f32::EPSILON);
|
||||
assert!((map.cs - 4.0).abs() <= f32::EPSILON);
|
||||
assert!((map.hp - 9.0).abs() <= f32::EPSILON);
|
||||
assert!((map.slider_mult - 1.4).abs() <= f64::EPSILON);
|
||||
assert!((map.tick_rate - 1.0).abs() <= f64::EPSILON);
|
||||
assert_eq!(map.hit_objects.len(), 3238);
|
||||
assert_eq!(map.sounds.len(), 3238);
|
||||
assert_eq!(map.timing_points.len(), 1);
|
||||
assert_eq!(map.difficulty_points.len(), 1740);
|
||||
assert_eq!(map.effect_points.len(), 1762);
|
||||
assert!((map.stack_leniency - 0.7).abs() <= f32::EPSILON);
|
||||
assert_eq!(map.breaks.len(), 0)
|
||||
}
|
||||
Reference in New Issue
Block a user