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}