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
use rosu_map::section::general::GameMode;

use crate::{
    catch::CatchScoreState, mania::ManiaScoreState, osu::OsuScoreState, taiko::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,
    /// 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,
}

impl ScoreState {
    /// Create a new empty score state.
    pub const fn new() -> Self {
        Self {
            max_combo: 0,
            n_geki: 0,
            n_katu: 0,
            n300: 0,
            n100: 0,
            n50: 0,
            misses: 0,
        }
    }

    /// 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,
            n300: state.n300,
            n100: state.n100,
            n50: state.n50,
            misses: state.misses,
        }
    }
}

impl From<ScoreState> for TaikoScoreState {
    fn from(state: ScoreState) -> Self {
        Self {
            max_combo: state.max_combo,
            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,
            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,
            n_geki: 0,
            n_katu: 0,
            n300: state.n300,
            n100: state.n100,
            n50: state.n50,
            misses: state.misses,
        }
    }
}

impl From<TaikoScoreState> for ScoreState {
    fn from(state: TaikoScoreState) -> Self {
        Self {
            max_combo: state.max_combo,
            n_geki: 0,
            n_katu: 0,
            n300: state.n300,
            n100: state.n100,
            n50: 0,
            misses: state.misses,
        }
    }
}

impl From<CatchScoreState> for ScoreState {
    fn from(state: CatchScoreState) -> Self {
        Self {
            max_combo: state.max_combo,
            n_geki: 0,
            n_katu: state.tiny_droplet_misses,
            n300: state.fruits,
            n100: state.droplets,
            n50: state.tiny_droplets,
            misses: state.misses,
        }
    }
}

impl From<ManiaScoreState> for ScoreState {
    fn from(state: ManiaScoreState) -> Self {
        Self {
            max_combo: 0,
            n_geki: state.n320,
            n_katu: state.n200,
            n300: state.n300,
            n100: state.n100,
            n50: state.n50,
            misses: state.misses,
        }
    }
}

impl Default for ScoreState {
    fn default() -> Self {
        Self::new()
    }
}