use std::{io, path::Path, str::FromStr};
use crate::{
decode::{DecodeBeatmap, DecodeState},
section::{
colors::{Color, Colors, ColorsState, CustomColor, ParseColorsError},
editor::{Editor, EditorState, ParseEditorError},
events::BreakPeriod,
general::{CountdownType, GameMode},
hit_objects::{
hit_samples::SampleBank, HitObject, HitObjects, HitObjectsState, ParseHitObjectsError,
},
metadata::{Metadata, MetadataState, ParseMetadataError},
timing_points::ControlPoints,
},
LATEST_FORMAT_VERSION,
};
#[derive(Clone, Debug, PartialEq)]
pub struct Beatmap {
pub format_version: i32,
pub audio_file: String,
pub audio_lead_in: f64,
pub preview_time: i32,
pub default_sample_bank: SampleBank,
pub default_sample_volume: i32,
pub stack_leniency: f32,
pub mode: GameMode,
pub letterbox_in_breaks: bool,
pub special_style: bool,
pub widescreen_storyboard: bool,
pub epilepsy_warning: bool,
pub samples_match_playback_rate: bool,
pub countdown: CountdownType,
pub countdown_offset: i32,
pub bookmarks: Vec<i32>,
pub distance_spacing: f64,
pub beat_divisor: i32,
pub grid_size: i32,
pub timeline_zoom: f64,
pub title: String,
pub title_unicode: String,
pub artist: String,
pub artist_unicode: String,
pub creator: String,
pub version: String,
pub source: String,
pub tags: String,
pub beatmap_id: i32,
pub beatmap_set_id: i32,
pub hp_drain_rate: f32,
pub circle_size: f32,
pub overall_difficulty: f32,
pub approach_rate: f32,
pub slider_multiplier: f64,
pub slider_tick_rate: f64,
pub background_file: String,
pub breaks: Vec<BreakPeriod>,
pub control_points: ControlPoints,
pub custom_combo_colors: Vec<Color>,
pub custom_colors: Vec<CustomColor>,
pub hit_objects: Vec<HitObject>,
}
impl Beatmap {
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, io::Error> {
crate::from_path(path)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, io::Error> {
crate::from_bytes(bytes)
}
}
impl FromStr for Beatmap {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
crate::from_str(s)
}
}
impl Default for Beatmap {
fn default() -> Self {
let editor = Editor::default();
let metadata = Metadata::default();
let colors = Colors::default();
let hit_objects = HitObjects::default();
Self {
format_version: LATEST_FORMAT_VERSION,
audio_file: hit_objects.audio_file,
audio_lead_in: hit_objects.audio_lead_in,
preview_time: hit_objects.preview_time,
default_sample_bank: hit_objects.default_sample_bank,
default_sample_volume: hit_objects.default_sample_volume,
stack_leniency: hit_objects.stack_leniency,
mode: hit_objects.mode,
letterbox_in_breaks: hit_objects.letterbox_in_breaks,
special_style: hit_objects.special_style,
widescreen_storyboard: hit_objects.widescreen_storyboard,
epilepsy_warning: hit_objects.epilepsy_warning,
samples_match_playback_rate: hit_objects.samples_match_playback_rate,
countdown: hit_objects.countdown,
countdown_offset: hit_objects.countdown_offset,
bookmarks: editor.bookmarks,
distance_spacing: editor.distance_spacing,
beat_divisor: editor.beat_divisor,
grid_size: editor.grid_size,
timeline_zoom: editor.timeline_zoom,
title: metadata.title,
title_unicode: metadata.title_unicode,
artist: metadata.artist,
artist_unicode: metadata.artist_unicode,
creator: metadata.creator,
version: metadata.version,
source: metadata.source,
tags: metadata.tags,
beatmap_id: metadata.beatmap_id,
beatmap_set_id: metadata.beatmap_set_id,
hp_drain_rate: hit_objects.hp_drain_rate,
circle_size: hit_objects.circle_size,
overall_difficulty: hit_objects.overall_difficulty,
approach_rate: hit_objects.approach_rate,
slider_multiplier: hit_objects.slider_multiplier,
slider_tick_rate: hit_objects.slider_tick_rate,
background_file: hit_objects.background_file,
breaks: hit_objects.breaks,
control_points: hit_objects.control_points,
custom_combo_colors: colors.custom_combo_colors,
custom_colors: colors.custom_colors,
hit_objects: hit_objects.hit_objects,
}
}
}
thiserror! {
#[derive(Debug)]
pub enum ParseBeatmapError {
#[error("failed to parse colors section")]
Colors(#[from] ParseColorsError),
#[error("failed to parse editor section")]
Editor(#[from] ParseEditorError),
#[error("failed to parse hit objects")]
HitOjects(#[from] ParseHitObjectsError),
#[error("failed to parse metadata section")]
Metadata(#[from] ParseMetadataError),
}
}
pub struct BeatmapState {
pub version: i32,
pub editor: EditorState,
pub metadata: MetadataState,
pub colors: ColorsState,
pub hit_objects: HitObjectsState,
}
impl DecodeState for BeatmapState {
fn create(version: i32) -> Self {
Self {
version,
editor: EditorState::create(version),
metadata: MetadataState::create(version),
colors: ColorsState::create(version),
hit_objects: HitObjectsState::create(version),
}
}
}
impl From<BeatmapState> for Beatmap {
#[allow(clippy::useless_conversion)]
fn from(state: BeatmapState) -> Self {
let editor: Editor = state.editor.into();
let metadata: Metadata = state.metadata.into();
let colors: Colors = state.colors.into();
let hit_objects: HitObjects = state.hit_objects.into();
Beatmap {
format_version: state.version,
audio_file: hit_objects.audio_file,
audio_lead_in: hit_objects.audio_lead_in,
preview_time: hit_objects.preview_time,
default_sample_bank: hit_objects.default_sample_bank,
default_sample_volume: hit_objects.default_sample_volume,
stack_leniency: hit_objects.stack_leniency,
mode: hit_objects.mode,
letterbox_in_breaks: hit_objects.letterbox_in_breaks,
special_style: hit_objects.special_style,
widescreen_storyboard: hit_objects.widescreen_storyboard,
epilepsy_warning: hit_objects.epilepsy_warning,
samples_match_playback_rate: hit_objects.samples_match_playback_rate,
countdown: hit_objects.countdown,
countdown_offset: hit_objects.countdown_offset,
bookmarks: editor.bookmarks,
distance_spacing: editor.distance_spacing,
beat_divisor: editor.beat_divisor,
grid_size: editor.grid_size,
timeline_zoom: editor.timeline_zoom,
title: metadata.title,
title_unicode: metadata.title_unicode,
artist: metadata.artist,
artist_unicode: metadata.artist_unicode,
creator: metadata.creator,
version: metadata.version,
source: metadata.source,
tags: metadata.tags,
beatmap_id: metadata.beatmap_id,
beatmap_set_id: metadata.beatmap_set_id,
hp_drain_rate: hit_objects.hp_drain_rate,
circle_size: hit_objects.circle_size,
overall_difficulty: hit_objects.overall_difficulty,
approach_rate: hit_objects.approach_rate,
slider_multiplier: hit_objects.slider_multiplier,
slider_tick_rate: hit_objects.slider_tick_rate,
background_file: hit_objects.background_file,
breaks: hit_objects.breaks,
control_points: hit_objects.control_points,
custom_combo_colors: colors.custom_combo_colors,
custom_colors: colors.custom_colors,
hit_objects: hit_objects.hit_objects,
}
}
}
impl DecodeBeatmap for Beatmap {
type Error = ParseBeatmapError;
type State = BeatmapState;
fn parse_general(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
HitObjects::parse_general(&mut state.hit_objects, line)
.map_err(ParseBeatmapError::HitOjects)
}
fn parse_editor(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
Editor::parse_editor(&mut state.editor, line).map_err(ParseBeatmapError::Editor)
}
fn parse_metadata(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
Metadata::parse_metadata(&mut state.metadata, line).map_err(ParseBeatmapError::Metadata)
}
fn parse_difficulty(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
HitObjects::parse_difficulty(&mut state.hit_objects, line)
.map_err(ParseBeatmapError::HitOjects)
}
fn parse_events(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
HitObjects::parse_events(&mut state.hit_objects, line).map_err(ParseBeatmapError::HitOjects)
}
fn parse_timing_points(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
HitObjects::parse_timing_points(&mut state.hit_objects, line)
.map_err(ParseBeatmapError::HitOjects)
}
fn parse_colors(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
Colors::parse_colors(&mut state.colors, line).map_err(ParseBeatmapError::Colors)
}
fn parse_hit_objects(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
HitObjects::parse_hit_objects(&mut state.hit_objects, line)
.map_err(ParseBeatmapError::HitOjects)
}
fn parse_variables(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
Ok(())
}
fn parse_catch_the_beat(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
Ok(())
}
fn parse_mania(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
Ok(())
}
}