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