finished mania + restructured a little
This commit is contained in:
@@ -109,7 +109,7 @@ pub struct EffectPoint {
|
||||
|
||||
impl EffectPoint {
|
||||
/// The default slider velocity for a [`DifficultyPoint`]
|
||||
pub const DEFAULT_KIAI: bool = true;
|
||||
pub const DEFAULT_KIAI: bool = false;
|
||||
|
||||
/// Create a new [`EffectPoint`].
|
||||
#[inline]
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
use std::hash::{BuildHasher, Hasher};
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub(crate) struct BuildByteHasher;
|
||||
|
||||
impl BuildHasher for BuildByteHasher {
|
||||
type Hasher = ByteHasher;
|
||||
|
||||
#[inline]
|
||||
fn build_hasher(&self) -> Self::Hasher {
|
||||
ByteHasher { byte: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ByteHasher {
|
||||
byte: u8,
|
||||
}
|
||||
|
||||
impl Hasher for ByteHasher {
|
||||
#[inline]
|
||||
fn finish(&self) -> u64 {
|
||||
self.byte as u64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write(&mut self, _: &[u8]) {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_u8(&mut self, byte: u8) {
|
||||
self.byte = byte;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ use std::cmp::Ordering;
|
||||
|
||||
use crate::{
|
||||
curve::{Curve, CurveBuffers},
|
||||
limited_queue::LimitedQueue,
|
||||
parse::{legacy_sort, HitObjectKind, Pos2},
|
||||
util::{FloatExt, LimitedQueue},
|
||||
Beatmap, GameMode,
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@ use self::{
|
||||
pattern_type::PatternType,
|
||||
};
|
||||
|
||||
mod byte_hasher;
|
||||
mod legacy_random;
|
||||
mod pattern;
|
||||
mod pattern_generator;
|
||||
@@ -32,12 +31,14 @@ impl Beatmap {
|
||||
let mut n_circles = 0;
|
||||
let mut n_sliders = 0;
|
||||
|
||||
let seed =
|
||||
(map.hp + map.cs).round() as i32 * 20 + (map.od * 41.2) as i32 + map.ar.round() as i32;
|
||||
let seed = (map.hp + map.cs).round_even() as i32 * 20
|
||||
+ (map.od * 41.2) as i32
|
||||
+ map.ar.round_even() as i32;
|
||||
|
||||
let mut random = Random::new(seed);
|
||||
|
||||
let rounded_cs = map.cs.round();
|
||||
let rounded_od = map.od.round();
|
||||
let rounded_cs = map.cs.round_even();
|
||||
let rounded_od = map.od.round_even();
|
||||
|
||||
let slider_or_spinner_count = self
|
||||
.hit_objects
|
||||
@@ -78,9 +79,7 @@ impl Beatmap {
|
||||
};
|
||||
|
||||
let total_columns = map.cs as i32;
|
||||
|
||||
let mut last_values = PrevValues::default();
|
||||
|
||||
let mut curve_bufs = CurveBuffers::default();
|
||||
|
||||
for (obj, sound) in self.hit_objects.iter().zip(self.sounds.iter()) {
|
||||
@@ -167,8 +166,8 @@ impl Beatmap {
|
||||
&last_values.pattern,
|
||||
);
|
||||
|
||||
last_values.time = obj.start_time;
|
||||
last_values.pos = obj.pos;
|
||||
last_values.time = end_time;
|
||||
last_values.pos = Pos2 { x: 256.0, y: 192.0 };
|
||||
|
||||
compute_density(end_time, &mut density);
|
||||
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::parse::{HitObject, HitObjectKind, Pos2};
|
||||
use crate::{
|
||||
parse::{HitObject, HitObjectKind, Pos2},
|
||||
util::ByteHasher,
|
||||
};
|
||||
|
||||
use super::{
|
||||
byte_hasher::BuildByteHasher,
|
||||
pattern_generator::{
|
||||
distance_object::DistanceObjectPatternGenerator,
|
||||
end_time_object::EndTimeObjectPatternGenerator, hit_object::HitObjectPatternGenerator,
|
||||
},
|
||||
use super::pattern_generator::{
|
||||
distance_object::DistanceObjectPatternGenerator,
|
||||
end_time_object::EndTimeObjectPatternGenerator, hit_object::HitObjectPatternGenerator,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Pattern {
|
||||
pub(crate) hit_objects: Vec<HitObject>,
|
||||
contained_columns: HashSet<u8, BuildByteHasher>,
|
||||
contained_columns: HashSet<u8, ByteHasher>,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
pub(crate) fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
hit_objects: Vec::with_capacity(capacity),
|
||||
contained_columns: HashSet::with_hasher(BuildByteHasher),
|
||||
contained_columns: HashSet::with_hasher(ByteHasher),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_single(hit_object: HitObject, column: u8) -> Self {
|
||||
let mut contained_columns = HashSet::with_capacity_and_hasher(1, BuildByteHasher);
|
||||
let mut contained_columns = HashSet::with_capacity_and_hasher(1, ByteHasher);
|
||||
contained_columns.insert(column);
|
||||
let hit_objects = vec![hit_object];
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
curve::Curve,
|
||||
mania::ManiaObject,
|
||||
parse::{HitObject, HitSound},
|
||||
util::FloatExt,
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
@@ -58,7 +59,7 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
let beat_len = timing_point.beat_len * difficulty_point.bpm_mult;
|
||||
|
||||
let span_count = (repeats + 1) as i32;
|
||||
let start_time = hit_object.start_time.round() as i32;
|
||||
let start_time = hit_object.start_time.round_even() as i32;
|
||||
|
||||
// * This matches stable's calculation.
|
||||
let end_time = (start_time as f64
|
||||
@@ -97,12 +98,12 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
||||
let mut end_time_pattern = Pattern::default();
|
||||
|
||||
for obj in orig_pattern.hit_objects {
|
||||
let column = ManiaObject::new(&obj).column(self.total_columns as f32) as u8;
|
||||
let col = ManiaObject::column(obj.pos.x, self.total_columns as f32) as u8;
|
||||
|
||||
if self.end_time != obj.end_time().round() as i32 {
|
||||
intermediate_pattern.add_object(obj, column);
|
||||
if self.end_time != obj.end_time().round_even() as i32 {
|
||||
intermediate_pattern.add_object(obj, col);
|
||||
} else {
|
||||
end_time_pattern.add_object(obj, column);
|
||||
end_time_pattern.add_object(obj, col);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,15 +98,14 @@ impl<'h> HitObjectPatternGenerator<'h> {
|
||||
pub(crate) fn generate(&mut self) -> Pattern {
|
||||
let pattern = self.generate_core();
|
||||
|
||||
for obj in pattern.hit_objects.iter().map(ManiaObject::new) {
|
||||
if self.convert_type.contains(PatternType::STAIR)
|
||||
&& obj.column(self.total_columns as f32) as i32 == self.total_columns - 1
|
||||
{
|
||||
for obj in pattern.hit_objects.iter() {
|
||||
let col = ManiaObject::column(obj.pos.x, self.total_columns as f32) as i32;
|
||||
|
||||
if self.convert_type.contains(PatternType::STAIR) && col == self.total_columns - 1 {
|
||||
self.stair_type = PatternType::REVERSE_STAIR;
|
||||
}
|
||||
|
||||
if self.convert_type.contains(PatternType::REVERSE_STAIR)
|
||||
&& obj.column(self.total_columns as f32) as i32 == self.random_start()
|
||||
if self.convert_type.contains(PatternType::REVERSE_STAIR) && col == self.random_start()
|
||||
{
|
||||
self.stair_type = PatternType::STAIR;
|
||||
}
|
||||
@@ -120,12 +119,9 @@ impl<'h> HitObjectPatternGenerator<'h> {
|
||||
return Pattern::new_note(self, 0);
|
||||
}
|
||||
|
||||
let last_column = self
|
||||
.prev_pattern
|
||||
.hit_objects
|
||||
.last()
|
||||
.map(ManiaObject::new)
|
||||
.map_or(0, |h| h.column(self.total_columns as f32) as u8);
|
||||
let last_column = self.prev_pattern.hit_objects.last().map_or(0, |h| {
|
||||
ManiaObject::column(h.pos.x, self.total_columns as f32) as u8
|
||||
});
|
||||
|
||||
let random_start = self.random_start() as u8;
|
||||
|
||||
|
||||
@@ -21,15 +21,13 @@ trait PatternGenerator {
|
||||
fn get_column(&self, allow_special: Option<bool>) -> u8 {
|
||||
let allow_special = allow_special.unwrap_or(false);
|
||||
|
||||
let res = if allow_special && self.total_columns() == 8 {
|
||||
if allow_special && self.total_columns() == 8 {
|
||||
const LOCAL_X_DIVISOR: f32 = 512.0 / 7.0;
|
||||
|
||||
((self.hit_object().pos.x / LOCAL_X_DIVISOR).floor() as u8).clamp(0, 6) + 1
|
||||
} else {
|
||||
ManiaObject::new(self.hit_object()).column(self.total_columns() as f32) as u8
|
||||
};
|
||||
|
||||
res
|
||||
ManiaObject::column(self.hit_object().pos.x, self.total_columns() as f32) as u8
|
||||
}
|
||||
}
|
||||
|
||||
fn get_random_note_count(
|
||||
|
||||
@@ -32,7 +32,7 @@ impl fmt::Display for PatternType {
|
||||
$(
|
||||
if $self.contains(Self::$pat) {
|
||||
if $written {
|
||||
$f.write_str(" | ")?;
|
||||
$f.write_str(", ")?;
|
||||
} else {
|
||||
$written = true;
|
||||
}
|
||||
|
||||
+1
-3
@@ -1,13 +1,12 @@
|
||||
use std::{borrow::Cow, cmp::Ordering};
|
||||
|
||||
use crate::parse::HitObject;
|
||||
use crate::{parse::HitObject, util::SortedVec};
|
||||
|
||||
pub use self::{
|
||||
attributes::{BeatmapAttributes, BeatmapAttributesBuilder, BeatmapHitWindows},
|
||||
breaks::Break,
|
||||
control_points::{DifficultyPoint, EffectPoint, TimingPoint},
|
||||
mode::GameMode,
|
||||
sorted_vec::SortedVec,
|
||||
};
|
||||
|
||||
mod attributes;
|
||||
@@ -15,7 +14,6 @@ mod breaks;
|
||||
mod control_points;
|
||||
mod converts;
|
||||
mod mode;
|
||||
mod sorted_vec;
|
||||
|
||||
/// The main beatmap struct containing all data relevant
|
||||
/// for difficulty and performance calculation
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
convert::identity,
|
||||
fmt::{Debug, Formatter, Result as FmtResult},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use super::{control_points::EffectPoint, DifficultyPoint, TimingPoint};
|
||||
|
||||
/// A [`Vec`] whose elements are guaranteed to be in order based on the given comparator.
|
||||
#[derive(Clone)]
|
||||
pub struct SortedVec<T> {
|
||||
inner: Vec<T>,
|
||||
cmp: fn(&T, &T) -> Ordering,
|
||||
}
|
||||
|
||||
impl<T> SortedVec<T> {
|
||||
/// If the value is found then [`Result::Ok`] is returned, containing the
|
||||
/// index of the matching element. If there are multiple matches, then any
|
||||
/// one of the matches could be returned.
|
||||
/// If the value is not found then [`Result::Err`] is returned, containing
|
||||
/// the index where a matching element could be inserted while maintaining
|
||||
/// sorted order.
|
||||
pub fn find(&self, value: &T) -> Result<usize, usize> {
|
||||
self.inner
|
||||
.binary_search_by(|probe| (self.cmp)(probe, value))
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, value: T) {
|
||||
let idx = self.find(&value).map_or_else(identity, identity);
|
||||
|
||||
self.inner.insert(idx, value);
|
||||
}
|
||||
|
||||
pub(crate) fn dedup_by_key<F, K>(&mut self, mut key: F)
|
||||
where
|
||||
F: FnMut(&mut T) -> K,
|
||||
K: PartialEq,
|
||||
{
|
||||
self.inner.dedup_by(|a, b| key(a) == key(b))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for SortedVec<T> {
|
||||
type Target = Vec<T>;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for SortedVec<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
Debug::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SortedVec<TimingPoint> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Vec::new(),
|
||||
cmp: |a, b| a.time.partial_cmp(&b.time).unwrap_or(Ordering::Equal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SortedVec<DifficultyPoint> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Vec::new(),
|
||||
cmp: |a, b| a.time.partial_cmp(&b.time).unwrap_or(Ordering::Equal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SortedVec<EffectPoint> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Vec::new(),
|
||||
cmp: |a, b| a.time.partial_cmp(&b.time).unwrap_or(Ordering::Equal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SortedVec<DifficultyPoint> {
|
||||
pub(crate) fn push_if_not_redundant(&mut self, value: DifficultyPoint) {
|
||||
let is_redundant = match self.find(&value).map_err(|idx| idx.checked_sub(1)) {
|
||||
Ok(idx) | Err(Some(idx)) => value.is_redundant(&self[idx]),
|
||||
Err(None) => value.is_redundant(&DifficultyPoint::default()),
|
||||
};
|
||||
|
||||
if !is_redundant {
|
||||
self.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user