use std::{io, path::Path, str::FromStr};
use rosu_map::{
section::{general::GameMode, hit_objects::hit_samples::HitSoundType},
LATEST_FORMAT_VERSION,
};
pub use rosu_map::section::events::BreakPeriod;
use crate::{
catch::Catch, mania::Mania, osu::Osu, taiko::Taiko, Difficulty, GradualDifficulty,
GradualPerformance, Performance,
};
pub use self::{
attributes::{BeatmapAttributes, BeatmapAttributesBuilder, HitWindows},
converted::Converted,
decode::{BeatmapState, ParseBeatmapError},
};
use super::{
control_point::{
difficulty_point_at, effect_point_at, timing_point_at, DifficultyPoint, EffectPoint,
TimingPoint,
},
hit_object::HitObject,
mode::{ConvertStatus, IGameMode},
};
mod attributes;
mod bpm;
mod converted;
mod decode;
#[derive(Clone, Debug, PartialEq)]
pub struct Beatmap {
pub version: i32,
pub is_convert: bool,
pub stack_leniency: f32,
pub mode: GameMode,
pub ar: f32,
pub cs: f32,
pub hp: f32,
pub od: f32,
pub slider_multiplier: f64,
pub slider_tick_rate: f64,
pub breaks: Vec<BreakPeriod>,
pub timing_points: Vec<TimingPoint>,
pub difficulty_points: Vec<DifficultyPoint>,
pub effect_points: Vec<EffectPoint>,
pub hit_objects: Vec<HitObject>,
pub hit_sounds: Vec<HitSoundType>,
}
impl Beatmap {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
rosu_map::from_path(path)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, io::Error> {
rosu_map::from_bytes(bytes)
}
pub const fn attributes(&self) -> BeatmapAttributesBuilder {
BeatmapAttributesBuilder::new().map(self)
}
pub fn bpm(&self) -> f64 {
bpm::bpm(self.hit_objects.last(), &self.timing_points)
}
pub fn performance(&self) -> Performance<'_> {
Performance::new(self)
}
pub fn gradual_difficulty(&self, difficulty: Difficulty) -> GradualDifficulty {
GradualDifficulty::new(difficulty, self)
}
pub fn gradual_performance(&self, difficulty: Difficulty) -> GradualPerformance {
GradualPerformance::new(difficulty, self)
}
pub(crate) fn timing_point_at(&self, time: f64) -> Option<&TimingPoint> {
timing_point_at(&self.timing_points, time)
}
pub(crate) fn difficulty_point_at(&self, time: f64) -> Option<&DifficultyPoint> {
difficulty_point_at(&self.difficulty_points, time)
}
pub(crate) fn effect_point_at(&self, time: f64) -> Option<&EffectPoint> {
effect_point_at(&self.effect_points, time)
}
pub fn total_break_time(&self) -> f64 {
self.breaks.iter().map(BreakPeriod::duration).sum()
}
pub fn convert_in_place(&mut self, mode: GameMode) -> ConvertStatus {
match mode {
GameMode::Osu => Osu::try_convert(self),
GameMode::Taiko => Taiko::try_convert(self),
GameMode::Catch => Catch::try_convert(self),
GameMode::Mania => Mania::try_convert(self),
}
}
pub fn try_as_converted<M: IGameMode>(&self) -> Option<Converted<'_, M>> {
Converted::try_from_ref(self)
}
pub fn unchecked_as_converted<M: IGameMode>(&self) -> Converted<'_, M> {
Converted::unchecked_from_ref(self)
}
pub fn try_as_converted_mut<M: IGameMode>(&mut self) -> Option<Converted<'_, M>> {
Converted::try_from_mut(self)
}
pub fn unchecked_as_converted_mut<M: IGameMode>(&mut self) -> Converted<'_, M> {
Converted::unchecked_from_mut(self)
}
#[allow(clippy::result_large_err)]
pub fn try_into_converted<'a, M: IGameMode>(self) -> Result<Converted<'a, M>, Self> {
Converted::try_from_owned(self)
}
pub fn unchecked_into_converted<'a, M: IGameMode>(self) -> Converted<'a, M> {
Converted::unchecked_from_owned(self)
}
}
impl FromStr for Beatmap {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
rosu_map::from_str(s)
}
}
const DEFAULT_SLIDER_LENIENCY: f32 = 0.7;
impl Default for Beatmap {
fn default() -> Self {
Self {
version: LATEST_FORMAT_VERSION,
is_convert: false,
stack_leniency: DEFAULT_SLIDER_LENIENCY,
mode: GameMode::default(),
ar: 5.0,
cs: 5.0,
hp: 5.0,
od: 5.0,
slider_multiplier: 1.4,
slider_tick_rate: 1.0,
breaks: Vec::default(),
timing_points: Vec::default(),
difficulty_points: Vec::default(),
effect_points: Vec::default(),
hit_objects: Vec::default(),
hit_sounds: Vec::default(),
}
}
}