expose BeatmapExt methods for mode-specific objects (#20)
* expose osu hitobjects * added pub method to create osu objects * expose mania objects * added some derived traits to public types * expose taiko objects * expose catch objects * add documentation * fix documentation
This commit is contained in:
+116
-3
@@ -1,7 +1,16 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
catch::{
|
||||||
|
calculate_catch_width, CatchDifficultyAttributes, CatchObject, FruitOrJuice, FruitParams,
|
||||||
|
ALLOWED_CATCH_RANGE,
|
||||||
|
},
|
||||||
|
curve::CurveBuffers,
|
||||||
|
mania::{ManiaObject, ObjectParameters},
|
||||||
|
osu::{OsuDifficultyAttributes, OsuObject, ScalingFactor},
|
||||||
|
taiko::{IntoTaikoObjectIter, TaikoObject},
|
||||||
|
util::FloatExt,
|
||||||
AnyPP, AnyStars, Beatmap, CatchPP, CatchStars, GameMode, GradualDifficultyAttributes,
|
AnyPP, AnyStars, Beatmap, CatchPP, CatchStars, GameMode, GradualDifficultyAttributes,
|
||||||
GradualPerformanceAttributes, ManiaPP, ManiaStars, OsuPP, OsuStars, PerformanceAttributes,
|
GradualPerformanceAttributes, ManiaPP, ManiaStars, Mods, OsuPP, OsuStars,
|
||||||
Strains, TaikoPP, TaikoStars,
|
PerformanceAttributes, Strains, TaikoPP, TaikoStars,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Provides some additional methods on [`Beatmap`].
|
/// Provides some additional methods on [`Beatmap`].
|
||||||
@@ -26,7 +35,7 @@ pub trait BeatmapExt {
|
|||||||
/// Suitable to plot the difficulty of a map over time.
|
/// Suitable to plot the difficulty of a map over time.
|
||||||
fn strains(&self, mods: u32) -> Strains;
|
fn strains(&self, mods: u32) -> Strains;
|
||||||
|
|
||||||
/// Return an iterator that gives you the [`DifficultyAttributes`] after each hit object.
|
/// Return an iterator that gives you the [`DifficultyAttributes`](crate::DifficultyAttributes) after each hit object.
|
||||||
///
|
///
|
||||||
/// Suitable to efficiently get the map's star rating after multiple different locations.
|
/// Suitable to efficiently get the map's star rating after multiple different locations.
|
||||||
fn gradual_difficulty(&self, mods: u32) -> GradualDifficultyAttributes<'_>;
|
fn gradual_difficulty(&self, mods: u32) -> GradualDifficultyAttributes<'_>;
|
||||||
@@ -36,6 +45,29 @@ pub trait BeatmapExt {
|
|||||||
/// Suitable to efficiently get a score's performance after multiple different locations,
|
/// Suitable to efficiently get a score's performance after multiple different locations,
|
||||||
/// i.e. live update a score's pp.
|
/// i.e. live update a score's pp.
|
||||||
fn gradual_performance(&self, mods: u32) -> GradualPerformanceAttributes<'_>;
|
fn gradual_performance(&self, mods: u32) -> GradualPerformanceAttributes<'_>;
|
||||||
|
|
||||||
|
/// Process each [`HitObject`](crate::parse::HitObject) into a an osu!-specific [`OsuObject`],
|
||||||
|
/// just like the difficulty calculation does.
|
||||||
|
fn osu_hitobjects(&self, mods: u32) -> Vec<OsuObject>;
|
||||||
|
|
||||||
|
/// Process each [`HitObject`](crate::parse::HitObject) into a an osu!taiko-specific [`TaikoObject`],
|
||||||
|
/// just like the difficulty calculation does.
|
||||||
|
///
|
||||||
|
/// Clockrate is *not* considered.
|
||||||
|
fn taiko_hitobjects(&self) -> Vec<TaikoObject>;
|
||||||
|
|
||||||
|
/// Process each [`HitObject`](crate::parse::HitObject) into a an osu!ctb-specific [`CatchObject`],
|
||||||
|
/// just like the difficulty calculation does.
|
||||||
|
///
|
||||||
|
/// A [`CatchObject`] is either a fruit or a droplet which means
|
||||||
|
/// tiny droplets and bananas are not included.
|
||||||
|
fn catch_hitobjects(&self, mods: u32) -> Vec<CatchObject>;
|
||||||
|
|
||||||
|
/// Process each [`HitObject`](crate::parse::HitObject) into a an osu!mania-specific [`ManiaObject`],
|
||||||
|
/// just like the difficulty calculation does.
|
||||||
|
///
|
||||||
|
/// Clockrate is *not* considered.
|
||||||
|
fn mania_hitobjects(&self) -> Vec<ManiaObject>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BeatmapExt for Beatmap {
|
impl BeatmapExt for Beatmap {
|
||||||
@@ -89,4 +121,85 @@ impl BeatmapExt for Beatmap {
|
|||||||
fn gradual_performance(&self, mods: u32) -> GradualPerformanceAttributes<'_> {
|
fn gradual_performance(&self, mods: u32) -> GradualPerformanceAttributes<'_> {
|
||||||
GradualPerformanceAttributes::new(self, mods)
|
GradualPerformanceAttributes::new(self, mods)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn osu_hitobjects(&self, mods: u32) -> Vec<OsuObject> {
|
||||||
|
let attrs = self.attributes().mods(mods).build();
|
||||||
|
let scaling_factor = ScalingFactor::new(attrs.cs);
|
||||||
|
let hr = mods.hr();
|
||||||
|
let time_preempt = (attrs.hit_windows.ar * attrs.clock_rate) as f32 as f64;
|
||||||
|
let mut attrs = OsuDifficultyAttributes::default();
|
||||||
|
|
||||||
|
crate::osu::create_osu_objects(
|
||||||
|
self,
|
||||||
|
&mut attrs,
|
||||||
|
&scaling_factor,
|
||||||
|
usize::MAX,
|
||||||
|
hr,
|
||||||
|
time_preempt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn taiko_hitobjects(&self) -> Vec<TaikoObject> {
|
||||||
|
let map = self.convert_mode(GameMode::Taiko);
|
||||||
|
|
||||||
|
map.taiko_objects()
|
||||||
|
.map(|(h, start_time)| TaikoObject {
|
||||||
|
start_time,
|
||||||
|
is_hit: h.is_hit,
|
||||||
|
is_rim: h.is_rim,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn catch_hitobjects(&self, mods: u32) -> Vec<CatchObject> {
|
||||||
|
let attrs = self.attributes().mods(mods).build();
|
||||||
|
|
||||||
|
let mut params = FruitParams {
|
||||||
|
attributes: CatchDifficultyAttributes::default(),
|
||||||
|
curve_bufs: CurveBuffers::default(),
|
||||||
|
last_pos: None,
|
||||||
|
last_time: 0.0,
|
||||||
|
map: self,
|
||||||
|
ticks: Vec::new(),
|
||||||
|
with_hr: mods.hr(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut hit_objects: Vec<_> = self
|
||||||
|
.hit_objects
|
||||||
|
.iter()
|
||||||
|
.filter_map(|h| FruitOrJuice::new(h, &mut params))
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let half_catcher_width =
|
||||||
|
(calculate_catch_width(attrs.cs as f32) / 2.0 / ALLOWED_CATCH_RANGE) as f64;
|
||||||
|
let mut last_direction = 0;
|
||||||
|
let mut last_excess = half_catcher_width;
|
||||||
|
|
||||||
|
for i in 1..hit_objects.len() {
|
||||||
|
// SAFETY: The indices are guaranteed the be included based on the loop condition
|
||||||
|
let window = unsafe { hit_objects.get_unchecked_mut(i - 1..=i) };
|
||||||
|
let [curr, next] = window else { unreachable!() };
|
||||||
|
|
||||||
|
curr.init_hyper_dash(
|
||||||
|
half_catcher_width,
|
||||||
|
&*next,
|
||||||
|
&mut last_direction,
|
||||||
|
&mut last_excess,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hit_objects
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mania_hitobjects(&self) -> Vec<ManiaObject> {
|
||||||
|
let map = self.convert_mode(GameMode::Mania);
|
||||||
|
let total_columns = map.cs.round_even().max(1.0);
|
||||||
|
let mut params = ObjectParameters::new(map.as_ref());
|
||||||
|
|
||||||
|
self.hit_objects
|
||||||
|
.iter()
|
||||||
|
.map(|h| ManiaObject::new(h, total_columns, &mut params))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,17 @@ use super::fruit_or_juice::FruitParams;
|
|||||||
const PLAYFIELD_WIDTH: f32 = 512.0;
|
const PLAYFIELD_WIDTH: f32 = 512.0;
|
||||||
const BASE_SPEED: f64 = 1.0;
|
const BASE_SPEED: f64 = 1.0;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
/// A [`HitObject`](crate::parse::HitObject) that was processed for the osu!ctb gamemode.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct CatchObject {
|
pub struct CatchObject {
|
||||||
pub(crate) pos: f32,
|
/// The X position of the object.
|
||||||
pub(crate) time: f64,
|
pub pos: f32,
|
||||||
|
/// The time of the object.
|
||||||
pub(crate) hyper_dash: bool,
|
pub time: f64,
|
||||||
pub(crate) hyper_dist: f32,
|
/// Whether the object is a hyper dash.
|
||||||
|
pub hyper_dash: bool,
|
||||||
|
/// The hyper distance to the next object
|
||||||
|
pub hyper_dist: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CatchObject {
|
impl CatchObject {
|
||||||
|
|||||||
+6
-7
@@ -6,20 +6,19 @@ mod gradual_performance;
|
|||||||
mod movement;
|
mod movement;
|
||||||
mod pp;
|
mod pp;
|
||||||
|
|
||||||
use catch_object::CatchObject;
|
|
||||||
use difficulty_object::DifficultyObject;
|
use difficulty_object::DifficultyObject;
|
||||||
use fruit_or_juice::FruitOrJuice;
|
|
||||||
pub use gradual_difficulty::*;
|
|
||||||
pub use gradual_performance::*;
|
|
||||||
use movement::Movement;
|
use movement::Movement;
|
||||||
pub use pp::*;
|
|
||||||
|
|
||||||
use crate::{catch::fruit_or_juice::FruitParams, curve::CurveBuffers, Beatmap, Mods, OsuStars};
|
pub use self::{catch_object::CatchObject, gradual_difficulty::*, gradual_performance::*, pp::*};
|
||||||
|
|
||||||
|
pub(crate) use self::fruit_or_juice::{FruitOrJuice, FruitParams};
|
||||||
|
|
||||||
|
use crate::{curve::CurveBuffers, Beatmap, Mods, OsuStars};
|
||||||
|
|
||||||
const SECTION_LENGTH: f64 = 750.0;
|
const SECTION_LENGTH: f64 = 750.0;
|
||||||
const STAR_SCALING_FACTOR: f64 = 0.153;
|
const STAR_SCALING_FACTOR: f64 = 0.153;
|
||||||
|
|
||||||
const ALLOWED_CATCH_RANGE: f32 = 0.8;
|
pub(crate) const ALLOWED_CATCH_RANGE: f32 = 0.8;
|
||||||
const CATCHER_SIZE: f32 = 106.75;
|
const CATCHER_SIZE: f32 = 106.75;
|
||||||
|
|
||||||
/// Difficulty calculator on osu!catch maps.
|
/// Difficulty calculator on osu!catch maps.
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ impl<'a> ObjectParameters<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct ManiaObject {
|
/// A [`HitObject`] that was processed for the osu!mania gamemode.
|
||||||
pub(crate) start_time: f64,
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub(crate) end_time: f64,
|
pub struct ManiaObject {
|
||||||
pub(crate) column: usize,
|
/// Start time of the object.
|
||||||
|
pub start_time: f64,
|
||||||
|
/// Endtime of the object.
|
||||||
|
pub end_time: f64,
|
||||||
|
/// Column of the object.
|
||||||
|
pub column: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManiaObject {
|
impl ManiaObject {
|
||||||
|
|||||||
+2
-3
@@ -9,13 +9,12 @@ use std::borrow::Cow;
|
|||||||
|
|
||||||
use crate::{beatmap::BeatmapHitWindows, util::FloatExt, Beatmap, GameMode, Mods, OsuStars};
|
use crate::{beatmap::BeatmapHitWindows, util::FloatExt, Beatmap, GameMode, Mods, OsuStars};
|
||||||
|
|
||||||
pub use self::{gradual_difficulty::*, gradual_performance::*, pp::*};
|
pub use self::{gradual_difficulty::*, gradual_performance::*, mania_object::ManiaObject, pp::*};
|
||||||
|
|
||||||
pub(crate) use self::mania_object::ManiaObject;
|
pub(crate) use self::mania_object::ObjectParameters;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
difficulty_object::ManiaDifficultyObject,
|
difficulty_object::ManiaDifficultyObject,
|
||||||
mania_object::ObjectParameters,
|
|
||||||
skills::{Skill, Strain},
|
skills::{Skill, Strain},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ impl Distances {
|
|||||||
|
|
||||||
let mut lazy_travel_dist: f32 = 0.0;
|
let mut lazy_travel_dist: f32 = 0.0;
|
||||||
|
|
||||||
for (curr_movement_obj, i) in slider.nested_iter().zip(1..) {
|
for (curr_movement_obj, i) in slider.nested_objects.iter().zip(1..) {
|
||||||
let mut curr_movement =
|
let mut curr_movement =
|
||||||
(curr_movement_obj.pos + hit_object.stack_offset) - curr_cursor_pos;
|
(curr_movement_obj.pos + hit_object.stack_offset) - curr_cursor_pos;
|
||||||
let mut curr_movement_len = scaling_factor * curr_movement.length() as f64;
|
let mut curr_movement_len = scaling_factor * curr_movement.length() as f64;
|
||||||
@@ -221,7 +221,7 @@ impl Distances {
|
|||||||
// * Amount of movement required so that the cursor position needs to be updated.
|
// * Amount of movement required so that the cursor position needs to be updated.
|
||||||
let mut required_movement = Self::ASSUMED_SLIDER_RADIUS as f64;
|
let mut required_movement = Self::ASSUMED_SLIDER_RADIUS as f64;
|
||||||
|
|
||||||
if i == slider.nested_len() {
|
if i == slider.nested_objects.len() {
|
||||||
// * The end of a slider has special aim rules due
|
// * The end of a slider has special aim rules due
|
||||||
// * to the relaxed time constraint on position.
|
// * to the relaxed time constraint on position.
|
||||||
// * There is both a lazy end position as well as the actual end slider position.
|
// * There is both a lazy end position as well as the actual end slider position.
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ impl OsuGradualDifficultyAttributes {
|
|||||||
OsuObjectKind::Circle => attrs.n_circles += 1,
|
OsuObjectKind::Circle => attrs.n_circles += 1,
|
||||||
OsuObjectKind::Slider(slider) => {
|
OsuObjectKind::Slider(slider) => {
|
||||||
attrs.n_sliders += 1;
|
attrs.n_sliders += 1;
|
||||||
attrs.max_combo += slider.nested_len();
|
attrs.max_combo += slider.nested_objects.len();
|
||||||
}
|
}
|
||||||
OsuObjectKind::Spinner { .. } => attrs.n_spinners += 1,
|
OsuObjectKind::Spinner { .. } => attrs.n_spinners += 1,
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-33
@@ -10,12 +10,12 @@ use crate::{curve::CurveBuffers, parse::Pos2, AnyStars, Beatmap, GameMode, Mods}
|
|||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
difficulty_object::{Distances, OsuDifficultyObject},
|
difficulty_object::{Distances, OsuDifficultyObject},
|
||||||
osu_object::{ObjectParameters, OsuObject},
|
|
||||||
scaling_factor::ScalingFactor,
|
|
||||||
skills::{Skill, Skills},
|
skills::{Skill, Skills},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use self::{gradual_difficulty::*, gradual_performance::*, pp::*};
|
pub use self::{gradual_difficulty::*, gradual_performance::*, osu_object::*, pp::*};
|
||||||
|
|
||||||
|
pub(crate) use self::scaling_factor::ScalingFactor;
|
||||||
|
|
||||||
const SECTION_LEN: f64 = 400.0;
|
const SECTION_LEN: f64 = 400.0;
|
||||||
const DIFFICULTY_MULTIPLIER: f64 = 0.0675;
|
const DIFFICULTY_MULTIPLIER: f64 = 0.0675;
|
||||||
@@ -269,33 +269,9 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut params = ObjectParameters {
|
let mut hit_objects =
|
||||||
map,
|
create_osu_objects(map, &mut attrs, &scaling_factor, take, hr, time_preempt);
|
||||||
attrs: &mut attrs,
|
let mut hit_objects_iter = hit_objects.iter_mut();
|
||||||
ticks: Vec::new(),
|
|
||||||
curve_bufs: CurveBuffers::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut hit_objects: Vec<_> = map
|
|
||||||
.hit_objects
|
|
||||||
.iter()
|
|
||||||
.take(take)
|
|
||||||
.map(|h| OsuObject::new(h, &mut params))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let stack_threshold = time_preempt * map.stack_leniency as f64;
|
|
||||||
|
|
||||||
if map.version >= 6 {
|
|
||||||
stacking(&mut hit_objects, stack_threshold);
|
|
||||||
} else {
|
|
||||||
old_stacking(&mut hit_objects, stack_threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut hit_objects = hit_objects.iter_mut().map(|h| {
|
|
||||||
h.post_process(hr, &scaling_factor);
|
|
||||||
|
|
||||||
h
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut skills = Skills::new(
|
let mut skills = Skills::new(
|
||||||
mods,
|
mods,
|
||||||
@@ -305,7 +281,7 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
|||||||
hit_window,
|
hit_window,
|
||||||
);
|
);
|
||||||
|
|
||||||
let last = match hit_objects.next() {
|
let last = match hit_objects_iter.next() {
|
||||||
Some(prev) => prev,
|
Some(prev) => prev,
|
||||||
None => return (skills, attrs),
|
None => return (skills, attrs),
|
||||||
};
|
};
|
||||||
@@ -316,9 +292,9 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
|||||||
Distances::compute_slider_cursor_pos(last, &scaling_factor);
|
Distances::compute_slider_cursor_pos(last, &scaling_factor);
|
||||||
|
|
||||||
let mut last = &*last;
|
let mut last = &*last;
|
||||||
let mut diff_objects = Vec::with_capacity(hit_objects.len());
|
let mut diff_objects = Vec::with_capacity(hit_objects_iter.len());
|
||||||
|
|
||||||
for (i, curr) in hit_objects.enumerate() {
|
for (i, curr) in hit_objects_iter.enumerate() {
|
||||||
let delta_time = (curr.start_time - last.start_time) / clock_rate;
|
let delta_time = (curr.start_time - last.start_time) / clock_rate;
|
||||||
|
|
||||||
// * Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
// * Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||||
@@ -347,6 +323,43 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
|||||||
(skills, attrs)
|
(skills, attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_osu_objects(
|
||||||
|
map: &Beatmap,
|
||||||
|
attrs: &mut OsuDifficultyAttributes,
|
||||||
|
scaling_factor: &ScalingFactor,
|
||||||
|
take: usize,
|
||||||
|
hr: bool,
|
||||||
|
time_preempt: f64,
|
||||||
|
) -> Vec<OsuObject> {
|
||||||
|
let mut params = ObjectParameters {
|
||||||
|
map,
|
||||||
|
attrs,
|
||||||
|
ticks: Vec::new(),
|
||||||
|
curve_bufs: CurveBuffers::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut hit_objects: Vec<_> = map
|
||||||
|
.hit_objects
|
||||||
|
.iter()
|
||||||
|
.take(take)
|
||||||
|
.map(|h| OsuObject::new(h, &mut params))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let stack_threshold = time_preempt * map.stack_leniency as f64;
|
||||||
|
|
||||||
|
if map.version >= 6 {
|
||||||
|
stacking(&mut hit_objects, stack_threshold);
|
||||||
|
} else {
|
||||||
|
old_stacking(&mut hit_objects, stack_threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
hit_objects
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|h| h.post_process(hr, scaling_factor));
|
||||||
|
|
||||||
|
hit_objects
|
||||||
|
}
|
||||||
|
|
||||||
fn stacking(hit_objects: &mut [OsuObject], stack_threshold: f64) {
|
fn stacking(hit_objects: &mut [OsuObject], stack_threshold: f64) {
|
||||||
let mut extended_start_idx = 0;
|
let mut extended_start_idx = 0;
|
||||||
|
|
||||||
|
|||||||
+85
-51
@@ -1,5 +1,3 @@
|
|||||||
use std::slice::Iter;
|
|
||||||
|
|
||||||
use super::{scaling_factor::ScalingFactor, OsuDifficultyAttributes, PLAYFIELD_BASE_SIZE};
|
use super::{scaling_factor::ScalingFactor, OsuDifficultyAttributes, PLAYFIELD_BASE_SIZE};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -11,56 +9,75 @@ use crate::{
|
|||||||
const LEGACY_LAST_TICK_OFFSET: f64 = 36.0;
|
const LEGACY_LAST_TICK_OFFSET: f64 = 36.0;
|
||||||
const BASE_SCORING_DISTANCE: f64 = 100.0;
|
const BASE_SCORING_DISTANCE: f64 = 100.0;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
/// A [`HitObject`] that was processed for the osu! gamemode.
|
||||||
pub(crate) struct OsuObject {
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pos: Pos2,
|
pub struct OsuObject {
|
||||||
pub(crate) start_time: f64,
|
/// Position of the object.
|
||||||
pub(crate) stack_offset: Pos2,
|
pub pos: Pos2,
|
||||||
pub(crate) stack_height: f32,
|
/// Start time of the object.
|
||||||
pub(crate) kind: OsuObjectKind,
|
pub start_time: f64,
|
||||||
|
/// The positional offset due to stacking.
|
||||||
|
pub stack_offset: Pos2,
|
||||||
|
/// The height of the stacking offset.
|
||||||
|
pub stack_height: f32,
|
||||||
|
/// Type of the object.
|
||||||
|
pub kind: OsuObjectKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
/// The type of an [`OsuObject`].
|
||||||
pub(crate) enum OsuObjectKind {
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum OsuObjectKind {
|
||||||
|
/// A hitcircle object.
|
||||||
Circle,
|
Circle,
|
||||||
|
/// A slider object.
|
||||||
Slider(OsuSlider),
|
Slider(OsuSlider),
|
||||||
Spinner { end_time: f64 },
|
/// A spinner object.
|
||||||
|
Spinner {
|
||||||
|
/// The endtime of the spinner.
|
||||||
|
end_time: f64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
/// A [`HitObject`] that was processed as a slider for the osu! gamemode.
|
||||||
pub(crate) struct OsuSlider {
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub(crate) end_time: f64,
|
pub struct OsuSlider {
|
||||||
pub(crate) lazy_end_pos: Pos2,
|
/// The endtime of the slider.
|
||||||
nested_objects: Vec<NestedObject>,
|
pub end_time: f64,
|
||||||
|
/// The lazy end position of the cursor.
|
||||||
|
pub lazy_end_pos: Pos2,
|
||||||
|
/// All nested objects of the slider except for the slider head.
|
||||||
|
pub nested_objects: Vec<NestedObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OsuSlider {
|
impl OsuSlider {
|
||||||
pub(crate) fn nested_len(&self) -> usize {
|
/// The amount of repeat points.
|
||||||
self.nested_objects.len()
|
pub fn repeat_count(&self) -> usize {
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn nested_iter(&self) -> Iter<'_, NestedObject> {
|
|
||||||
self.nested_objects.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn repeat_count(&self) -> usize {
|
|
||||||
self.nested_objects.iter().fold(0, |count, nested| {
|
self.nested_objects.iter().fold(0, |count, nested| {
|
||||||
count + matches!(nested.kind, NestedObjectKind::Repeat) as usize
|
count + matches!(nested.kind, NestedObjectKind::Repeat) as usize
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn end_pos(&self) -> Option<Pos2> {
|
/// The position of the slider tail.
|
||||||
|
///
|
||||||
|
/// This is usually but not necessarily the position of the last nested object.
|
||||||
|
pub fn end_pos(&self) -> Option<Pos2> {
|
||||||
self.tail().map(|tail| tail.pos)
|
self.tail().map(|tail| tail.pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tail(&self) -> Option<&NestedObject> {
|
/// A shared reference to the slider tail.
|
||||||
|
///
|
||||||
|
/// This is usually but not necessarily the last nested object.
|
||||||
|
pub fn tail(&self) -> Option<&NestedObject> {
|
||||||
self.nested_objects
|
self.nested_objects
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.find(|nested| matches!(nested.kind, NestedObjectKind::Tail))
|
.find(|nested| matches!(nested.kind, NestedObjectKind::Tail))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tail_mut(&mut self) -> Option<(usize, &mut NestedObject)> {
|
/// An exclusive reference to the slider tail and its index in the nested object list.
|
||||||
|
///
|
||||||
|
/// This is usually but not necessarily the last nested object.
|
||||||
|
pub fn tail_mut(&mut self) -> Option<(usize, &mut NestedObject)> {
|
||||||
self.nested_objects
|
self.nested_objects
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -69,18 +86,27 @@ impl OsuSlider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
/// A nested object within a slider.
|
||||||
pub(crate) struct NestedObject {
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
/// Note: `pos` does not include stacking!
|
pub struct NestedObject {
|
||||||
pub(crate) pos: Pos2,
|
/// Position of the object.
|
||||||
pub(crate) start_time: f64,
|
///
|
||||||
pub(crate) kind: NestedObjectKind,
|
/// Note: `pos` does not include stacking.
|
||||||
|
pub pos: Pos2,
|
||||||
|
/// Start time of the object.
|
||||||
|
pub start_time: f64,
|
||||||
|
/// Type of the object.
|
||||||
|
pub kind: NestedObjectKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
/// Type of a [`NestedObject`].
|
||||||
pub(crate) enum NestedObjectKind {
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum NestedObjectKind {
|
||||||
|
/// A repeat point.
|
||||||
Repeat,
|
Repeat,
|
||||||
|
/// The logical slider tail i.e. the legacy last tick.
|
||||||
Tail,
|
Tail,
|
||||||
|
/// A regular slider tick.
|
||||||
Tick,
|
Tick,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +344,8 @@ impl OsuObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn end_time(&self) -> f64 {
|
/// Endtime of the object.
|
||||||
|
pub fn end_time(&self) -> f64 {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
OsuObjectKind::Circle => self.start_time,
|
OsuObjectKind::Circle => self.start_time,
|
||||||
OsuObjectKind::Slider(slider) => slider.end_time,
|
OsuObjectKind::Slider(slider) => slider.end_time,
|
||||||
@@ -326,7 +353,8 @@ impl OsuObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn end_pos(&self) -> Pos2 {
|
/// End position of the object.
|
||||||
|
pub fn end_pos(&self) -> Pos2 {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
OsuObjectKind::Circle | OsuObjectKind::Spinner { .. } => self.pos,
|
OsuObjectKind::Circle | OsuObjectKind::Spinner { .. } => self.pos,
|
||||||
OsuObjectKind::Slider(slider) => slider.end_pos().unwrap_or(self.pos),
|
OsuObjectKind::Slider(slider) => slider.end_pos().unwrap_or(self.pos),
|
||||||
@@ -356,7 +384,8 @@ impl OsuObject {
|
|||||||
.map_or(self.pos, |end_pos| self.pos + end_pos)
|
.map_or(self.pos, |end_pos| self.pos + end_pos)
|
||||||
} else {
|
} else {
|
||||||
slider
|
slider
|
||||||
.nested_iter()
|
.nested_objects
|
||||||
|
.iter()
|
||||||
.find(|nested| matches!(nested.kind, NestedObjectKind::Repeat))
|
.find(|nested| matches!(nested.kind, NestedObjectKind::Repeat))
|
||||||
.map_or(self.pos, |repeat| repeat.pos)
|
.map_or(self.pos, |repeat| repeat.pos)
|
||||||
}
|
}
|
||||||
@@ -364,26 +393,31 @@ impl OsuObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn pos(&self) -> Pos2 {
|
/// Position of the object without stacking.
|
||||||
|
pub const fn pos(&self) -> Pos2 {
|
||||||
self.pos
|
self.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn stacked_pos(&self) -> Pos2 {
|
/// Stacked position of the object.
|
||||||
|
pub fn stacked_pos(&self) -> Pos2 {
|
||||||
self.pos + self.stack_offset
|
self.pos + self.stack_offset
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn stacked_end_pos(&self) -> Pos2 {
|
/// Stacked end position of the object.
|
||||||
|
pub fn stacked_end_pos(&self) -> Pos2 {
|
||||||
self.end_pos() + self.stack_offset
|
self.end_pos() + self.stack_offset
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lazy_end_pos(&self) -> Pos2 {
|
/// Lazy end position of the object. Stacking is included.
|
||||||
|
pub fn lazy_end_pos(&self) -> Pos2 {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
OsuObjectKind::Circle | OsuObjectKind::Spinner { .. } => self.stacked_pos(),
|
OsuObjectKind::Circle | OsuObjectKind::Spinner { .. } => self.stacked_pos(),
|
||||||
OsuObjectKind::Slider(slider) => slider.lazy_end_pos,
|
OsuObjectKind::Slider(slider) => slider.lazy_end_pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lazy_travel_time(&self) -> f64 {
|
/// Lazy travel time of the object.
|
||||||
|
pub fn lazy_travel_time(&self) -> f64 {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
OsuObjectKind::Circle | OsuObjectKind::Spinner { .. } => 0.0,
|
OsuObjectKind::Circle | OsuObjectKind::Spinner { .. } => 0.0,
|
||||||
OsuObjectKind::Slider(slider) => slider
|
OsuObjectKind::Slider(slider) => slider
|
||||||
@@ -393,18 +427,18 @@ impl OsuObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Whether the object is a hitcircle.
|
||||||
pub(crate) fn is_circle(&self) -> bool {
|
pub const fn is_circle(&self) -> bool {
|
||||||
matches!(self.kind, OsuObjectKind::Circle)
|
matches!(self.kind, OsuObjectKind::Circle)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Whether the object is a slider.
|
||||||
pub(crate) fn is_slider(&self) -> bool {
|
pub const fn is_slider(&self) -> bool {
|
||||||
matches!(self.kind, OsuObjectKind::Slider { .. })
|
matches!(self.kind, OsuObjectKind::Slider { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Whether the object is a spinner.
|
||||||
pub(crate) fn is_spinner(&self) -> bool {
|
pub const fn is_spinner(&self) -> bool {
|
||||||
matches!(self.kind, OsuObjectKind::Spinner { .. })
|
matches!(self.kind, OsuObjectKind::Spinner { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -9,7 +9,12 @@ mod taiko_object;
|
|||||||
|
|
||||||
use std::{borrow::Cow, cell::RefCell, rc::Rc};
|
use std::{borrow::Cow, cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
pub use self::{gradual_difficulty::*, gradual_performance::*, pp::*};
|
pub use self::{
|
||||||
|
gradual_difficulty::*, gradual_performance::*, pp::*,
|
||||||
|
taiko_object::TaikoObjectPub as TaikoObject,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) use self::taiko_object::IntoTaikoObjectIter;
|
||||||
|
|
||||||
use crate::{beatmap::BeatmapHitWindows, Beatmap, GameMode, Mods, OsuStars};
|
use crate::{beatmap::BeatmapHitWindows, Beatmap, GameMode, Mods, OsuStars};
|
||||||
|
|
||||||
@@ -17,7 +22,6 @@ use self::{
|
|||||||
colours::ColourDifficultyPreprocessor,
|
colours::ColourDifficultyPreprocessor,
|
||||||
difficulty_object::{MonoIndex, ObjectLists, TaikoDifficultyObject},
|
difficulty_object::{MonoIndex, ObjectLists, TaikoDifficultyObject},
|
||||||
skills::{Peaks, PeaksDifficultyValues, PeaksRaw, Skill},
|
skills::{Peaks, PeaksDifficultyValues, PeaksRaw, Skill},
|
||||||
taiko_object::IntoTaikoObjectIter,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SECTION_LEN: usize = 400;
|
const SECTION_LEN: usize = 400;
|
||||||
|
|||||||
@@ -62,3 +62,14 @@ impl ExactSizeIterator for TaikoObjectIter<'_> {
|
|||||||
self.hit_objects.len()
|
self.hit_objects.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`HitObject`] that was processed for the osu!taiko gamemode.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub struct TaikoObjectPub {
|
||||||
|
/// Start time of the object.
|
||||||
|
pub start_time: f64,
|
||||||
|
/// Whether the object is a circle i.e. this is false if the object is a drum roll or a swell.
|
||||||
|
pub is_hit: bool,
|
||||||
|
/// Whether it's a rim or center hit.
|
||||||
|
pub is_rim: bool,
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user