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::{
|
||||
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,
|
||||
GradualPerformanceAttributes, ManiaPP, ManiaStars, OsuPP, OsuStars, PerformanceAttributes,
|
||||
Strains, TaikoPP, TaikoStars,
|
||||
GradualPerformanceAttributes, ManiaPP, ManiaStars, Mods, OsuPP, OsuStars,
|
||||
PerformanceAttributes, Strains, TaikoPP, TaikoStars,
|
||||
};
|
||||
|
||||
/// Provides some additional methods on [`Beatmap`].
|
||||
@@ -26,7 +35,7 @@ pub trait BeatmapExt {
|
||||
/// Suitable to plot the difficulty of a map over time.
|
||||
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.
|
||||
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,
|
||||
/// i.e. live update a score's pp.
|
||||
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 {
|
||||
@@ -89,4 +121,85 @@ impl BeatmapExt for Beatmap {
|
||||
fn gradual_performance(&self, mods: u32) -> GradualPerformanceAttributes<'_> {
|
||||
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 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(crate) pos: f32,
|
||||
pub(crate) time: f64,
|
||||
|
||||
pub(crate) hyper_dash: bool,
|
||||
pub(crate) hyper_dist: f32,
|
||||
/// The X position of the object.
|
||||
pub pos: f32,
|
||||
/// The time of the object.
|
||||
pub time: f64,
|
||||
/// Whether the object is a hyper dash.
|
||||
pub hyper_dash: bool,
|
||||
/// The hyper distance to the next object
|
||||
pub hyper_dist: f32,
|
||||
}
|
||||
|
||||
impl CatchObject {
|
||||
|
||||
+6
-7
@@ -6,20 +6,19 @@ mod gradual_performance;
|
||||
mod movement;
|
||||
mod pp;
|
||||
|
||||
use catch_object::CatchObject;
|
||||
use difficulty_object::DifficultyObject;
|
||||
use fruit_or_juice::FruitOrJuice;
|
||||
pub use gradual_difficulty::*;
|
||||
pub use gradual_performance::*;
|
||||
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 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;
|
||||
|
||||
/// Difficulty calculator on osu!catch maps.
|
||||
|
||||
@@ -22,10 +22,15 @@ impl<'a> ObjectParameters<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ManiaObject {
|
||||
pub(crate) start_time: f64,
|
||||
pub(crate) end_time: f64,
|
||||
pub(crate) column: usize,
|
||||
/// A [`HitObject`] that was processed for the osu!mania gamemode.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ManiaObject {
|
||||
/// 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 {
|
||||
|
||||
+2
-3
@@ -9,13 +9,12 @@ use std::borrow::Cow;
|
||||
|
||||
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::{
|
||||
difficulty_object::ManiaDifficultyObject,
|
||||
mania_object::ObjectParameters,
|
||||
skills::{Skill, Strain},
|
||||
};
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ impl Distances {
|
||||
|
||||
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 =
|
||||
(curr_movement_obj.pos + hit_object.stack_offset) - curr_cursor_pos;
|
||||
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.
|
||||
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
|
||||
// * to the relaxed time constraint on 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::Slider(slider) => {
|
||||
attrs.n_sliders += 1;
|
||||
attrs.max_combo += slider.nested_len();
|
||||
attrs.max_combo += slider.nested_objects.len();
|
||||
}
|
||||
OsuObjectKind::Spinner { .. } => attrs.n_spinners += 1,
|
||||
}
|
||||
|
||||
+46
-33
@@ -10,12 +10,12 @@ use crate::{curve::CurveBuffers, parse::Pos2, AnyStars, Beatmap, GameMode, Mods}
|
||||
|
||||
use self::{
|
||||
difficulty_object::{Distances, OsuDifficultyObject},
|
||||
osu_object::{ObjectParameters, OsuObject},
|
||||
scaling_factor::ScalingFactor,
|
||||
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 DIFFICULTY_MULTIPLIER: f64 = 0.0675;
|
||||
@@ -269,33 +269,9 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut params = ObjectParameters {
|
||||
map,
|
||||
attrs: &mut 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);
|
||||
}
|
||||
|
||||
let mut hit_objects = hit_objects.iter_mut().map(|h| {
|
||||
h.post_process(hr, &scaling_factor);
|
||||
|
||||
h
|
||||
});
|
||||
let mut hit_objects =
|
||||
create_osu_objects(map, &mut attrs, &scaling_factor, take, hr, time_preempt);
|
||||
let mut hit_objects_iter = hit_objects.iter_mut();
|
||||
|
||||
let mut skills = Skills::new(
|
||||
mods,
|
||||
@@ -305,7 +281,7 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
hit_window,
|
||||
);
|
||||
|
||||
let last = match hit_objects.next() {
|
||||
let last = match hit_objects_iter.next() {
|
||||
Some(prev) => prev,
|
||||
None => return (skills, attrs),
|
||||
};
|
||||
@@ -316,9 +292,9 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
Distances::compute_slider_cursor_pos(last, &scaling_factor);
|
||||
|
||||
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;
|
||||
|
||||
// * Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||
@@ -347,6 +323,43 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
(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) {
|
||||
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 crate::{
|
||||
@@ -11,56 +9,75 @@ use crate::{
|
||||
const LEGACY_LAST_TICK_OFFSET: f64 = 36.0;
|
||||
const BASE_SCORING_DISTANCE: f64 = 100.0;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct OsuObject {
|
||||
pos: Pos2,
|
||||
pub(crate) start_time: f64,
|
||||
pub(crate) stack_offset: Pos2,
|
||||
pub(crate) stack_height: f32,
|
||||
pub(crate) kind: OsuObjectKind,
|
||||
/// A [`HitObject`] that was processed for the osu! gamemode.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct OsuObject {
|
||||
/// Position of the object.
|
||||
pub pos: Pos2,
|
||||
/// Start time of the object.
|
||||
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)]
|
||||
pub(crate) enum OsuObjectKind {
|
||||
/// The type of an [`OsuObject`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum OsuObjectKind {
|
||||
/// A hitcircle object.
|
||||
Circle,
|
||||
/// A slider object.
|
||||
Slider(OsuSlider),
|
||||
Spinner { end_time: f64 },
|
||||
/// A spinner object.
|
||||
Spinner {
|
||||
/// The endtime of the spinner.
|
||||
end_time: f64,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct OsuSlider {
|
||||
pub(crate) end_time: f64,
|
||||
pub(crate) lazy_end_pos: Pos2,
|
||||
nested_objects: Vec<NestedObject>,
|
||||
/// A [`HitObject`] that was processed as a slider for the osu! gamemode.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct OsuSlider {
|
||||
/// The endtime of the slider.
|
||||
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 {
|
||||
pub(crate) fn nested_len(&self) -> usize {
|
||||
self.nested_objects.len()
|
||||
}
|
||||
|
||||
pub(crate) fn nested_iter(&self) -> Iter<'_, NestedObject> {
|
||||
self.nested_objects.iter()
|
||||
}
|
||||
|
||||
pub(crate) fn repeat_count(&self) -> usize {
|
||||
/// The amount of repeat points.
|
||||
pub fn repeat_count(&self) -> usize {
|
||||
self.nested_objects.iter().fold(0, |count, nested| {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
.iter()
|
||||
.rev()
|
||||
.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
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
@@ -69,18 +86,27 @@ impl OsuSlider {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct NestedObject {
|
||||
/// Note: `pos` does not include stacking!
|
||||
pub(crate) pos: Pos2,
|
||||
pub(crate) start_time: f64,
|
||||
pub(crate) kind: NestedObjectKind,
|
||||
/// A nested object within a slider.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NestedObject {
|
||||
/// Position of the object.
|
||||
///
|
||||
/// 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)]
|
||||
pub(crate) enum NestedObjectKind {
|
||||
/// Type of a [`NestedObject`].
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum NestedObjectKind {
|
||||
/// A repeat point.
|
||||
Repeat,
|
||||
/// The logical slider tail i.e. the legacy last tick.
|
||||
Tail,
|
||||
/// A regular slider 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 {
|
||||
OsuObjectKind::Circle => self.start_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 {
|
||||
OsuObjectKind::Circle | OsuObjectKind::Spinner { .. } => 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)
|
||||
} else {
|
||||
slider
|
||||
.nested_iter()
|
||||
.nested_objects
|
||||
.iter()
|
||||
.find(|nested| matches!(nested.kind, NestedObjectKind::Repeat))
|
||||
.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
|
||||
}
|
||||
|
||||
pub(crate) fn stacked_pos(&self) -> Pos2 {
|
||||
/// Stacked position of the object.
|
||||
pub fn stacked_pos(&self) -> Pos2 {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
OsuObjectKind::Circle | OsuObjectKind::Spinner { .. } => self.stacked_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 {
|
||||
OsuObjectKind::Circle | OsuObjectKind::Spinner { .. } => 0.0,
|
||||
OsuObjectKind::Slider(slider) => slider
|
||||
@@ -393,18 +427,18 @@ impl OsuObject {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_circle(&self) -> bool {
|
||||
/// Whether the object is a hitcircle.
|
||||
pub const fn is_circle(&self) -> bool {
|
||||
matches!(self.kind, OsuObjectKind::Circle)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_slider(&self) -> bool {
|
||||
/// Whether the object is a slider.
|
||||
pub const fn is_slider(&self) -> bool {
|
||||
matches!(self.kind, OsuObjectKind::Slider { .. })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_spinner(&self) -> bool {
|
||||
/// Whether the object is a spinner.
|
||||
pub const fn is_spinner(&self) -> bool {
|
||||
matches!(self.kind, OsuObjectKind::Spinner { .. })
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -9,7 +9,12 @@ mod taiko_object;
|
||||
|
||||
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};
|
||||
|
||||
@@ -17,7 +22,6 @@ use self::{
|
||||
colours::ColourDifficultyPreprocessor,
|
||||
difficulty_object::{MonoIndex, ObjectLists, TaikoDifficultyObject},
|
||||
skills::{Peaks, PeaksDifficultyValues, PeaksRaw, Skill},
|
||||
taiko_object::IntoTaikoObjectIter,
|
||||
};
|
||||
|
||||
const SECTION_LEN: usize = 400;
|
||||
|
||||
@@ -62,3 +62,14 @@ impl ExactSizeIterator for TaikoObjectIter<'_> {
|
||||
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