1use rosu_map::section::general::GameMode;
2
3use crate::{
4    catch::CatchPerformance, mania::ManiaPerformance, osu::OsuPerformance, taiko::TaikoPerformance,
5    Difficulty, GameMods,
6};
7
8use self::into::IntoPerformance;
9
10use super::{attributes::PerformanceAttributes, score_state::ScoreState};
11
12pub mod gradual;
13pub mod into;
14
15#[derive(Clone, Debug, PartialEq)]
17#[must_use]
18pub enum Performance<'map> {
19    Osu(OsuPerformance<'map>),
20    Taiko(TaikoPerformance<'map>),
21    Catch(CatchPerformance<'map>),
22    Mania(ManiaPerformance<'map>),
23}
24
25impl<'map> Performance<'map> {
26    pub fn new(map_or_attrs: impl IntoPerformance<'map>) -> Self {
47        map_or_attrs.into_performance()
48    }
49
50    #[allow(clippy::missing_panics_doc)]
53    pub fn calculate(self) -> PerformanceAttributes {
54        match self {
55            Self::Osu(o) => {
56                PerformanceAttributes::Osu(o.calculate().expect("no conversion required"))
57            }
58            Self::Taiko(t) => {
59                PerformanceAttributes::Taiko(t.calculate().expect("no conversion required"))
60            }
61            Self::Catch(f) => {
62                PerformanceAttributes::Catch(f.calculate().expect("no conversion required"))
63            }
64            Self::Mania(m) => {
65                PerformanceAttributes::Mania(m.calculate().expect("no conversion required"))
66            }
67        }
68    }
69
70    #[allow(clippy::result_large_err)]
82    pub fn try_mode(self, mode: GameMode) -> Result<Self, Self> {
83        match (self, mode) {
84            (Self::Osu(o), _) => o.try_mode(mode).map_err(Self::Osu),
85            (this @ Self::Taiko(_), GameMode::Taiko)
86            | (this @ Self::Catch(_), GameMode::Catch)
87            | (this @ Self::Mania(_), GameMode::Mania) => Ok(this),
88            (this, _) => Err(this),
89        }
90    }
91
92    pub fn mode_or_ignore(self, mode: GameMode) -> Self {
102        if let Self::Osu(osu) = self {
103            osu.mode_or_ignore(mode)
104        } else {
105            self
106        }
107    }
108
109    pub fn mods(self, mods: impl Into<GameMods>) -> Self {
120        match self {
121            Self::Osu(o) => Self::Osu(o.mods(mods)),
122            Self::Taiko(t) => Self::Taiko(t.mods(mods)),
123            Self::Catch(f) => Self::Catch(f.mods(mods)),
124            Self::Mania(m) => Self::Mania(m.mods(mods)),
125        }
126    }
127
128    pub fn difficulty(self, difficulty: Difficulty) -> Self {
130        match self {
131            Self::Osu(o) => Self::Osu(o.difficulty(difficulty)),
132            Self::Taiko(t) => Self::Taiko(t.difficulty(difficulty)),
133            Self::Catch(f) => Self::Catch(f.difficulty(difficulty)),
134            Self::Mania(m) => Self::Mania(m.difficulty(difficulty)),
135        }
136    }
137
138    pub fn passed_objects(self, passed_objects: u32) -> Self {
146        match self {
147            Self::Osu(o) => Self::Osu(o.passed_objects(passed_objects)),
148            Self::Taiko(t) => Self::Taiko(t.passed_objects(passed_objects)),
149            Self::Catch(f) => Self::Catch(f.passed_objects(passed_objects)),
150            Self::Mania(m) => Self::Mania(m.passed_objects(passed_objects)),
151        }
152    }
153
154    pub fn clock_rate(self, clock_rate: f64) -> Self {
163        match self {
164            Self::Osu(o) => Self::Osu(o.clock_rate(clock_rate)),
165            Self::Taiko(t) => Self::Taiko(t.clock_rate(clock_rate)),
166            Self::Catch(f) => Self::Catch(f.clock_rate(clock_rate)),
167            Self::Mania(m) => Self::Mania(m.clock_rate(clock_rate)),
168        }
169    }
170
171    pub fn ar(self, ar: f32, with_mods: bool) -> Self {
183        match self {
184            Self::Osu(o) => Self::Osu(o.ar(ar, with_mods)),
185            Self::Catch(c) => Self::Catch(c.ar(ar, with_mods)),
186            Self::Taiko(_) | Self::Mania(_) => self,
187        }
188    }
189
190    pub fn cs(self, cs: f32, with_mods: bool) -> Self {
202        match self {
203            Self::Osu(o) => Self::Osu(o.cs(cs, with_mods)),
204            Self::Catch(c) => Self::Catch(c.cs(cs, with_mods)),
205            Self::Taiko(_) | Self::Mania(_) => self,
206        }
207    }
208
209    pub fn hp(self, hp: f32, with_mods: bool) -> Self {
219        match self {
220            Self::Osu(o) => Self::Osu(o.hp(hp, with_mods)),
221            Self::Taiko(t) => Self::Taiko(t.hp(hp, with_mods)),
222            Self::Catch(c) => Self::Catch(c.hp(hp, with_mods)),
223            Self::Mania(m) => Self::Mania(m.hp(hp, with_mods)),
224        }
225    }
226
227    pub fn od(self, od: f32, with_mods: bool) -> Self {
237        match self {
238            Self::Osu(o) => Self::Osu(o.od(od, with_mods)),
239            Self::Taiko(t) => Self::Taiko(t.od(od, with_mods)),
240            Self::Catch(c) => Self::Catch(c.od(od, with_mods)),
241            Self::Mania(m) => Self::Mania(m.od(od, with_mods)),
242        }
243    }
244
245    pub fn hardrock_offsets(self, hardrock_offsets: bool) -> Self {
249        if let Self::Catch(catch) = self {
250            Self::Catch(catch.hardrock_offsets(hardrock_offsets))
251        } else {
252            self
253        }
254    }
255
256    pub fn state(self, state: ScoreState) -> Self {
258        match self {
259            Self::Osu(o) => Self::Osu(o.state(state.into())),
260            Self::Taiko(t) => Self::Taiko(t.state(state.into())),
261            Self::Catch(f) => Self::Catch(f.state(state.into())),
262            Self::Mania(m) => Self::Mania(m.state(state.into())),
263        }
264    }
265
266    pub fn accuracy(self, acc: f64) -> Self {
268        match self {
269            Self::Osu(o) => Self::Osu(o.accuracy(acc)),
270            Self::Taiko(t) => Self::Taiko(t.accuracy(acc)),
271            Self::Catch(f) => Self::Catch(f.accuracy(acc)),
272            Self::Mania(m) => Self::Mania(m.accuracy(acc)),
273        }
274    }
275
276    pub fn misses(self, n_misses: u32) -> Self {
278        match self {
279            Self::Osu(o) => Self::Osu(o.misses(n_misses)),
280            Self::Taiko(t) => Self::Taiko(t.misses(n_misses)),
281            Self::Catch(f) => Self::Catch(f.misses(n_misses)),
282            Self::Mania(m) => Self::Mania(m.misses(n_misses)),
283        }
284    }
285
286    pub fn combo(self, combo: u32) -> Self {
290        match self {
291            Self::Osu(o) => Self::Osu(o.combo(combo)),
292            Self::Taiko(t) => Self::Taiko(t.combo(combo)),
293            Self::Catch(f) => Self::Catch(f.combo(combo)),
294            Self::Mania(_) => self,
295        }
296    }
297
298    pub fn hitresult_priority(self, priority: HitResultPriority) -> Self {
302        match self {
303            Self::Osu(o) => Self::Osu(o.hitresult_priority(priority)),
304            Self::Taiko(t) => Self::Taiko(t.hitresult_priority(priority)),
305            Self::Catch(_) => self,
306            Self::Mania(m) => Self::Mania(m.hitresult_priority(priority)),
307        }
308    }
309
310    pub fn lazer(self, lazer: bool) -> Self {
320        match self {
321            Self::Osu(o) => Self::Osu(o.lazer(lazer)),
322            Self::Taiko(_) | Self::Catch(_) => self,
323            Self::Mania(m) => Self::Mania(m.lazer(lazer)),
324        }
325    }
326
327    pub fn large_tick_hits(self, large_tick_hits: u32) -> Self {
338        if let Self::Osu(osu) = self {
339            Self::Osu(osu.large_tick_hits(large_tick_hits))
340        } else {
341            self
342        }
343    }
344
345    pub fn small_tick_hits(self, small_tick_hits: u32) -> Self {
350        if let Self::Osu(osu) = self {
351            Self::Osu(osu.small_tick_hits(small_tick_hits))
352        } else {
353            self
354        }
355    }
356
357    pub fn slider_end_hits(self, slider_end_hits: u32) -> Self {
361        if let Self::Osu(osu) = self {
362            Self::Osu(osu.slider_end_hits(slider_end_hits))
363        } else {
364            self
365        }
366    }
367
368    pub fn n300(self, n300: u32) -> Self {
370        match self {
371            Self::Osu(o) => Self::Osu(o.n300(n300)),
372            Self::Taiko(t) => Self::Taiko(t.n300(n300)),
373            Self::Catch(f) => Self::Catch(f.fruits(n300)),
374            Self::Mania(m) => Self::Mania(m.n300(n300)),
375        }
376    }
377
378    pub fn n100(self, n100: u32) -> Self {
380        match self {
381            Self::Osu(o) => Self::Osu(o.n100(n100)),
382            Self::Taiko(t) => Self::Taiko(t.n100(n100)),
383            Self::Catch(f) => Self::Catch(f.droplets(n100)),
384            Self::Mania(m) => Self::Mania(m.n100(n100)),
385        }
386    }
387
388    pub fn n50(self, n50: u32) -> Self {
392        match self {
393            Self::Osu(o) => Self::Osu(o.n50(n50)),
394            Self::Taiko(_) => self,
395            Self::Catch(f) => Self::Catch(f.tiny_droplets(n50)),
396            Self::Mania(m) => Self::Mania(m.n50(n50)),
397        }
398    }
399
400    pub fn n_katu(self, n_katu: u32) -> Self {
405        match self {
406            Self::Osu(_) | Self::Taiko(_) => self,
407            Self::Catch(f) => Self::Catch(f.tiny_droplet_misses(n_katu)),
408            Self::Mania(m) => Self::Mania(m.n200(n_katu)),
409        }
410    }
411
412    pub fn n_geki(self, n_geki: u32) -> Self {
417        match self {
418            Self::Osu(_) | Self::Taiko(_) | Self::Catch(_) => self,
419            Self::Mania(m) => Self::Mania(m.n320(n_geki)),
420        }
421    }
422
423    #[allow(clippy::missing_panics_doc)]
425    pub fn generate_state(&mut self) -> ScoreState {
426        match self {
427            Self::Osu(o) => o.generate_state().expect("no conversion required").into(),
428            Self::Taiko(t) => t.generate_state().expect("no conversion required").into(),
429            Self::Catch(f) => f.generate_state().expect("no conversion required").into(),
430            Self::Mania(m) => m.generate_state().expect("no conversion required").into(),
431        }
432    }
433}
434
435#[derive(Copy, Clone, Debug, Eq, PartialEq)]
437#[non_exhaustive]
438pub enum HitResultPriority {
439    BestCase,
441    WorstCase,
443    Fastest,
445}
446
447impl HitResultPriority {
448    pub(crate) const DEFAULT: Self = Self::BestCase;
449}
450
451impl Default for HitResultPriority {
452    fn default() -> Self {
453        Self::DEFAULT
454    }
455}
456
457impl<'a, T: IntoPerformance<'a>> From<T> for Performance<'a> {
458    fn from(into: T) -> Self {
459        into.into_performance()
460    }
461}
462
463#[cfg(test)]
464mod tests {
465    use crate::{
466        any::DifficultyAttributes,
467        catch::{CatchDifficultyAttributes, CatchPerformanceAttributes},
468        mania::{ManiaDifficultyAttributes, ManiaPerformanceAttributes},
469        osu::{OsuDifficultyAttributes, OsuPerformanceAttributes},
470        taiko::{TaikoDifficultyAttributes, TaikoPerformanceAttributes},
471        Beatmap,
472    };
473
474    use super::*;
475
476    #[test]
477    fn create() {
478        let map = Beatmap::from_path("./resources/1028484.osu").unwrap();
479
480        let _ = Performance::new(&map);
481        let _ = Performance::new(map.clone());
482
483        let _ = Performance::new(OsuDifficultyAttributes::default());
484        let _ = Performance::new(TaikoDifficultyAttributes::default());
485        let _ = Performance::new(CatchDifficultyAttributes::default());
486        let _ = Performance::new(ManiaDifficultyAttributes::default());
487
488        let _ = Performance::new(OsuPerformanceAttributes::default());
489        let _ = Performance::new(TaikoPerformanceAttributes::default());
490        let _ = Performance::new(CatchPerformanceAttributes::default());
491        let _ = Performance::new(ManiaPerformanceAttributes::default());
492
493        let _ = Performance::new(DifficultyAttributes::Osu(OsuDifficultyAttributes::default()));
494        let _ = Performance::new(PerformanceAttributes::Taiko(
495            TaikoPerformanceAttributes::default(),
496        ));
497
498        let _ = Performance::from(&map);
499        let _ = Performance::from(map);
500
501        let _ = Performance::from(OsuDifficultyAttributes::default());
502        let _ = Performance::from(TaikoDifficultyAttributes::default());
503        let _ = Performance::from(CatchDifficultyAttributes::default());
504        let _ = Performance::from(ManiaDifficultyAttributes::default());
505
506        let _ = Performance::from(OsuPerformanceAttributes::default());
507        let _ = Performance::from(TaikoPerformanceAttributes::default());
508        let _ = Performance::from(CatchPerformanceAttributes::default());
509        let _ = Performance::from(ManiaPerformanceAttributes::default());
510
511        let _ = Performance::from(DifficultyAttributes::Osu(OsuDifficultyAttributes::default()));
512        let _ = Performance::from(PerformanceAttributes::Taiko(
513            TaikoPerformanceAttributes::default(),
514        ));
515
516        let _ = DifficultyAttributes::Osu(OsuDifficultyAttributes::default()).performance();
517        let _ = PerformanceAttributes::Taiko(TaikoPerformanceAttributes::default()).performance();
518    }
519}