Skip to main content

rosu_pp/any/
score_state.rs

1use rosu_map::section::general::GameMode;
2
3use crate::{
4    catch::{CatchHitResults, CatchScoreState},
5    mania::ManiaScoreState,
6    osu::{OsuHitResults, OsuScoreState},
7    taiko::{TaikoHitResults, TaikoScoreState},
8};
9
10/// Aggregation for a score's current state.
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct ScoreState {
13    /// Maximum combo that the score has had so far. **Not** the maximum
14    /// possible combo of the map so far.
15    ///
16    /// Note that for osu!catch only fruits and droplets are considered for
17    /// combo.
18    ///
19    /// Irrelevant for osu!mania.
20    pub max_combo: u32,
21    /// "Large tick" hits for osu!standard.
22    ///
23    /// The meaning depends on the kind of score:
24    /// - if set on osu!stable, this field is irrelevant and can be `0`
25    /// - if set on osu!lazer *with* slider accuracy, this field is the amount
26    ///   of hit slider ticks and repeats
27    /// - if set on osu!lazer *without* slider accuracy, this field is the
28    ///   amount of hit slider heads, ticks, and repeats
29    ///
30    /// Only relevant for osu!lazer.
31    pub osu_large_tick_hits: u32,
32    /// "Small ticks" hits for osu!standard.
33    ///
34    /// These are essentially the slider end hits for lazer scores without
35    /// slider accuracy.
36    ///
37    /// Only relevant for osu!lazer.
38    pub osu_small_tick_hits: u32,
39    /// Amount of successfully hit slider ends.
40    ///
41    /// Only relevant for osu!standard in lazer.
42    pub slider_end_hits: u32,
43    /// Amount of current gekis (n320 for osu!mania).
44    pub n_geki: u32,
45    /// Amount of current katus (tiny droplet misses for osu!catch / n200 for
46    /// osu!mania).
47    pub n_katu: u32,
48    /// Amount of current 300s (fruits for osu!catch).
49    pub n300: u32,
50    /// Amount of current 100s (droplets for osu!catch).
51    pub n100: u32,
52    /// Amount of current 50s (tiny droplets for osu!catch).
53    pub n50: u32,
54    /// Amount of current misses (fruits + droplets for osu!catch).
55    pub misses: u32,
56    /// Legacy total score.
57    ///
58    /// Only relevant for osu!standard in stable.
59    pub legacy_total_score: Option<u32>,
60}
61
62impl ScoreState {
63    /// Create a new empty score state.
64    pub const fn new() -> Self {
65        Self {
66            max_combo: 0,
67            osu_large_tick_hits: 0,
68            osu_small_tick_hits: 0,
69            slider_end_hits: 0,
70            n_geki: 0,
71            n_katu: 0,
72            n300: 0,
73            n100: 0,
74            n50: 0,
75            misses: 0,
76            legacy_total_score: None,
77        }
78    }
79
80    /// Return the total amount of hits by adding everything up based on the
81    /// mode.
82    pub fn total_hits(&self, mode: GameMode) -> u32 {
83        let mut amount = self.n300 + self.n100 + self.misses;
84
85        if mode != GameMode::Taiko {
86            amount += self.n50;
87
88            if mode != GameMode::Osu {
89                amount += self.n_katu;
90                amount += u32::from(mode != GameMode::Catch) * self.n_geki;
91            }
92        }
93
94        amount
95    }
96}
97
98impl From<ScoreState> for OsuScoreState {
99    fn from(state: ScoreState) -> Self {
100        Self {
101            max_combo: state.max_combo,
102            hitresults: OsuHitResults {
103                large_tick_hits: state.osu_large_tick_hits,
104                small_tick_hits: state.osu_small_tick_hits,
105                slider_end_hits: state.slider_end_hits,
106                n300: state.n300,
107                n100: state.n100,
108                n50: state.n50,
109                misses: state.misses,
110            },
111            legacy_total_score: state.legacy_total_score,
112        }
113    }
114}
115
116impl From<ScoreState> for TaikoScoreState {
117    fn from(state: ScoreState) -> Self {
118        Self {
119            max_combo: state.max_combo,
120            hitresults: TaikoHitResults {
121                n300: state.n300,
122                n100: state.n100,
123                misses: state.misses,
124            },
125        }
126    }
127}
128
129impl From<ScoreState> for CatchScoreState {
130    fn from(state: ScoreState) -> Self {
131        Self {
132            max_combo: state.max_combo,
133            hitresults: CatchHitResults {
134                fruits: state.n300,
135                droplets: state.n100,
136                tiny_droplets: state.n50,
137                tiny_droplet_misses: state.n_katu,
138                misses: state.misses,
139            },
140        }
141    }
142}
143
144impl From<ScoreState> for ManiaScoreState {
145    fn from(state: ScoreState) -> Self {
146        Self {
147            n320: state.n_geki,
148            n300: state.n300,
149            n200: state.n_katu,
150            n100: state.n100,
151            n50: state.n50,
152            misses: state.misses,
153        }
154    }
155}
156
157impl From<OsuScoreState> for ScoreState {
158    fn from(state: OsuScoreState) -> Self {
159        Self {
160            max_combo: state.max_combo,
161            osu_large_tick_hits: state.hitresults.large_tick_hits,
162            osu_small_tick_hits: state.hitresults.small_tick_hits,
163            slider_end_hits: state.hitresults.slider_end_hits,
164            n_geki: 0,
165            n_katu: 0,
166            n300: state.hitresults.n300,
167            n100: state.hitresults.n100,
168            n50: state.hitresults.n50,
169            misses: state.hitresults.misses,
170            legacy_total_score: state.legacy_total_score,
171        }
172    }
173}
174
175impl From<TaikoScoreState> for ScoreState {
176    fn from(state: TaikoScoreState) -> Self {
177        Self {
178            max_combo: state.max_combo,
179            osu_large_tick_hits: 0,
180            osu_small_tick_hits: 0,
181            slider_end_hits: 0,
182            n_geki: 0,
183            n_katu: 0,
184            n300: state.hitresults.n300,
185            n100: state.hitresults.n100,
186            n50: 0,
187            misses: state.hitresults.misses,
188            legacy_total_score: None,
189        }
190    }
191}
192
193impl From<CatchScoreState> for ScoreState {
194    fn from(state: CatchScoreState) -> Self {
195        Self {
196            max_combo: state.max_combo,
197            osu_large_tick_hits: 0,
198            osu_small_tick_hits: 0,
199            slider_end_hits: 0,
200            n_geki: 0,
201            n_katu: state.hitresults.tiny_droplet_misses,
202            n300: state.hitresults.fruits,
203            n100: state.hitresults.droplets,
204            n50: state.hitresults.tiny_droplets,
205            misses: state.hitresults.misses,
206            legacy_total_score: None,
207        }
208    }
209}
210
211impl From<ManiaScoreState> for ScoreState {
212    fn from(state: ManiaScoreState) -> Self {
213        Self {
214            max_combo: 0,
215            osu_large_tick_hits: 0,
216            osu_small_tick_hits: 0,
217            slider_end_hits: 0,
218            n_geki: state.n320,
219            n_katu: state.n200,
220            n300: state.n300,
221            n100: state.n100,
222            n50: state.n50,
223            misses: state.misses,
224            legacy_total_score: None,
225        }
226    }
227}
228
229impl Default for ScoreState {
230    fn default() -> Self {
231        Self::new()
232    }
233}