rosu_map/section/hit_objects/
mod.rs

1use std::{
2    num::ParseIntError,
3    ops::{BitAnd, BitAndAssign},
4    str::FromStr,
5};
6
7use self::hit_samples::HitSampleInfo;
8pub use self::{
9    circle::HitObjectCircle,
10    decode::{HitObjects, HitObjectsState, ParseHitObjectsError},
11    hold::HitObjectHold,
12    slider::{
13        curve::{BorrowedCurve, Curve, CurveBuffers},
14        event::{SliderEvent, SliderEventType, SliderEventsIter},
15        path::{PathControlPoint, SliderPath},
16        path_type::{PathType, SplineType},
17        HitObjectSlider,
18    },
19    spinner::HitObjectSpinner,
20};
21
22mod circle;
23pub(crate) mod decode; // pub(crate) for intradoc-links
24mod hold;
25mod slider;
26mod spinner;
27
28/// Audio-related types.
29pub mod hit_samples;
30
31pub(crate) const BASE_SCORING_DIST: f32 = 100.0;
32
33/// A hit object of a [`Beatmap`].
34///
35/// [`Beatmap`]: crate::beatmap::Beatmap
36#[derive(Clone, Debug, PartialEq)]
37pub struct HitObject {
38    pub start_time: f64,
39    pub kind: HitObjectKind,
40    pub samples: Vec<HitSampleInfo>,
41}
42
43impl HitObject {
44    /// Whether the [`HitObject`] starts a new combo.
45    pub const fn new_combo(&self) -> bool {
46        self.kind.new_combo()
47    }
48
49    /// Returns the end time of the [`HitObject`].
50    ///
51    /// If the curve has not yet been accessed, it needs to be calculated
52    /// first.
53    ///
54    /// In case curves of multiple slider paths are being calculated, it is
55    /// recommended to initialize [`CurveBuffers`] and pass a mutable reference
56    /// of it to [`HitObject::end_time_with_bufs`] so the buffers are re-used
57    /// for all sliders.
58    pub fn end_time(&mut self) -> f64 {
59        self.end_time_with_bufs(&mut CurveBuffers::default())
60    }
61
62    /// Returns the end time of the [`HitObject`].
63    ///
64    /// If the slider's curve has not yet been accessed, it needs to be
65    /// calculated first for which the given [`CurveBuffers`] are used.
66    pub fn end_time_with_bufs(&mut self, bufs: &mut CurveBuffers) -> f64 {
67        match self.kind {
68            HitObjectKind::Circle(_) => self.start_time,
69            HitObjectKind::Slider(ref mut h) => self.start_time + h.duration_with_bufs(bufs),
70            HitObjectKind::Spinner(ref h) => self.start_time + h.duration,
71            HitObjectKind::Hold(ref h) => self.start_time + h.duration,
72        }
73    }
74}
75
76/// Additional data for a [`HitObject`] depending on its type.
77#[derive(Clone, Debug, PartialEq)]
78pub enum HitObjectKind {
79    Circle(HitObjectCircle),
80    Slider(HitObjectSlider),
81    Spinner(HitObjectSpinner),
82    Hold(HitObjectHold),
83}
84
85impl HitObjectKind {
86    /// Whether the [`HitObjectKind`] starts a new combo.
87    pub const fn new_combo(&self) -> bool {
88        match self {
89            Self::Circle(h) => h.new_combo,
90            Self::Slider(h) => h.new_combo,
91            Self::Spinner(h) => h.new_combo,
92            Self::Hold(_) => false,
93        }
94    }
95}
96
97/// The type of a [`HitObject`].
98#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
99pub struct HitObjectType(i32);
100
101impl HitObjectType {
102    pub const CIRCLE: i32 = 1;
103    pub const SLIDER: i32 = 1 << 1;
104    pub const NEW_COMBO: i32 = 1 << 2;
105    pub const SPINNER: i32 = 1 << 3;
106    pub const COMBO_OFFSET: i32 = (1 << 4) | (1 << 5) | (1 << 6);
107    pub const HOLD: i32 = 1 << 7;
108
109    /// Check whether any of the given bitflags are set.
110    pub const fn has_flag(self, flag: i32) -> bool {
111        (self.0 & flag) != 0
112    }
113}
114
115impl From<&HitObject> for HitObjectType {
116    fn from(hit_object: &HitObject) -> Self {
117        let mut kind = 0;
118
119        match hit_object.kind {
120            HitObjectKind::Circle(ref h) => {
121                kind |= h.combo_offset << 4;
122
123                if h.new_combo {
124                    kind |= Self::NEW_COMBO;
125                }
126
127                kind |= Self::CIRCLE;
128            }
129            HitObjectKind::Slider(ref h) => {
130                kind |= h.combo_offset << 4;
131
132                if h.new_combo {
133                    kind |= Self::NEW_COMBO;
134                }
135
136                kind |= Self::SLIDER;
137            }
138            HitObjectKind::Spinner(ref h) => {
139                if h.new_combo {
140                    kind |= Self::NEW_COMBO;
141                }
142
143                kind |= Self::SPINNER;
144            }
145            HitObjectKind::Hold(_) => kind |= Self::HOLD,
146        }
147
148        Self(kind)
149    }
150}
151
152impl FromStr for HitObjectType {
153    type Err = ParseHitObjectTypeError;
154
155    fn from_str(s: &str) -> Result<Self, Self::Err> {
156        s.parse().map(Self).map_err(ParseHitObjectTypeError)
157    }
158}
159
160thiserror! {
161    #[error("invalid hit object type")]
162    /// Error when failing to parse a [`HitObjectType`].
163    #[derive(Clone, Debug, PartialEq, Eq)]
164    pub struct ParseHitObjectTypeError(ParseIntError);
165}
166
167impl From<HitObjectType> for i32 {
168    fn from(kind: HitObjectType) -> Self {
169        kind.0
170    }
171}
172
173impl BitAnd<i32> for HitObjectType {
174    type Output = i32;
175
176    fn bitand(self, rhs: i32) -> Self::Output {
177        self.0 & rhs
178    }
179}
180
181impl BitAndAssign<i32> for HitObjectType {
182    fn bitand_assign(&mut self, rhs: i32) {
183        self.0 &= rhs;
184    }
185}