use serde::{Deserialize, Serialize};
use serde_repr::*;
use std::{fmt::Display, fs::File, path::Path, str::FromStr};
#[derive(Debug)]
pub enum QuaError {
IoError(std::io::Error),
SerdeError(serde_yaml::Error),
}
#[derive(Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "PascalCase")]
#[serde(default)]
pub struct Qua {
pub audio_file: String,
pub song_preview_time: i32,
pub background_file: String,
pub banner_file: String,
pub map_id: i32,
pub map_set_id: i32,
#[serde(rename = "Mode")]
pub game_mode: GameMode,
pub title: String,
pub artist: String,
pub source: String,
pub tags: String,
pub creator: String,
pub difficulty_name: String,
pub description: String,
pub genre: String,
pub bpm_does_not_affect_scroll_velocity: bool,
pub initial_scroll_velocity: f32,
pub has_scratch_key: bool,
pub editor_layers: Vec<EditorLayerInfo>,
pub custom_audio_samples: Vec<CustomAudioSampleInfo>,
pub sound_effects: Vec<SoundEffectInfo>,
pub timing_points: Vec<TimingPointInfo>,
pub slider_velocities: Vec<ScrollVelocityInfo>,
pub hit_objects: Vec<HitObjectInfo>,
}
impl Qua {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Qua, QuaError> {
let path = Path::new(path.as_ref());
let file = File::open(path).map_err(QuaError::IoError)?;
let qua = Qua::from_reader(file)?;
Ok(qua)
}
pub fn from_reader<R>(reader: R) -> Result<Qua, QuaError>
where
R: std::io::Read,
{
let qua: Qua = serde_yaml::from_reader(reader).map_err(QuaError::SerdeError)?;
Ok(qua)
}
pub fn from_slice(bytes: &[u8]) -> Result<Qua, QuaError> {
let qua: Qua = serde_yaml::from_slice(bytes).map_err(QuaError::SerdeError)?;
Ok(qua)
}
pub fn to_writer<W>(&self, writer: W) -> Result<(), QuaError>
where
W: std::io::Write,
{
serde_yaml::to_writer(writer, self).map_err(QuaError::SerdeError)?;
Ok(())
}
}
impl FromStr for Qua {
type Err = QuaError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let qua: Qua = serde_yaml::from_str(s).map_err(QuaError::SerdeError)?;
Ok(qua)
}
}
impl Display for Qua {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = serde_yaml::to_string(self).map_err(|_| std::fmt::Error)?;
write!(f, "{}", s)
}
}
impl Default for Qua {
fn default() -> Self {
Self {
audio_file: "".to_string(),
song_preview_time: 0,
background_file: "".to_string(),
banner_file: "".to_string(),
map_id: -1,
map_set_id: -1,
game_mode: GameMode::Keys4,
title: "".to_string(),
artist: "".to_string(),
source: "".to_string(),
tags: "".to_string(),
creator: "".to_string(),
difficulty_name: "".to_string(),
description: "".to_string(),
genre: "".to_string(),
bpm_does_not_affect_scroll_velocity: false,
initial_scroll_velocity: 1.0,
has_scratch_key: false,
editor_layers: Vec::new(),
custom_audio_samples: Vec::new(),
sound_effects: Vec::new(),
timing_points: Vec::new(),
slider_velocities: Vec::new(),
hit_objects: Vec::new(),
}
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub enum GameMode {
Keys4 = 1,
Keys7 = 2,
}
impl GameMode {
pub fn from_key_count(key_count: i32) -> Option<GameMode> {
match key_count {
4 => Some(GameMode::Keys4),
7 => Some(GameMode::Keys7),
_ => None,
}
}
pub fn get_key_count(self) -> i32 {
match self {
GameMode::Keys4 => 4,
GameMode::Keys7 => 7,
}
}
}
#[derive(Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "PascalCase")]
#[serde(default)]
pub struct EditorLayerInfo {
pub name: String,
pub hidden: bool,
pub color_rgb: String,
}
impl Default for EditorLayerInfo {
fn default() -> Self {
Self {
name: "".to_string(),
hidden: false,
color_rgb: "255,255,255".to_string(),
}
}
}
#[derive(Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "PascalCase")]
#[serde(default)]
pub struct CustomAudioSampleInfo {
pub path: String,
pub unaffected_by_rate: bool,
}
impl Default for CustomAudioSampleInfo {
fn default() -> Self {
Self {
path: "".to_string(),
unaffected_by_rate: false,
}
}
}
#[derive(Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "PascalCase")]
#[serde(default)]
pub struct SoundEffectInfo {
pub start_time: f32,
pub sample: i32,
pub volume: i32,
}
impl Default for SoundEffectInfo {
fn default() -> Self {
Self {
start_time: 0.0,
sample: 0,
volume: 0,
}
}
}
#[derive(Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "PascalCase")]
#[serde(default)]
pub struct TimingPointInfo {
pub start_time: f32,
pub bpm: f32,
pub signature: TimeSignature,
pub hidden: bool,
}
impl Default for TimingPointInfo {
fn default() -> Self {
Self {
start_time: 0.0,
bpm: 0.0,
signature: TimeSignature::Quadruple,
hidden: false,
}
}
}
#[derive(Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct ScrollVelocityInfo {
pub start_time: i32,
pub multiplier: f32,
}
#[derive(Serialize_repr, Deserialize_repr, Clone, PartialEq)]
#[repr(u8)]
pub enum TimeSignature {
Quadruple = 4,
Triple = 3,
}
#[derive(Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "PascalCase")]
#[serde(default)]
pub struct HitObjectInfo {
pub start_time: i32,
pub lane: i32,
pub end_time: i32,
pub hit_sound: u8,
pub key_sounds: Vec<KeySoundInfo>,
pub editor_layer: i32,
}
impl Default for HitObjectInfo {
fn default() -> Self {
Self {
start_time: 0,
lane: 1,
end_time: 0,
hit_sound: 0,
key_sounds: Vec::new(),
editor_layer: 0,
}
}
}
#[derive(Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "PascalCase")]
#[serde(default)]
pub struct KeySoundInfo {
pub sample: i32,
pub volume: i32,
}
impl Default for KeySoundInfo {
fn default() -> Self {
Self {
sample: 0,
volume: 100,
}
}
}