refactored beatmap types into a new module
This commit is contained in:
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
- __Additions__:
|
- __Additions__:
|
||||||
- Added the `ControlPoint` and `ControlPointerIter` types to the public interface, aswell as the `Beatmap::control_points` method
|
- Added the `ControlPoint` and `ControlPointerIter` types to the public interface, aswell as the `Beatmap::control_points` method
|
||||||
|
- __Breaking changes:__
|
||||||
|
- Moved some types to a different module. The following types can now be found in `rosu_pp::beatmap`:
|
||||||
|
- `Beatmap`
|
||||||
|
- `BeatmapAttributes`
|
||||||
|
- `ControlPoint`
|
||||||
|
- `ControlPointIter`
|
||||||
|
- `DifficultyPoint`
|
||||||
|
- `GameMode`
|
||||||
|
- `TimingPoint`
|
||||||
|
|
||||||
# v0.5.2 (2022-06-14)
|
# v0.5.2 (2022-06-14)
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +1,87 @@
|
|||||||
use crate::Mods;
|
use crate::Mods;
|
||||||
|
|
||||||
/// Summary struct for a [`Beatmap`](crate::Beatmap)'s attributes.
|
/// Summary struct for a [`Beatmap`](crate::Beatmap)'s attributes.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BeatmapAttributes {
|
pub struct BeatmapAttributes {
|
||||||
/// The approach rate.
|
/// The approach rate.
|
||||||
pub ar: f64,
|
pub ar: f64,
|
||||||
/// The overall difficulty.
|
/// The overall difficulty.
|
||||||
pub od: f64,
|
pub od: f64,
|
||||||
/// The circle size.
|
/// The circle size.
|
||||||
pub cs: f64,
|
pub cs: f64,
|
||||||
/// The health drain rate
|
/// The health drain rate
|
||||||
pub hp: f64,
|
pub hp: f64,
|
||||||
/// The clock rate with respect to mods.
|
/// The clock rate with respect to mods.
|
||||||
pub clock_rate: f64,
|
pub clock_rate: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BeatmapAttributes {
|
impl BeatmapAttributes {
|
||||||
const AR0_MS: f64 = 1800.0;
|
const AR0_MS: f64 = 1800.0;
|
||||||
const AR5_MS: f64 = 1200.0;
|
const AR5_MS: f64 = 1200.0;
|
||||||
const AR10_MS: f64 = 450.0;
|
const AR10_MS: f64 = 450.0;
|
||||||
const AR_MS_STEP_1: f64 = (Self::AR0_MS - Self::AR5_MS) / 5.0;
|
const AR_MS_STEP_1: f64 = (Self::AR0_MS - Self::AR5_MS) / 5.0;
|
||||||
const AR_MS_STEP_2: f64 = (Self::AR5_MS - Self::AR10_MS) / 5.0;
|
const AR_MS_STEP_2: f64 = (Self::AR5_MS - Self::AR10_MS) / 5.0;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn new(ar: f32, od: f32, cs: f32, hp: f32) -> Self {
|
pub(crate) fn new(ar: f32, od: f32, cs: f32, hp: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ar: ar as f64,
|
ar: ar as f64,
|
||||||
od: od as f64,
|
od: od as f64,
|
||||||
cs: cs as f64,
|
cs: cs as f64,
|
||||||
hp: hp as f64,
|
hp: hp as f64,
|
||||||
clock_rate: 1.0,
|
clock_rate: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adjusts attributes w.r.t. mods.
|
/// Adjusts attributes w.r.t. mods.
|
||||||
/// AR is further adjusted by its hitwindow.
|
/// AR is further adjusted by its hitwindow.
|
||||||
/// OD is __not__ adjusted by its hitwindow.
|
/// OD is __not__ adjusted by its hitwindow.
|
||||||
pub fn mods(self, mods: impl Mods) -> Self {
|
pub fn mods(self, mods: impl Mods) -> Self {
|
||||||
if !mods.change_map() {
|
if !mods.change_map() {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
let clock_rate = mods.clock_rate();
|
let clock_rate = mods.clock_rate();
|
||||||
let multiplier = mods.od_ar_hp_multiplier();
|
let multiplier = mods.od_ar_hp_multiplier();
|
||||||
|
|
||||||
// AR
|
// AR
|
||||||
let mut ar = (self.ar * multiplier) as f64;
|
let mut ar = (self.ar * multiplier) as f64;
|
||||||
let mut ar_ms = if ar <= 5.0 {
|
let mut ar_ms = if ar <= 5.0 {
|
||||||
Self::AR0_MS - Self::AR_MS_STEP_1 * ar
|
Self::AR0_MS - Self::AR_MS_STEP_1 * ar
|
||||||
} else {
|
} else {
|
||||||
Self::AR5_MS - Self::AR_MS_STEP_2 * (ar - 5.0)
|
Self::AR5_MS - Self::AR_MS_STEP_2 * (ar - 5.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
ar_ms = ar_ms.max(Self::AR10_MS).min(Self::AR0_MS);
|
ar_ms = ar_ms.max(Self::AR10_MS).min(Self::AR0_MS);
|
||||||
ar_ms /= clock_rate;
|
ar_ms /= clock_rate;
|
||||||
|
|
||||||
ar = if ar_ms > Self::AR5_MS {
|
ar = if ar_ms > Self::AR5_MS {
|
||||||
(Self::AR0_MS - ar_ms) / Self::AR_MS_STEP_1
|
(Self::AR0_MS - ar_ms) / Self::AR_MS_STEP_1
|
||||||
} else {
|
} else {
|
||||||
5.0 + (Self::AR5_MS - ar_ms) / Self::AR_MS_STEP_2
|
5.0 + (Self::AR5_MS - ar_ms) / Self::AR_MS_STEP_2
|
||||||
};
|
};
|
||||||
|
|
||||||
// OD
|
// OD
|
||||||
let od = (self.od * multiplier).min(10.0);
|
let od = (self.od * multiplier).min(10.0);
|
||||||
|
|
||||||
// CS
|
// CS
|
||||||
let mut cs = self.cs;
|
let mut cs = self.cs;
|
||||||
if mods.hr() {
|
if mods.hr() {
|
||||||
cs *= 1.3;
|
cs *= 1.3;
|
||||||
} else if mods.ez() {
|
} else if mods.ez() {
|
||||||
cs *= 0.5;
|
cs *= 0.5;
|
||||||
}
|
}
|
||||||
cs = cs.min(10.0);
|
cs = cs.min(10.0);
|
||||||
|
|
||||||
// HP
|
// HP
|
||||||
let hp = (self.hp * multiplier).min(10.0);
|
let hp = (self.hp * multiplier).min(10.0);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ar,
|
ar,
|
||||||
od,
|
od,
|
||||||
cs,
|
cs,
|
||||||
hp,
|
hp,
|
||||||
clock_rate,
|
clock_rate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,56 @@
|
|||||||
use crate::{
|
use std::{cmp::Ordering, iter::Copied, slice::Iter};
|
||||||
parse::{DifficultyPoint, TimingPoint},
|
|
||||||
Beatmap,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::{iter::Copied, slice::Iter};
|
use crate::Beatmap;
|
||||||
|
|
||||||
|
/// New rhythm speed change.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub struct TimingPoint {
|
||||||
|
/// The beat length for this timing section
|
||||||
|
pub beat_len: f64,
|
||||||
|
/// The start time of this timing section
|
||||||
|
pub time: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for TimingPoint {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
self.time.partial_cmp(&other.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`TimingPoint`] that depends on a previous one.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub struct DifficultyPoint {
|
||||||
|
/// The start time for the current speed multiplier
|
||||||
|
pub time: f64,
|
||||||
|
/// The speed multiplier until the next timing point
|
||||||
|
pub speed_multiplier: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for DifficultyPoint {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
self.time.partial_cmp(&other.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Control point for a [`Beatmap`].
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum ControlPoint {
|
||||||
|
/// A timing point containing the current beat length.
|
||||||
|
Timing(TimingPoint),
|
||||||
|
/// A difficulty point containing the current speed multiplier.
|
||||||
|
Difficulty(DifficultyPoint),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlPoint {
|
||||||
|
/// Provides the timestamp of the control point.
|
||||||
|
#[inline]
|
||||||
|
pub fn time(&self) -> f64 {
|
||||||
|
match self {
|
||||||
|
Self::Timing(point) => point.time,
|
||||||
|
Self::Difficulty(point) => point.time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterator for a [`Beatmap`]'s timing- and difficulty points sorted by timestamp
|
/// Iterator for a [`Beatmap`]'s timing- and difficulty points sorted by timestamp
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -31,26 +78,6 @@ impl<'p> ControlPointIter<'p> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Control point for a [`Beatmap`].
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum ControlPoint {
|
|
||||||
/// A timing point containing the current beat length.
|
|
||||||
Timing(TimingPoint),
|
|
||||||
/// A difficulty point containing the current speed multiplier.
|
|
||||||
Difficulty(DifficultyPoint),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ControlPoint {
|
|
||||||
/// Provides the timestamp of the control point.
|
|
||||||
#[inline]
|
|
||||||
pub fn time(&self) -> f64 {
|
|
||||||
match self {
|
|
||||||
Self::Timing(point) => point.time,
|
|
||||||
Self::Difficulty(point) => point.time,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'p> Iterator for ControlPointIter<'p> {
|
impl<'p> Iterator for ControlPointIter<'p> {
|
||||||
type Item = ControlPoint;
|
type Item = ControlPoint;
|
||||||
|
|
||||||
@@ -80,8 +107,8 @@ impl<'p> Iterator for ControlPointIter<'p> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::{
|
||||||
parse::{DifficultyPoint, TimingPoint},
|
beatmap::{ControlPoint, ControlPointIter, DifficultyPoint, TimingPoint},
|
||||||
Beatmap, ControlPoint, ControlPointIter,
|
Beatmap,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
use crate::parse::HitObject;
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
attributes::BeatmapAttributes,
|
||||||
|
control_points::{ControlPoint, ControlPointIter, DifficultyPoint, TimingPoint},
|
||||||
|
mode::GameMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod attributes;
|
||||||
|
mod control_points;
|
||||||
|
mod mode;
|
||||||
|
|
||||||
|
/// The main beatmap struct containing all data relevant
|
||||||
|
/// for difficulty and performance calculation
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
|
pub struct Beatmap {
|
||||||
|
/// The game mode.
|
||||||
|
pub mode: GameMode,
|
||||||
|
/// The version of the .osu file.
|
||||||
|
pub version: u8,
|
||||||
|
|
||||||
|
/// The amount of circles.
|
||||||
|
pub n_circles: u32,
|
||||||
|
/// The amount of sliders.
|
||||||
|
pub n_sliders: u32,
|
||||||
|
/// The amount of spinners.
|
||||||
|
pub n_spinners: u32,
|
||||||
|
|
||||||
|
/// The approach rate.
|
||||||
|
pub ar: f32,
|
||||||
|
/// The overall difficulty.
|
||||||
|
pub od: f32,
|
||||||
|
/// The circle size.
|
||||||
|
pub cs: f32,
|
||||||
|
/// The health drain rate.
|
||||||
|
pub hp: f32,
|
||||||
|
/// Base slider velocity in pixels per beat
|
||||||
|
pub slider_mult: f64,
|
||||||
|
/// Amount of slider ticks per beat.
|
||||||
|
pub tick_rate: f64,
|
||||||
|
/// All hitobjects of the beatmap.
|
||||||
|
pub hit_objects: Vec<HitObject>,
|
||||||
|
/// Store the sounds for all objects in their own Vec to minimize the struct size.
|
||||||
|
/// Hitsounds are only used in osu!taiko in which they represent color.
|
||||||
|
pub sounds: Vec<u8>,
|
||||||
|
|
||||||
|
/// Timing points that indicate a new timing section.
|
||||||
|
pub timing_points: Vec<TimingPoint>,
|
||||||
|
|
||||||
|
/// Timing point for the current timing section.
|
||||||
|
pub difficulty_points: Vec<DifficultyPoint>,
|
||||||
|
|
||||||
|
/// The stack leniency that is used to calculate
|
||||||
|
/// the stack offset for stacked positions.
|
||||||
|
pub stack_leniency: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Beatmap {
|
||||||
|
/// Extract a beatmap's attributes into their own type.
|
||||||
|
#[inline]
|
||||||
|
pub fn attributes(&self) -> BeatmapAttributes {
|
||||||
|
BeatmapAttributes::new(self.ar, self.od, self.cs, self.hp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The beats per minute of the map.
|
||||||
|
#[inline]
|
||||||
|
pub fn bpm(&self) -> f64 {
|
||||||
|
match self.timing_points.first() {
|
||||||
|
Some(point) => point.beat_len.recip() * 1000.0 * 60.0,
|
||||||
|
None => 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an iterator over the map's timing- and difficulty points sorted by timestamp.
|
||||||
|
#[inline]
|
||||||
|
pub fn control_points(&self) -> ControlPointIter<'_> {
|
||||||
|
ControlPointIter::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/// The mode of a beatmap.
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
pub enum GameMode {
|
||||||
|
/// osu!standard
|
||||||
|
STD = 0,
|
||||||
|
/// osu!taiko
|
||||||
|
TKO = 1,
|
||||||
|
/// osu!catch
|
||||||
|
CTB = 2,
|
||||||
|
/// osu!mania
|
||||||
|
MNA = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GameMode {
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::STD
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Beatmap, ControlPoint, ControlPointIter};
|
use crate::beatmap::{Beatmap, ControlPoint, ControlPointIter};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct SliderState<'p> {
|
pub(crate) struct SliderState<'p> {
|
||||||
@@ -45,10 +45,7 @@ impl<'p> SliderState<'p> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::beatmap::{Beatmap, DifficultyPoint, TimingPoint};
|
||||||
parse::{DifficultyPoint, TimingPoint},
|
|
||||||
Beatmap,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::SliderState;
|
use super::SliderState;
|
||||||
|
|
||||||
|
|||||||
+6
-5
@@ -191,9 +191,13 @@ pub mod osu;
|
|||||||
/// Everything about osu!taiko.
|
/// Everything about osu!taiko.
|
||||||
pub mod taiko;
|
pub mod taiko;
|
||||||
|
|
||||||
/// Beatmap parsing and the contained types.
|
/// Beatmap parsing
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
|
|
||||||
|
/// Beatmap and contained types
|
||||||
|
pub mod beatmap;
|
||||||
|
pub use beatmap::{Beatmap, GameMode};
|
||||||
|
|
||||||
mod gradual;
|
mod gradual;
|
||||||
pub use gradual::{GradualDifficultyAttributes, GradualPerformanceAttributes, ScoreState};
|
pub use gradual::{GradualDifficultyAttributes, GradualPerformanceAttributes, ScoreState};
|
||||||
|
|
||||||
@@ -206,16 +210,13 @@ pub use stars::AnyStars;
|
|||||||
mod curve;
|
mod curve;
|
||||||
mod mods;
|
mod mods;
|
||||||
|
|
||||||
mod control_point_iter;
|
|
||||||
pub use control_point_iter::{ControlPoint, ControlPointIter};
|
|
||||||
|
|
||||||
pub use catch::{CatchPP, CatchStars};
|
pub use catch::{CatchPP, CatchStars};
|
||||||
pub use mania::{ManiaPP, ManiaStars};
|
pub use mania::{ManiaPP, ManiaStars};
|
||||||
pub use osu::{OsuPP, OsuStars};
|
pub use osu::{OsuPP, OsuStars};
|
||||||
pub use taiko::{TaikoPP, TaikoStars};
|
pub use taiko::{TaikoPP, TaikoStars};
|
||||||
|
|
||||||
pub use mods::Mods;
|
pub use mods::Mods;
|
||||||
pub use parse::{Beatmap, BeatmapAttributes, GameMode, ParseError, ParseResult};
|
pub use parse::{ParseError, ParseResult};
|
||||||
|
|
||||||
/// Provides some additional methods on [`Beatmap`](crate::Beatmap).
|
/// Provides some additional methods on [`Beatmap`](crate::Beatmap).
|
||||||
pub trait BeatmapExt {
|
pub trait BeatmapExt {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Beatmap, ControlPoint, ControlPointIter};
|
use crate::beatmap::{Beatmap, ControlPoint, ControlPointIter};
|
||||||
|
|
||||||
pub(crate) struct SliderState<'p> {
|
pub(crate) struct SliderState<'p> {
|
||||||
control_points: ControlPointIter<'p>,
|
control_points: ControlPointIter<'p>,
|
||||||
@@ -44,10 +44,7 @@ impl<'p> SliderState<'p> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::beatmap::{Beatmap, DifficultyPoint, TimingPoint};
|
||||||
parse::{DifficultyPoint, TimingPoint},
|
|
||||||
Beatmap,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::SliderState;
|
use super::SliderState;
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
use std::cmp::Ordering;
|
|
||||||
|
|
||||||
/// New rhythm speed change.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub struct TimingPoint {
|
|
||||||
/// The beat length for this timing section
|
|
||||||
pub beat_len: f64,
|
|
||||||
/// The start time of this timing section
|
|
||||||
pub time: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for TimingPoint {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
self.time.partial_cmp(&other.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [`TimingPoint`](crate::parse::TimingPoint) that depends on a previous one.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub struct DifficultyPoint {
|
|
||||||
/// The start time for the current speed multiplier
|
|
||||||
pub time: f64,
|
|
||||||
/// The speed multiplier until the next timing point
|
|
||||||
pub speed_multiplier: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for DifficultyPoint {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
self.time.partial_cmp(&other.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+80
-80
@@ -1,80 +1,80 @@
|
|||||||
use super::Pos2;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use super::{PathControlPoint, Pos2};
|
||||||
|
|
||||||
/// "Intermediate" hitobject created through parsing.
|
/// "Intermediate" hitobject created through parsing.
|
||||||
/// Each mode will handle them differently.
|
/// Each mode will handle them differently.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct HitObject {
|
pub struct HitObject {
|
||||||
/// The position of the object.
|
/// The position of the object.
|
||||||
pub pos: Pos2,
|
pub pos: Pos2,
|
||||||
/// The start time of the object.
|
/// The start time of the object.
|
||||||
pub start_time: f64,
|
pub start_time: f64,
|
||||||
/// The type of the object.
|
/// The type of the object.
|
||||||
pub kind: HitObjectKind,
|
pub kind: HitObjectKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HitObject {
|
impl HitObject {
|
||||||
/// The end time of the object.
|
/// The end time of the object.
|
||||||
#[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,
|
||||||
HitObjectKind::Hold { end_time, .. } => *end_time,
|
HitObjectKind::Hold { end_time, .. } => *end_time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_slider(&self) -> bool {
|
pub fn is_slider(&self) -> bool {
|
||||||
matches!(self.kind, HitObjectKind::Slider { .. })
|
matches!(self.kind, HitObjectKind::Slider { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the object is a spinner.
|
/// If the object is a spinner.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_spinner(&self) -> bool {
|
pub fn is_spinner(&self) -> bool {
|
||||||
matches!(self.kind, HitObjectKind::Spinner { .. })
|
matches!(self.kind, HitObjectKind::Spinner { .. })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for HitObject {
|
impl PartialOrd for HitObject {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
self.start_time.partial_cmp(&other.start_time)
|
self.start_time.partial_cmp(&other.start_time)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Further data related to specific object types.
|
/// Further data related to specific object types.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum HitObjectKind {
|
pub enum HitObjectKind {
|
||||||
/// A circle object.
|
/// A circle object.
|
||||||
Circle,
|
Circle,
|
||||||
/// A full slider object.
|
/// A full slider object.
|
||||||
Slider {
|
Slider {
|
||||||
/// Total length of the slider in pixels.
|
/// Total length of the slider in pixels.
|
||||||
pixel_len: f64,
|
pixel_len: f64,
|
||||||
/// The amount of repeat points of the slider.
|
/// The amount of repeat points of the slider.
|
||||||
repeats: usize,
|
repeats: usize,
|
||||||
/// The control points of the slider.
|
/// The control points of the slider.
|
||||||
control_points: Vec<super::PathControlPoint>,
|
control_points: Vec<PathControlPoint>,
|
||||||
},
|
},
|
||||||
/// A spinner object.
|
/// A spinner object.
|
||||||
Spinner {
|
Spinner {
|
||||||
/// The end time of the spinner.
|
/// The end time of the spinner.
|
||||||
end_time: f64,
|
end_time: f64,
|
||||||
},
|
},
|
||||||
/// A hold note object for osu!mania.
|
/// A hold note object for osu!mania.
|
||||||
Hold {
|
Hold {
|
||||||
/// The end time of the hold object.
|
/// The end time of the hold object.
|
||||||
end_time: f64,
|
end_time: f64,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-96
@@ -1,5 +1,3 @@
|
|||||||
mod attributes;
|
|
||||||
mod control_point;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod hitobject;
|
mod hitobject;
|
||||||
mod hitsound;
|
mod hitsound;
|
||||||
@@ -7,8 +5,6 @@ mod pos2;
|
|||||||
mod reader;
|
mod reader;
|
||||||
mod sort;
|
mod sort;
|
||||||
|
|
||||||
pub use attributes::BeatmapAttributes;
|
|
||||||
pub use control_point::{DifficultyPoint, TimingPoint};
|
|
||||||
pub use error::{ParseError, ParseResult};
|
pub use error::{ParseError, ParseResult};
|
||||||
pub use hitobject::{HitObject, HitObjectKind};
|
pub use hitobject::{HitObject, HitObjectKind};
|
||||||
pub use hitsound::HitSound;
|
pub use hitsound::HitSound;
|
||||||
@@ -32,7 +28,7 @@ use std::path::Path;
|
|||||||
#[cfg(feature = "async_std")]
|
#[cfg(feature = "async_std")]
|
||||||
use async_std::{fs::File, io::Read as AsyncRead, path::Path};
|
use async_std::{fs::File, io::Read as AsyncRead, path::Path};
|
||||||
|
|
||||||
use crate::control_point_iter::ControlPointIter;
|
use crate::beatmap::{Beatmap, DifficultyPoint, GameMode, TimingPoint};
|
||||||
|
|
||||||
fn sort_unstable<T: PartialOrd>(slice: &mut [T]) {
|
fn sort_unstable<T: PartialOrd>(slice: &mut [T]) {
|
||||||
slice.sort_unstable_by(|p1, p2| p1.partial_cmp(p2).unwrap_or(Ordering::Equal));
|
slice.sort_unstable_by(|p1, p2| p1.partial_cmp(p2).unwrap_or(Ordering::Equal));
|
||||||
@@ -595,72 +591,6 @@ macro_rules! from_path {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The mode of a beatmap.
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
|
||||||
pub enum GameMode {
|
|
||||||
/// osu!standard
|
|
||||||
STD = 0,
|
|
||||||
/// osu!taiko
|
|
||||||
TKO = 1,
|
|
||||||
/// osu!catch
|
|
||||||
CTB = 2,
|
|
||||||
/// osu!mania
|
|
||||||
MNA = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for GameMode {
|
|
||||||
#[inline]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::STD
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The main beatmap struct containing all data relevant
|
|
||||||
/// for difficulty and pp calculation
|
|
||||||
#[derive(Clone, Default, Debug)]
|
|
||||||
pub struct Beatmap {
|
|
||||||
/// The game mode.
|
|
||||||
pub mode: GameMode,
|
|
||||||
/// The version of the .osu file.
|
|
||||||
pub version: u8,
|
|
||||||
|
|
||||||
/// The amount of circles.
|
|
||||||
pub n_circles: u32,
|
|
||||||
/// The amount of sliders.
|
|
||||||
pub n_sliders: u32,
|
|
||||||
/// The amount of spinners.
|
|
||||||
pub n_spinners: u32,
|
|
||||||
|
|
||||||
/// The approach rate.
|
|
||||||
pub ar: f32,
|
|
||||||
/// The overall difficulty.
|
|
||||||
pub od: f32,
|
|
||||||
/// The circle size.
|
|
||||||
pub cs: f32,
|
|
||||||
/// The health drain rate.
|
|
||||||
pub hp: f32,
|
|
||||||
/// Base slider velocity in pixels per beat
|
|
||||||
pub slider_mult: f64,
|
|
||||||
/// Amount of slider ticks per beat.
|
|
||||||
pub tick_rate: f64,
|
|
||||||
/// All hitobjects of the beatmap.
|
|
||||||
pub hit_objects: Vec<HitObject>,
|
|
||||||
/// Store the sounds for all objects in their own Vec to minimize the struct size.
|
|
||||||
/// Hitsounds are only used in osu!taiko in which they represent color.
|
|
||||||
pub sounds: Vec<u8>,
|
|
||||||
|
|
||||||
/// Timing points that indicate a new timing section.
|
|
||||||
pub timing_points: Vec<TimingPoint>,
|
|
||||||
|
|
||||||
/// Timing point for the current timing section.
|
|
||||||
pub difficulty_points: Vec<DifficultyPoint>,
|
|
||||||
|
|
||||||
/// The stack leniency that is used to calculate
|
|
||||||
/// the stack offset for stacked positions.
|
|
||||||
pub stack_leniency: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Beatmap {
|
impl Beatmap {
|
||||||
const CIRCLE_FLAG: u8 = 1 << 0;
|
const CIRCLE_FLAG: u8 = 1 << 0;
|
||||||
const SLIDER_FLAG: u8 = 1 << 1;
|
const SLIDER_FLAG: u8 = 1 << 1;
|
||||||
@@ -668,27 +598,6 @@ impl Beatmap {
|
|||||||
const SPINNER_FLAG: u8 = 1 << 3;
|
const SPINNER_FLAG: u8 = 1 << 3;
|
||||||
// const COMBO_OFFSET_FLAG: u8 = (1 << 4) | (1 << 5) | (1 << 6);
|
// const COMBO_OFFSET_FLAG: u8 = (1 << 4) | (1 << 5) | (1 << 6);
|
||||||
const HOLD_FLAG: u8 = 1 << 7;
|
const HOLD_FLAG: u8 = 1 << 7;
|
||||||
|
|
||||||
/// Extract a beatmap's attributes into their own type.
|
|
||||||
#[inline]
|
|
||||||
pub fn attributes(&self) -> BeatmapAttributes {
|
|
||||||
BeatmapAttributes::new(self.ar, self.od, self.cs, self.hp)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The beats per minute of the map.
|
|
||||||
#[inline]
|
|
||||||
pub fn bpm(&self) -> f64 {
|
|
||||||
match self.timing_points.first() {
|
|
||||||
Some(point) => point.beat_len.recip() * 1000.0 * 60.0,
|
|
||||||
None => 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an iterator over the map's timing- and difficulty points sorted by timestamp.
|
|
||||||
#[inline]
|
|
||||||
pub fn control_points(&self) -> ControlPointIter<'_> {
|
|
||||||
ControlPointIter::new(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod slider_parsing {
|
mod slider_parsing {
|
||||||
@@ -927,11 +836,11 @@ mod tests {
|
|||||||
println!("map_id: {}", map_id);
|
println!("map_id: {}", map_id);
|
||||||
|
|
||||||
let map = match Beatmap::from_path(format!("./maps/{}.osu", map_id)).await {
|
let map = match Beatmap::from_path(format!("./maps/{}.osu", map_id)).await {
|
||||||
Ok(map) => map,
|
Ok(map) => beatmap,
|
||||||
Err(why) => panic!("Error while parsing map: {}", why),
|
Err(why) => panic!("Error while parsing map: {}", why),
|
||||||
};
|
};
|
||||||
|
|
||||||
print_info(map);
|
print_info(beatmap);
|
||||||
println!("---");
|
println!("---");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -945,11 +854,11 @@ mod tests {
|
|||||||
println!("map_id: {}", map_id);
|
println!("map_id: {}", map_id);
|
||||||
|
|
||||||
let map = match Beatmap::from_path(format!("./maps/{}.osu", map_id)).await {
|
let map = match Beatmap::from_path(format!("./maps/{}.osu", map_id)).await {
|
||||||
Ok(map) => map,
|
Ok(map) => beatmap,
|
||||||
Err(why) => panic!("Error while parsing map: {}", why),
|
Err(why) => panic!("Error while parsing map: {}", why),
|
||||||
};
|
};
|
||||||
|
|
||||||
print_info(map);
|
print_info(beatmap);
|
||||||
println!("---");
|
println!("---");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user