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!:
|
||||||
- osu!lazer: Commit `85adfc2df7d931164181e145377a6ced8db2bfb3` (Wed Sep 28 18:26:36 2022 +0300)
|
- osu!lazer: Commit `85adfc2df7d931164181e145377a6ced8db2bfb3` (Wed Sep 28 18:26:36 2022 +0300)
|
||||||
- osu!tools: Commit `146d5916937161ef65906aa97f85d367035f3712` (Sat Oct 8 14:28:49 2022 +0900)
|
- 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:
|
- taiko:
|
||||||
- osu!lazer: Commit `234c6ac7998fbc6742503e1a589536255554e56a` (Wed Oct 5 20:21:15 2022 +0900)
|
- osu!lazer: Commit `234c6ac7998fbc6742503e1a589536255554e56a` (Wed Oct 5 20:21:15 2022 +0900)
|
||||||
- osu!tools: Commit `146d5916937161ef65906aa97f85d367035f3712` (Sat Oct 8 14:28:49 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)
|
- catch: (will be updated on the next rework)
|
||||||
- osu!lazer: -
|
- osu!lazer: -
|
||||||
@@ -182,6 +184,7 @@ A large portion of this repository is a port of [osu!lazer](https://github.com/p
|
|||||||
- mania:
|
- mania:
|
||||||
- osu!lazer: Commit `7342fb7f51b34533a42bffda89c3d6c569cc69ce` (Tue Oct 11 14:34:50 2022 +0900)
|
- osu!lazer: Commit `7342fb7f51b34533a42bffda89c3d6c569cc69ce` (Tue Oct 11 14:34:50 2022 +0900)
|
||||||
- osu!tools: Commit `146d5916937161ef65906aa97f85d367035f3712` (Sat Oct 8 14:28:49 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
|
### Accuracy
|
||||||
|
|
||||||
|
|||||||
@@ -89,8 +89,6 @@ impl Beatmap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`TimingPoint`] for the given timestamp.
|
/// Return the [`TimingPoint`] for the given timestamp.
|
||||||
///
|
|
||||||
/// If `time` is before the first timing point, `None` is returned.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn timing_point_at(&self, time: f64) -> TimingPoint {
|
pub fn timing_point_at(&self, time: f64) -> TimingPoint {
|
||||||
let idx_result = self
|
let idx_result = self
|
||||||
|
|||||||
+4
-2
@@ -16,8 +16,8 @@ use crate::{Beatmap, DifficultyAttributes, Mods, OsuPP, PerformanceAttributes};
|
|||||||
/// let pp_result = CatchPP::new(&map)
|
/// let pp_result = CatchPP::new(&map)
|
||||||
/// .mods(8 + 64) // HDDT
|
/// .mods(8 + 64) // HDDT
|
||||||
/// .combo(1234)
|
/// .combo(1234)
|
||||||
/// .misses(1)
|
|
||||||
/// .accuracy(98.5)
|
/// .accuracy(98.5)
|
||||||
|
/// .misses(1)
|
||||||
/// .calculate();
|
/// .calculate();
|
||||||
///
|
///
|
||||||
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
||||||
@@ -181,7 +181,8 @@ impl<'map> CatchPP<'map> {
|
|||||||
self
|
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.
|
/// Be sure to set `misses` beforehand! Also, if available, set `attributes` beforehand.
|
||||||
pub fn accuracy(mut self, mut acc: f64) -> Self {
|
pub fn accuracy(mut self, mut acc: f64) -> Self {
|
||||||
@@ -438,6 +439,7 @@ impl CatchPPInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'map> From<OsuPP<'map>> for CatchPP<'map> {
|
impl<'map> From<OsuPP<'map>> for CatchPP<'map> {
|
||||||
|
#[inline]
|
||||||
fn from(osu: OsuPP<'map>) -> Self {
|
fn from(osu: OsuPP<'map>) -> Self {
|
||||||
let OsuPP {
|
let OsuPP {
|
||||||
map,
|
map,
|
||||||
|
|||||||
+1
-46
@@ -217,7 +217,7 @@ pub use mods::Mods;
|
|||||||
pub use parse::{ParseError, ParseResult};
|
pub use parse::{ParseError, ParseResult};
|
||||||
pub use util::SortedVec;
|
pub use util::SortedVec;
|
||||||
|
|
||||||
/// Provides some additional methods on [`Beatmap`](crate::Beatmap).
|
/// Provides some additional methods on [`Beatmap`].
|
||||||
pub trait BeatmapExt {
|
pub trait BeatmapExt {
|
||||||
/// Calculate the stars and other attributes of a beatmap which are required for pp calculation.
|
/// Calculate the stars and other attributes of a beatmap which are required for pp calculation.
|
||||||
fn stars(&self) -> AnyStars<'_>;
|
fn stars(&self) -> AnyStars<'_>;
|
||||||
@@ -509,48 +509,3 @@ impl From<taiko::TaikoPerformanceAttributes> for PerformanceAttributes {
|
|||||||
|
|
||||||
#[cfg(all(feature = "async_tokio", feature = "async_std"))]
|
#[cfg(all(feature = "async_tokio", feature = "async_std"))]
|
||||||
compile_error!("Only one of the features `async_tokio` and `async_std` should be enabled");
|
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);
|
let ManiaResult { strain, .. } = calculate_result(self);
|
||||||
|
|
||||||
ManiaStrains {
|
ManiaStrains {
|
||||||
section_len: SECTION_LEN * clock_rate, // TODO: clock_rate correct here?
|
section_len: SECTION_LEN * clock_rate,
|
||||||
strains: strain.strain_peaks,
|
strains: strain.strain_peaks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,6 +224,14 @@ pub struct ManiaDifficultyAttributes {
|
|||||||
pub max_combo: usize,
|
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.
|
/// The result of a performance calculation on an osu!mania map.
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||||
pub struct ManiaPerformanceAttributes {
|
pub struct ManiaPerformanceAttributes {
|
||||||
|
|||||||
+4
-6
@@ -5,7 +5,6 @@ use crate::{
|
|||||||
Beatmap, DifficultyAttributes, GameMode, HitResultPriority, Mods, OsuPP, PerformanceAttributes,
|
Beatmap, DifficultyAttributes, GameMode, HitResultPriority, Mods, OsuPP, PerformanceAttributes,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: update
|
|
||||||
/// Performance calculator on osu!mania maps.
|
/// Performance calculator on osu!mania maps.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@@ -124,7 +123,7 @@ impl<'map> ManiaPP<'map> {
|
|||||||
self
|
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.
|
/// This will be used to generate matching hitresults.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn accuracy(mut self, acc: f64) -> Self {
|
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);
|
let n_misses = self.n_misses.unwrap_or(0);
|
||||||
|
|
||||||
if let Some(acc) = self.acc {
|
if let Some(acc) = self.acc {
|
||||||
// TODO: test
|
|
||||||
let target_total = (acc * (n_objects * 6) as f64).round() as usize;
|
let target_total = (acc * (n_objects * 6) as f64).round() as usize;
|
||||||
|
|
||||||
match (self.n320, self.n300, self.n200, self.n100, self.n50) {
|
match (self.n320, self.n300, self.n200, self.n100, self.n50) {
|
||||||
@@ -530,11 +528,11 @@ impl ManiaPpInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn compute_difficulty_value(&self) -> f64 {
|
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)
|
(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)
|
* (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))
|
* (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 strain_value_at(&mut self, curr: &ManiaDifficultyObject) -> f64;
|
||||||
|
|
||||||
fn process(&mut self, curr: &ManiaDifficultyObject, diff_objects: &[ManiaDifficultyObject]) {
|
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 {
|
if curr.idx == 0 {
|
||||||
// currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
|
|
||||||
*self.curr_section_end_mut() = (curr.start_time / SECTION_LEN).ceil() * SECTION_LEN;
|
*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 difficulty = 0.0;
|
||||||
let mut weight = 1.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).
|
// * 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.
|
// * These sections will not contribute to the difficulty.
|
||||||
let mut peaks = self.get_curr_strain_peaks();
|
let mut peaks = self.get_curr_strain_peaks();
|
||||||
peaks.retain(|&peak| peak > 0.0);
|
peaks.retain(|&peak| peak > 0.0);
|
||||||
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
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.
|
// * Difficulty is the weighted sum of the highest strains from every section.
|
||||||
// We're sorting from highest to lowest strain.
|
// * We're sorting from highest to lowest strain.
|
||||||
for strain in peaks {
|
for strain in peaks {
|
||||||
difficulty += strain * weight;
|
difficulty += strain * weight;
|
||||||
weight *= Self::DECAY_WEIGHT;
|
weight *= Self::DECAY_WEIGHT;
|
||||||
|
|||||||
+31
-20
@@ -43,10 +43,12 @@ impl Strain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Skill for Strain {
|
impl Skill for Strain {
|
||||||
|
#[inline]
|
||||||
fn process(&mut self, curr: &ManiaDifficultyObject, diff_objects: &[ManiaDifficultyObject]) {
|
fn process(&mut self, curr: &ManiaDifficultyObject, diff_objects: &[ManiaDifficultyObject]) {
|
||||||
<Self as StrainSkill>::process(self, curr, diff_objects)
|
<Self as StrainSkill>::process(self, curr, diff_objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn difficulty_value(self) -> f64 {
|
fn difficulty_value(self) -> f64 {
|
||||||
<Self as StrainSkill>::difficulty_value(self)
|
<Self as StrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
@@ -55,30 +57,37 @@ impl Skill for Strain {
|
|||||||
impl StrainSkill for Strain {
|
impl StrainSkill for Strain {
|
||||||
const DECAY_WEIGHT: f64 = 0.9;
|
const DECAY_WEIGHT: f64 = 0.9;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_end(&self) -> f64 {
|
fn curr_section_end(&self) -> f64 {
|
||||||
self.curr_section_end
|
self.curr_section_end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_end_mut(&mut self) -> &mut f64 {
|
fn curr_section_end_mut(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_end
|
&mut self.curr_section_end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_peak(&self) -> f64 {
|
fn curr_section_peak(&self) -> f64 {
|
||||||
self.curr_section_peak
|
self.curr_section_peak
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_peak_mut(&mut self) -> &mut f64 {
|
fn curr_section_peak_mut(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_peak
|
&mut self.curr_section_peak
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||||
&mut self.strain_peaks
|
&mut self.strain_peaks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject) -> f64 {
|
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject) -> f64 {
|
||||||
<Self as StrainDecaySkill>::strain_value_at(self, curr)
|
<Self as StrainDecaySkill>::strain_value_at(self, curr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn calculate_initial_strain(
|
fn calculate_initial_strain(
|
||||||
&self,
|
&self,
|
||||||
time: f64,
|
time: f64,
|
||||||
@@ -93,10 +102,12 @@ impl StrainDecaySkill for Strain {
|
|||||||
const SKILL_MULTIPLIER: f64 = 1.0;
|
const SKILL_MULTIPLIER: f64 = 1.0;
|
||||||
const STRAIN_DECAY_BASE: f64 = 1.0;
|
const STRAIN_DECAY_BASE: f64 = 1.0;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_strain(&self) -> f64 {
|
fn curr_strain(&self) -> f64 {
|
||||||
self.curr_strain
|
self.curr_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_strain_mut(&mut self) -> &mut f64 {
|
fn curr_strain_mut(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_strain
|
&mut self.curr_strain
|
||||||
}
|
}
|
||||||
@@ -108,19 +119,19 @@ impl StrainDecaySkill for Strain {
|
|||||||
let col = mania_curr.base_column;
|
let col = mania_curr.base_column;
|
||||||
let mut is_overlapping = false;
|
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();
|
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;
|
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;
|
let mut hold_addition = 0.0;
|
||||||
|
|
||||||
for i in 0..self.end_times.len() {
|
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 |=
|
is_overlapping |=
|
||||||
self.end_times[i] > start_time + 1.0 && end_time > self.end_times[i] + 1.0;
|
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 {
|
if self.end_times[i] > end_time + 1.0 {
|
||||||
hold_factor = 1.25;
|
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);
|
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.
|
// * 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.
|
// * 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
|
// * holdAddition
|
||||||
// ^
|
// * ^
|
||||||
// 1.0 + - - - - - -+-----------
|
// * 1.0 + - - - - - -+-----------
|
||||||
// | /
|
// * | /
|
||||||
// 0.5 + - - - - -/ Sigmoid Curve
|
// * 0.5 + - - - - -/ Sigmoid Curve
|
||||||
// | /|
|
// * | /|
|
||||||
// 0.0 +--------+-+---------------> Release Difference / ms
|
// * 0.0 +--------+-+---------------> Release Difference / ms
|
||||||
// release_threshold
|
// * release_threshold
|
||||||
if is_overlapping {
|
if is_overlapping {
|
||||||
hold_addition =
|
hold_addition =
|
||||||
(1.0 + (0.5 * (Self::RELEASE_THRESHOLD - closest_end_time)).exp()).recip();
|
(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] = Self::apply_decay(
|
||||||
self.individual_strains[col],
|
self.individual_strains[col],
|
||||||
start_time - self.start_times[col],
|
start_time - self.start_times[col],
|
||||||
@@ -151,14 +162,14 @@ impl StrainDecaySkill for Strain {
|
|||||||
);
|
);
|
||||||
self.individual_strains[col] += 2.0 * hold_factor;
|
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 = if mania_curr.delta_time <= 1.0 {
|
||||||
self.individual_strain.max(self.individual_strains[col])
|
self.individual_strain.max(self.individual_strains[col])
|
||||||
} else {
|
} else {
|
||||||
self.individual_strains[col]
|
self.individual_strains[col]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decay and increase overallStrain
|
// * Decay and increase overallStrain
|
||||||
self.overall_strain = Self::apply_decay(
|
self.overall_strain = Self::apply_decay(
|
||||||
self.overall_strain,
|
self.overall_strain,
|
||||||
curr.delta_time,
|
curr.delta_time,
|
||||||
@@ -166,11 +177,11 @@ impl StrainDecaySkill for Strain {
|
|||||||
);
|
);
|
||||||
self.overall_strain += (1.0 + hold_addition) * hold_factor;
|
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.start_times[col] = start_time;
|
||||||
self.end_times[col] = end_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
|
self.individual_strain + self.overall_strain - self.curr_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ impl Debug for OsuGradualDifficultyAttributes {
|
|||||||
.field("attrs", &self.attrs)
|
.field("attrs", &self.attrs)
|
||||||
.field("diff_objects", &self.diff_objects)
|
.field("diff_objects", &self.diff_objects)
|
||||||
.field("skills", &"<cannot be displayed>")
|
.field("skills", &"<cannot be displayed>")
|
||||||
|
.field("hit_window", &self.hit_window)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,13 +108,11 @@ impl OsuGradualDifficultyAttributes {
|
|||||||
curve_bufs: CurveBuffers::default(),
|
curve_bufs: CurveBuffers::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let hit_objects_iter = map
|
let mut hit_objects: Vec<_> = map
|
||||||
.hit_objects
|
.hit_objects
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|h| OsuObject::new(h, &mut params));
|
.map(|h| OsuObject::new(h, &mut params))
|
||||||
|
.collect();
|
||||||
let mut hit_objects = Vec::with_capacity(map.hit_objects.len());
|
|
||||||
hit_objects.extend(hit_objects_iter);
|
|
||||||
|
|
||||||
attrs.n_circles = 0;
|
attrs.n_circles = 0;
|
||||||
attrs.n_sliders = 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(),
|
curve_bufs: CurveBuffers::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let hit_objects_iter = map
|
let mut hit_objects: Vec<_> = map
|
||||||
.hit_objects
|
.hit_objects
|
||||||
.iter()
|
.iter()
|
||||||
.take(take)
|
.take(take)
|
||||||
.filter_map(|h| OsuObject::new(h, &mut params));
|
.map(|h| OsuObject::new(h, &mut params))
|
||||||
|
.collect();
|
||||||
let mut hit_objects = Vec::with_capacity(take.min(map.hit_objects.len()));
|
|
||||||
hit_objects.extend(hit_objects_iter);
|
|
||||||
|
|
||||||
let stack_threshold = time_preempt * map.stack_leniency as f64;
|
let stack_threshold = time_preempt * map.stack_leniency as f64;
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ pub(crate) struct ObjectParameters<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OsuObject {
|
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 {
|
let ObjectParameters {
|
||||||
map,
|
map,
|
||||||
attrs,
|
attrs,
|
||||||
@@ -103,7 +103,7 @@ impl OsuObject {
|
|||||||
attrs.max_combo += 1; // hitcircle, slider head, or spinner
|
attrs.max_combo += 1; // hitcircle, slider head, or spinner
|
||||||
let pos = h.pos;
|
let pos = h.pos;
|
||||||
|
|
||||||
let obj = match &h.kind {
|
match &h.kind {
|
||||||
HitObjectKind::Circle => {
|
HitObjectKind::Circle => {
|
||||||
attrs.n_circles += 1;
|
attrs.n_circles += 1;
|
||||||
|
|
||||||
@@ -317,7 +317,7 @@ impl OsuObject {
|
|||||||
kind: OsuObjectKind::Slider(slider),
|
kind: OsuObjectKind::Slider(slider),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HitObjectKind::Spinner { end_time } => {
|
HitObjectKind::Spinner { end_time } | HitObjectKind::Hold { end_time } => {
|
||||||
attrs.n_spinners += 1;
|
attrs.n_spinners += 1;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -330,10 +330,7 @@ impl OsuObject {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HitObjectKind::Hold { .. } => return None,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Some(obj)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn end_time(&self) -> f64 {
|
pub(crate) fn end_time(&self) -> f64 {
|
||||||
|
|||||||
+2
-2
@@ -21,8 +21,8 @@ use crate::{
|
|||||||
/// let pp_result = OsuPP::new(&map)
|
/// let pp_result = OsuPP::new(&map)
|
||||||
/// .mods(8 + 64) // HDDT
|
/// .mods(8 + 64) // HDDT
|
||||||
/// .combo(1234)
|
/// .combo(1234)
|
||||||
|
/// .accuracy(98.5)
|
||||||
/// .n_misses(1)
|
/// .n_misses(1)
|
||||||
/// .accuracy(98.5) // should be set last
|
|
||||||
/// .calculate();
|
/// .calculate();
|
||||||
///
|
///
|
||||||
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
||||||
@@ -199,7 +199,7 @@ impl<'map> OsuPP<'map> {
|
|||||||
self
|
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.
|
/// This will be used to generate matching hitresults.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn accuracy(mut self, acc: f64) -> Self {
|
pub fn accuracy(mut self, acc: f64) -> Self {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ impl Aim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Skill for Aim {
|
impl Skill for Aim {
|
||||||
|
#[inline]
|
||||||
fn process(
|
fn process(
|
||||||
&mut self,
|
&mut self,
|
||||||
curr: &OsuDifficultyObject<'_>,
|
curr: &OsuDifficultyObject<'_>,
|
||||||
@@ -46,36 +47,44 @@ impl Skill for Aim {
|
|||||||
<Self as StrainSkill>::process(self, curr, diff_objects, hit_window)
|
<Self as StrainSkill>::process(self, curr, diff_objects, hit_window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn difficulty_value(&mut self) -> f64 {
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
<Self as OsuStrainSkill>::difficulty_value(self)
|
<Self as OsuStrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
||||||
mem::take(&mut self.strain_peaks)
|
mem::take(&mut self.strain_peaks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StrainSkill for Aim {
|
impl StrainSkill for Aim {
|
||||||
|
#[inline]
|
||||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||||
&mut self.strain_peaks
|
&mut self.strain_peaks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_peak
|
&mut self.curr_section_peak
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_end(&mut self) -> &mut f64 {
|
fn curr_section_end(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_end
|
&mut self.curr_section_end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_value_at(
|
fn strain_value_at(
|
||||||
&mut self,
|
&mut self,
|
||||||
curr: &OsuDifficultyObject<'_>,
|
curr: &OsuDifficultyObject<'_>,
|
||||||
@@ -89,6 +98,7 @@ impl StrainSkill for Aim {
|
|||||||
self.curr_strain
|
self.curr_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn calculate_initial_strain(
|
fn calculate_initial_strain(
|
||||||
&self,
|
&self,
|
||||||
time: f64,
|
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))
|
self.curr_strain * Self::strain_decay(time - previous_start_time(diff_objects, curr.idx, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn difficulty_value(&mut self) -> f64 {
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
<Self as OsuStrainSkill>::difficulty_value(self)
|
<Self as OsuStrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ impl Flashlight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Skill for Flashlight {
|
impl Skill for Flashlight {
|
||||||
|
#[inline]
|
||||||
fn process(
|
fn process(
|
||||||
&mut self,
|
&mut self,
|
||||||
curr: &OsuDifficultyObject<'_>,
|
curr: &OsuDifficultyObject<'_>,
|
||||||
@@ -51,18 +52,22 @@ impl Skill for Flashlight {
|
|||||||
<Self as StrainSkill>::process(self, curr, diff_objects, hit_window)
|
<Self as StrainSkill>::process(self, curr, diff_objects, hit_window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn difficulty_value(&mut self) -> f64 {
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
<Self as StrainSkill>::difficulty_value(self)
|
<Self as StrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
||||||
mem::take(&mut self.strain_peaks)
|
mem::take(&mut self.strain_peaks)
|
||||||
}
|
}
|
||||||
@@ -71,18 +76,22 @@ impl Skill for Flashlight {
|
|||||||
impl StrainSkill for Flashlight {
|
impl StrainSkill for Flashlight {
|
||||||
const DECAY_WEIGHT: f64 = 0.9;
|
const DECAY_WEIGHT: f64 = 0.9;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||||
&mut self.strain_peaks
|
&mut self.strain_peaks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_peak
|
&mut self.curr_section_peak
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_end(&mut self) -> &mut f64 {
|
fn curr_section_end(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_end
|
&mut self.curr_section_end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_value_at(
|
fn strain_value_at(
|
||||||
&mut self,
|
&mut self,
|
||||||
curr: &OsuDifficultyObject<'_>,
|
curr: &OsuDifficultyObject<'_>,
|
||||||
@@ -102,6 +111,7 @@ impl StrainSkill for Flashlight {
|
|||||||
self.curr_strain
|
self.curr_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn calculate_initial_strain(
|
fn calculate_initial_strain(
|
||||||
&self,
|
&self,
|
||||||
time: f64,
|
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))
|
self.curr_strain * Self::strain_decay(time - previous_start_time(diff_objects, curr.idx, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn difficulty_value(&mut self) -> f64 {
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
self.get_curr_strain_peaks().into_iter().sum::<f64>() * Self::DIFFICULTY_MULTIPLER
|
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());
|
.max(*self.curr_section_peak());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn save_curr_peak(&mut self) {
|
fn save_curr_peak(&mut self) {
|
||||||
let peak = *self.curr_section_peak();
|
let peak = *self.curr_section_peak();
|
||||||
self.strain_peaks_mut().push(peak);
|
self.strain_peaks_mut().push(peak);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn start_new_section_from(
|
fn start_new_section_from(
|
||||||
&mut self,
|
&mut self,
|
||||||
time: f64,
|
time: f64,
|
||||||
@@ -89,6 +91,7 @@ pub(crate) trait StrainSkill: Skill + Sized {
|
|||||||
|
|
||||||
fn difficulty_value(&mut self) -> f64;
|
fn difficulty_value(&mut self) -> f64;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn get_curr_strain_peaks(&mut self) -> Vec<f64> {
|
fn get_curr_strain_peaks(&mut self) -> Vec<f64> {
|
||||||
let curr_peak = *self.curr_section_peak();
|
let curr_peak = *self.curr_section_peak();
|
||||||
let mut strain_peaks = mem::take(self.strain_peaks_mut());
|
let mut strain_peaks = mem::take(self.strain_peaks_mut());
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ impl Speed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Skill for Speed {
|
impl Skill for Speed {
|
||||||
|
#[inline]
|
||||||
fn process(
|
fn process(
|
||||||
&mut self,
|
&mut self,
|
||||||
curr: &OsuDifficultyObject<'_>,
|
curr: &OsuDifficultyObject<'_>,
|
||||||
@@ -57,36 +58,44 @@ impl Skill for Speed {
|
|||||||
<Self as StrainSkill>::process(self, curr, diff_objects, hit_window)
|
<Self as StrainSkill>::process(self, curr, diff_objects, hit_window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn difficulty_value(&mut self) -> f64 {
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
<Self as OsuStrainSkill>::difficulty_value(self)
|
<Self as OsuStrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
fn take_strain_peaks(&mut self) -> Vec<f64> {
|
||||||
mem::take(&mut self.strain_peaks)
|
mem::take(&mut self.strain_peaks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StrainSkill for Speed {
|
impl StrainSkill for Speed {
|
||||||
|
#[inline]
|
||||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||||
&mut self.strain_peaks
|
&mut self.strain_peaks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_peak
|
&mut self.curr_section_peak
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_end(&mut self) -> &mut f64 {
|
fn curr_section_end(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_end
|
&mut self.curr_section_end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_value_at(
|
fn strain_value_at(
|
||||||
&mut self,
|
&mut self,
|
||||||
curr: &OsuDifficultyObject<'_>,
|
curr: &OsuDifficultyObject<'_>,
|
||||||
@@ -104,6 +113,7 @@ impl StrainSkill for Speed {
|
|||||||
total_strain
|
total_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn calculate_initial_strain(
|
fn calculate_initial_strain(
|
||||||
&self,
|
&self,
|
||||||
time: f64,
|
time: f64,
|
||||||
@@ -114,6 +124,7 @@ impl StrainSkill for Speed {
|
|||||||
* Self::strain_decay(time - previous_start_time(diff_objects, curr.idx, 0))
|
* Self::strain_decay(time - previous_start_time(diff_objects, curr.idx, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn difficulty_value(&mut self) -> f64 {
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
<Self as OsuStrainSkill>::difficulty_value(self)
|
<Self as OsuStrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ impl HitObject {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn end_time(&self) -> f64 {
|
pub fn end_time(&self) -> f64 {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
HitObjectKind::Circle { .. } => self.start_time,
|
HitObjectKind::Circle => self.start_time,
|
||||||
// incorrect, only called in mania which has no sliders though
|
// incorrect, only called in mania which has no sliders though
|
||||||
HitObjectKind::Slider { .. } => self.start_time,
|
HitObjectKind::Slider { .. } => self.start_time,
|
||||||
HitObjectKind::Spinner { end_time } => *end_time,
|
HitObjectKind::Spinner { end_time } => *end_time,
|
||||||
@@ -30,7 +30,7 @@ impl HitObject {
|
|||||||
/// If the object is a circle.
|
/// If the object is a circle.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_circle(&self) -> bool {
|
pub fn is_circle(&self) -> bool {
|
||||||
matches!(self.kind, HitObjectKind::Circle { .. })
|
matches!(self.kind, HitObjectKind::Circle)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the object is a slider.
|
/// If the object is a slider.
|
||||||
|
|||||||
@@ -12,18 +12,22 @@ pub trait HitSound {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HitSound for u8 {
|
impl HitSound for u8 {
|
||||||
|
#[inline]
|
||||||
fn normal(self) -> bool {
|
fn normal(self) -> bool {
|
||||||
self == 0
|
self == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn whistle(self) -> bool {
|
fn whistle(self) -> bool {
|
||||||
self & Self::HITSOUND_WHISTLE > 0
|
self & Self::HITSOUND_WHISTLE > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn finish(self) -> bool {
|
fn finish(self) -> bool {
|
||||||
self & Self::HITSOUND_FINISH > 0
|
self & Self::HITSOUND_FINISH > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn clap(self) -> bool {
|
fn clap(self) -> bool {
|
||||||
self & Self::HITSOUND_CLAP > 0
|
self & Self::HITSOUND_CLAP > 0
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-89
@@ -689,7 +689,6 @@ macro_rules! parse_hitobjects_body {
|
|||||||
sorter.toggle_marks();
|
sorter.toggle_marks();
|
||||||
sorter.sort(&mut $self.sounds);
|
sorter.sort(&mut $self.sounds);
|
||||||
}
|
}
|
||||||
// No need to sort hitsounds for the rest
|
|
||||||
GameMode::Mania => {
|
GameMode::Mania => {
|
||||||
// First a _stable_ sort by time
|
// First a _stable_ sort by time
|
||||||
$self
|
$self
|
||||||
@@ -975,6 +974,7 @@ impl Beatmap {
|
|||||||
) -> ParseResult<bool> {
|
) -> ParseResult<bool> {
|
||||||
parse_timingpoints_body!(self, reader, section)
|
parse_timingpoints_body!(self, reader, section)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pass the path to a `.osu` file.
|
/// Pass the path to a `.osu` file.
|
||||||
///
|
///
|
||||||
/// Useful when you don't want to create the [`File`](std::fs::File) manually.
|
/// 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> {
|
pub fn from_path<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
|
||||||
Self::parse(File::open(path)?)
|
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"))]
|
#[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> {
|
pub async fn from_path<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
|
||||||
Self::parse(File::open(path).await?).await
|
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)]
|
#[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,8 +21,8 @@ use crate::{
|
|||||||
/// let pp_result = AnyPP::new(&map)
|
/// let pp_result = AnyPP::new(&map)
|
||||||
/// .mods(8 + 64) // HDDT
|
/// .mods(8 + 64) // HDDT
|
||||||
/// .combo(1234)
|
/// .combo(1234)
|
||||||
|
/// .accuracy(98.5)
|
||||||
/// .n_misses(1)
|
/// .n_misses(1)
|
||||||
/// .accuracy(98.5) // should be set last
|
|
||||||
/// .calculate();
|
/// .calculate();
|
||||||
///
|
///
|
||||||
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
||||||
@@ -151,10 +151,7 @@ impl<'map> AnyPP<'map> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the accuracy between 0.0 and 100.0.
|
/// 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`.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn accuracy(self, acc: f64) -> Self {
|
pub fn accuracy(self, acc: f64) -> Self {
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
+2
-2
@@ -20,8 +20,8 @@ use crate::{
|
|||||||
/// let pp_result = TaikoPP::new(&map)
|
/// let pp_result = TaikoPP::new(&map)
|
||||||
/// .mods(8 + 64) // HDDT
|
/// .mods(8 + 64) // HDDT
|
||||||
/// .combo(1234)
|
/// .combo(1234)
|
||||||
/// .n_misses(1)
|
|
||||||
/// .accuracy(98.5)
|
/// .accuracy(98.5)
|
||||||
|
/// .n_misses(1)
|
||||||
/// .calculate();
|
/// .calculate();
|
||||||
///
|
///
|
||||||
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
|
||||||
@@ -134,7 +134,7 @@ impl<'map> TaikoPP<'map> {
|
|||||||
self
|
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.
|
/// This will be used to generate matching hitresults.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn accuracy(mut self, acc: f64) -> Self {
|
pub fn accuracy(mut self, acc: f64) -> Self {
|
||||||
|
|||||||
@@ -30,32 +30,39 @@ impl Colour {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Skill for Colour {
|
impl Skill for Colour {
|
||||||
|
#[inline]
|
||||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||||
<Self as StrainSkill>::process(self, curr, hit_objects)
|
<Self as StrainSkill>::process(self, curr, hit_objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn difficulty_value(self) -> f64 {
|
fn difficulty_value(self) -> f64 {
|
||||||
<Self as StrainSkill>::difficulty_value(self)
|
<Self as StrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StrainSkill for Colour {
|
impl StrainSkill for Colour {
|
||||||
|
#[inline]
|
||||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||||
&mut self.strain_peaks
|
&mut self.strain_peaks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_peak
|
&mut self.curr_section_peak
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_end(&mut self) -> &mut f64 {
|
fn curr_section_end(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_end
|
&mut self.curr_section_end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
||||||
<Self as StrainDecaySkill>::strain_value_at(self, curr, hit_objects)
|
<Self as StrainDecaySkill>::strain_value_at(self, curr, hit_objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
||||||
<Self as StrainDecaySkill>::calculate_initial_strain(self, time, curr)
|
<Self as StrainDecaySkill>::calculate_initial_strain(self, time, curr)
|
||||||
}
|
}
|
||||||
@@ -65,14 +72,17 @@ impl StrainDecaySkill for Colour {
|
|||||||
const SKILL_MULTIPLIER: f64 = 0.12;
|
const SKILL_MULTIPLIER: f64 = 0.12;
|
||||||
const STRAIN_DECAY_BASE: f64 = 0.8;
|
const STRAIN_DECAY_BASE: f64 = 0.8;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_strain(&self) -> f64 {
|
fn curr_strain(&self) -> f64 {
|
||||||
self.curr_strain
|
self.curr_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_strain_mut(&mut self) -> &mut f64 {
|
fn curr_strain_mut(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_strain
|
&mut self.curr_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_value_of(&mut self, curr: &TaikoDifficultyObject, _: &ObjectLists) -> f64 {
|
fn strain_value_of(&mut self, curr: &TaikoDifficultyObject, _: &ObjectLists) -> f64 {
|
||||||
ColourEvaluator::evaluate_diff_of(curr)
|
ColourEvaluator::evaluate_diff_of(curr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,13 @@ pub(crate) trait StrainSkill: Skill {
|
|||||||
.max(*self.curr_section_peak());
|
.max(*self.curr_section_peak());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn save_curr_peak(&mut self) {
|
fn save_curr_peak(&mut self) {
|
||||||
let peak = *self.curr_section_peak();
|
let peak = *self.curr_section_peak();
|
||||||
self.strain_peaks_mut().push(peak);
|
self.strain_peaks_mut().push(peak);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn start_new_section_from(&mut self, time: f64, curr: &TaikoDifficultyObject) {
|
fn start_new_section_from(&mut self, time: f64, curr: &TaikoDifficultyObject) {
|
||||||
// * The maximum strain of the new section is not zero by default
|
// * 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,
|
// * 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
|
difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn get_curr_strain_peaks(mut self) -> Vec<f64> {
|
fn get_curr_strain_peaks(mut self) -> Vec<f64> {
|
||||||
let curr_peak = *self.curr_section_peak();
|
let curr_peak = *self.curr_section_peak();
|
||||||
let mut strain_peaks = mem::take(self.strain_peaks_mut());
|
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;
|
fn strain_value_of(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
||||||
self.curr_strain() * self.strain_decay(time - curr.prev_time)
|
self.curr_strain() * self.strain_decay(time - curr.prev_time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
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_decay(curr.delta);
|
||||||
*self.curr_strain_mut() += self.strain_value_of(curr, hit_objects) * Self::SKILL_MULTIPLIER;
|
*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()
|
self.curr_strain()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_decay(&self, ms: f64) -> f64 {
|
fn strain_decay(&self, ms: f64) -> f64 {
|
||||||
Self::STRAIN_DECAY_BASE.powf(ms / 1000.0)
|
Self::STRAIN_DECAY_BASE.powf(ms / 1000.0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ impl Peaks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Skill for Peaks {
|
impl Skill for Peaks {
|
||||||
|
#[inline]
|
||||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||||
<Colour as Skill>::process(&mut self.colour, curr, hit_objects);
|
<Colour as Skill>::process(&mut self.colour, curr, hit_objects);
|
||||||
<Rhythm as Skill>::process(&mut self.rhythm, curr, hit_objects);
|
<Rhythm as Skill>::process(&mut self.rhythm, curr, hit_objects);
|
||||||
|
|||||||
@@ -100,32 +100,39 @@ impl Rhythm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Skill for Rhythm {
|
impl Skill for Rhythm {
|
||||||
|
#[inline]
|
||||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||||
<Self as StrainSkill>::process(self, curr, hit_objects)
|
<Self as StrainSkill>::process(self, curr, hit_objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn difficulty_value(self) -> f64 {
|
fn difficulty_value(self) -> f64 {
|
||||||
<Self as StrainSkill>::difficulty_value(self)
|
<Self as StrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StrainSkill for Rhythm {
|
impl StrainSkill for Rhythm {
|
||||||
|
#[inline]
|
||||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||||
&mut self.strain_peaks
|
&mut self.strain_peaks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_peak
|
&mut self.curr_section_peak
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_end(&mut self) -> &mut f64 {
|
fn curr_section_end(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_end
|
&mut self.curr_section_end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
||||||
<Self as StrainDecaySkill>::strain_value_at(self, curr, hit_objects)
|
<Self as StrainDecaySkill>::strain_value_at(self, curr, hit_objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
||||||
<Self as StrainDecaySkill>::calculate_initial_strain(self, time, curr)
|
<Self as StrainDecaySkill>::calculate_initial_strain(self, time, curr)
|
||||||
}
|
}
|
||||||
@@ -135,10 +142,12 @@ impl StrainDecaySkill for Rhythm {
|
|||||||
const SKILL_MULTIPLIER: f64 = 10.0;
|
const SKILL_MULTIPLIER: f64 = 10.0;
|
||||||
const STRAIN_DECAY_BASE: f64 = 0.0;
|
const STRAIN_DECAY_BASE: f64 = 0.0;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_strain(&self) -> f64 {
|
fn curr_strain(&self) -> f64 {
|
||||||
self.curr_decay_strain
|
self.curr_decay_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_strain_mut(&mut self) -> &mut f64 {
|
fn curr_strain_mut(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_decay_strain
|
&mut self.curr_decay_strain
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,32 +22,39 @@ impl Stamina {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Skill for Stamina {
|
impl Skill for Stamina {
|
||||||
|
#[inline]
|
||||||
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
fn process(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) {
|
||||||
<Self as StrainSkill>::process(self, curr, hit_objects)
|
<Self as StrainSkill>::process(self, curr, hit_objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn difficulty_value(self) -> f64 {
|
fn difficulty_value(self) -> f64 {
|
||||||
<Self as StrainSkill>::difficulty_value(self)
|
<Self as StrainSkill>::difficulty_value(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StrainSkill for Stamina {
|
impl StrainSkill for Stamina {
|
||||||
|
#[inline]
|
||||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
fn strain_peaks_mut(&mut self) -> &mut Vec<f64> {
|
||||||
&mut self.strain_peaks
|
&mut self.strain_peaks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_peak(&mut self) -> &mut f64 {
|
fn curr_section_peak(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_peak
|
&mut self.curr_section_peak
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_section_end(&mut self) -> &mut f64 {
|
fn curr_section_end(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_section_end
|
&mut self.curr_section_end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
fn strain_value_at(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
||||||
<Self as StrainDecaySkill>::strain_value_at(self, curr, hit_objects)
|
<Self as StrainDecaySkill>::strain_value_at(self, curr, hit_objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
fn calculate_initial_strain(&self, time: f64, curr: &TaikoDifficultyObject) -> f64 {
|
||||||
<Self as StrainDecaySkill>::calculate_initial_strain(self, time, curr)
|
<Self as StrainDecaySkill>::calculate_initial_strain(self, time, curr)
|
||||||
}
|
}
|
||||||
@@ -57,14 +64,17 @@ impl StrainDecaySkill for Stamina {
|
|||||||
const SKILL_MULTIPLIER: f64 = 1.1;
|
const SKILL_MULTIPLIER: f64 = 1.1;
|
||||||
const STRAIN_DECAY_BASE: f64 = 0.4;
|
const STRAIN_DECAY_BASE: f64 = 0.4;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_strain(&self) -> f64 {
|
fn curr_strain(&self) -> f64 {
|
||||||
self.curr_strain
|
self.curr_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn curr_strain_mut(&mut self) -> &mut f64 {
|
fn curr_strain_mut(&mut self) -> &mut f64 {
|
||||||
&mut self.curr_strain
|
&mut self.curr_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn strain_value_of(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
fn strain_value_of(&mut self, curr: &TaikoDifficultyObject, hit_objects: &ObjectLists) -> f64 {
|
||||||
StaminaEvaluator::evaluate_diff_of(curr, hit_objects)
|
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