1use std::fmt::{Debug, Formatter, Result as FmtResult};
2
3use rosu_mods::{
4 GameMod, GameModIntermode, GameMods as GameModsLazer, GameModsIntermode, GameModsLegacy,
5 generated_mods::DifficultyAdjustCatch,
6};
7
8pub mod rosu_mods {
10 pub use rosu_mods::*;
11}
12
13#[derive(Clone, PartialEq)]
35pub enum GameMods {
36 Lazer(GameModsLazer),
37 Intermode(GameModsIntermode),
38 Legacy(GameModsLegacy),
39}
40
41impl Debug for GameMods {
42 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
43 match self {
44 Self::Lazer(mods) => Debug::fmt(mods, f),
45 Self::Intermode(mods) => Debug::fmt(mods, f),
46 Self::Legacy(mods) => Debug::fmt(mods, f),
47 }
48 }
49}
50
51impl GameMods {
52 pub(crate) const DEFAULT: Self = Self::Legacy(GameModsLegacy::NoMod);
53
54 pub(crate) fn clock_rate(&self) -> f64 {
59 match self {
60 Self::Lazer(mods) => mods
61 .iter()
62 .find_map(|m| {
63 let default = match m.intermode() {
64 GameModIntermode::DoubleTime | GameModIntermode::HalfTime => {
65 return m.clock_rate();
66 }
67 GameModIntermode::Nightcore => 1.5,
68 GameModIntermode::Daycore => 0.75,
69 _ => return None,
70 };
71
72 Some(default * (m.clock_rate()? / default))
73 })
74 .unwrap_or(1.0),
75 Self::Intermode(mods) => mods.legacy_clock_rate(),
76 Self::Legacy(mods) => mods.clock_rate(),
77 }
78 }
79
80 pub(crate) fn hardrock_offsets(&self) -> bool {
82 fn custom_hardrock_offsets(mods: &GameMods) -> Option<bool> {
83 match mods {
84 GameMods::Lazer(mods) => mods.iter().find_map(|gamemod| match gamemod {
85 GameMod::DifficultyAdjustCatch(DifficultyAdjustCatch {
86 hard_rock_offsets,
87 ..
88 }) => *hard_rock_offsets,
89 _ => None,
90 }),
91 GameMods::Intermode(_) | GameMods::Legacy(_) => None,
92 }
93 }
94
95 custom_hardrock_offsets(self).unwrap_or_else(|| self.hr())
96 }
97
98 pub(crate) fn no_slider_head_acc(&self, lazer: bool) -> bool {
99 match self {
100 Self::Lazer(mods) => mods
101 .iter()
102 .find_map(|m| match m {
103 GameMod::ClassicOsu(cl) => Some(cl.no_slider_head_accuracy.unwrap_or(true)),
104 _ => None,
105 })
106 .unwrap_or(!lazer),
107 Self::Intermode(mods) => mods.contains(GameModIntermode::Classic) || !lazer,
108 Self::Legacy(_) => !lazer,
109 }
110 }
111
112 pub(crate) fn reflection(&self) -> Reflection {
113 match self {
114 Self::Lazer(mods) => mods
115 .iter()
116 .find_map(|m| match m {
117 GameMod::HardRockOsu(_) => Some(Reflection::Vertical),
118 GameMod::MirrorOsu(mr) => match mr.reflection.as_deref() {
119 None => Some(Reflection::Horizontal),
120 Some("1") => Some(Reflection::Vertical),
121 Some("2") => Some(Reflection::Both),
122 Some(_) => Some(Reflection::None),
123 },
124 GameMod::MirrorCatch(_) => Some(Reflection::Horizontal),
125 _ => None,
126 })
127 .unwrap_or(Reflection::None),
128 Self::Intermode(mods) => {
129 if mods.contains(GameModIntermode::HardRock) {
130 Reflection::Vertical
131 } else {
132 Reflection::None
133 }
134 }
135 Self::Legacy(mods) => {
136 if mods.contains(GameModsLegacy::HardRock) {
137 Reflection::Vertical
138 } else {
139 Reflection::None
140 }
141 }
142 }
143 }
144
145 pub(crate) fn mania_keys(&self) -> Option<f32> {
146 match self {
147 Self::Lazer(mods) => {
148 if mods.contains_intermode(GameModIntermode::OneKey) {
149 Some(1.0)
150 } else if mods.contains_intermode(GameModIntermode::TwoKeys) {
151 Some(2.0)
152 } else if mods.contains_intermode(GameModIntermode::ThreeKeys) {
153 Some(3.0)
154 } else if mods.contains_intermode(GameModIntermode::FourKeys) {
155 Some(4.0)
156 } else if mods.contains_intermode(GameModIntermode::FiveKeys) {
157 Some(5.0)
158 } else if mods.contains_intermode(GameModIntermode::SixKeys) {
159 Some(6.0)
160 } else if mods.contains_intermode(GameModIntermode::SevenKeys) {
161 Some(7.0)
162 } else if mods.contains_intermode(GameModIntermode::EightKeys) {
163 Some(8.0)
164 } else if mods.contains_intermode(GameModIntermode::NineKeys) {
165 Some(9.0)
166 } else if mods.contains_intermode(GameModIntermode::TenKeys) {
167 Some(10.0)
168 } else {
169 None
170 }
171 }
172 Self::Intermode(mods) => {
173 if mods.contains(GameModIntermode::OneKey) {
174 Some(1.0)
175 } else if mods.contains(GameModIntermode::TwoKeys) {
176 Some(2.0)
177 } else if mods.contains(GameModIntermode::ThreeKeys) {
178 Some(3.0)
179 } else if mods.contains(GameModIntermode::FourKeys) {
180 Some(4.0)
181 } else if mods.contains(GameModIntermode::FiveKeys) {
182 Some(5.0)
183 } else if mods.contains(GameModIntermode::SixKeys) {
184 Some(6.0)
185 } else if mods.contains(GameModIntermode::SevenKeys) {
186 Some(7.0)
187 } else if mods.contains(GameModIntermode::EightKeys) {
188 Some(8.0)
189 } else if mods.contains(GameModIntermode::NineKeys) {
190 Some(9.0)
191 } else if mods.contains(GameModIntermode::TenKeys) {
192 Some(10.0)
193 } else {
194 None
195 }
196 }
197 Self::Legacy(mods) => {
198 if mods.contains(GameModsLegacy::Key1) {
199 Some(1.0)
200 } else if mods.contains(GameModsLegacy::Key2) {
201 Some(2.0)
202 } else if mods.contains(GameModsLegacy::Key3) {
203 Some(3.0)
204 } else if mods.contains(GameModsLegacy::Key4) {
205 Some(4.0)
206 } else if mods.contains(GameModsLegacy::Key5) {
207 Some(5.0)
208 } else if mods.contains(GameModsLegacy::Key6) {
209 Some(6.0)
210 } else if mods.contains(GameModsLegacy::Key7) {
211 Some(7.0)
212 } else if mods.contains(GameModsLegacy::Key8) {
213 Some(8.0)
214 } else if mods.contains(GameModsLegacy::Key9) {
215 Some(9.0)
216 } else {
217 None
218 }
219 }
220 }
221 }
222
223 pub(crate) fn scroll_speed(&self) -> Option<f64> {
224 let Self::Lazer(mods) = self else { return None };
225
226 mods.iter()
227 .find_map(|m| match m {
228 GameMod::DifficultyAdjustTaiko(da) => Some(da.scroll_speed),
229 _ => None,
230 })
231 .flatten()
232 }
233
234 pub(crate) fn random_seed(&self) -> Option<i32> {
235 let Self::Lazer(mods) = self else { return None };
236
237 mods.iter()
238 .find_map(|m| match m {
239 GameMod::RandomTaiko(m) => m.seed,
242 GameMod::RandomMania(m) => m.seed,
243 _ => None,
244 })
245 .map(|seed| seed as i32)
246 }
247
248 pub(crate) fn attraction_strength(&self) -> Option<f64> {
249 let Self::Lazer(mods) = self else { return None };
250
251 mods.iter()
252 .find_map(|m| match m {
253 GameMod::MagnetisedOsu(mg) => Some(mg.attraction_strength),
254 _ => None,
255 })
256 .flatten()
257 }
258
259 pub(crate) fn deflate_start_scale(&self) -> Option<f64> {
260 let Self::Lazer(mods) = self else { return None };
261
262 mods.iter()
263 .find_map(|m| match m {
264 GameMod::DeflateOsu(df) => Some(df.start_scale),
265 _ => None,
266 })
267 .flatten()
268 }
269
270 pub(crate) fn hd_only_fade_approach_circles(&self) -> Option<bool> {
271 let Self::Lazer(mods) = self else { return None };
272
273 mods.iter()
274 .find_map(|m| match m {
275 GameMod::HiddenOsu(hd) => Some(hd.only_fade_approach_circles),
276 _ => None,
277 })
278 .flatten()
279 }
280}
281
282macro_rules! impl_has_mod {
283 ( $( $fn:ident: $is_legacy:tt $name:ident [ $s:literal ], )* ) => {
284 impl GameMods {
285 $(
286 #[doc = "Check whether [`GameMods`] contain `"]
288 #[doc = $s]
289 #[doc = "`."]
290 pub(crate) fn $fn(&self) -> bool {
291 match self {
292 Self::Lazer(mods) => {
293 mods.contains_intermode(GameModIntermode::$name)
294 },
295 Self::Intermode(mods) => {
296 mods.contains(GameModIntermode::$name)
297 },
298 Self::Legacy(_mods) => {
299 impl_has_mod!(LEGACY $is_legacy $name _mods)
300 },
301 }
302 }
303 )*
304 }
305 };
306
307 ( LEGACY + $name:ident $mods:ident ) => {
308 $mods.contains(GameModsLegacy::$name)
309 };
310
311 ( LEGACY - $name:ident $mods:ident ) => {
312 false
313 };
314}
315
316impl_has_mod! {
317 nf: + NoFail ["NoFail"],
318 ez: + Easy ["Easy"],
319 td: + TouchDevice ["TouchDevice"],
320 hd: + Hidden ["Hidden"],
321 hr: + HardRock ["HardRock"],
322 rx: + Relax ["Relax"],
323 fl: + Flashlight ["Flashlight"],
324 so: + SpunOut ["SpunOut"],
325 ap: + Autopilot ["Autopilot"],
326 sv2: + ScoreV2 ["ScoreV2"],
327 bl: - Blinds ["Blinds"],
328 cl: - Classic ["Classic"],
329 invert: - Invert ["Invert"],
330 ho: - HoldOff ["HoldOff"],
331 tc: - Traceable ["Traceable"],
332}
333
334impl Default for GameMods {
335 fn default() -> Self {
336 Self::DEFAULT
337 }
338}
339
340impl From<GameModsLazer> for GameMods {
341 fn from(mods: GameModsLazer) -> Self {
342 Self::Lazer(mods)
343 }
344}
345
346impl From<GameModsIntermode> for GameMods {
347 fn from(mods: GameModsIntermode) -> Self {
348 Self::Intermode(mods)
349 }
350}
351
352impl From<&GameModsIntermode> for GameMods {
353 fn from(mods: &GameModsIntermode) -> Self {
354 match mods.checked_bits() {
357 Some(bits) => bits.into(),
358 None => mods.to_owned().into(),
359 }
360 }
361}
362
363impl From<GameModsLegacy> for GameMods {
364 fn from(mods: GameModsLegacy) -> Self {
365 Self::Legacy(mods)
366 }
367}
368
369impl From<u32> for GameMods {
370 fn from(bits: u32) -> Self {
371 GameModsLegacy::from_bits(bits).into()
372 }
373}
374
375#[derive(Copy, Clone, Debug, PartialEq, Eq)]
376pub(crate) enum Reflection {
377 None,
378 Vertical,
379 Horizontal,
380 Both,
381}