use serde::{Deserialize, Serialize}; use std::fmt; bitflags::bitflags! { /// The mods available to osu! #[derive(std::default::Default, Serialize, Deserialize)] pub struct Mods: u64 { const NOMOD = 0; const NF = 1 << 0; const EZ = 1 << 1; const TD = 1 << 2; const HD = 1 << 3; const HR = 1 << 4; const SD = 1 << 5; const DT = 1 << 6; const RX = 1 << 7; const HT = 1 << 8; const NC = 1 << 9; const FL = 1 << 10; const AT = 1 << 11; const SO = 1 << 12; const AP = 1 << 13; const PF = 1 << 14; const KEY4 = 1 << 15; /* TODO: what are these abbreviated to? */ const KEY5 = 1 << 16; const KEY6 = 1 << 17; const KEY7 = 1 << 18; const KEY8 = 1 << 19; const FADEIN = 1 << 20; const RANDOM = 1 << 21; const CINEMA = 1 << 22; const TARGET = 1 << 23; const KEY9 = 1 << 24; const KEYCOOP = 1 << 25; const KEY1 = 1 << 26; const KEY3 = 1 << 27; const KEY2 = 1 << 28; const SCOREV2 = 1 << 29; const TOUCH_DEVICE = Self::TD.bits; const NOVIDEO = Self::TD.bits; /* never forget */ const SPEED_CHANGING = Self::DT.bits | Self::HT.bits | Self::NC.bits; const MAP_CHANGING = Self::HR.bits | Self::EZ.bits | Self::SPEED_CHANGING.bits; } } const MODS_WITH_NAMES: &[(Mods, &'static str)] = &[ (Mods::NF, "NF"), (Mods::EZ, "EZ"), (Mods::TD, "TD"), (Mods::HD, "HD"), (Mods::HR, "HR"), (Mods::SD, "SD"), (Mods::DT, "DT"), (Mods::RX, "RX"), (Mods::HT, "HT"), (Mods::NC, "NC"), (Mods::FL, "FL"), (Mods::AT, "AT"), (Mods::SO, "SO"), (Mods::AP, "AP"), (Mods::PF, "PF"), (Mods::KEY1, "1K"), (Mods::KEY2, "2K"), (Mods::KEY3, "3K"), (Mods::KEY4, "4K"), (Mods::KEY5, "5K"), (Mods::KEY6, "6K"), (Mods::KEY7, "7K"), (Mods::KEY8, "8K"), (Mods::KEY9, "9K"), ]; impl std::str::FromStr for Mods { type Err = String; fn from_str(mut s: &str) -> Result { let mut res = Self::default(); while s.len() >= 2 { let (m, nw) = s.split_at(2); s = nw; match &m.to_uppercase()[..] { "NF" => res |= Mods::NF, "EZ" => res |= Mods::EZ, "TD" => res |= Mods::TD, "HD" => res |= Mods::HD, "HR" => res |= Mods::HR, "SD" => res |= Mods::SD, "DT" => res |= Mods::DT, "RX" => res |= Mods::RX, "HT" => res |= Mods::HT, "NC" => res |= Mods::NC, "FL" => res |= Mods::FL, "AT" => res |= Mods::AT, "SO" => res |= Mods::SO, "AP" => res |= Mods::AP, "PF" => res |= Mods::PF, "1K" => res |= Mods::KEY1, "2K" => res |= Mods::KEY2, "3K" => res |= Mods::KEY3, "4K" => res |= Mods::KEY4, "5K" => res |= Mods::KEY5, "6K" => res |= Mods::KEY6, "7K" => res |= Mods::KEY7, "8K" => res |= Mods::KEY8, "9K" => res |= Mods::KEY9, v => return Err(format!("{} is not a valid mod", v)), } } if s.len() > 0 { Err("String of odd length is not a mod string".to_owned()) } else { Ok(res) } } } impl fmt::Display for Mods { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_empty() { // Return an empty string return Ok(()); } write!(f, "+")?; for p in MODS_WITH_NAMES.iter() { if self.contains(p.0) { write!(f, "{}", p.1)?; } } Ok(()) } } impl From for oppai_rs::Mods { fn from(m: Mods) -> Self { oppai_rs::Mods::from_bits_truncate(m.bits() as i32) } }