use std::fmt::{Debug, Formatter, Result as FmtResult};
use rosu_mods::{
generated_mods::{
DifficultyAdjustCatch, DifficultyAdjustMania, DifficultyAdjustOsu, DifficultyAdjustTaiko,
},
GameMod, GameModIntermode, GameMods as GameModsLazer, GameModsIntermode, GameModsLegacy,
};
#[derive(Clone, PartialEq)]
pub struct GameMods {
inner: GameModsInner,
}
impl Debug for GameMods {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self.inner {
GameModsInner::Lazer(ref mods) => Debug::fmt(mods, f),
GameModsInner::Intermode(ref mods) => Debug::fmt(mods, f),
GameModsInner::Legacy(ref mods) => Debug::fmt(mods, f),
}
}
}
#[derive(Clone, PartialEq)]
enum GameModsInner {
Lazer(GameModsLazer),
Intermode(GameModsIntermode),
Legacy(GameModsLegacy),
}
impl GameMods {
pub(crate) const DEFAULT: Self = Self {
inner: GameModsInner::Legacy(GameModsLegacy::NoMod),
};
pub(crate) fn clock_rate(&self) -> f32 {
match self.inner {
GameModsInner::Lazer(ref mods) => mods.clock_rate().unwrap_or(1.0),
GameModsInner::Intermode(ref mods) => mods.legacy_clock_rate(),
GameModsInner::Legacy(mods) => mods.clock_rate(),
}
}
pub(crate) fn od_ar_hp_multiplier(&self) -> f64 {
if self.hr() {
1.4
} else if self.ez() {
0.5
} else {
1.0
}
}
pub(crate) fn hardrock_offsets(&self) -> bool {
fn custom_hardrock_offsets(mods: &GameMods) -> Option<bool> {
match mods.inner {
GameModsInner::Lazer(ref mods) => mods.iter().find_map(|gamemod| match gamemod {
GameMod::DifficultyAdjustCatch(DifficultyAdjustCatch {
hard_rock_offsets,
..
}) => *hard_rock_offsets,
_ => None,
}),
GameModsInner::Intermode(_) | GameModsInner::Legacy(_) => None,
}
}
custom_hardrock_offsets(self).unwrap_or_else(|| self.hr())
}
}
macro_rules! impl_map_attr {
( $( $fn:ident: $field:ident [ $( $mode:ident ),* ] [$s:literal] ;)* ) => {
impl GameMods {
$(
#[doc = "Check whether the mods specify a custom "]
#[doc = $s]
#[doc = "value."]
pub(crate) fn $fn(&self) -> Option<f32> {
match self.inner {
GameModsInner::Lazer(ref mods) => mods.iter().find_map(|gamemod| match gamemod {
$( impl_map_attr!( @ $mode $field) => *$field, )*
_ => None,
}),
GameModsInner::Intermode(_) | GameModsInner::Legacy(_) => None,
}
}
)*
}
};
( @ Osu $field:ident) => { GameMod::DifficultyAdjustOsu(DifficultyAdjustOsu { $field, .. }) };
( @ Taiko $field:ident) => { GameMod::DifficultyAdjustTaiko(DifficultyAdjustTaiko { $field, .. }) };
( @ Catch $field:ident) => { GameMod::DifficultyAdjustCatch(DifficultyAdjustCatch { $field, .. }) };
( @ Mania $field:ident) => { GameMod::DifficultyAdjustMania(DifficultyAdjustMania { $field, .. }) };
}
impl_map_attr! {
ar: approach_rate [Osu, Catch] ["ar"];
cs: circle_size [Osu, Catch] ["cs"];
hp: drain_rate [Osu, Taiko, Catch, Mania] ["hp"];
od: overall_difficulty [Osu, Taiko, Catch, Mania] ["od"];
}
macro_rules! impl_has_mod {
( $( $fn:ident: $sign:tt $name:ident [ $s:literal ], )* ) => {
impl GameMods {
$(
#[doc = "Check whether [`GameMods`] contain `"]
#[doc = $s]
#[doc = "`."]
pub(crate) fn $fn(&self) -> bool {
match self.inner {
GameModsInner::Lazer(ref mods) => {
mods.contains_intermode(GameModIntermode::$name)
},
GameModsInner::Intermode(ref mods) => {
mods.contains(GameModIntermode::$name)
},
GameModsInner::Legacy(_mods) => {
impl_has_mod!(LEGACY $sign $name _mods)
},
}
}
)*
}
};
( LEGACY + $name:ident $mods:ident ) => {
$mods.contains(GameModsLegacy::$name)
};
( LEGACY - $name:ident $mods:ident ) => {
false
};
}
impl_has_mod! {
nf: + NoFail ["NoFail"],
ez: + Easy ["Easy"],
td: + TouchDevice ["TouchDevice"],
hd: + Hidden ["Hidden"],
hr: + HardRock ["HardRock"],
rx: + Relax ["Relax"],
fl: + Flashlight ["Flashlight"],
so: + SpunOut ["SpunOut"],
bl: - Blinds ["Blinds"],
}
impl Default for GameMods {
fn default() -> Self {
Self::DEFAULT
}
}
impl From<GameModsLazer> for GameMods {
fn from(mods: GameModsLazer) -> Self {
Self {
inner: GameModsInner::Lazer(mods),
}
}
}
impl From<GameModsIntermode> for GameMods {
fn from(mods: GameModsIntermode) -> Self {
Self {
inner: GameModsInner::Intermode(mods),
}
}
}
impl From<&GameModsIntermode> for GameMods {
fn from(mods: &GameModsIntermode) -> Self {
match mods.checked_bits() {
Some(bits) => bits.into(),
None => mods.to_owned().into(),
}
}
}
impl From<GameModsLegacy> for GameMods {
fn from(mods: GameModsLegacy) -> Self {
Self {
inner: GameModsInner::Legacy(mods),
}
}
}
impl From<u32> for GameMods {
fn from(bits: u32) -> Self {
GameModsLegacy::from_bits(bits).into()
}
}